diff options
author | Maxime Henrion <mux@FreeBSD.org> | 2006-03-03 04:11:29 +0000 |
---|---|---|
committer | Maxime Henrion <mux@FreeBSD.org> | 2006-03-03 04:11:29 +0000 |
commit | bb215397eea295477c3d8b6a058590abba067a9f (patch) | |
tree | 0e3be645144d47f66959901031be3e2230e7eaf0 |
Initial import of csup.vendor/csup/20060302
Notes
Notes:
svn path=/vendor/csup/dist/; revision=156230
svn path=/vendor/csup/20060302/; revision=156232; tag=vendor/csup/20060302
49 files changed, 12927 insertions, 0 deletions
diff --git a/contrib/csup/GNUmakefile b/contrib/csup/GNUmakefile new file mode 100644 index 000000000000..441a6b85cab9 --- /dev/null +++ b/contrib/csup/GNUmakefile @@ -0,0 +1,63 @@ +# A simple gmake Makefile, to be used on Linux and Darwin. It shouldn't +# be used elsewhere because it assumes that the target system doesn't +# support BSD extended file flags. +# +# $FreeBSD$ +# + +PREFIX?=/usr/local +OWNER?= 0 +GROUP?= 0 + +UNAME= $(shell uname -s) + +SRCS= attrstack.c config.c detailer.c diff.c fattr.c fixups.c fnmatch.c \ + globtree.c keyword.c lister.c main.c misc.c mux.c pathcomp.c parse.c \ + proto.c status.c stream.c threads.c token.c updater.c +OBJS= $(SRCS:.c=.o) + +WARNS= -Wall -W -Wno-unused-parameter -Wmissing-prototypes -Wpointer-arith \ + -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wshadow \ + -Wcast-align -Wunused-parameter -Wchar-subscripts -Winline \ + -Wnested-externs -Wredundant-decls -Wno-format-y2k + +CFLAGS+= -g -O -pipe -DNDEBUG -I$(PREFIX)/include +ifeq ($(UNAME), "Linux") + CFLAGS+= -D_XOPEN_SOURCE -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 +endif +ifeq ($(UNAME), "Darwin") + CFLAGS+= -DHAVE_FFLAGS +endif +CFLAGS+= $(WARNS) +LDFLAGS= -L$(PREFIX)/lib -lcrypto -lz -lpthread + +.PHONY: all clean install + +all: csup csup.1.gz + +csup: $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +config.c: parse.h + +token.c: token.l + +parse.c: parse.y + +parse.h: parse.c + +clean: + rm -f csup $(OBJS) parse.c parse.h token.c csup.1.gz + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.c: %.y + $(YACC) -d -o $@ $< + +csup.1.gz: csup.1 + gzip -cn $< > $@ + +install: csup csup.1.gz + install -s -o $(OWNER) -g $(GROUP) csup $(PREFIX)/bin + install -s -o $(OWNER) -g $(GROUP) csup.1.gz $(PREFIX)/share/man/man1 diff --git a/contrib/csup/Makefile b/contrib/csup/Makefile new file mode 100644 index 000000000000..06f7b92c0321 --- /dev/null +++ b/contrib/csup/Makefile @@ -0,0 +1,62 @@ +# $FreeBSD$ + +PREFIX?= /usr/local +BINDIR?= ${PREFIX}/bin +MANDIR?= ${PREFIX}/man/man + +UNAME!= /usr/bin/uname -s + +PROG= csup +SRCS= attrstack.c attrstack.h \ + config.c config.h \ + detailer.c detailer.h \ + diff.c diff.h \ + fattr.c fattr.h fattr_bsd.h \ + fixups.c fixups.h \ + fnmatch.c fnmatch.h \ + globtree.c globtree.h \ + keyword.c keyword.h \ + lister.c lister.h \ + main.c main.h \ + misc.c misc.h \ + mux.c mux.h \ + parse.h parse.y \ + pathcomp.c pathcomp.h \ + proto.c proto.h \ + status.c status.h \ + stream.c stream.h \ + threads.c threads.h \ + token.h token.l \ + updater.c updater.h + +CFLAGS+= -I. -I${.CURDIR} -g -pthread -DHAVE_FFLAGS -DNDEBUG +WARNS?= 6 + +# A bit of tweaking is needed to get this Makefile working +# with the bsd.prog.mk of all the *BSD OSes... +.if (${UNAME} == "NetBSD") +LDFLAGS+= -pthread +YHEADER= yes + +.elif (${UNAME} == "OpenBSD") +# I bet there's a better way to do this with the OpenBSD mk +# framework but well, this works and I got bored. +LDFLAGS+= -pthread +YFLAGS= -d +CLEANFILES+= parse.c parse.h y.tab.h + +config.c: parse.h + +token.l: parse.h + +y.tab.h: parse.c + +parse.h: y.tab.h + cp ${.ALLSRC} ${.TARGET} + +.endif + +DPADD= ${LIBCRYPTO} ${LIBZ} +LDADD= -lcrypto -lz + +.include <bsd.prog.mk> diff --git a/contrib/csup/README b/contrib/csup/README new file mode 100644 index 000000000000..879c4811792e --- /dev/null +++ b/contrib/csup/README @@ -0,0 +1,39 @@ +$FreeBSD$ + +Authors +------- + +CVSup was originally written in Modula-3 by + John Polstra <jdp@polstra.com>. + +Csup is a rewrite of CVSup in C. It has been mostly written by + Maxime Henrion <mux@FreeBSD.org>. + +A few contributors have helped him in his task and they are listed here in +alphabetical order : + + Olivier Houchard <cognet@FreeBSD.org> + Ulf Lilleengen <lulf@kerneled.org> + Christoph Mathys <cmathys@bluewin.ch> (Google SoC Project) + Etienne Vidal <etienne.vidal@gmail.com> + + +Building & Installing +--------------------- + +Csup should build and run fine under any *BSD OS (that includes FreeBSD, +NetBSD, OpenBSD and DragonFlyBSD), as well as Linux and Darwin. If you +have a problem building from source, drop me a mail! + +There is one Makefile specifically tailored for *BSD systems named +Makefile and another one that is gmake-specific for Darwin and Linux +users named GNUmakefile. You don't really need to worry about that +since whatever your "make" command is, it should pick up the correct +Makefile. + +As usual, to build the source code, just run "make". Once this is done, +just run "make install" to install the binary and manual page. + +Be warned however that if the packaging system of your OS knows about +csup, it is certainly better to install it from there rather than by +hand, so that it can then be properly deinstalled. diff --git a/contrib/csup/TODO b/contrib/csup/TODO new file mode 100644 index 000000000000..5f67831ff768 --- /dev/null +++ b/contrib/csup/TODO @@ -0,0 +1,34 @@ +$FreeBSD$ + +BUGS: + +- Fix every XXX in the code :-). +- The stream API needs some polishing. It needs proper error numbers + and a stream_error() function similar to the ferror() function. +- The yacc/lex code to parse the configuration file is sub-optimal. It + has global variables because of yacc, but I think it should be possible + to do it better by using YYFUNC_PROTOTYPE or something. I think it + should also be possible to completely get rid of the lex file. +- Some code uses getpwnam() while it should use the thread-safe variant, + getpwnam_r(). Same for getpwuid() and getgrgid(). We probably need a + UID/GID lookup cache here. +- The $Log$ CVS keyword is not supported. +- Add missing support for supfile keywords and add sanity checks for + some of them. Also, we're not supposed to choke on unknown keywords + to stay in line with CVSup, which just ignores them in order to + maintain compatibility with sup configuration files. + +MISSING FEATURES: + +- Add support for authentication. +- Add support for shell commands sent by the server. +- Add missing support for various CVSup options : -k, -d, -D, + -a (requires authentication support), -e and -E (requires shell + commands support) and the destDir parameter. +- For now, this code should build fine on FreeBSD, NetBSD, OpenBSD, + Linux and Darwin. Solaris support would also be nice at some point. +- Implement some new useful options : the ability to generate CVS + checkout files (files in CVS/ subdirectores), a command line override + to only update a specific collection and a third verbosity level to + display commit log messages. +- Add support for CVS mode (maybe?). diff --git a/contrib/csup/attrstack.c b/contrib/csup/attrstack.c new file mode 100644 index 000000000000..f5f56b34b5a3 --- /dev/null +++ b/contrib/csup/attrstack.c @@ -0,0 +1,90 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include <assert.h> +#include <stdlib.h> + +#include "attrstack.h" +#include "fattr.h" +#include "misc.h" + +#define ATTRSTACK_DEFSIZE 16 /* Initial size of the stack. */ + +struct attrstack { + struct fattr **stack; + size_t cur; + size_t size; +}; + +struct attrstack * +attrstack_new(void) +{ + struct attrstack *as; + + as = xmalloc(sizeof(struct attrstack)); + as->stack = xmalloc(sizeof(struct fattr *) * ATTRSTACK_DEFSIZE); + as->size = ATTRSTACK_DEFSIZE; + as->cur = 0; + return (as); +} + +struct fattr * +attrstack_pop(struct attrstack *as) +{ + + assert(as->cur > 0); + return (as->stack[--as->cur]); +} + +void +attrstack_push(struct attrstack *as, struct fattr *fa) +{ + + if (as->cur >= as->size) { + as->size *= 2; + as->stack = xrealloc(as->stack, + sizeof(struct fattr *) * as->size); + } + as->stack[as->cur++] = fa; +} + +size_t +attrstack_size(struct attrstack *as) +{ + + return (as->cur); +} + +void +attrstack_free(struct attrstack *as) +{ + + assert(as->cur == 0); + free(as->stack); + free(as); +} diff --git a/contrib/csup/attrstack.h b/contrib/csup/attrstack.h new file mode 100644 index 000000000000..721313c16bdb --- /dev/null +++ b/contrib/csup/attrstack.h @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ +#ifndef _ATTRSTACK_H_ +#define _ATTRSTACK_H_ + +struct fattr; +struct attrstack; + +struct attrstack *attrstack_new(void); +void attrstack_push(struct attrstack *, struct fattr *); +struct fattr *attrstack_pop(struct attrstack *); +size_t attrstack_size(struct attrstack *); +void attrstack_free(struct attrstack *); + +#endif /* !_ATTRSTACK_H_ */ diff --git a/contrib/csup/config.c b/contrib/csup/config.c new file mode 100644 index 000000000000..73be865b7999 --- /dev/null +++ b/contrib/csup/config.c @@ -0,0 +1,561 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "globtree.h" +#include "keyword.h" +#include "misc.h" +#include "parse.h" +#include "stream.h" +#include "token.h" + +static int config_parse_refusefiles(struct coll *); +static int config_parse_refusefile(struct coll *, char *); + +extern FILE *yyin; + +/* These are globals because I can't think of a better way with yacc. */ +static STAILQ_HEAD(, coll) colls; +static struct coll *cur_coll; +static struct coll *defaults; +static const char *cfgfile; + +/* + * Extract all the configuration information from the config + * file and some command line parameters. + */ +struct config * +config_init(const char *file, struct coll *override, int overridemask) +{ + struct config *config; + struct coll *coll; + size_t slen; + char *prefix; + int error; + mode_t mask; + + config = xmalloc(sizeof(struct config)); + memset(config, 0, sizeof(struct config)); + STAILQ_INIT(&colls); + + defaults = coll_new(NULL); + /* Set the default umask. */ + mask = umask(0); + umask(mask); + defaults->co_umask = mask; + + /* Extract a list of collections from the configuration file. */ + cur_coll = coll_new(defaults); + yyin = fopen(file, "r"); + if (yyin == NULL) { + lprintf(-1, "Cannot open \"%s\": %s\n", file, + strerror(errno)); + goto bad; + } + cfgfile = file; + error = yyparse(); + fclose(yyin); + if (error) + goto bad; + + memcpy(&config->colls, &colls, sizeof(colls)); + if (STAILQ_EMPTY(&config->colls)) { + lprintf(-1, "Empty supfile\n"); + goto bad; + } + + /* Fixup the list of collections. */ + STAILQ_FOREACH(coll, &config->colls, co_next) { + coll_override(coll, override, overridemask); + if (coll->co_base == NULL) + coll->co_base = xstrdup("/usr/local/etc/cvsup"); + if (coll->co_colldir == NULL) + coll->co_colldir = "sup"; + if (coll->co_prefix == NULL) { + coll->co_prefix = xstrdup(coll->co_base); + /* + * If prefix is not an absolute pathname, it is + * interpreted relative to base. + */ + } else if (coll->co_prefix[0] != '/') { + slen = strlen(coll->co_base); + if (slen > 0 && coll->co_base[slen - 1] != '/') + xasprintf(&prefix, "%s/%s", coll->co_base, + coll->co_prefix); + else + xasprintf(&prefix, "%s%s", coll->co_base, + coll->co_prefix); + free(coll->co_prefix); + coll->co_prefix = prefix; + } + coll->co_prefixlen = strlen(coll->co_prefix); + /* Determine whether to checksum RCS files or not. */ + if (coll->co_options & CO_EXACTRCS) + coll->co_options |= CO_CHECKRCS; + else + coll->co_options &= ~CO_CHECKRCS; + /* In recent versions, we always try to set the file modes. */ + coll->co_options |= CO_SETMODE; + /* XXX We don't support the rsync updating algorithm yet. */ + coll->co_options |= CO_NORSYNC; + error = config_parse_refusefiles(coll); + if (error) + goto bad; + } + + coll_free(cur_coll); + coll_free(defaults); + config->host = STAILQ_FIRST(&config->colls)->co_host; + return (config); +bad: + coll_free(cur_coll); + coll_free(defaults); + config_free(config); + return (NULL); +} + +int +config_checkcolls(struct config *config) +{ + char linkname[4]; + struct stat sb; + struct coll *coll; + int error, numvalid, ret; + + numvalid = 0; + STAILQ_FOREACH(coll, &config->colls, co_next) { + error = stat(coll->co_prefix, &sb); + if (error || !S_ISDIR(sb.st_mode)) { + /* Skip this collection, and warn about it unless its + prefix is a symbolic link pointing to "SKIP". */ + coll->co_options |= CO_SKIP; + ret = readlink(coll->co_prefix, linkname, + sizeof(linkname)); + if (ret != 4 || memcmp(linkname, "SKIP", 4) != 0) { + lprintf(-1,"Nonexistent prefix \"%s\" for " + "%s/%s\n", coll->co_prefix, coll->co_name, + coll->co_release); + } + continue; + } + numvalid++; + } + return (numvalid); +} + +static int +config_parse_refusefiles(struct coll *coll) +{ + char *collstem, *suffix, *supdir, *path; + int error; + + if (coll->co_colldir[0] == '/') + supdir = xstrdup(coll->co_colldir); + else + xasprintf(&supdir, "%s/%s", coll->co_base, coll->co_colldir); + + /* First, the global refuse file that applies to all collections. */ + xasprintf(&path, "%s/refuse", supdir); + error = config_parse_refusefile(coll, path); + free(path); + if (error) { + free(supdir); + return (error); + } + + /* Next the per-collection refuse files that applies to all release/tag + combinations. */ + xasprintf(&collstem, "%s/%s/refuse", supdir, coll->co_name); + free(supdir); + error = config_parse_refusefile(coll, collstem); + if (error) { + free(collstem); + return (error); + } + + /* Finally, the per-release and per-tag refuse file. */ + suffix = coll_statussuffix(coll); + if (suffix != NULL) { + xasprintf(&path, "%s%s", collstem, suffix); + free(suffix); + error = config_parse_refusefile(coll, path); + free(path); + } + free(collstem); + return (error); +} + +/* + * Parses a "refuse" file, and records the relevant information in + * coll->co_refusals. If the file does not exist, it is silently + * ignored. + */ +static int +config_parse_refusefile(struct coll *coll, char *path) +{ + struct stream *rd; + char *line; + + rd = stream_open_file(path, O_RDONLY); + if (rd == NULL) + return (0); + while ((line = stream_getln(rd, NULL)) != NULL) + pattlist_add(coll->co_refusals, line); + if (!stream_eof(rd)) { + stream_close(rd); + lprintf(-1, "Read failure from \"%s\": %s\n", path, + strerror(errno)); + return (-1); + } + stream_close(rd); + return (0); +} + +void +config_free(struct config *config) +{ + struct coll *coll; + + while (!STAILQ_EMPTY(&config->colls)) { + coll = STAILQ_FIRST(&config->colls); + STAILQ_REMOVE_HEAD(&config->colls, co_next); + coll_free(coll); + } + if (config->server != NULL) + stream_close(config->server); + if (config->laddr != NULL) + free(config->laddr); + free(config); +} + +/* Create a new collection, inheriting options from the default collection. */ +struct coll * +coll_new(struct coll *def) +{ + struct coll *new; + + new = xmalloc(sizeof(struct coll)); + memset(new, 0, sizeof(struct coll)); + if (def != NULL) { + new->co_options = def->co_options; + new->co_umask = def->co_umask; + if (def->co_host != NULL) + new->co_host = xstrdup(def->co_host); + if (def->co_base != NULL) + new->co_base = xstrdup(def->co_base); + if (def->co_date != NULL) + new->co_date = xstrdup(def->co_date); + if (def->co_prefix != NULL) + new->co_prefix = xstrdup(def->co_prefix); + if (def->co_release != NULL) + new->co_release = xstrdup(def->co_release); + if (def->co_tag != NULL) + new->co_tag = xstrdup(def->co_tag); + if (def->co_listsuffix != NULL) + new->co_listsuffix = xstrdup(def->co_listsuffix); + } else { + new->co_tag = xstrdup("."); + new->co_date = xstrdup("."); + } + new->co_keyword = keyword_new(); + new->co_accepts = pattlist_new(); + new->co_refusals = pattlist_new(); + new->co_attrignore = FA_DEV | FA_INODE; + return (new); +} + +void +coll_override(struct coll *coll, struct coll *from, int mask) +{ + size_t i; + int newoptions, oldoptions; + + newoptions = from->co_options & mask; + oldoptions = coll->co_options & (CO_MASK & ~mask); + + if (from->co_release != NULL) { + if (coll->co_release != NULL) + free(coll->co_release); + coll->co_release = xstrdup(from->co_release); + } + if (from->co_host != NULL) { + if (coll->co_host != NULL) + free(coll->co_host); + coll->co_host = xstrdup(from->co_host); + } + if (from->co_base != NULL) { + if (coll->co_base != NULL) + free(coll->co_base); + coll->co_base = xstrdup(from->co_base); + } + if (from->co_colldir != NULL) + coll->co_colldir = from->co_colldir; + if (from->co_prefix != NULL) { + if (coll->co_prefix != NULL) + free(coll->co_prefix); + coll->co_prefix = xstrdup(from->co_prefix); + } + if (newoptions & CO_CHECKOUTMODE) { + if (from->co_tag != NULL) { + if (coll->co_tag != NULL) + free(coll->co_tag); + coll->co_tag = xstrdup(from->co_tag); + } + if (from->co_date != NULL) { + if (coll->co_date != NULL) + free(coll->co_date); + coll->co_date = xstrdup(from->co_date); + } + } + if (from->co_listsuffix != NULL) { + if (coll->co_listsuffix != NULL) + free(coll->co_listsuffix); + coll->co_listsuffix = xstrdup(from->co_listsuffix); + } + for (i = 0; i < pattlist_size(from->co_accepts); i++) { + pattlist_add(coll->co_accepts, + pattlist_get(from->co_accepts, i)); + } + for (i = 0; i < pattlist_size(from->co_refusals); i++) { + pattlist_add(coll->co_refusals, + pattlist_get(from->co_refusals, i)); + } + coll->co_options = oldoptions | newoptions; +} + +char * +coll_statussuffix(struct coll *coll) +{ + const char *tag; + char *suffix; + + if (coll->co_listsuffix != NULL) { + xasprintf(&suffix, ".%s", coll->co_listsuffix); + } else if (coll->co_options & CO_USERELSUFFIX) { + if (coll->co_tag == NULL) + tag = "."; + else + tag = coll->co_tag; + if (coll->co_release != NULL) { + if (coll->co_options & CO_CHECKOUTMODE) { + xasprintf(&suffix, ".%s:%s", + coll->co_release, tag); + } else { + xasprintf(&suffix, ".%s", coll->co_release); + } + } else if (coll->co_options & CO_CHECKOUTMODE) { + xasprintf(&suffix, ":%s", tag); + } + } else + suffix = NULL; + return (suffix); +} + +char * +coll_statuspath(struct coll *coll) +{ + char *path, *suffix; + + suffix = coll_statussuffix(coll); + if (suffix != NULL) { + if (coll->co_colldir[0] == '/') + xasprintf(&path, "%s/%s/checkouts%s", coll->co_colldir, + coll->co_name, suffix); + else + xasprintf(&path, "%s/%s/%s/checkouts%s", coll->co_base, + coll->co_colldir, coll->co_name, suffix); + } else { + if (coll->co_colldir[0] == '/') + xasprintf(&path, "%s/%s/checkouts", coll->co_colldir, + coll->co_name); + else + xasprintf(&path, "%s/%s/%s/checkouts", coll->co_base, + coll->co_colldir, coll->co_name); + } + free(suffix); + return (path); +} + +void +coll_add(char *name) +{ + struct coll *coll; + + cur_coll->co_name = name; + if (cur_coll->co_release == NULL) { + lprintf(-1, "Release not specified for collection " + "\"%s\"\n", cur_coll->co_name); + exit(1); + } + if (cur_coll->co_host == NULL) { + lprintf(-1, "Host not specified for collection " + "\"%s\"\n", cur_coll->co_name); + exit(1); + } + if (!(cur_coll->co_options & CO_CHECKOUTMODE)) { + lprintf(-1, "Client only supports checkout mode\n"); + exit(1); + } + if (!STAILQ_EMPTY(&colls)) { + coll = STAILQ_LAST(&colls, coll, co_next); + if (strcmp(coll->co_host, cur_coll->co_host) != 0) { + lprintf(-1, "All \"host\" fields in the supfile " + "must be the same\n"); + exit(1); + } + } + STAILQ_INSERT_TAIL(&colls, cur_coll, co_next); + cur_coll = coll_new(defaults); +} + +void +coll_free(struct coll *coll) +{ + + if (coll == NULL) + return; + if (coll->co_host != NULL) + free(coll->co_host); + if (coll->co_base != NULL) + free(coll->co_base); + if (coll->co_date != NULL) + free(coll->co_date); + if (coll->co_prefix != NULL) + free(coll->co_prefix); + if (coll->co_release != NULL) + free(coll->co_release); + if (coll->co_tag != NULL) + free(coll->co_tag); + if (coll->co_cvsroot != NULL) + free(coll->co_cvsroot); + if (coll->co_name != NULL) + free(coll->co_name); + if (coll->co_listsuffix != NULL) + free(coll->co_listsuffix); + keyword_free(coll->co_keyword); + if (coll->co_dirfilter != NULL) + globtree_free(coll->co_dirfilter); + if (coll->co_dirfilter != NULL) + globtree_free(coll->co_filefilter); + if (coll->co_accepts != NULL) + pattlist_free(coll->co_accepts); + if (coll->co_refusals != NULL) + pattlist_free(coll->co_refusals); + free(coll); +} + +void +coll_setopt(int opt, char *value) +{ + struct coll *coll; + + coll = cur_coll; + switch (opt) { + case PT_HOST: + if (coll->co_host != NULL) + free(coll->co_host); + coll->co_host = value; + break; + case PT_BASE: + if (coll->co_base != NULL) + free(coll->co_base); + coll->co_base = value; + break; + case PT_DATE: + if (coll->co_date != NULL) + free(coll->co_date); + coll->co_date = value; + coll->co_options |= CO_CHECKOUTMODE; + break; + case PT_PREFIX: + if (coll->co_prefix != NULL) + free(coll->co_prefix); + coll->co_prefix = value; + break; + case PT_RELEASE: + if (coll->co_release != NULL) + free(coll->co_release); + coll->co_release = value; + break; + case PT_TAG: + if (coll->co_tag != NULL) + free(coll->co_tag); + coll->co_tag = value; + coll->co_options |= CO_CHECKOUTMODE; + break; + case PT_LIST: + if (strchr(value, '/') != NULL) { + lprintf(-1, "Parse error in \"%s\": \"list\" suffix " + "must not contain slashes\n", cfgfile); + exit(1); + } + if (coll->co_listsuffix != NULL) + free(coll->co_listsuffix); + coll->co_listsuffix = value; + break; + case PT_UMASK: + errno = 0; + coll->co_umask = strtol(value, NULL, 8); + free(value); + if (errno) { + lprintf(-1, "Parse error in \"%s\": Invalid " + "umask value\n", cfgfile); + exit(1); + } + break; + case PT_USE_REL_SUFFIX: + coll->co_options |= CO_USERELSUFFIX; + break; + case PT_DELETE: + coll->co_options |= CO_DELETE | CO_EXACTRCS; + break; + case PT_COMPRESS: + coll->co_options |= CO_COMPRESS; + break; + } +} + +/* Set "coll" as being the default collection. */ +void +coll_setdef(void) +{ + + coll_free(defaults); + defaults = cur_coll; + cur_coll = coll_new(defaults); +} diff --git a/contrib/csup/config.h b/contrib/csup/config.h new file mode 100644 index 000000000000..aa12cb604c41 --- /dev/null +++ b/contrib/csup/config.h @@ -0,0 +1,124 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <time.h> + +#include "fattr.h" +#include "queue.h" +#include "misc.h" + +/* + * Collection options. + */ +#define CO_BACKUP 0x00000001 +#define CO_DELETE 0x00000002 +#define CO_KEEP 0x00000004 +#define CO_OLD 0x00000008 +#define CO_UNLINKBUSY 0x00000010 +#define CO_NOUPDATE 0x00000020 +#define CO_COMPRESS 0x00000040 +#define CO_USERELSUFFIX 0x00000080 +#define CO_EXACTRCS 0x00000100 +#define CO_CHECKRCS 0x00000200 +#define CO_SKIP 0x00000400 +#define CO_CHECKOUTMODE 0x00000800 +#define CO_NORSYNC 0x00001000 +#define CO_KEEPBADFILES 0x00002000 +#define CO_EXECUTE 0x00004000 +#define CO_SETOWNER 0x00008000 +#define CO_SETMODE 0x00010000 +#define CO_SETFLAGS 0x00020000 +#define CO_NORCS 0x00040000 +#define CO_STRICTCHECKRCS 0x00080000 +#define CO_TRUSTSTATUSFILE 0x00100000 +#define CO_DODELETESONLY 0x00200000 +#define CO_DETAILALLRCSFILES 0x00400000 + +#define CO_MASK 0x007fffff + +/* Options that the server is allowed to set. */ +#define CO_SERVMAYSET (CO_SKIP | CO_NORSYNC | CO_NORCS) +/* Options that the server is allowed to clear. */ +#define CO_SERVMAYCLEAR CO_CHECKRCS + +struct coll { + char *co_name; + char *co_host; + char *co_base; + char *co_date; + char *co_prefix; + size_t co_prefixlen; + char *co_release; + char *co_tag; + char *co_cvsroot; + int co_attrignore; + struct pattlist *co_accepts; + struct pattlist *co_refusals; + struct globtree *co_dirfilter; + struct globtree *co_filefilter; + const char *co_colldir; + char *co_listsuffix; + time_t co_scantime; /* Set by the detailer thread. */ + int co_options; + mode_t co_umask; + struct keyword *co_keyword; + STAILQ_ENTRY(coll) co_next; +}; + +struct config { + STAILQ_HEAD(, coll) colls; + struct fixups *fixups; + char *host; + struct sockaddr *laddr; + socklen_t laddrlen; + int socket; + struct chan *chan0; + struct chan *chan1; + struct stream *server; + fattr_support_t fasupport; +}; + +struct config *config_init(const char *, struct coll *, int); +int config_checkcolls(struct config *); +void config_free(struct config *); + +struct coll *coll_new(struct coll *); +void coll_override(struct coll *, struct coll *, int); +char *coll_statuspath(struct coll *); +char *coll_statussuffix(struct coll *); +void coll_add(char *); +void coll_free(struct coll *); +void coll_setdef(void); +void coll_setopt(int, char *); + +#endif /* !_CONFIG_H_ */ diff --git a/contrib/csup/csup.1 b/contrib/csup/csup.1 new file mode 100644 index 000000000000..7a80c01abef0 --- /dev/null +++ b/contrib/csup/csup.1 @@ -0,0 +1,880 @@ +.\" Copyright 1996-2003 John D. Polstra. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $Id: cvsup.1,v 1.70 2003/03/04 18:23:46 jdp Exp $ +.\" $FreeBSD$ +.\" +.Dd February 1, 2006 +.Os FreeBSD +.Dt CSUP 1 +.Sh NAME +.Nm csup +.Nd network distribution package for CVS repositories +.Sh SYNOPSIS +.Nm +.Op Fl 146svzZ +.Op Fl A Ar addr +.Op Fl b Ar base +.Op Fl c Ar collDir +.Op Fl h Ar host +.Op Fl i Ar pattern +.Op Fl l Ar lockfile +.Op Fl L Ar verbosity +.Op Fl p Ar port +.Op Fl r Ar maxRetries +.Ar supfile +.Sh DESCRIPTION +.Nm +is a software package for updating collections of files across a network. +It is a rewrite of the +.Nm CVSup +software in C. +This manual page describes the usage of the +.Nm +client program. +.Pp +Unlike more traditional network distribution packages, such as +.Nm rdist +and +.Nm sup , +.Nm +has specific optimizations for distributing CVS repositories. +.Nm +takes advantage of the properties of CVS repositories and the files they +contain (in particular, RCS files), enabling it to perform updates much +faster than traditional systems. +.Pp +.Nm +is a general-purpose network file updating package. +It is extremely fast, +even for collections of files which have nothing to do with CVS or +RCS. +.Sh OPTIONS +The client program +.Nm +requires at least a single argument, +.Ar supfile . +It names a file describing one or more collections of files to be +transferred and/or updated from the server. +The +.Ar supfile +has a format similar to the corresponding file used by +.Nm sup . +In most cases, +.Nm +can use existing +.Nm sup Ar supfiles . +.Pp +The following options are supported by +.Nm : +.Bl -tag -width Fl +.It Fl 1 +Disables automatic retries when transient failures occur. +Without this option, a transient failure such as a dropped network +connection causes +.Nm +to retry repeatedly, using randomized exponential backoff to space the +retries. +This option is equivalent to +.Fl r Cm 0 . +.It Fl 4 +Forces +.Nm +to use IPv4 addresses only. +.It Fl 6 +Forces +.Nm +to use IPv6 addresses only. +.It Fl A Ar addr +Specifies a local address to bind to when connecting to the server. +The local address might be a hostname or a numeric host address string +consisting of a dotted decimal IPv4 address or an IPv6 address. +This may be useful on hosts which have multiple IP addresses. +.It Fl b Ar base +Specifies the base directory under which +.Nm +will maintain its bookkeeping files, overriding any +.Cm base +specifications in the +.Ar supfile . +.It Fl c Ar collDir +Specifies the subdirectory of +.Ar base +where the information about the collections is maintained. +The default is +.Pa sup . +.It Fl h Ar host +Specifies the server host to contact, overriding any +.Cm host +specifications in the +.Ar supfile . +.It Fl i Ar pattern +Causes +.Nm +to include only files and directories matching +.Ar pattern +in the update. If a directory matches the pattern, then the entire +subtree rooted at the directory is included. If this option is +specified multiple times, the patterns are combined using the +.Ql or +operation. If no +.Fl i +options are given, the default is to update all files in each +collection. +.Pp +The +.Ar pattern +is a standard file name pattern. +It is interpreted relative to the collection's prefix directory. +Slash characters are matched only by explicit slashes in the pattern. +Leading periods in file name are not treated specially. +.It Fl l Ar lockfile +Creates and locks the +.Ar lockfile +while the update is in progress. +If +.Ar lockfile +is already locked, +.Nm +fails without performing automatic retries. +This option is useful when +.Nm +is executed periodically from +.Nm cron . +It prevents a job from interfering with an earlier job that is perhaps +taking extra long because of network problems. +.Pp +The process-ID is written to the lock file in text form when the lock +is successfully acquired. +Upon termination of the update, the lock file is removed. +.It Fl L Ar verbosity +Sets the verbosity level for output. +A level of 0 causes +.Nm +to be completely silent unless errors occur. +A level of 1 (the default) causes each updated file to be listed. +A level of 2 provides more detailed information about the updates +performed on each file. +All messages are directed to the standard output. +.It Fl p Ar port +Sets the TCP port to which +.Nm +attempts to connect on the server host. +The default port is 5999. +.It Fl r Ar maxRetries +Limits the number of automatic retries that will be attempted when +transient errors such as lost network connections are encountered. +By default, +.Nm +will retry indefinitely until an update is successfully completed. +The retries are spaced using randomized exponential backoff. +Note that +.Fl r Cm 0 +is equivalent to the +.Fl 1 +option. +.It Fl s +Suppresses the check of each client file's status against what is +recorded in the list file. Instead, the list file is assumed to be +accurate. This option greatly reduces the amount of disk activity and +results in faster updates with less load on the client host. However +it should only be used if client's files are never modified locally in +any way. Mirror sites may find this option beneficial to reduce the +disk load on their systems. For safety, even mirror sites should run +.Nm +occasionally (perhaps once a day) without the +.Fl s +option. +.Pp +Without the +.Fl s +option, +.Nm +performs a +.Xr stat 2 +call on each file and verifies that its attributes match those +recorded in the list file. This ensures that any file changes made +outside of +.Nm +are detected and corrected. +.Pp +If the +.Fl s +option is used when one or more files have been modified locally, the +results are undefined. Local file damage may remain uncorrected, +updates may be missed, or +.Nm +may abort prematurely. +.It Fl v +Prints the version number and exits, without contacting the server. +.It Fl z +Enables compression for all collections, as if the +.Cm compress +keyword were added to every collection in the +.Ar supfile . +.It Fl Z +Disables compression for all collections, as if the +.Cm compress +keyword were removed from every collection in the +.Ar supfile . +.El +.Pp +The +.Ar supfile +is a text file which specifies the file collections to be updated. +Comments begin with +.Ql # +and extend to the end of the line. Lines that are empty except for +comments and white space are ignored. Each remaining line begins +with the name of a server-defined collection of files. Following the +collection name on the line are zero or more keywords or keyword=value +pairs. +.Pp +Default settings may be specified in lines whose collection name is +.Cm *default . +Such defaults will apply to subsequent lines in the +.Ar supfile . +Multiple +.Cm *default +lines may be present. +New values augment or override any defaults specified earlier in the +.Ar supfile . +Values specified explicitly for a collection override any default +values. +.Pp +The most commonly used keywords are: +.Bl -tag -width Fl +.It Cm release= Ns Ar releaseName +This specifies the release of the files within a collection. +Like collection names, release names are defined by the server +configuration files. Usually there is only one release in each +collection, but there may be any number. Collections which come from +a CVS repository often use +.Cm release=cvs +by convention. Non-CVS collections conventionally use +.Cm release=current . +.It Cm base= Ns Ar base +This specifies a directory under which +.Nm +will maintain its bookkeeping files, describing the state of each +collection on the client machine. +The +.Ar base +directory must already exist; +.Nm +will not create it. +The default +.Ar base +directory is +.Pa /usr/local/etc/csup . +.It Cm prefix= Ns Ar prefix +This is the directory under which updated files will be placed. +By default, it is the same as +.Ar base . +If it is not an absolute pathname, it is interpreted relative to +.Ar base . +The +.Ar prefix +directory must already exist; +.Nm +will not create it. +.Pp +As a special case, if +.Ar prefix +is a symbolic link pointing to a nonexistent file named +.Ql SKIP , +then +.Nm +will skip the collection. +The parameters associated with the collection are still checked for +validity, but none of its files will be updated. +This feature allows a site to use a standard +.Ar supfile +on several machines, yet control which collections get updated on a +per-machine basis. +.It Cm host= Ns Ar hostname +This specifies the server machine from which all files will be taken. +.Nm +requires that all collections in a single run come from the same host. +If you wish to update collections from several different hosts, you must +run +.Nm +several times. +.It Cm delete +The presence of this keyword gives +.Nm +permission to delete files. +If it is missing, no files will be deleted. +.Pp +The presence of the +.Cm delete +keyword puts +.Nm +into so-called +.Em exact +mode. In exact mode, +.Nm +does its best to make the client's files correspond to those on the server. +This includes deleting individual deltas and symbolic tags from RCS +files, as well as deleting entire files. +In exact mode, +.Nm +verifies every edited file with a checksum, to ensure that the edits +have produced a file identical to the master copy on the server. +If the checksum test fails for a file, then +.Nm +falls back upon transferring the entire file. +.Pp +In general, +.Nm +deletes only files which are known to the server. +Extra files present in the client's tree are left alone, even in exact +mode. +More precisely, +.Nm +is willing to delete two classes of files: +.Bl -bullet -compact +.It +Files that were previously created or updated by +.Nm +itself. +.It +Checked-out versions of files which are marked as dead on the server. +.El +.It Cm use-rel-suffix +Causes +.Nm +to append a suffix constructed from the release and tag to the name of +each list file that it maintains. +See +.Sx THE LIST FILE +for details. +.It Cm compress +This enables compression of all data sent across the network. +Compression is quite effective, normally eliminating 65% to 75% of the +bytes that would otherwise need to be transferred. +However, it is costly in terms of CPU time on both the client and the +server. +On local area networks, compression is generally counter-productive; it +actually slows down file updates. +On links with speeds of 56K bits/second or less, compression is almost +always beneficial. +For network links with speeds between these two extremes, let +experimentation be your guide. +.Pp +The +.Fl z +command line option enables the +.Cm compress +keyword for all collections, regardless of what is specified in the supfile. +Likewise, the +.Fl Z +command line option disables the +.Cm compress +option for all collections. +.Nm +uses a looser checksum for RCS files, which ignores harmless +differences in white space. Different versions of CVS and RCS produce +a variety of differences in white space for the same RCS files. Thus +the strict checksum can report spurious mismatches for files which are +logically identical. This can lead to numerous unneeded +.Dq fixups , +and thus to slow updates. +.It Cm umask= Ns Ar n +Causes +.Nm +to use a umask value of +.Ar n +(an octal number) when updating the files in the collection. +This option is ignored if +.Cm preserve +is specified. +.El +.Pp +Some additional, more specialized keywords are described below. +Unrecognized keywords are silently ignored for backward compatibility +with +.Nm sup . +.Sh CVS MODE +.Nm CVSup +supports two primary modes of operation. +They are called +.Em CVS +mode and +.Em checkout +mode. +.Nm +only supports the checkout mode for now. +.Pp +In CVS mode, the client receives copies of the actual RCS files making +up the master CVS repository. CVS mode is the default mode of operation. +It is appropriate when the user wishes to maintain a full copy of the +CVS repository on the client machine. +.Pp +CVS mode is also appropriate for file collections which are not +based upon a CVS repository. The files are simply transferred +verbatim, without interpretation. +.Sh CHECKOUT MODE +In checkout mode, the client receives specific revisions of files, +checked out directly from the server's CVS repository. +Checkout mode allows the client to receive any version from the +repository, without requiring any extra disk space on the server for +storing multiple versions in checked-out form. +Checkout mode provides much flexibility beyond that basic functionality, +however. +The client can specify any CVS symbolic tag, or any date, or both, and +.Nm +will provide the corresponding checked-out versions of the files in the +repository. +.Pp +Checkout mode is selected on a per-collection basis, by the presence of +one or both of the following keywords in the +.Ar supfile : +.Bl -tag -width Fl +.It Cm tag= Ns Ar tagname +This specifies a symbolic tag that should be used to select the +revisions that are checked out from the CVS repository. +The tag may refer to either a branch or a specific revision. +It must be symbolic; numeric revision numbers are not supported. +.Pp +For the FreeBSD source repository, the most commonly used tags will be: +.Bl -tag -width RELENG_6 +.It Li RELENG_6 +The +.Ql stable +branch. +.It Li \&. +The main branch (the +.Ql current +release). +This is the default, if only the +.Cm date +keyword is given. +.El +.Sm off +.It Xo Cm date= +.Op Ar cc +.Ar yy.mm.dd.hh.mm.ss +.Xc +.Sm on +This specifies a date that should be used to select the revisions that +are checked out from the CVS repository. +The client will receive the revisions that were in effect at the +specified date and time. +.Pp +At present, the date format is inflexible. All 17 or 19 characters must +be specified, exactly as shown. +For the years 2000 and beyond, specify the century +.Ar cc . +For earlier years, specify only the last two digits +.Ar yy . +Dates and times are considered to +be GMT. +The default date is +.Ql \&. , +which means +.Dq as late as possible . +.El +.Pp +To enable checkout mode, you must specify at least one of these keywords. +If both are missing, +.Nm +defaults to CVS mode. +.Pp +If both a branch tag and a date are specified, then the revisions on the +given branch, as of the given date, will be checked out. It is +permitted, but not particularly useful, to specify a date with a +specific release tag. +.Pp +In checkout mode, the tag and/or date may be changed between updates. +For example, suppose that a collection has been transferred using the +specification +.Ql tag=. . +The user could later change the specification to +.Ql tag=RELENG_3 . +This would cause +.Nm +to edit the checked-out files in such a way as to transform them from the +.Ql current +versions to the +.Ql stable +versions. +In general, +.Nm +is willing to transform any tag/date combination into any other tag/date +combination, by applying the intervening RCS deltas to the existing files. +.Pp +When transforming a collection of checked-out files from one tag to +another, it is important to specify the +.Cm list +keyword in the +.Ar supfile , +to ensure that the same list file is used both before and after the +transformation. +The list file is described in +.Sx THE LIST FILE , +below. +.Sh THE LIST FILE +For efficiency, +.Nm +maintains a bookkeeping file for each collection, called the list file. +The list file contains information about which files and revisions the client +currently possesses. +It also contains information used for verifying that the list file +is consistent with the actual files in the client's tree. +.Pp +The list file is not strictly necessary. If it is deleted, or becomes +inconsistent with the actual client files, +.Nm +falls back upon a less efficient method of identifying the client's +files and performing its updates. +Depending on +.Nm csup Ns No 's +mode of operation, the fallback method employs time stamps, checksums, or +analysis of RCS files. +.Pp +Because the list file is not essential, +.Nm +is able to +.Dq adopt +an existing file tree acquired by FTP or from a CD-ROM. +.Nm +identifies the client's versions of the files, updates them as +necessary, and creates a list file for future use. +Adopting a foreign file tree is not as fast as performing a normal +update. +It also produces a heavier load on the server. +.Pp +The list file is stored in a collection-specific directory; see +.Sx FILES +for details. +Its name always begins with +.Ql checkouts . +If the keyword +.Cm use-rel-suffix +is specified in the +.Ar supfile , +a suffix, formed from the release and tag, is appended to the name. +The default suffix can be overridden by specifying an explicit suffix in +the +.Ar supfile : +.Bl -tag -width Fl +.It Cm list= Ns Ar suffix +This specifies a suffix for the name of the list file. A leading dot is +provided automatically. +For example, +.Ql list=stable +would produce a list file named +.Pa checkouts.stable , +regardless of the release, tag, or +.Cm use-rel-suffix +keyword. +.El +.Sh REFUSE FILES +The user can specify sets of files that he does not wish to receive. +The files are specified as file name patterns in so-called +.Em refuse +files. +The patterns are separated by whitespace, and multiple patterns are +permitted on each line. +Files and directories matching the patterns are neither updated nor +deleted; they are simply ignored. +.Pp +There is currently no provision for comments in refuse files. +.Pp +The patterns are similar to those of +.Xr sh 1 , +except that there is no special treatment for slashes or for +filenames that begin with a period. +For example, the pattern +.Ql *.c +will match any file name ending with +.Ql \&.c +including those in subdirectories, such as +.Ql foo/bar/lam.c . +All patterns are interpreted relative to the collection's prefix +directory. +.Pp +If the files are coming from a CVS repository, as is usually +the case, then they will be RCS files. These have a +.Ql \&,v +suffix which must be taken into account in the patterns. For +example, the FreeBSD documentation files are in a sub-directory of +.Ar base +called +.Ql doc . +If +.Ql Makefile +from that directory is not required then the line +.Pp +.Bl -item -compact -offset indent +.It +.Pa doc/Makefile +.El +.Pp +will not work because the file on the server is called +.Ql Makefile,v. +A better solution would be +.Pp +.Bl -item -compact -offset indent +.It +.Pa doc/Makefile* +.El +.Pp +which will match whether +.Ql Makefile +is an RCS file or not. +.Pp +As another example, to receive the FreeBSD documentation files without +the Japanese, Russian, and Chinese translations, create a refuse file +containing the following lines: +.Pp +.Bl -item -compact -offset indent +.It +.Pa doc/ja* +.It +.Pa doc/ru* +.It +.Pa doc/zh* +.El +.Pp +As many as three refuse files are examined for each +.Ar supfile +line. +There can be a global refuse file named +.Sm off +.Ar base / Ar collDir Pa /refuse +.Sm on +which applies to all collections and releases. +There can be a per-collection refuse file named +.Sm off +.Xo Ar base / Ar collDir / Ar collection +.Pa /refuse +.Xc +.Sm on +which applies to a specific collection. +Finally, there can be a per-release and tag refuse file which applies only +to a given release/tag combination within a collection. +The name of the latter is formed by suffixing the name of the +per-collection refuse file in the same manner as described above for the +list file. +None of the refuse files are required to exist. +.Pp +.Nm +has a built-in default value of +.Ar /usr/local/etc/cvsup +for +.Ar base +and +.Ar sup +for +.Ar collDir +but it is possible to override both of these. The value of +.Ar base +can be changed using the +.Fl b +option or a +.Ar base=pathname +entry in the +.Ar supfile . +(If both are used the +.Fl b +option will override the +.Ar supfile +entry.) The value of +.Ar collDir +can only be changed with the +.Fl c +option; there is no +.Ar supfile +command to change it. +.Pp +As an example, suppose that the +.Ar base +and +.Ar collDir +both have their default values, and that the collection and release are +.Ql src-all +and +.Ql cvs , +respectively. +Assume further that checkout mode is being used with +.Ql tag=RELENG_3 . +The three possible refuse files would then be named: +.Pp +.Bl -item -compact -offset indent +.It +.Pa /usr/local/etc/cvsup/sup/refuse +.It +.Pa /usr/local/etc/cvsup/sup/src-all/refuse +.It +.Pa /usr/local/etc/cvsup/sup/src-all/refuse.cvs:RELENG_3 +.El +.Pp +If the +.Ar supfile +includes the command +.Ar base=/foo +the refuse files would be: +.Pp +.Bl -item -compact -offset indent +.It +.Pa /foo/sup/refuse +.It +.Pa /foo/sup/src-all/refuse +.It +.Pa /foo/sup/src-all/refuse.cvs:RELENG_3 +.El +.Pp +If +.Fl b +.Ar /bar +is used (even with +.Ar base=/foo +in the +.Ar supfile ) : +.Pp +.Bl -item -compact -offset indent +.It +.Pa /bar/sup/refuse +.It +.Pa /bar/sup/src-all/refuse +.It +.Pa /bar/sup/src-all/refuse.cvs:RELENG_3 +.El +.Pp +and with +.Fl c +.Ar stool +as well: +.Pp +.Bl -item -compact -offset indent +.It +.Pa /bar/stool/refuse +.It +.Pa /bar/stool/src-all/refuse +.It +.Pa /bar/stool/src-all/refuse.cvs:RELENG_3 +.El +.Sh csup AND FIREWALLS +In its default mode, +.Nm +will work through any firewall which permits outbound connections to +port 5999 of the server host. +.Sh USING csup WITH SOCKS +.Nm +can be used through a SOCKS proxy server with the standard +.Nm runsocks +command. +Your +.Nm +executable needs to be dynamically-linked with the system +libraries for +.Nm runsocks +to work properly. +.Sh USING ssh PORT FORWARDING +As an alternative to SOCKS, a user behind a firewall can penetrate it +with the TCP port forwarding provided by the Secure Shell package +.Nm ssh . +The user must have a login account on the +.Nm CVSup +server host in order to do this. +The procedure is as follows: +.Bl -enum +.It +Establish a connection to the server host with +.Nm ssh , +like this: +.Bd -literal +ssh -f -x -L 5999:localhost:5999 serverhost sleep 60 +.Ed +.Pp +Replace +.Ar serverhost +with the hostname of the CVSup server, but type +.Ql localhost +literally. +This sets up the required port forwarding. +You must start +.Nm +before the 60-second +.Nm sleep +finishes. +Once the update has begun, +.Nm ssh +will keep the forwarded channels open as long as they are needed. +.It +Run +.Nm +on the local host, including the arguments +.Ql -h localhost +on the command line. +.El +.Sh FILES +.Bl -tag -width base/sup/collection/checkouts*xx -compact +.It Pa /usr/local/etc/cvsup +Default +.Ar base +directory. +.It Pa sup +Default +.Ar collDir +subdirectory. +.Sm off +.It Xo Ar base / Ar collDir / Ar collection +.Pa /checkouts* +.Xc +.Sm on +List files. +.El +.Sh SEE ALSO +.Xr cvs 1 , +.Xr rcsintro 1 , +.Xr ssh 1 . +.Pp +.Bd -literal +http://mu.org/~mux/csup.html +.Ed +.Sh AUTHORS +.An -nosplit +.An Maxime Henrion Aq mux@FreeBSD.org +is the author of +.Nm , +the rewrite of +.Nm CVSup +in C. +.An John Polstra Aq jdp@polstra.com +is the author of +.Nm CVSup . +.Sh LEGALITIES +CVSup is a registered trademark of John D. Polstra. +.Pp +.Nm +is released under a 2-clauses BSD license. +.Sh BUGS +An RCS file is not recognized as such unless its name ends with +.Ql \&,v . +.Pp +Any directory named +.Ql Attic +is assumed to be a CVS Attic, and is treated specially. diff --git a/contrib/csup/detailer.c b/contrib/csup/detailer.c new file mode 100644 index 000000000000..bf2ddb74a502 --- /dev/null +++ b/contrib/csup/detailer.c @@ -0,0 +1,339 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "detailer.h" +#include "fixups.h" +#include "misc.h" +#include "mux.h" +#include "proto.h" +#include "status.h" +#include "stream.h" + +/* Internal error codes. */ +#define DETAILER_ERR_PROTO (-1) /* Protocol error. */ +#define DETAILER_ERR_MSG (-2) /* Error is in detailer->errmsg. */ +#define DETAILER_ERR_READ (-3) /* Error reading from server. */ +#define DETAILER_ERR_WRITE (-4) /* Error writing to server. */ + +struct detailer { + struct config *config; + struct stream *rd; + struct stream *wr; + char *errmsg; +}; + +static int detailer_batch(struct detailer *); +static int detailer_coll(struct detailer *, struct coll *, + struct status *); +static int detailer_dofile(struct detailer *, struct coll *, + struct status *, char *); + +void * +detailer(void *arg) +{ + struct thread_args *args; + struct detailer dbuf, *d; + int error; + + args = arg; + + d = &dbuf; + d->config = args->config; + d->rd = args->rd; + d->wr = args->wr; + d->errmsg = NULL; + + error = detailer_batch(d); + switch (error) { + case DETAILER_ERR_PROTO: + xasprintf(&args->errmsg, "Detailer failed: Protocol error"); + args->status = STATUS_FAILURE; + break; + case DETAILER_ERR_MSG: + xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg); + free(d->errmsg); + args->status = STATUS_FAILURE; + break; + case DETAILER_ERR_READ: + if (stream_eof(d->rd)) { + xasprintf(&args->errmsg, "Detailer failed: " + "Premature EOF from server"); + } else { + xasprintf(&args->errmsg, "Detailer failed: " + "Network read failure: %s", strerror(errno)); + } + args->status = STATUS_TRANSIENTFAILURE; + break; + case DETAILER_ERR_WRITE: + xasprintf(&args->errmsg, "Detailer failed: " + "Network write failure: %s", strerror(errno)); + args->status = STATUS_TRANSIENTFAILURE; + break; + default: + assert(error == 0); + args->status = STATUS_SUCCESS; + } + return (NULL); +} + +static int +detailer_batch(struct detailer *d) +{ + struct config *config; + struct stream *rd, *wr; + struct coll *coll; + struct status *st; + struct fixup *fixup; + char *cmd, *collname, *line, *release; + int error, fixupseof; + + config = d->config; + rd = d->rd; + wr = d->wr; + STAILQ_FOREACH(coll, &config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + line = stream_getln(rd, NULL); + cmd = proto_get_ascii(&line); + collname = proto_get_ascii(&line); + release = proto_get_ascii(&line); + error = proto_get_time(&line, &coll->co_scantime); + if (error || line != NULL || strcmp(cmd, "COLL") != 0 || + strcmp(collname, coll->co_name) != 0 || + strcmp(release, coll->co_release) != 0) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "COLL %s %s\n", coll->co_name, + coll->co_release); + if (error) + return (DETAILER_ERR_WRITE); + stream_flush(wr); + if (coll->co_options & CO_COMPRESS) { + stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); + stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); + } + st = status_open(coll, -1, &d->errmsg); + if (st == NULL) + return (DETAILER_ERR_MSG); + error = detailer_coll(d, coll, st); + status_close(st, NULL); + if (error) + return (error); + if (coll->co_options & CO_COMPRESS) { + stream_filter_stop(rd); + stream_filter_stop(wr); + } + stream_flush(wr); + } + line = stream_getln(rd, NULL); + if (line == NULL) + return (DETAILER_ERR_READ); + if (strcmp(line, ".") != 0) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + stream_flush(wr); + + /* Now send fixups if needed. */ + fixup = NULL; + fixupseof = 0; + STAILQ_FOREACH(coll, &config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + error = proto_printf(wr, "COLL %s %s\n", coll->co_name, + coll->co_release); + if (error) + return (DETAILER_ERR_WRITE); + if (coll->co_options & CO_COMPRESS) + stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); + while (!fixupseof) { + if (fixup == NULL) + fixup = fixups_get(config->fixups); + if (fixup == NULL) { + fixupseof = 1; + break; + } + if (fixup->f_coll != coll) + break; + error = proto_printf(wr, "Y %s %s %s\n", fixup->f_name, + coll->co_tag, coll->co_date); + if (error) + return (DETAILER_ERR_WRITE); + fixup = NULL; + } + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + if (coll->co_options & CO_COMPRESS) + stream_filter_stop(wr); + stream_flush(wr); + } + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} + +static int +detailer_coll(struct detailer *d, struct coll *coll, struct status *st) +{ + struct stream *rd, *wr; + char *cmd, *file, *line, *msg; + int error; + + rd = d->rd; + wr = d->wr; + line = stream_getln(rd, NULL); + if (line == NULL) + return (DETAILER_ERR_READ); + while (strcmp(line, ".") != 0) { + cmd = proto_get_ascii(&line); + if (cmd == NULL || strlen(cmd) != 1) + return (DETAILER_ERR_PROTO); + switch (cmd[0]) { + case 'D': + /* Delete file. */ + file = proto_get_ascii(&line); + if (file == NULL || line != NULL) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "D %s\n", file); + if (error) + return (DETAILER_ERR_WRITE); + break; + case 'U': + /* Add or update file. */ + file = proto_get_ascii(&line); + if (file == NULL || line != NULL) + return (DETAILER_ERR_PROTO); + error = detailer_dofile(d, coll, st, file); + if (error) + return (error); + break; + case '!': + /* Warning from server. */ + msg = proto_get_rest(&line); + if (msg == NULL) + return (DETAILER_ERR_PROTO); + lprintf(-1, "Server warning: %s\n", msg); + break; + default: + return (DETAILER_ERR_PROTO); + } + stream_flush(wr); + line = stream_getln(rd, NULL); + if (line == NULL) + return (DETAILER_ERR_READ); + } + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} + +static int +detailer_dofile(struct detailer *d, struct coll *coll, struct status *st, + char *file) +{ + char md5[MD5_DIGEST_SIZE]; + struct stream *wr; + struct fattr *fa; + struct statusrec *sr; + char *path; + int error, ret; + + wr = d->wr; + path = checkoutpath(coll->co_prefix, file); + if (path == NULL) + return (DETAILER_ERR_PROTO); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + /* We don't have the file, so the only option at this + point is to tell the server to send it. The server + may figure out that the file is dead, in which case + it will tell us. */ + error = proto_printf(wr, "C %s %s %s\n", + file, coll->co_tag, coll->co_date); + free(path); + if (error) + return (DETAILER_ERR_WRITE); + return (0); + } + ret = status_get(st, file, 0, 0, &sr); + if (ret == -1) { + d->errmsg = status_errmsg(st); + free(path); + return (DETAILER_ERR_MSG); + } + if (ret == 0) + sr = NULL; + + /* If our recorded information doesn't match the file that the + client has, then ignore the recorded information. */ + if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE || + !fattr_equal(sr->sr_clientattr, fa))) + sr = NULL; + fattr_free(fa); + if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) { + error = proto_printf(wr, "U %s %s %s %s %s\n", file, + coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate); + free(path); + if (error) + return (DETAILER_ERR_WRITE); + return (0); + } + + /* + * We don't have complete and/or accurate recorded information + * about what version of the file we have. Compute the file's + * checksum as an aid toward identifying which version it is. + */ + error = MD5_File(path, md5); + if (error) { + xasprintf(&d->errmsg, + "Cannot calculate checksum for \"%s\": %s", path, + strerror(errno)); + return (DETAILER_ERR_MSG); + } + free(path); + if (sr == NULL) { + error = proto_printf(wr, "S %s %s %s %s\n", file, + coll->co_tag, coll->co_date, md5); + } else { + error = proto_printf(wr, "s %s %s %s %s %s\n", file, + coll->co_tag, coll->co_date, sr->sr_revnum, md5); + } + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} diff --git a/contrib/csup/detailer.h b/contrib/csup/detailer.h new file mode 100644 index 000000000000..fe82b27f6812 --- /dev/null +++ b/contrib/csup/detailer.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _DETAILER_H_ +#define _DETAILER_H_ + +void *detailer(void *); + +#endif /* !_DETAILER_H_ */ diff --git a/contrib/csup/diff.c b/contrib/csup/diff.c new file mode 100644 index 000000000000..ea53c367901f --- /dev/null +++ b/contrib/csup/diff.c @@ -0,0 +1,214 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "diff.h" +#include "keyword.h" +#include "misc.h" +#include "stream.h" + +typedef long lineno_t; + +#define EC_ADD 0 +#define EC_DEL 1 + +/* Editing command and state. */ +struct editcmd { + int cmd; + lineno_t where; + lineno_t count; + lineno_t lasta; + lineno_t lastd; + lineno_t editline; + /* For convenience. */ + struct keyword *keyword; + struct diffinfo *di; + struct stream *orig; + struct stream *dest; +}; + +static int diff_geteditcmd(struct editcmd *, char *); +static int diff_copyln(struct editcmd *, lineno_t); +static void diff_write(struct editcmd *, void *, size_t); + +int +diff_apply(struct stream *rd, struct stream *orig, struct stream *dest, + struct keyword *keyword, struct diffinfo *di) +{ + struct editcmd ec; + lineno_t i; + char *line; + size_t size; + int empty, error, noeol; + + memset(&ec, 0, sizeof(ec)); + empty = 0; + noeol = 0; + ec.di = di; + ec.keyword = keyword; + ec.orig = orig; + ec.dest = dest; + line = stream_getln(rd, NULL); + while (line != NULL && strcmp(line, ".") != 0 && + strcmp(line, ".+") != 0) { + /* + * The server sends an empty line and then terminates + * with .+ for forced (and thus empty) commits. + */ + if (*line == '\0') { + if (empty) + return (-1); + empty = 1; + line = stream_getln(rd, NULL); + continue; + } + error = diff_geteditcmd(&ec, line); + if (error) + return (-1); + + if (ec.cmd == EC_ADD) { + error = diff_copyln(&ec, ec.where); + if (error) + return (-1); + for (i = 0; i < ec.count; i++) { + line = stream_getln(rd, &size); + if (line == NULL) + return (-1); + if (line[0] == '.') { + line++; + size--; + } + diff_write(&ec, line, size); + } + } else { + assert(ec.cmd == EC_DEL); + error = diff_copyln(&ec, ec.where - 1); + if (error) + return (-1); + for (i = 0; i < ec.count; i++) { + line = stream_getln(orig, NULL); + if (line == NULL) + return (-1); + ec.editline++; + } + } + line = stream_getln(rd, NULL); + } + if (line == NULL) + return (-1); + /* If we got ".+", there's no ending newline. */ + if (strcmp(line, ".+") == 0 && !empty) + noeol = 1; + ec.where = 0; + while ((line = stream_getln(orig, &size)) != NULL) + diff_write(&ec, line, size); + stream_flush(dest); + if (noeol) { + error = stream_truncate_rel(dest, -1); + if (error) { + warn("stream_truncate_rel"); + return (-1); + } + } + return (0); +} + +/* Get an editing command from the diff. */ +static int +diff_geteditcmd(struct editcmd *ec, char *line) +{ + char *end; + + if (line[0] == 'a') + ec->cmd = EC_ADD; + else if (line[0] == 'd') + ec->cmd = EC_DEL; + else + return (-1); + errno = 0; + ec->where = strtol(line + 1, &end, 10); + if (errno || ec->where < 0 || *end != ' ') + return (-1); + line = end + 1; + errno = 0; + ec->count = strtol(line, &end, 10); + if (errno || ec->count <= 0 || *end != '\0') + return (-1); + if (ec->cmd == EC_ADD) { + if (ec->where < ec->lasta) + return (-1); + ec->lasta = ec->where + 1; + } else { + if (ec->where < ec->lasta || ec->where < ec->lastd) + return (-1); + ec->lasta = ec->where; + ec->lastd = ec->where + ec->count; + } + return (0); +} + +/* Copy lines from the original version of the file up to line "to". */ +static int +diff_copyln(struct editcmd *ec, lineno_t to) +{ + char *line; + size_t size; + + while (ec->editline < to) { + line = stream_getln(ec->orig, &size); + if (line == NULL) + return (-1); + ec->editline++; + diff_write(ec, line, size); + } + return (0); +} + +/* Write a new line to the file, expanding RCS keywords appropriately. */ +static void +diff_write(struct editcmd *ec, void *buf, size_t size) +{ + char *line, *newline; + size_t newsize; + int ret; + + line = buf; + ret = keyword_expand(ec->keyword, ec->di, line, size, + &newline, &newsize); + if (ret) { + stream_write(ec->dest, newline, newsize); + free(newline); + } else { + stream_write(ec->dest, buf, size); + } +} diff --git a/contrib/csup/diff.h b/contrib/csup/diff.h new file mode 100644 index 000000000000..cbd9e50054a9 --- /dev/null +++ b/contrib/csup/diff.h @@ -0,0 +1,50 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _DIFF_H_ +#define _DIFF_H_ + +struct stream; +struct keyword; +struct file_update; + +/* Description of an RCS delta. */ +struct diffinfo { + char *di_rcsfile; /* RCS filename */ + char *di_cvsroot; /* CVS root prefix */ + char *di_revnum; /* Revision number */ + char *di_revdate; /* Revision date */ + char *di_author; /* Author of the delta */ + char *di_tag; /* CVS tag, if any */ + char *di_state; /* State of the branch */ + int di_expand; /* CVS expansion mode */ +}; + +int diff_apply(struct stream *, struct stream *, struct stream *, + struct keyword *, struct diffinfo *); + +#endif /* !_DIFF_H_ */ diff --git a/contrib/csup/fattr.c b/contrib/csup/fattr.c new file mode 100644 index 000000000000..59b66077c628 --- /dev/null +++ b/contrib/csup/fattr.c @@ -0,0 +1,946 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "fattr.h" +#include "misc.h" + +/* + * Include the appropriate definition for the file attributes we support. + * There are two different files: fattr_bsd.h for BSD-like systems that + * support the extended file flags à la chflags() and fattr_posix.h for + * bare POSIX systems that don't. + */ +#ifdef HAVE_FFLAGS +#include "fattr_bsd.h" +#else +#include "fattr_posix.h" +#endif + +#ifdef __FreeBSD__ +#include <osreldate.h> +#endif + +/* Define fflags_t if we're on a system that doesn't have it. */ +#if !defined(__FreeBSD_version) || __FreeBSD_version < 500030 +typedef uint32_t fflags_t; +#endif + +#define FA_MASKRADIX 16 +#define FA_FILETYPERADIX 10 +#define FA_MODTIMERADIX 10 +#define FA_SIZERADIX 10 +#define FA_RDEVRADIX 16 +#define FA_MODERADIX 8 +#define FA_FLAGSRADIX 16 +#define FA_LINKCOUNTRADIX 10 +#define FA_DEVRADIX 16 +#define FA_INODERADIX 10 + +#define FA_PERMMASK (S_IRWXU | S_IRWXG | S_IRWXO) +#define FA_SETIDMASK (S_ISUID | S_ISGID | S_ISVTX) + +struct fattr { + int mask; + int type; + time_t modtime; + off_t size; + char *linktarget; + dev_t rdev; + uid_t uid; + gid_t gid; + mode_t mode; + fflags_t flags; + nlink_t linkcount; + dev_t dev; + ino_t inode; +}; + +static const struct fattr bogus = { + FA_MODTIME | FA_SIZE | FA_MODE, + FT_UNKNOWN, + 1, + 0, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +static struct fattr *defaults[FT_NUMBER]; + +void +fattr_init(void) +{ + struct fattr *fa; + int i; + + for (i = 0; i < FT_NUMBER; i++) { + fa = fattr_new(i, -1); + if (i == FT_DIRECTORY) + fa->mode = 0777; + else + fa->mode = 0666; + fa->mask |= FA_MODE; + defaults[i] = fa; + } +} + +void +fattr_fini(void) +{ + int i; + + for (i = 0; i < FT_NUMBER; i++) + fattr_free(defaults[i]); +} + +const struct fattr *fattr_bogus = &bogus; + +static char *fattr_scanattr(struct fattr *, int, const char *); + +int +fattr_supported(int type) +{ + + return (fattr_support[type]); +} + +struct fattr * +fattr_new(int type, time_t modtime) +{ + struct fattr *new; + + new = xmalloc(sizeof(struct fattr)); + memset(new, 0, sizeof(struct fattr)); + new->type = type; + if (type != FT_UNKNOWN) + new->mask |= FA_FILETYPE; + if (modtime != -1) { + new->modtime = modtime; + new->mask |= FA_MODTIME; + } + if (fattr_supported(new->type) & FA_LINKCOUNT) { + new->mask |= FA_LINKCOUNT; + new->linkcount = 1; + } + return (new); +} + +/* Returns a new file attribute structure based on a stat structure. */ +struct fattr * +fattr_fromstat(struct stat *sb) +{ + struct fattr *fa; + + fa = fattr_new(FT_UNKNOWN, -1); + if (S_ISREG(sb->st_mode)) + fa->type = FT_FILE; + else if (S_ISDIR(sb->st_mode)) + fa->type = FT_DIRECTORY; + else if (S_ISCHR(sb->st_mode)) + fa->type = FT_CDEV; + else if (S_ISBLK(sb->st_mode)) + fa->type = FT_BDEV; + else if (S_ISLNK(sb->st_mode)) + fa->type = FT_SYMLINK; + else + fa->type = FT_UNKNOWN; + + fa->mask = FA_FILETYPE | fattr_supported(fa->type); + if (fa->mask & FA_MODTIME) + fa->modtime = sb->st_mtime; + if (fa->mask & FA_SIZE) + fa->size = sb->st_size; + if (fa->mask & FA_RDEV) + fa->rdev = sb->st_rdev; + if (fa->mask & FA_OWNER) + fa->uid = sb->st_uid; + if (fa->mask & FA_GROUP) + fa->gid = sb->st_gid; + if (fa->mask & FA_MODE) + fa->mode = sb->st_mode & (FA_SETIDMASK | FA_PERMMASK); +#ifdef HAVE_FFLAGS + if (fa->mask & FA_FLAGS) + fa->flags = sb->st_flags; +#endif + if (fa->mask & FA_LINKCOUNT) + fa->linkcount = sb->st_nlink; + if (fa->mask & FA_DEV) + fa->dev = sb->st_dev; + if (fa->mask & FA_INODE) + fa->inode = sb->st_ino; + return (fa); +} + +struct fattr * +fattr_frompath(const char *path, int nofollow) +{ + struct fattr *fa; + struct stat sb; + int error, len; + + if (nofollow) + error = lstat(path, &sb); + else + error = stat(path, &sb); + if (error) + return (NULL); + fa = fattr_fromstat(&sb); + if (fa->mask & FA_LINKTARGET) { + char buf[1024]; + + len = readlink(path, buf, sizeof(buf)); + if (len == -1) { + fattr_free(fa); + return (NULL); + } + if ((unsigned)len > sizeof(buf) - 1) { + fattr_free(fa); + errno = ENAMETOOLONG; + return (NULL); + } + buf[len] = '\0'; + fa->linktarget = xstrdup(buf); + } + return (fa); +} + +struct fattr * +fattr_fromfd(int fd) +{ + struct fattr *fa; + struct stat sb; + int error; + + error = fstat(fd, &sb); + if (error) + return (NULL); + fa = fattr_fromstat(&sb); + return (fa); +} + +int +fattr_type(const struct fattr *fa) +{ + + return (fa->type); +} + +/* Returns a new file attribute structure from its encoded text form. */ +struct fattr * +fattr_decode(char *attr) +{ + struct fattr *fa; + char *next; + + fa = fattr_new(FT_UNKNOWN, -1); + next = fattr_scanattr(fa, FA_MASK, attr); + if (next == NULL || (fa->mask & ~FA_MASK) > 0) + goto bad; + if (fa->mask & FA_FILETYPE) { + next = fattr_scanattr(fa, FA_FILETYPE, next); + if (next == NULL) + goto bad; + if (fa->type < 0 || fa->type > FT_MAX) + fa->type = FT_UNKNOWN; + } else { + /* The filetype attribute is always valid. */ + fa->mask |= FA_FILETYPE; + fa->type = FT_UNKNOWN; + } + fa->mask = fa->mask & fattr_supported(fa->type); + if (fa->mask & FA_MODTIME) + next = fattr_scanattr(fa, FA_MODTIME, next); + if (fa->mask & FA_SIZE) + next = fattr_scanattr(fa, FA_SIZE, next); + if (fa->mask & FA_LINKTARGET) + next = fattr_scanattr(fa, FA_LINKTARGET, next); + if (fa->mask & FA_RDEV) + next = fattr_scanattr(fa, FA_RDEV, next); + if (fa->mask & FA_OWNER) + next = fattr_scanattr(fa, FA_OWNER, next); + if (fa->mask & FA_GROUP) + next = fattr_scanattr(fa, FA_GROUP, next); + if (fa->mask & FA_MODE) + next = fattr_scanattr(fa, FA_MODE, next); + if (fa->mask & FA_FLAGS) + next = fattr_scanattr(fa, FA_FLAGS, next); + if (fa->mask & FA_LINKCOUNT) { + next = fattr_scanattr(fa, FA_LINKCOUNT, next); + } else if (fattr_supported(fa->type) & FA_LINKCOUNT) { + /* If the link count is missing but supported, fake it as 1. */ + fa->mask |= FA_LINKCOUNT; + fa->linkcount = 1; + } + if (fa->mask & FA_DEV) + next = fattr_scanattr(fa, FA_DEV, next); + if (fa->mask & FA_INODE) + next = fattr_scanattr(fa, FA_INODE, next); + if (next == NULL) + goto bad; + return (fa); +bad: + fattr_free(fa); + return (NULL); +} + +char * +fattr_encode(const struct fattr *fa, fattr_support_t support, int ignore) +{ + struct { + char val[32]; + char len[4]; + int extval; + char *ext; + } pieces[FA_NUMBER], *piece; + struct passwd *pw; + struct group *gr; + char *cp, *s; + size_t len, vallen; + mode_t mode, modemask; + int mask, n, i; + + pw = NULL; + gr = NULL; + if (support == NULL) + mask = fa->mask; + else + mask = fa->mask & support[fa->type]; + mask &= ~ignore; + /* XXX - Use getpwuid_r() and getgrgid_r(). */ + if (fa->mask & FA_OWNER) { + pw = getpwuid(fa->uid); + if (pw == NULL) + mask &= ~FA_OWNER; + } + if (fa->mask & FA_GROUP) { + gr = getgrgid(fa->gid); + if (gr == NULL) + mask &= ~FA_GROUP; + } + if (fa->mask & FA_LINKCOUNT && fa->linkcount == 1) + mask &= ~FA_LINKCOUNT; + + memset(pieces, 0, FA_NUMBER * sizeof(*pieces)); + len = 0; + piece = pieces; + vallen = snprintf(piece->val, sizeof(piece->val), "%x", mask); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + if (mask & FA_FILETYPE) { + vallen = snprintf(piece->val, sizeof(piece->val), + "%d", fa->type); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_MODTIME) { + vallen = snprintf(piece->val, sizeof(piece->val), + "%lld", (long long)fa->modtime); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_SIZE) { + vallen = snprintf(piece->val, sizeof(piece->val), + "%lld", (long long)fa->size); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_LINKTARGET) { + vallen = strlen(fa->linktarget); + piece->extval = 1; + piece->ext = fa->linktarget; + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_RDEV) { + vallen = snprintf(piece->val, sizeof(piece->val), + "%lld", (long long)fa->rdev); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_OWNER) { + vallen = strlen(pw->pw_name); + piece->extval = 1; + piece->ext = pw->pw_name; + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_GROUP) { + vallen = strlen(gr->gr_name); + piece->extval = 1; + piece->ext = gr->gr_name; + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_MODE) { + if (mask & FA_OWNER && mask & FA_GROUP) + modemask = FA_SETIDMASK | FA_PERMMASK; + else + modemask = FA_PERMMASK; + mode = fa->mode & modemask; + vallen = snprintf(piece->val, sizeof(piece->val), + "%o", mode); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_FLAGS) { + vallen = snprintf(piece->val, sizeof(piece->val), "%llx", + (long long)fa->flags); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_LINKCOUNT) { + vallen = snprintf(piece->val, sizeof(piece->val), "%lld", + (long long)fa->linkcount); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_DEV) { + vallen = snprintf(piece->val, sizeof(piece->val), "%lld", + (long long)fa->dev); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + if (mask & FA_INODE) { + vallen = snprintf(piece->val, sizeof(piece->val), "%lld", + (long long)fa->inode); + len += snprintf(piece->len, sizeof(piece->len), "%lld", + (long long)vallen) + vallen + 1; + piece++; + } + + s = xmalloc(len + 1); + + n = piece - pieces; + piece = pieces; + cp = s; + for (i = 0; i < n; i++) { + if (piece->extval) + len = sprintf(cp, "%s#%s", piece->len, piece->ext); + else + len = sprintf(cp, "%s#%s", piece->len, piece->val); + cp += len; + piece++; + } + return (s); +} + +struct fattr * +fattr_dup(const struct fattr *from) +{ + struct fattr *fa; + + fa = fattr_new(FT_UNKNOWN, -1); + fattr_override(fa, from, FA_MASK); + return (fa); +} + +void +fattr_free(struct fattr *fa) +{ + + if (fa == NULL) + return; + if (fa->linktarget != NULL) + free(fa->linktarget); + free(fa); +} + +void +fattr_umask(struct fattr *fa, mode_t newumask) +{ + + if (fa->mask & FA_MODE) + fa->mode = fa->mode & ~newumask; +} + +void +fattr_maskout(struct fattr *fa, int mask) +{ + + /* Don't forget to free() the linktarget attribute if we remove it. */ + if (mask & FA_LINKTARGET && fa->mask & FA_LINKTARGET) { + free(fa->linktarget); + fa->linktarget = NULL; + } + fa->mask &= ~mask; +} + +int +fattr_getmask(const struct fattr *fa) +{ + + return (fa->mask); +} + +nlink_t +fattr_getlinkcount(const struct fattr *fa) +{ + + return (fa->linkcount); +} + +/* + * Eat the specified attribute and put it in the file attribute + * structure. Returns NULL on error, or a pointer to the next + * attribute to parse. + * + * This would be much prettier if we had strntol() so that we're + * not forced to write '\0' to the string before calling strtol() + * and then put back the old value... + * + * We need to use (unsigned) long long types here because some + * of the opaque types we're parsing (off_t, time_t...) may need + * 64bits to fit. + */ +static char * +fattr_scanattr(struct fattr *fa, int type, const char *attr) +{ + struct passwd *pw; + struct group *gr; + char *attrend, *attrstart, *end; + size_t len; + unsigned long attrlen; + mode_t modemask; + char tmp; + + if (attr == NULL) + return (NULL); + errno = 0; + attrlen = strtoul(attr, &end, 10); + if (errno || *end != '#') + return (NULL); + len = strlen(attr); + attrstart = end + 1; + attrend = attrstart + attrlen; + tmp = *attrend; + *attrend = '\0'; + switch (type) { + /* Using FA_MASK here is a bit bogus semantically. */ + case FA_MASK: + errno = 0; + fa->mask = (int)strtol(attrstart, &end, FA_MASKRADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_FILETYPE: + errno = 0; + fa->type = (int)strtol(attrstart, &end, FA_FILETYPERADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_MODTIME: + errno = 0; + fa->modtime = (time_t)strtoll(attrstart, &end, FA_MODTIMERADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_SIZE: + errno = 0; + fa->size = (off_t)strtoll(attrstart, &end, FA_SIZERADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_LINKTARGET: + fa->linktarget = xstrdup(attrstart); + break; + case FA_RDEV: + errno = 0; + fa->rdev = (dev_t)strtoll(attrstart, &end, FA_RDEVRADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_OWNER: + /* + * XXX - We need to use getpwnam_r() since getpwnam() + * is not thread-safe, and we also need to use a cache. + */ + pw = getpwnam(attrstart); + if (pw != NULL) + fa->uid = pw->pw_uid; + else + fa->mask &= ~FA_OWNER; + break; + case FA_GROUP: + gr = getgrnam(attrstart); + if (gr != NULL) + fa->gid = gr->gr_gid; + else + fa->mask &= ~FA_GROUP; + break; + case FA_MODE: + errno = 0; + fa->mode = (mode_t)strtol(attrstart, &end, FA_MODERADIX); + if (errno || end != attrend) + goto bad; + if (fa->mask & FA_OWNER && fa->mask & FA_GROUP) + modemask = FA_SETIDMASK | FA_PERMMASK; + else + modemask = FA_PERMMASK; + fa->mode &= modemask; + break; + case FA_FLAGS: + errno = 0; + fa->flags = (fflags_t)strtoul(attrstart, &end, FA_FLAGSRADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_LINKCOUNT: + errno = 0; + fa->linkcount = (nlink_t)strtol(attrstart, &end, FA_FLAGSRADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_DEV: + errno = 0; + fa->dev = (dev_t)strtoll(attrstart, &end, FA_DEVRADIX); + if (errno || end != attrend) + goto bad; + break; + case FA_INODE: + errno = 0; + fa->inode = (ino_t)strtoll(attrstart, &end, FA_INODERADIX); + if (errno || end != attrend) + goto bad; + break; + } + *attrend = tmp; + return (attrend); +bad: + *attrend = tmp; + return (NULL); +} + +/* Return a file attribute structure built from the RCS file attributes. */ +struct fattr * +fattr_forcheckout(const struct fattr *rcsattr, mode_t mask) +{ + struct fattr *fa; + + fa = fattr_new(FT_FILE, -1); + if (rcsattr->mask & FA_MODE) { + if ((rcsattr->mode & 0111) > 0) + fa->mode = 0777; + else + fa->mode = 0666; + fa->mode &= ~mask; + fa->mask |= FA_MODE; + } + return (fa); +} + +/* Merge attributes from "from" that aren't present in "fa". */ +void +fattr_merge(struct fattr *fa, const struct fattr *from) +{ + + fattr_override(fa, from, from->mask & ~fa->mask); +} + +/* Merge default attributes. */ +void +fattr_mergedefault(struct fattr *fa) +{ + + fattr_merge(fa, defaults[fa->type]); +} + +/* Override selected attributes of "fa" with values from "from". */ +void +fattr_override(struct fattr *fa, const struct fattr *from, int mask) +{ + + mask &= from->mask; + if (fa->mask & FA_LINKTARGET && mask & FA_LINKTARGET) + free(fa->linktarget); + fa->mask |= mask; + if (mask & FA_FILETYPE) + fa->type = from->type; + if (mask & FA_MODTIME) + fa->modtime = from->modtime; + if (mask & FA_SIZE) + fa->size = from->size; + if (mask & FA_LINKTARGET) + fa->linktarget = xstrdup(from->linktarget); + if (mask & FA_RDEV) + fa->rdev = from->rdev; + if (mask & FA_OWNER) + fa->uid = from->uid; + if (mask & FA_GROUP) + fa->gid = from->gid; + if (mask & FA_MODE) + fa->mode = from->mode; + if (mask & FA_FLAGS) + fa->flags = from->flags; + if (mask & FA_LINKCOUNT) + fa->linkcount = from->linkcount; + if (mask & FA_DEV) + fa->dev = from->dev; + if (mask & FA_INODE) + fa->inode = from->inode; +} + +/* Create a node. */ +int +fattr_makenode(const struct fattr *fa, const char *path) +{ + mode_t modemask, mode; + int error; + + if (fa->mask & FA_OWNER && fa->mask & FA_GROUP) + modemask = FA_SETIDMASK | FA_PERMMASK; + else + modemask = FA_PERMMASK; + + /* We only implement fattr_makenode() for dirs for now. */ + assert(fa->type == FT_DIRECTORY); + if (fa->mask & FA_MODE) + mode = fa->mode & modemask; + else + mode = 0700; + error = mkdir(path, mode); + return (error); +} + +int +fattr_delete(const char *path) +{ + struct fattr *fa; + int error; + + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + if (errno == ENOENT) + return (0); + return (-1); + } + + /* Clear flags. */ + if (fa->mask & FA_FLAGS && fa->flags != 0) { + fa->flags = 0; + (void)chflags(path, fa->flags); + } + + if (fa->type == FT_DIRECTORY) + error = rmdir(path); + else + error = unlink(path); + fattr_free(fa); + return (error); +} + +/* + * Changes those attributes we can change. Returns -1 on error, + * 0 if no update was needed, and 1 if an update was needed and + * it has been applied successfully. + */ +int +fattr_install(struct fattr *fa, const char *topath, const char *frompath) +{ + struct timeval tv[2]; + struct fattr *old; + int error, inplace, mask; + mode_t modemask, newmode; + uid_t uid; + gid_t gid; + + mask = fa->mask & fattr_supported(fa->type); + if (mask & FA_OWNER && mask & FA_GROUP) + modemask = FA_SETIDMASK | FA_PERMMASK; + else + modemask = FA_PERMMASK; + + inplace = 0; + if (frompath == NULL) { + /* Changing attributes in place. */ + frompath = topath; + inplace = 1; + } + old = fattr_frompath(topath, FATTR_NOFOLLOW); + if (old == NULL) + return (-1); + if (inplace && fattr_equal(fa, old)) { + fattr_free(old); + return (0); + } + +#ifdef HAVE_FFLAGS + /* + * Determine whether we need to clear the flags of the target. + * This is bogus in that it assumes a value of 0 is safe and + * that non-zero is unsafe. I'm not really worried by that + * since as far as I know that's the way things are. + */ + if ((old->mask & FA_FLAGS) && old->flags > 0) { + (void)chflags(topath, 0); + old->flags = 0; + } +#endif + + /* Determine whether we need to remove the target first. */ + if (!inplace && (fa->type == FT_DIRECTORY) != + (old->type == FT_DIRECTORY)) { + if (old->type == FT_DIRECTORY) + error = rmdir(topath); + else + error = unlink(topath); + if (error) + goto bad; + } + + /* Change those attributes that we can before moving the file + * into place. That makes installation atomic in most cases. */ + if (mask & FA_MODTIME) { + gettimeofday(tv, NULL); /* Access time. */ + tv[1].tv_sec = fa->modtime; /* Modification time. */ + tv[1].tv_usec = 0; + error = utimes(frompath, tv); + if (error) + goto bad; + } + if (mask & FA_OWNER || mask & FA_GROUP) { + uid = -1; + gid = -1; + if (mask & FA_OWNER) + uid = fa->uid; + if (mask & FA_GROUP) + gid = fa->gid; + error = chown(frompath, uid, gid); + if (error) + goto bad; + } + if (mask & FA_MODE) { + newmode = fa->mode & modemask; + /* Merge in set*id bits from the old attribute. XXX - Why? */ + if (old->mask & FA_MODE) { + newmode |= (old->mode & ~modemask); + newmode &= (FA_SETIDMASK | FA_PERMMASK); + } + error = chmod(frompath, newmode); + if (error) + goto bad; + } + + if (!inplace) { + error = rename(frompath, topath); + if (error) + goto bad; + } + +#ifdef HAVE_FFLAGS + /* Set the flags. */ + if (mask & FA_FLAGS) + (void)chflags(topath, fa->flags); +#endif + fattr_free(old); + return (1); +bad: + fattr_free(old); + return (-1); +} + +/* + * Returns 1 if both attributes are equal, 0 otherwise. + * + * This function only compares attributes that are valid in both + * files. A file of unknown type ("FT_UNKNOWN") is unequal to + * anything, including itself. + */ +int +fattr_equal(const struct fattr *fa1, const struct fattr *fa2) +{ + int mask; + + mask = fa1->mask & fa2->mask; + if (fa1->type == FT_UNKNOWN || fa2->type == FT_UNKNOWN) + return (0); + if (mask & FA_MODTIME) + if (fa1->modtime != fa2->modtime) + return (0); + if (mask & FA_SIZE) + if (fa1->size != fa2->size) + return (0); + if (mask & FA_LINKTARGET) + if (strcmp(fa1->linktarget, fa2->linktarget) != 0) + return (0); + if (mask & FA_RDEV) + if (fa1->rdev != fa2->rdev) + return (0); + if (mask & FA_OWNER) + if (fa1->uid != fa2->uid) + return (0); + if (mask & FA_GROUP) + if (fa1->gid != fa2->gid) + return (0); + if (mask & FA_MODE) + if (fa1->mode != fa2->mode) + return (0); + if (mask & FA_FLAGS) + if (fa1->flags != fa2->flags) + return (0); + if (mask & FA_LINKCOUNT) + if (fa1->linkcount != fa2->linkcount) + return (0); + if (mask & FA_DEV) + if (fa1->dev != fa2->dev) + return (0); + if (mask & FA_INODE) + if (fa1->inode != fa2->inode) + return (0); + return (1); +} diff --git a/contrib/csup/fattr.h b/contrib/csup/fattr.h new file mode 100644 index 000000000000..6015fbb3b43a --- /dev/null +++ b/contrib/csup/fattr.h @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _FATTR_H_ +#define _FATTR_H_ + +#include <sys/types.h> + +#include <fcntl.h> +#include <time.h> + +/* + * File types. + */ +#define FT_UNKNOWN 0 /* Unknown file type. */ +#define FT_FILE 1 /* Regular file. */ +#define FT_DIRECTORY 2 /* Directory. */ +#define FT_CDEV 3 /* Character device. */ +#define FT_BDEV 4 /* Block device. */ +#define FT_SYMLINK 5 /* Symbolic link. */ +#define FT_MAX FT_SYMLINK /* Maximum file type number. */ +#define FT_NUMBER (FT_MAX + 1) /* Number of file types. */ + +/* + * File attributes. + */ +#define FA_FILETYPE 0x0001 /* True for all supported file types. */ +#define FA_MODTIME 0x0002 /* Last file modification time. */ +#define FA_SIZE 0x0004 /* Size of the file. */ +#define FA_LINKTARGET 0x0008 /* Target of a symbolic link. */ +#define FA_RDEV 0x0010 /* Device for a device node. */ +#define FA_OWNER 0x0020 /* Owner of the file. */ +#define FA_GROUP 0x0040 /* Group of the file. */ +#define FA_MODE 0x0080 /* File permissions. */ +#define FA_FLAGS 0x0100 /* 4.4BSD flags, a la chflags(2). */ +#define FA_LINKCOUNT 0x0200 /* Hard link count. */ +#define FA_DEV 0x0400 /* Device holding the inode. */ +#define FA_INODE 0x0800 /* Inode number. */ + +#define FA_MASK 0x0fff + +#define FA_NUMBER 12 /* Number of file attributes. */ + +/* Attributes that we might be able to change. */ +#define FA_CHANGEABLE (FA_MODTIME | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS) + +/* + * Attributes that we don't want to save in the "checkouts" file + * when in checkout mode. + */ +#define FA_COIGNORE (FA_MASK & ~(FA_FILETYPE|FA_MODTIME|FA_SIZE|FA_MODE)) + +/* These are for fattr_frompath(). */ +#define FATTR_FOLLOW 0 +#define FATTR_NOFOLLOW 1 + +struct stat; +struct fattr; + +typedef int fattr_support_t[FT_NUMBER]; + +extern const struct fattr *fattr_bogus; + +void fattr_init(void); +void fattr_fini(void); + +struct fattr *fattr_new(int, time_t); +struct fattr *fattr_default(int); +struct fattr *fattr_fromstat(struct stat *); +struct fattr *fattr_frompath(const char *, int); +struct fattr *fattr_fromfd(int); +struct fattr *fattr_decode(char *); +struct fattr *fattr_forcheckout(const struct fattr *, mode_t); +struct fattr *fattr_dup(const struct fattr *); +char *fattr_encode(const struct fattr *, fattr_support_t, int); +int fattr_type(const struct fattr *); +void fattr_maskout(struct fattr *, int); +int fattr_getmask(const struct fattr *); +nlink_t fattr_getlinkcount(const struct fattr *); +void fattr_umask(struct fattr *, mode_t); +void fattr_merge(struct fattr *, const struct fattr *); +void fattr_mergedefault(struct fattr *); +void fattr_override(struct fattr *, const struct fattr *, int); +int fattr_makenode(const struct fattr *, const char *); +int fattr_delete(const char *path); +int fattr_install(struct fattr *, const char *, const char *); +int fattr_equal(const struct fattr *, const struct fattr *); +void fattr_free(struct fattr *); +int fattr_supported(int); + +#endif /* !_FATTR_H_ */ diff --git a/contrib/csup/fattr_bsd.h b/contrib/csup/fattr_bsd.h new file mode 100644 index 000000000000..112a8248a21f --- /dev/null +++ b/contrib/csup/fattr_bsd.h @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +/* + * The file attributes we support in a BSD environment. + * + * This is similar to fattr_posix.h, except that we support the FA_FLAGS + * attribute when it makes sense. The FA_FLAGS attribute is for the + * extended BSD file flags, see chflags(2). + */ +fattr_support_t fattr_support = { + /* FT_UNKNOWN */ + 0, + /* FT_FILE */ + FA_FILETYPE | FA_MODTIME | FA_SIZE | FA_OWNER | FA_GROUP | FA_MODE | + FA_FLAGS | FA_LINKCOUNT | FA_INODE | FA_DEV, + /* FT_DIRECTORY */ + FA_FILETYPE | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS, + /* FT_CDEV */ + FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS | + FA_LINKCOUNT | FA_DEV | FA_INODE, + /* FT_BDEV */ + FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS | + FA_LINKCOUNT | FA_DEV | FA_INODE, + /* FT_SYMLINK */ + FA_FILETYPE | FA_LINKTARGET +}; diff --git a/contrib/csup/fattr_posix.h b/contrib/csup/fattr_posix.h new file mode 100644 index 000000000000..c4650e4293b7 --- /dev/null +++ b/contrib/csup/fattr_posix.h @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +/* + * The file attributes we support in a POSIX environment. + */ +fattr_support_t fattr_support = { + /* FT_UNKNOWN */ + 0, + /* FT_FILE */ + FA_FILETYPE | FA_MODTIME | FA_SIZE | FA_OWNER | FA_GROUP | FA_MODE | + FA_LINKCOUNT | FA_INODE | FA_DEV, + /* FT_DIRECTORY */ + FA_FILETYPE | FA_OWNER | FA_GROUP | FA_MODE, + /* FT_CDEV */ + FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_LINKCOUNT | + FA_DEV | FA_INODE, + /* FT_BDEV */ + FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_LINKCOUNT | + FA_DEV | FA_INODE, + /* FT_SYMLINK */ + FA_FILETYPE | FA_LINKTARGET +}; diff --git a/contrib/csup/fixups.c b/contrib/csup/fixups.c new file mode 100644 index 000000000000..b105a8f7d48f --- /dev/null +++ b/contrib/csup/fixups.c @@ -0,0 +1,198 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include "fixups.h" +#include "misc.h" +#include "queue.h" + +/* + * A synchronized queue to implement fixups. The updater thread adds + * fixup requests to the queue with fixups_put() when a checksum + * mismatch error occured. It then calls fixups_close() when he's + * done requesting fixups. The detailer thread gets the fixups with + * fixups_get() and then send the requests to the server. + * + * The queue is synchronized with a mutex and a condition variable. + */ + +struct fixups { + pthread_mutex_t lock; + pthread_cond_t cond; + STAILQ_HEAD(, fixup) fixupq; + struct fixup *cur; + size_t size; + int closed; +}; + +static void fixups_lock(struct fixups *); +static void fixups_unlock(struct fixups *); + +static struct fixup *fixup_new(struct coll *, const char *); +static void fixup_free(struct fixup *); + +static void +fixups_lock(struct fixups *f) +{ + int error; + + error = pthread_mutex_lock(&f->lock); + assert(!error); +} + +static void +fixups_unlock(struct fixups *f) +{ + int error; + + error = pthread_mutex_unlock(&f->lock); + assert(!error); +} + +static struct fixup * +fixup_new(struct coll *coll, const char *name) +{ + struct fixup *fixup; + + fixup = xmalloc(sizeof(struct fixup)); + fixup->f_name = xstrdup(name); + fixup->f_coll = coll; + return (fixup); +} + +static void +fixup_free(struct fixup *fixup) +{ + + free(fixup->f_name); + free(fixup); +} + +/* Create a new fixup queue. */ +struct fixups * +fixups_new(void) +{ + struct fixups *f; + + f = xmalloc(sizeof(struct fixups)); + f->size = 0; + f->closed = 0; + f->cur = NULL; + STAILQ_INIT(&f->fixupq); + pthread_mutex_init(&f->lock, NULL); + pthread_cond_init(&f->cond, NULL); + return (f); +} + +/* Add a fixup request to the queue. */ +void +fixups_put(struct fixups *f, struct coll *coll, const char *name) +{ + struct fixup *fixup; + int dosignal; + + dosignal = 0; + fixup = fixup_new(coll, name); + fixups_lock(f); + assert(!f->closed); + STAILQ_INSERT_TAIL(&f->fixupq, fixup, f_link); + if (f->size++ == 0) + dosignal = 1; + fixups_unlock(f); + if (dosignal) + pthread_cond_signal(&f->cond); +} + +/* Get a fixup request from the queue. */ +struct fixup * +fixups_get(struct fixups *f) +{ + struct fixup *fixup, *tofree; + + fixups_lock(f); + while (f->size == 0 && !f->closed) + pthread_cond_wait(&f->cond, &f->lock); + if (f->closed) { + fixups_unlock(f); + return (NULL); + } + assert(f->size > 0); + fixup = STAILQ_FIRST(&f->fixupq); + tofree = f->cur; + f->cur = fixup; + STAILQ_REMOVE_HEAD(&f->fixupq, f_link); + f->size--; + fixups_unlock(f); + if (tofree != NULL) + fixup_free(tofree); + return (fixup); +} + +/* Close the writing end of the queue. */ +void +fixups_close(struct fixups *f) +{ + int dosignal; + + dosignal = 0; + fixups_lock(f); + if (f->size == 0 && !f->closed) + dosignal = 1; + f->closed = 1; + fixups_unlock(f); + if (dosignal) + pthread_cond_signal(&f->cond); +} + +/* Free a fixups queue. */ +void +fixups_free(struct fixups *f) +{ + struct fixup *fixup, *fixup2; + + assert(f->closed); + /* + * Free any fixup that has been left on the queue. + * This can happen if we have been aborted prematurely. + */ + fixup = STAILQ_FIRST(&f->fixupq); + while (fixup != NULL) { + fixup2 = STAILQ_NEXT(fixup, f_link); + fixup_free(fixup); + fixup = fixup2; + } + if (f->cur != NULL) + fixup_free(f->cur); + pthread_cond_destroy(&f->cond); + pthread_mutex_destroy(&f->lock); + free(f); +} diff --git a/contrib/csup/fixups.h b/contrib/csup/fixups.h new file mode 100644 index 000000000000..0dddc905d1a0 --- /dev/null +++ b/contrib/csup/fixups.h @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _FIXUPS_H_ +#define _FIXUPS_H_ + +#include "queue.h" + +struct coll; +struct fixups; + +struct fixup { + struct coll *f_coll; + char *f_name; + STAILQ_ENTRY(fixup) f_link; /* Not for consumers. */ +}; + +struct fixups *fixups_new(void); +void fixups_put(struct fixups *, struct coll *, const char *); +struct fixup *fixups_get(struct fixups *); +void fixups_close(struct fixups *); +void fixups_free(struct fixups *); + +#endif /* !_FIXUPS_H_ */ diff --git a/contrib/csup/fnmatch.c b/contrib/csup/fnmatch.c new file mode 100644 index 000000000000..a63f016f3045 --- /dev/null +++ b/contrib/csup/fnmatch.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * From FreeBSD fnmatch.c 1.11 + * $Id: fnmatch.c,v 1.3 1997/08/19 02:34:30 jdp Exp $ + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include <ctype.h> +#include <string.h> +#include <stdio.h> + +#include "fnmatch.h" + +#define EOS '\0' + +static const char *rangematch(const char *, char, int); + +int +fnmatch(const char *pattern, const char *string, int flags) +{ + const char *stringstart; + char c, test; + + for (stringstart = string;;) + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return (0); + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) + if (flags & FNM_PATHNAME) + return ((flags & FNM_LEADING_DIR) || + strchr(string, '/') == NULL ? + 0 : FNM_NOMATCH); + else + return (0); + else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = strchr(string, '/')) == NULL) + return (FNM_NOMATCH); + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!fnmatch(pattern, string, flags & ~FNM_PERIOD)) + return (0); + if (test == '/' && flags & FNM_PATHNAME) + break; + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && flags & FNM_PATHNAME) + return (FNM_NOMATCH); + if ((pattern = + rangematch(pattern, *string, flags)) == NULL) + return (FNM_NOMATCH); + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + if (c == *string) + ; + else if ((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string))) + ; + else if ((flags & FNM_PREFIX_DIRS) && *string == EOS && + ((c == '/' && string != stringstart) || + (string == stringstart+1 && *stringstart == '/'))) + return (0); + else + return (FNM_NOMATCH); + string++; + break; + } + /* NOTREACHED */ +} + +static const char * +rangematch(const char *pattern, char test, int flags) +{ + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ( (negate = (*pattern == '!' || *pattern == '^')) ) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = tolower((unsigned char)test); + + for (ok = 0; (c = *pattern++) != ']';) { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (NULL); + + if (flags & FNM_CASEFOLD) + c = tolower((unsigned char)c); + + if (*pattern == '-' + && (c2 = *(pattern+1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (NULL); + + if (flags & FNM_CASEFOLD) + c2 = tolower((unsigned char)c2); + + if ((unsigned char)c <= (unsigned char)test && + (unsigned char)test <= (unsigned char)c2) + ok = 1; + } else if (c == test) + ok = 1; + } + return (ok == negate ? NULL : pattern); +} diff --git a/contrib/csup/fnmatch.h b/contrib/csup/fnmatch.h new file mode 100644 index 000000000000..21d2f64a725a --- /dev/null +++ b/contrib/csup/fnmatch.h @@ -0,0 +1,58 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)fnmatch.h 8.1 (Berkeley) 6/2/93 + * + * From FreeBSD fnmatch.h 1.7 + * $Id: fnmatch.h,v 1.4 2001/10/04 02:46:21 jdp Exp $ + */ + +#ifndef _FNMATCH_H_ +#define _FNMATCH_H_ + +#define FNM_NOMATCH 1 /* Match failed. */ + +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ +#define FNM_PREFIX_DIRS 0x20 /* Directory prefixes of pattern match too. */ + +/* Make this compile successfully with "gcc -traditional" */ +#ifndef __STDC__ +#define const /* empty */ +#endif + +int fnmatch(const char *, const char *, int); + +#endif /* !_FNMATCH_H_ */ diff --git a/contrib/csup/globtree.c b/contrib/csup/globtree.c new file mode 100644 index 000000000000..520931afa136 --- /dev/null +++ b/contrib/csup/globtree.c @@ -0,0 +1,393 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> + +#include <assert.h> +#include <fnmatch.h> +#include <regex.h> +#include <stdlib.h> + +#include "globtree.h" +#include "misc.h" + +/* + * The "GlobTree" interface allows one to construct arbitrarily complex + * boolean expressions for evaluating whether to accept or reject a + * filename. The globtree_test() function returns true or false + * according to whether the name is accepted or rejected by the + * expression. + * + * Expressions are trees constructed from nodes representing either + * primitive matching operations (primaries) or operators that are + * applied to their subexpressions. The simplest primitives are + * globtree_false(), which matches nothing, and globtree_true(), which + * matches everything. + * + * A more useful primitive is the matching operation, constructed with + * globtree_match(). It will call fnmatch() with the suppliedi + * shell-style pattern to determine if the filename matches. + * + * Expressions can be combined with the boolean operators AND, OR, and + * NOT, to form more complex expressions. + */ + +/* Node types. */ +#define GLOBTREE_NOT 0 +#define GLOBTREE_AND 1 +#define GLOBTREE_OR 2 +#define GLOBTREE_MATCH 3 +#define GLOBTREE_REGEX 4 +#define GLOBTREE_TRUE 5 +#define GLOBTREE_FALSE 6 + +/* A node. */ +struct globtree { + int type; + struct globtree *left; + struct globtree *right; + + /* The "data" field points to the text pattern for GLOBTREE_MATCH + nodes, and to the regex_t for GLOBTREE_REGEX nodes. For any + other node, it is set to NULL. */ + void *data; + /* The "flags" field contains the flags to pass to fnmatch() for + GLOBTREE_MATCH nodes. */ + int flags; +}; + +static struct globtree *globtree_new(int); +static int globtree_eval(struct globtree *, const char *); + +static struct globtree * +globtree_new(int type) +{ + struct globtree *gt; + + gt = xmalloc(sizeof(struct globtree)); + gt->type = type; + gt->data = NULL; + gt->flags = 0; + gt->left = NULL; + gt->right = NULL; + return (gt); +} + +struct globtree * +globtree_true(void) +{ + struct globtree *gt; + + gt = globtree_new(GLOBTREE_TRUE); + return (gt); +} + +struct globtree * +globtree_false(void) +{ + struct globtree *gt; + + gt = globtree_new(GLOBTREE_FALSE); + return (gt); +} + +struct globtree * +globtree_match(const char *pattern, int flags) +{ + struct globtree *gt; + + gt = globtree_new(GLOBTREE_MATCH); + gt->data = xstrdup(pattern); + gt->flags = flags; + return (gt); +} + +struct globtree * +globtree_regex(const char *pattern) +{ + struct globtree *gt; + int error; + + gt = globtree_new(GLOBTREE_REGEX); + gt->data = xmalloc(sizeof(regex_t)); + error = regcomp(gt->data, pattern, REG_NOSUB); + assert(!error); + return (gt); +} + +struct globtree * +globtree_and(struct globtree *left, struct globtree *right) +{ + struct globtree *gt; + + if (left->type == GLOBTREE_FALSE || right->type == GLOBTREE_FALSE) { + globtree_free(left); + globtree_free(right); + gt = globtree_false(); + return (gt); + } + if (left->type == GLOBTREE_TRUE) { + globtree_free(left); + return (right); + } + if (right->type == GLOBTREE_TRUE) { + globtree_free(right); + return (left); + } + gt = globtree_new(GLOBTREE_AND); + gt->left = left; + gt->right = right; + return (gt); +} + +struct globtree * +globtree_or(struct globtree *left, struct globtree *right) +{ + struct globtree *gt; + + if (left->type == GLOBTREE_TRUE || right->type == GLOBTREE_TRUE) { + globtree_free(left); + globtree_free(right); + gt = globtree_true(); + return (gt); + } + if (left->type == GLOBTREE_FALSE) { + globtree_free(left); + return (right); + } + if (right->type == GLOBTREE_FALSE) { + globtree_free(right); + return (left); + } + gt = globtree_new(GLOBTREE_OR); + gt->left = left; + gt->right = right; + return (gt); +} + +struct globtree * +globtree_not(struct globtree *child) +{ + struct globtree *gt; + + if (child->type == GLOBTREE_TRUE) { + globtree_free(child); + gt = globtree_new(GLOBTREE_FALSE); + return (gt); + } + if (child->type == GLOBTREE_FALSE) { + globtree_free(child); + gt = globtree_new(GLOBTREE_TRUE); + return (gt); + } + gt = globtree_new(GLOBTREE_NOT); + gt->left = child; + return (gt); +} + +/* Evaluate one node (must be a leaf node). */ +static int +globtree_eval(struct globtree *gt, const char *path) +{ + int rv; + + switch (gt->type) { + case GLOBTREE_TRUE: + return (1); + case GLOBTREE_FALSE: + return (0); + case GLOBTREE_MATCH: + assert(gt->data != NULL); + rv = fnmatch(gt->data, path, gt->flags); + if (rv == 0) + return (1); + assert(rv == FNM_NOMATCH); + return (0); + case GLOBTREE_REGEX: + assert(gt->data != NULL); + rv = regexec(gt->data, path, 0, NULL, 0); + if (rv == 0) + return (1); + assert(rv == REG_NOMATCH); + return (0); + } + + assert(0); + return (-1); +} + +/* Small stack API to walk the tree iteratively. */ +typedef enum { + STATE_DOINGLEFT, + STATE_DOINGRIGHT +} walkstate_t; + +struct stack { + struct stackelem *stack; + size_t size; + size_t in; +}; + +struct stackelem { + struct globtree *node; + walkstate_t state; +}; + +static void +stack_init(struct stack *stack) +{ + + stack->in = 0; + stack->size = 8; /* Initial size. */ + stack->stack = xmalloc(sizeof(struct stackelem) * stack->size); +} + +static size_t +stack_size(struct stack *stack) +{ + + return (stack->in); +} + +static void +stack_push(struct stack *stack, struct globtree *node, walkstate_t state) +{ + struct stackelem *e; + + if (stack->in == stack->size) { + stack->size *= 2; + stack->stack = xrealloc(stack->stack, + sizeof(struct stackelem) * stack->size); + } + e = stack->stack + stack->in++; + e->node = node; + e->state = state; +} + +static void +stack_pop(struct stack *stack, struct globtree **node, walkstate_t *state) +{ + struct stackelem *e; + + assert(stack->in > 0); + e = stack->stack + --stack->in; + *node = e->node; + *state = e->state; +} + +static void +stack_free(struct stack *s) +{ + + free(s->stack); +} + +/* Tests if the supplied filename matches. */ +int +globtree_test(struct globtree *gt, const char *path) +{ + struct stack stack; + walkstate_t state; + int val; + + stack_init(&stack); + for (;;) { +doleft: + /* Descend to the left until we hit bottom. */ + while (gt->left != NULL) { + stack_push(&stack, gt, STATE_DOINGLEFT); + gt = gt->left; + } + + /* Now we're at a leaf node. Evaluate it. */ + val = globtree_eval(gt, path); + /* Ascend, propagating the value through operator nodes. */ + for (;;) { + if (stack_size(&stack) == 0) { + stack_free(&stack); + return (val); + } + stack_pop(&stack, >, &state); + switch (gt->type) { + case GLOBTREE_NOT: + val = !val; + break; + case GLOBTREE_AND: + /* If we haven't yet evaluated the right subtree + and the partial result is true, descend to + the right. Otherwise the result is already + determined to be val. */ + if (state == STATE_DOINGLEFT && val) { + stack_push(&stack, gt, + STATE_DOINGRIGHT); + gt = gt->right; + goto doleft; + } + break; + case GLOBTREE_OR: + /* If we haven't yet evaluated the right subtree + and the partial result is false, descend to + the right. Otherwise the result is already + determined to be val. */ + if (state == STATE_DOINGLEFT && !val) { + stack_push(&stack, gt, + STATE_DOINGRIGHT); + gt = gt->right; + goto doleft; + } + break; + default: + /* We only push nodes that have children. */ + assert(0); + return (-1); + } + } + } +} + +/* + * We could de-recursify this function using a stack, but it would be + * overkill since it is never called from a thread context with a + * limited stack size nor used in a critical path, so I think we can + * afford keeping it recursive. + */ +void +globtree_free(struct globtree *gt) +{ + + if (gt->data != NULL) { + if (gt->type == GLOBTREE_REGEX) + regfree(gt->data); + free(gt->data); + } + if (gt->left != NULL) + globtree_free(gt->left); + if (gt->right != NULL) + globtree_free(gt->right); + free(gt); +} diff --git a/contrib/csup/globtree.h b/contrib/csup/globtree.h new file mode 100644 index 000000000000..43882e3a8f9a --- /dev/null +++ b/contrib/csup/globtree.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _GLOBTREE_H_ +#define _GLOBTREE_H_ + +#include "fnmatch.h" + +struct globtree; + +struct globtree *globtree_true(void); +struct globtree *globtree_false(void); +struct globtree *globtree_match(const char *, int); +struct globtree *globtree_regex(const char *); +struct globtree *globtree_and(struct globtree *, struct globtree *); +struct globtree *globtree_or(struct globtree *, struct globtree *); +struct globtree *globtree_not(struct globtree *); +int globtree_test(struct globtree *, const char *); +void globtree_free(struct globtree *); + +#endif /* !_GLOBTREE_H_ */ diff --git a/contrib/csup/keyword.c b/contrib/csup/keyword.c new file mode 100644 index 000000000000..dab44f0851e0 --- /dev/null +++ b/contrib/csup/keyword.c @@ -0,0 +1,502 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "diff.h" +#include "keyword.h" +#include "misc.h" +#include "queue.h" +#include "stream.h" + +/* + * The keyword API is used to expand the CVS/RCS keywords in files, + * such as $Id$, $Revision$, etc. The server does it for us when it + * sends us entire files, but we need to handle the expansion when + * applying a diff update. + */ + +enum rcskey { + RCSKEY_AUTHOR, + RCSKEY_CVSHEADER, + RCSKEY_DATE, + RCSKEY_HEADER, + RCSKEY_ID, + RCSKEY_LOCKER, + RCSKEY_LOG, + RCSKEY_NAME, + RCSKEY_RCSFILE, + RCSKEY_REVISION, + RCSKEY_SOURCE, + RCSKEY_STATE +}; + +typedef enum rcskey rcskey_t; + +struct tag { + char *ident; + rcskey_t key; + int enabled; + STAILQ_ENTRY(tag) next; +}; + +static struct tag *tag_new(const char *, rcskey_t); +static char *tag_expand(struct tag *, struct diffinfo *); +static void tag_free(struct tag *); + +struct keyword { + STAILQ_HEAD(, tag) keywords; /* Enabled keywords. */ + size_t minkeylen; + size_t maxkeylen; +}; + +/* Default CVS keywords. */ +static struct { + const char *ident; + rcskey_t key; +} tag_defaults[] = { + { "Author", RCSKEY_AUTHOR }, + { "CVSHeader", RCSKEY_CVSHEADER }, + { "Date", RCSKEY_DATE }, + { "Header", RCSKEY_HEADER }, + { "Id", RCSKEY_ID }, + { "Locker", RCSKEY_LOCKER }, + { "Log", RCSKEY_LOG }, + { "Name", RCSKEY_NAME }, + { "RCSfile", RCSKEY_RCSFILE }, + { "Revision", RCSKEY_REVISION }, + { "Source", RCSKEY_SOURCE }, + { "State", RCSKEY_STATE }, + { NULL, 0, } +}; + +struct keyword * +keyword_new(void) +{ + struct keyword *new; + struct tag *tag; + size_t len; + int i; + + new = xmalloc(sizeof(struct keyword)); + STAILQ_INIT(&new->keywords); + new->minkeylen = ~0; + new->maxkeylen = 0; + for (i = 0; tag_defaults[i].ident != NULL; i++) { + tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key); + STAILQ_INSERT_TAIL(&new->keywords, tag, next); + len = strlen(tag->ident); + /* + * These values are only computed here and not updated when + * adding an alias. This is a bug, but CVSup has it and we + * need to be bug-to-bug compatible since the server will + * expect us to do the same, and we will fail with an MD5 + * checksum mismatch if we don't. + */ + new->minkeylen = min(new->minkeylen, len); + new->maxkeylen = max(new->maxkeylen, len); + } + return (new); +} + +int +keyword_decode_expand(const char *expand) +{ + + if (strcmp(expand, ".") == 0) + return (EXPAND_DEFAULT); + else if (strcmp(expand, "kv") == 0) + return (EXPAND_KEYVALUE); + else if (strcmp(expand, "kvl") == 0) + return (EXPAND_KEYVALUELOCKER); + else if (strcmp(expand, "k") == 0) + return (EXPAND_KEY); + else if (strcmp(expand, "o") == 0) + return (EXPAND_OLD); + else if (strcmp(expand, "b") == 0) + return (EXPAND_BINARY); + else if (strcmp(expand, "v") == 0) + return (EXPAND_VALUE); + else + return (-1); +} + +void +keyword_free(struct keyword *keyword) +{ + struct tag *tag; + + if (keyword == NULL) + return; + while (!STAILQ_EMPTY(&keyword->keywords)) { + tag = STAILQ_FIRST(&keyword->keywords); + STAILQ_REMOVE_HEAD(&keyword->keywords, next); + tag_free(tag); + } + free(keyword); +} + +int +keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey) +{ + struct tag *new, *tag; + + STAILQ_FOREACH(tag, &keyword->keywords, next) { + if (strcmp(tag->ident, rcskey) == 0) { + new = tag_new(ident, tag->key); + STAILQ_INSERT_HEAD(&keyword->keywords, new, next); + return (0); + } + } + errno = ENOENT; + return (-1); +} + +int +keyword_enable(struct keyword *keyword, const char *ident) +{ + struct tag *tag; + int all; + + all = 0; + if (strcmp(ident, ".") == 0) + all = 1; + + STAILQ_FOREACH(tag, &keyword->keywords, next) { + if (!all && strcmp(tag->ident, ident) != 0) + continue; + tag->enabled = 1; + if (!all) + return (0); + } + if (!all) { + errno = ENOENT; + return (-1); + } + return (0); +} + +int +keyword_disable(struct keyword *keyword, const char *ident) +{ + struct tag *tag; + int all; + + all = 0; + if (strcmp(ident, ".") == 0) + all = 1; + + STAILQ_FOREACH(tag, &keyword->keywords, next) { + if (!all && strcmp(tag->ident, ident) != 0) + continue; + tag->enabled = 0; + if (!all) + return (0); + } + + if (!all) { + errno = ENOENT; + return (-1); + } + return (0); +} + +void +keyword_prepare(struct keyword *keyword) +{ + struct tag *tag, *temp; + + STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) { + if (!tag->enabled) { + STAILQ_REMOVE(&keyword->keywords, tag, tag, next); + tag_free(tag); + continue; + } + } +} + +/* + * Expand appropriate RCS keywords. If there's no tag to expand, + * keyword_expand() returns 0, otherwise it returns 1 and writes a + * pointer to the new line in *buf and the new len in *len. The + * new line is allocated with malloc() and needs to be freed by the + * caller after use. + */ +int +keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line, + size_t size, char **buf, size_t *len) +{ + struct tag *tag; + char *dollar, *keystart, *valstart, *vallim, *next; + char *linestart, *newline, *newval, *cp, *tmp; + size_t left, newsize, vallen; + + if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY) + return (0); + newline = NULL; + newsize = 0; + left = size; + linestart = cp = line; +again: + dollar = memchr(cp, '$', left); + if (dollar == NULL) { + if (newline != NULL) { + *buf = newline; + *len = newsize; + return (1); + } + return (0); + } + keystart = dollar + 1; + left -= keystart - cp; + vallim = memchr(keystart, '$', left); + if (vallim == NULL) { + if (newline != NULL) { + *buf = newline; + *len = newsize; + return (1); + } + return (0); + } + if (vallim == keystart) { + cp = keystart; + goto again; + } + valstart = memchr(keystart, ':', left); + if (valstart == keystart) { + cp = vallim; + left -= vallim - keystart; + goto again; + } + if (valstart == NULL || valstart > vallim) + valstart = vallim; + + if (valstart < keystart + keyword->minkeylen || + valstart > keystart + keyword->maxkeylen) { + cp = vallim; + left -= vallim -keystart; + goto again; + } + STAILQ_FOREACH(tag, &keyword->keywords, next) { + if (strncmp(tag->ident, keystart, valstart - keystart) == 0 && + tag->ident[valstart - keystart] == '\0') { + if (newline != NULL) + tmp = newline; + else + tmp = NULL; + newval = NULL; + if (di->di_expand == EXPAND_KEY) { + newsize = dollar - linestart + 1 + + valstart - keystart + 1 + + size - (vallim + 1 - linestart); + newline = xmalloc(newsize); + cp = newline; + memcpy(cp, linestart, dollar - linestart); + cp += dollar - linestart; + *cp++ = '$'; + memcpy(cp, keystart, valstart - keystart); + cp += valstart - keystart; + *cp++ = '$'; + next = cp; + memcpy(cp, vallim + 1, + size - (vallim + 1 - linestart)); + } else if (di->di_expand == EXPAND_VALUE) { + newval = tag_expand(tag, di); + if (newval == NULL) + vallen = 0; + else + vallen = strlen(newval); + newsize = dollar - linestart + + vallen + + size - (vallim + 1 - linestart); + newline = xmalloc(newsize); + cp = newline; + memcpy(cp, linestart, dollar - linestart); + cp += dollar - linestart; + if (newval != NULL) { + memcpy(cp, newval, vallen); + cp += vallen; + } + next = cp; + memcpy(cp, vallim + 1, + size - (vallim + 1 - linestart)); + } else { + assert(di->di_expand == EXPAND_DEFAULT || + di->di_expand == EXPAND_KEYVALUE || + di->di_expand == EXPAND_KEYVALUELOCKER); + newval = tag_expand(tag, di); + if (newval == NULL) + vallen = 0; + else + vallen = strlen(newval); + newsize = dollar - linestart + 1 + + valstart - keystart + 2 + + vallen + 2 + + size - (vallim + 1 - linestart); + newline = xmalloc(newsize); + cp = newline; + memcpy(cp, linestart, dollar - linestart); + cp += dollar - linestart; + *cp++ = '$'; + memcpy(cp, keystart, valstart - keystart); + cp += valstart - keystart; + *cp++ = ':'; + *cp++ = ' '; + if (newval != NULL) { + memcpy(cp, newval, vallen); + cp += vallen; + } + *cp++ = ' '; + *cp++ = '$'; + next = cp; + memcpy(cp, vallim + 1, + size - (vallim + 1 - linestart)); + } + if (newval != NULL) + free(newval); + if (tmp != NULL) + free(tmp); + /* + * Continue looking for tags in the rest of the line. + */ + cp = next; + size = newsize; + left = size - (cp - newline); + linestart = newline; + goto again; + } + } + cp = vallim; + left = size - (cp - linestart); + goto again; +} + +static struct tag * +tag_new(const char *ident, rcskey_t key) +{ + struct tag *new; + + new = xmalloc(sizeof(struct tag)); + new->ident = xstrdup(ident); + new->key = key; + new->enabled = 1; + return (new); +} + +static void +tag_free(struct tag *tag) +{ + + free(tag->ident); + free(tag); +} + +/* + * Expand a specific tag and return the new value. If NULL + * is returned, the tag is empty. + */ +static char * +tag_expand(struct tag *tag, struct diffinfo *di) +{ + /* + * CVS formats dates as "XXXX/XX/XX XX:XX:XX". 32 bytes + * is big enough until year 10,000,000,000,000,000 :-). + */ + char cvsdate[32]; + struct tm tm; + char *filename, *val; + int error; + + error = rcsdatetotm(di->di_revdate, &tm); + if (error) + err(1, "strptime"); + if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0) + err(1, "strftime"); + filename = strrchr(di->di_rcsfile, '/'); + if (filename == NULL) + filename = di->di_rcsfile; + else + filename++; + + switch (tag->key) { + case RCSKEY_AUTHOR: + xasprintf(&val, "%s", di->di_author); + break; + case RCSKEY_CVSHEADER: + xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile, + di->di_revnum, cvsdate, di->di_author, di->di_state); + break; + case RCSKEY_DATE: + xasprintf(&val, "%s", cvsdate); + break; + case RCSKEY_HEADER: + xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot, + di->di_rcsfile, di->di_revnum, cvsdate, di->di_author, + di->di_state); + break; + case RCSKEY_ID: + xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum, + cvsdate, di->di_author, di->di_state); + break; + case RCSKEY_LOCKER: + /* + * Unimplemented even in CVSup sources. It seems we don't + * even have this information sent by the server. + */ + return (NULL); + case RCSKEY_LOG: + /* XXX */ + printf("%s: Implement Log keyword expansion\n", __func__); + return (NULL); + case RCSKEY_NAME: + if (di->di_tag != NULL) + xasprintf(&val, "%s", di->di_tag); + else + return (NULL); + break; + case RCSKEY_RCSFILE: + xasprintf(&val, "%s", filename); + break; + case RCSKEY_REVISION: + xasprintf(&val, "%s", di->di_revnum); + break; + case RCSKEY_SOURCE: + xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile); + break; + case RCSKEY_STATE: + xasprintf(&val, "%s", di->di_state); + break; + } + return (val); +} diff --git a/contrib/csup/keyword.h b/contrib/csup/keyword.h new file mode 100644 index 000000000000..3f152c11ea45 --- /dev/null +++ b/contrib/csup/keyword.h @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _KEYWORD_H_ +#define _KEYWORD_H_ + +/* CVS expansion modes. */ +#define EXPAND_DEFAULT 0 +#define EXPAND_KEYVALUE 1 +#define EXPAND_KEYVALUELOCKER 2 +#define EXPAND_KEY 3 +#define EXPAND_OLD 4 +#define EXPAND_BINARY 5 +#define EXPAND_VALUE 6 + +struct diffinfo; +struct keyword; + +struct keyword *keyword_new(void); +int keyword_decode_expand(const char *); +int keyword_alias(struct keyword *, const char *, const char *); +int keyword_enable(struct keyword *, const char *); +int keyword_disable(struct keyword *, const char *); +void keyword_prepare(struct keyword *); +int keyword_expand(struct keyword *, struct diffinfo *, char *, + size_t, char **, size_t *); +void keyword_free(struct keyword *); + +#endif /* !_KEYWORD_H_ */ diff --git a/contrib/csup/lister.c b/contrib/csup/lister.c new file mode 100644 index 000000000000..a048d25d888b --- /dev/null +++ b/contrib/csup/lister.c @@ -0,0 +1,438 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "attrstack.h" +#include "config.h" +#include "fattr.h" +#include "globtree.h" +#include "lister.h" +#include "misc.h" +#include "mux.h" +#include "proto.h" +#include "status.h" +#include "stream.h" + +/* Internal error codes. */ +#define LISTER_ERR_WRITE (-1) /* Error writing to server. */ +#define LISTER_ERR_STATUS (-2) /* Status file error in lstr->errmsg. */ + +struct lister { + struct config *config; + struct stream *wr; + char *errmsg; +}; + +static int lister_batch(struct lister *); +static int lister_coll(struct lister *, struct coll *, struct status *); +static int lister_dodirdown(struct lister *, struct coll *, + struct statusrec *, struct attrstack *as); +static int lister_dodirup(struct lister *, struct coll *, + struct statusrec *, struct attrstack *as); +static int lister_dofile(struct lister *, struct coll *, + struct statusrec *); +static int lister_dodead(struct lister *, struct coll *, + struct statusrec *); + +void * +lister(void *arg) +{ + struct thread_args *args; + struct lister lbuf, *l; + int error; + + args = arg; + l = &lbuf; + l->config = args->config; + l->wr = args->wr; + l->errmsg = NULL; + error = lister_batch(l); + switch (error) { + case LISTER_ERR_WRITE: + xasprintf(&args->errmsg, + "TreeList failed: Network write failure: %s", + strerror(errno)); + args->status = STATUS_TRANSIENTFAILURE; + break; + case LISTER_ERR_STATUS: + xasprintf(&args->errmsg, + "TreeList failed: %s. Delete it and try again.", + l->errmsg); + free(l->errmsg); + args->status = STATUS_FAILURE; + break; + default: + assert(error == 0); + args->status = STATUS_SUCCESS; + }; + return (NULL); +} + +static int +lister_batch(struct lister *l) +{ + struct config *config; + struct stream *wr; + struct status *st; + struct coll *coll; + int error; + + config = l->config; + wr = l->wr; + STAILQ_FOREACH(coll, &config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + st = status_open(coll, -1, &l->errmsg); + if (st == NULL) + return (LISTER_ERR_STATUS); + error = proto_printf(wr, "COLL %s %s\n", coll->co_name, + coll->co_release); + if (error) + return (LISTER_ERR_WRITE); + stream_flush(wr); + if (coll->co_options & CO_COMPRESS) + stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); + error = lister_coll(l, coll, st); + status_close(st, NULL); + if (error) + return (error); + if (coll->co_options & CO_COMPRESS) + stream_filter_stop(wr); + stream_flush(wr); + } + error = proto_printf(wr, ".\n"); + if (error) + return (LISTER_ERR_WRITE); + return (0); +} + +/* List a single collection based on the status file. */ +static int +lister_coll(struct lister *l, struct coll *coll, struct status *st) +{ + struct stream *wr; + struct attrstack *as; + struct statusrec *sr; + struct fattr *fa; + int depth, error, ret, prunedepth; + + wr = l->wr; + depth = 0; + prunedepth = INT_MAX; + as = attrstack_new(); + while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) { + switch (sr->sr_type) { + case SR_DIRDOWN: + depth++; + if (depth < prunedepth) { + error = lister_dodirdown(l, coll, sr, as); + if (error < 0) + goto bad; + if (error) + prunedepth = depth; + } + break; + case SR_DIRUP: + if (depth < prunedepth) { + error = lister_dodirup(l, coll, sr, as); + if (error) + goto bad; + } else if (depth == prunedepth) { + /* Finished pruning. */ + prunedepth = INT_MAX; + } + depth--; + continue; + case SR_CHECKOUTLIVE: + if (depth < prunedepth) { + error = lister_dofile(l, coll, sr); + if (error) + goto bad; + } + break; + case SR_CHECKOUTDEAD: + if (depth < prunedepth) { + error = lister_dodead(l, coll, sr); + if (error) + goto bad; + } + break; + } + } + if (ret == -1) { + l->errmsg = status_errmsg(st); + error = LISTER_ERR_STATUS; + goto bad; + } + assert(status_eof(st)); + assert(depth == 0); + error = proto_printf(wr, ".\n"); + attrstack_free(as); + if (error) + return (LISTER_ERR_WRITE); + return (0); +bad: + while (depth-- > 0) { + fa = attrstack_pop(as); + fattr_free(fa); + } + attrstack_free(as); + return (error); +} + +/* Handle a directory up entry found in the status file. */ +static int +lister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr, + struct attrstack *as) +{ + struct config *config; + struct stream *wr; + struct fattr *fa, *fa2; + char *path; + int error; + + config = l->config; + wr = l->wr; + if (!globtree_test(coll->co_dirfilter, sr->sr_file)) + return (1); + if (coll->co_options & CO_TRUSTSTATUSFILE) { + fa = fattr_new(FT_DIRECTORY, -1); + } else { + xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + /* The directory doesn't exist, prune + * everything below it. */ + free(path); + return (1); + } + if (fattr_type(fa) == FT_SYMLINK) { + fa2 = fattr_frompath(path, FATTR_FOLLOW); + if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) { + /* XXX - When not in checkout mode, CVSup warns + * here about the file being a symlink to a + * directory instead of a directory. */ + fattr_free(fa); + fa = fa2; + } else { + fattr_free(fa2); + } + } + free(path); + } + + if (fattr_type(fa) != FT_DIRECTORY) { + fattr_free(fa); + /* Report it as something bogus so + * that it will be replaced. */ + error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), + fattr_bogus, config->fasupport, coll->co_attrignore); + if (error) + return (LISTER_ERR_WRITE); + return (1); + } + + /* It really is a directory. */ + attrstack_push(as, fa); + error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file)); + if (error) + return (LISTER_ERR_WRITE); + return (0); +} + +/* Handle a directory up entry found in the status file. */ +static int +lister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr, + struct attrstack *as) +{ + struct config *config; + const struct fattr *sendattr; + struct stream *wr; + struct fattr *fa, *fa2; + int error; + + config = l->config; + wr = l->wr; + fa = attrstack_pop(as); + if (coll->co_options & CO_TRUSTSTATUSFILE) { + fattr_free(fa); + fa = sr->sr_clientattr; + } + + fa2 = sr->sr_clientattr; + if (fattr_equal(fa, fa2)) + sendattr = fa; + else + sendattr = fattr_bogus; + error = proto_printf(wr, "U %F\n", sendattr, config->fasupport, + coll->co_attrignore); + if (error) + return (LISTER_ERR_WRITE); + if (!(coll->co_options & CO_TRUSTSTATUSFILE)) + fattr_free(fa); + /* XXX CVSup flushes here for some reason with a comment saying + "Be smarter". We don't flush when listing other file types. */ + stream_flush(wr); + return (0); +} + +/* Handle a checkout live entry found in the status file. */ +static int +lister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr) +{ + struct config *config; + struct stream *wr; + const struct fattr *sendattr, *fa; + struct fattr *fa2, *rfa; + char *path, *spath; + int error; + + if (!globtree_test(coll->co_filefilter, sr->sr_file)) + return (0); + config = l->config; + wr = l->wr; + rfa = NULL; + sendattr = NULL; + error = 0; + if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { + path = checkoutpath(coll->co_prefix, sr->sr_file); + if (path == NULL) { + spath = coll_statuspath(coll); + xasprintf(&l->errmsg, "Error in \"%s\": " + "Invalid filename \"%s\"", spath, sr->sr_file); + free(spath); + return (LISTER_ERR_STATUS); + } + rfa = fattr_frompath(path, FATTR_NOFOLLOW); + free(path); + if (rfa == NULL) { + /* + * According to the checkouts file we should have + * this file but we don't. Maybe the user deleted + * the file, or maybe the checkouts file is wrong. + * List the file with bogus attributes to cause the + * server to get things back in sync again. + */ + sendattr = fattr_bogus; + goto send; + } + fa = rfa; + } else { + fa = sr->sr_clientattr; + } + fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); + if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) || + strcmp(coll->co_tag, sr->sr_tag) != 0 || + strcmp(coll->co_date, sr->sr_date) != 0) { + /* + * The file corresponds to the information we have + * recorded about it, and its moded is correct for + * the requested umask setting. + */ + sendattr = fattr_bogus; + } else { + /* + * Either the file has been touched, or we are asking + * for a different revision than the one we recorded + * information about, or its mode isn't right (because + * it was last updated using a version of CVSup that + * wasn't so strict about modes). + */ + sendattr = sr->sr_serverattr; + } + fattr_free(fa2); + if (rfa != NULL) + fattr_free(rfa); +send: + error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, + config->fasupport, coll->co_attrignore); + if (error) + return (LISTER_ERR_WRITE); + return (0); +} + +/* Handle a checkout dead entry found in the status file. */ +static int +lister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr) +{ + struct config *config; + struct stream *wr; + const struct fattr *sendattr; + struct fattr *fa; + char *path, *spath; + int error; + + if (!globtree_test(coll->co_filefilter, sr->sr_file)) + return (0); + config = l->config; + wr = l->wr; + if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { + path = checkoutpath(coll->co_prefix, sr->sr_file); + if (path == NULL) { + spath = coll_statuspath(coll); + xasprintf(&l->errmsg, "Error in \"%s\": " + "Invalid filename \"%s\"", spath, sr->sr_file); + free(spath); + return (LISTER_ERR_STATUS); + } + fa = fattr_frompath(path, FATTR_NOFOLLOW); + free(path); + if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) { + /* + * We shouldn't have this file but we do. Report + * it to the server, which will either send a + * deletion request, of (if the file has come alive) + * sent the correct version. + */ + fattr_free(fa); + error = proto_printf(wr, "F %s %F\n", + pathlast(sr->sr_file), fattr_bogus, + config->fasupport, coll->co_attrignore); + if (error) + return (LISTER_ERR_WRITE); + return (0); + } + fattr_free(fa); + } + if (strcmp(coll->co_tag, sr->sr_tag) != 0 || + strcmp(coll->co_date, sr->sr_date) != 0) + sendattr = fattr_bogus; + else + sendattr = sr->sr_serverattr; + error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, + config->fasupport, coll->co_attrignore); + if (error) + return (LISTER_ERR_WRITE); + return (0); +} diff --git a/contrib/csup/lister.h b/contrib/csup/lister.h new file mode 100644 index 000000000000..a0a9bbe3556b --- /dev/null +++ b/contrib/csup/lister.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _LISTER_H_ +#define _LISTER_H_ + +void *lister(void *); + +#endif /* !_LISTER_H_ */ diff --git a/contrib/csup/main.c b/contrib/csup/main.c new file mode 100644 index 000000000000..33f591510270 --- /dev/null +++ b/contrib/csup/main.c @@ -0,0 +1,315 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/file.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "fattr.h" +#include "misc.h" +#include "proto.h" +#include "stream.h" + +#define USAGE_OPTFMT " %-12s %s\n" +#define USAGE_OPTFMTSUB " %-14s %s\n", "" + +int verbose = 1; + +static void +usage(char *argv0) +{ + + lprintf(-1, "Usage: %s [options] supfile\n", basename(argv0)); + lprintf(-1, " Options:\n"); + lprintf(-1, USAGE_OPTFMT, "-1", "Don't retry automatically on failure " + "(same as \"-r 0\")"); + lprintf(-1, USAGE_OPTFMT, "-4", "Force usage of IPv4 addresses"); + lprintf(-1, USAGE_OPTFMT, "-6", "Force usage of IPv6 addresses"); + lprintf(-1, USAGE_OPTFMT, "-A addr", + "Bind local socket to a specific address"); + lprintf(-1, USAGE_OPTFMT, "-b base", + "Override supfile's \"base\" directory"); + lprintf(-1, USAGE_OPTFMT, "-c collDir", + "Subdirectory of \"base\" for collections (default \"sup\")"); + lprintf(-1, USAGE_OPTFMT, "-h host", + "Override supfile's \"host\" name"); + lprintf(-1, USAGE_OPTFMT, "-i pattern", + "Include only files/directories matching pattern."); + lprintf(-1, USAGE_OPTFMTSUB, + "May be repeated for an OR operation. Default is"); + lprintf(-1, USAGE_OPTFMTSUB, "to include each entire collection."); + lprintf(-1, USAGE_OPTFMT, "-l lockfile", + "Lock file during update; fail if already locked"); + lprintf(-1, USAGE_OPTFMT, "-L n", + "Verbosity level (0..2, default 1)"); + lprintf(-1, USAGE_OPTFMT, "-p port", + "Alternate server port (default 5999)"); + lprintf(-1, USAGE_OPTFMT, "-r n", + "Maximum retries on transient errors (default unlimited)"); + lprintf(-1, USAGE_OPTFMT, "-s", + "Don't stat client files; trust the checkouts file"); + lprintf(-1, USAGE_OPTFMT, "-v", "Print version and exit"); + lprintf(-1, USAGE_OPTFMT, "-z", "Enable compression for all " + "collections"); + lprintf(-1, USAGE_OPTFMT, "-Z", "Disable compression for all " + "collections"); +} + +int +main(int argc, char *argv[]) +{ + struct tm tm; + struct backoff_timer *timer; + struct config *config; + struct coll *override; + struct addrinfo *res; + struct sockaddr *laddr; + socklen_t laddrlen; + struct stream *lock; + char *argv0, *file, *lockfile; + uint16_t port; + int family, error, lockfd, lflag, overridemask; + int c, i, retries, status; + time_t nexttry; + + error = 0; + family = PF_UNSPEC; + port = 0; + lflag = 0; + lockfd = 0; + nexttry = 0; + retries = -1; + argv0 = argv[0]; + laddr = NULL; + laddrlen = 0; + lockfile = NULL; + override = coll_new(NULL); + overridemask = 0; + + while ((c = getopt(argc, argv, "146A:b:c:gh:i:l:L:p:P:r:svzZ")) != -1) { + switch (c) { + case '1': + retries = 0; + break; + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; + case 'A': + error = getaddrinfo(optarg, NULL, NULL, &res); + if (error) { + lprintf(-1, "%s: %s\n", optarg, + gai_strerror(error)); + return (1); + } + laddrlen = res->ai_addrlen; + laddr = xmalloc(laddrlen); + memcpy(laddr, res->ai_addr, laddrlen); + freeaddrinfo(res); + break; + case 'b': + if (override->co_base != NULL) + free(override->co_base); + override->co_base = xstrdup(optarg); + break; + case 'c': + override->co_colldir = optarg; + break; + case 'g': + /* For compatibility. */ + break; + case 'h': + if (override->co_host != NULL) + free(override->co_host); + override->co_host = xstrdup(optarg); + break; + case 'i': + pattlist_add(override->co_accepts, optarg); + break; + case 'l': + lockfile = optarg; + lflag = 1; + lockfd = open(lockfile, + O_CREAT | O_WRONLY | O_TRUNC, 0700); + if (lockfd != -1) { + error = flock(lockfd, LOCK_EX | LOCK_NB); + if (error == -1 && errno == EWOULDBLOCK) { + if (lockfd != -1) + close(lockfd); + lprintf(-1, "\"%s\" is already locked " + "by another process\n", lockfile); + return (1); + } + } + if (lockfd == -1 || error == -1) { + if (lockfd != -1) + close(lockfd); + lprintf(-1, "Error locking \"%s\": %s\n", + lockfile, strerror(errno)); + return (1); + } + lock = stream_open_fd(lockfd, + NULL, stream_write_fd, NULL); + (void)stream_printf(lock, "%10ld\n", (long)getpid()); + stream_close(lock); + break; + case 'L': + errno = 0; + verbose = strtol(optarg, NULL, 0); + if (errno == EINVAL) { + lprintf(-1, "Invalid verbosity\n"); + usage(argv0); + return (1); + } + break; + case 'p': + /* Use specified server port. */ + errno = 0; + port = strtol(optarg, NULL, 0); + if (errno == EINVAL) { + lprintf(-1, "Invalid server port\n"); + usage(argv0); + return (1); + } + break; + case 'P': + /* For compatibility. */ + if (strcmp(optarg, "m") != 0) { + lprintf(-1, + "Client only supports multiplexed mode\n"); + return (1); + } + break; + case 'r': + errno = 0; + retries = strtol(optarg, NULL, 0); + if (errno == EINVAL || retries < 0) { + lprintf(-1, "Invalid retry limit\n"); + usage(argv0); + return (1); + } + break; + case 's': + override->co_options |= CO_TRUSTSTATUSFILE; + overridemask |= CO_TRUSTSTATUSFILE; + break; + case 'v': + lprintf(0, "CVSup client written in C\n"); + lprintf(0, "Software version: %s\n", PROTO_SWVER); + lprintf(0, "Protocol version: %d.%d\n", + PROTO_MAJ, PROTO_MIN); + lprintf(0, "http://mu.org/~mux/csup.html\n"); + return (0); + break; + case 'z': + /* Force compression on all collections. */ + override->co_options |= CO_COMPRESS; + overridemask |= CO_COMPRESS; + break; + case 'Z': + /* Disables compression on all collections. */ + override->co_options &= ~CO_COMPRESS; + overridemask &= ~CO_COMPRESS; + break; + case '?': + default: + usage(argv0); + return (1); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(argv0); + return (1); + } + + file = argv[0]; + lprintf(2, "Parsing supfile \"%s\"\n", file); + config = config_init(file, override, overridemask); + coll_free(override); + if (config == NULL) + return (1); + + if (laddr != NULL) { + config->laddr = laddr; + config->laddrlen = laddrlen; + } + if (config_checkcolls(config) == 0) { + lprintf(-1, "No collections selected\n"); + return (1); + } + + lprintf(2, "Connecting to %s\n", config->host); + + i = 0; + fattr_init(); /* Initialize the fattr API. */ + timer = bt_new(300, 7200, 2.0, 0.1); + for (;;) { + status = proto_connect(config, family, port); + if (status == STATUS_SUCCESS) { + status = proto_run(config); + if (status != STATUS_TRANSIENTFAILURE) + break; + } + if (retries >= 0 && i >= retries) + break; + nexttry = time(0) + bt_get(timer); + localtime_r(&nexttry, &tm); + lprintf(1, "Will retry at %02d:%02d:%02d\n", + tm.tm_hour, tm.tm_min, tm.tm_sec); + bt_pause(timer); + lprintf(1, "Retrying\n"); + i++; + } + bt_free(timer); + fattr_fini(); + if (lflag) { + unlink(lockfile); + flock(lockfd, LOCK_UN); + close(lockfd); + } + config_free(config); + if (status != STATUS_SUCCESS) + return (1); + return (0); +} diff --git a/contrib/csup/main.h b/contrib/csup/main.h new file mode 100644 index 000000000000..217c7eb9c1ac --- /dev/null +++ b/contrib/csup/main.h @@ -0,0 +1,29 @@ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +extern int verbose; diff --git a/contrib/csup/misc.c b/contrib/csup/misc.c new file mode 100644 index 000000000000..ff9403e0c794 --- /dev/null +++ b/contrib/csup/misc.c @@ -0,0 +1,503 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <openssl/md5.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "fattr.h" +#include "main.h" +#include "misc.h" + +struct pattlist { + char **patterns; + size_t size; + size_t in; +}; + +struct backoff_timer { + time_t min; + time_t max; + time_t interval; + float backoff; + float jitter; +}; + +static void bt_update(struct backoff_timer *); +static void bt_addjitter(struct backoff_timer *); + +int +lprintf(int level, const char *fmt, ...) +{ + FILE *to; + va_list ap; + int ret; + + if (level > verbose) + return (0); + if (level == -1) + to = stderr; + else + to = stdout; + va_start(ap, fmt); + ret = vfprintf(to, fmt, ap); + va_end(ap); + fflush(to); + return (ret); +} + +/* + * Compute the MD5 checksum of a file. The md parameter must + * point to a buffer containing at least MD5_DIGEST_SIZE bytes. + * + * Do not confuse OpenSSL's MD5_DIGEST_LENGTH with our own + * MD5_DIGEST_SIZE macro. + */ +int +MD5_File(char *path, char *md) +{ + char buf[1024]; + MD5_CTX ctx; + ssize_t n; + int fd; + + fd = open(path, O_RDONLY); + if (fd == -1) + return (-1); + MD5_Init(&ctx); + while ((n = read(fd, buf, sizeof(buf))) > 0) + MD5_Update(&ctx, buf, n); + close(fd); + if (n == -1) + return (-1); + MD5_End(md, &ctx); + return (0); +} + +/* + * Wrapper around MD5_Final() that converts the 128 bits MD5 hash + * to an ASCII string representing this value in hexadecimal. + */ +void +MD5_End(char *md, MD5_CTX *c) +{ + unsigned char md5[MD5_DIGEST_LENGTH]; + const char hex[] = "0123456789abcdef"; + int i, j; + + MD5_Final(md5, c); + j = 0; + for (i = 0; i < MD5_DIGEST_LENGTH; i++) { + md[j++] = hex[md5[i] >> 4]; + md[j++] = hex[md5[i] & 0xf]; + } + md[j] = '\0'; +} + +int +pathcmp(const char *s1, const char *s2) +{ + char c1, c2; + + do { + c1 = *s1++; + if (c1 == '/') + c1 = 1; + c2 = *s2++; + if (c2 == '/') + c2 = 1; + } while (c1 == c2 && c1 != '\0'); + + return (c1 - c2); +} + +size_t +commonpathlength(const char *a, size_t alen, const char *b, size_t blen) +{ + size_t i, minlen, lastslash; + + minlen = min(alen, blen); + lastslash = 0; + for (i = 0; i < minlen; i++) { + if (a[i] != b[i]) + return (lastslash); + if (a[i] == '/') { + if (i == 0) /* Include the leading slash. */ + lastslash = 1; + else + lastslash = i; + } + } + + /* One path is a prefix of the other/ */ + if (alen > minlen) { /* Path "b" is a prefix of "a". */ + if (a[minlen] == '/') + return (minlen); + else + return (lastslash); + } else if (blen > minlen) { /* Path "a" is a prefix of "b". */ + if (b[minlen] == '/') + return (minlen); + else + return (lastslash); + } + + /* The paths are identical. */ + return (minlen); +} + +char * +pathlast(char *path) +{ + char *s; + + s = strrchr(path, '/'); + if (s == NULL) + return (path); + return (++s); +} + +int +rcsdatetotm(const char *revdate, struct tm *tm) +{ + char *cp; + size_t len; + + cp = strchr(revdate, '.'); + if (cp == NULL) + return (-1); + len = cp - revdate; + if (len == 4) + cp = strptime(revdate, "%Y.%m.%d.%H.%M.%S", tm); + else if (len == 2) + cp = strptime(revdate, "%y.%m.%d.%H.%M.%S", tm); + else + return (-1); + if (cp == NULL || *cp != '\0') + return (-1); + return (0); +} + +time_t +rcsdatetotime(const char *revdate) +{ + struct tm tm; + time_t t; + int error; + + error = rcsdatetotm(revdate, &tm); + if (error) + return (error); + t = timegm(&tm); + return (t); +} + +/* + * Returns a buffer allocated with malloc() containing the absolute + * pathname to the checkout file made from the prefix and the path + * of the corresponding RCS file relatively to the prefix. If the + * filename is not an RCS filename, NULL will be returned. + */ +char * +checkoutpath(const char *prefix, const char *file) +{ + const char *cp; + char *path; + size_t len; + + if (file[0] == '/') + return (NULL); + cp = file; + while ((cp = strstr(cp, "..")) != NULL) { + if (cp == file || cp[2] == '\0' || + (cp[-1] == '/' && cp[2] == '/')) + return (NULL); + cp += 2; + } + len = strlen(file); + if (len < 2 || file[len - 1] != 'v' || file[len - 2] != ',') + return (NULL); + xasprintf(&path, "%s/%.*s", prefix, (int)len - 2, file); + return (path); +} + +int +mkdirhier(char *path, mode_t mask) +{ + struct fattr *fa; + size_t i, last, len; + int error, finish, rv; + + finish = 0; + last = 0; + len = strlen(path); + for (i = len - 1; i > 0; i--) { + if (path[i] == '/') { + path[i] = '\0'; + if (access(path, F_OK) == 0) { + path[i] = '/'; + break; + } + if (errno != ENOENT) { + path[i] = '/'; + if (last == 0) + return (-1); + finish = 1; + break; + } + last = i; + } + } + if (last == 0) + return (0); + + i = strlen(path); + fa = fattr_new(FT_DIRECTORY, -1); + fattr_mergedefault(fa); + fattr_umask(fa, mask); + while (i < len) { + if (!finish) { + rv = 0; + error = fattr_makenode(fa, path); + if (!error) + rv = fattr_install(fa, path, NULL); + if (error || rv == -1) + finish = 1; + } + path[i] = '/'; + i += strlen(path + i); + } + assert(i == len); + if (finish) + return (-1); + return (0); +} + +/* + * Compute temporary pathnames. + * This can look a bit like overkill but we mimic CVSup's behaviour. + */ +#define TEMPNAME_PREFIX "#cvs.csup" + +static pthread_mutex_t tempname_mtx = PTHREAD_MUTEX_INITIALIZER; +static pid_t tempname_pid = -1; +static int tempname_count; + +char * +tempname(const char *path) +{ + char *cp, *temp; + int count, error; + + error = pthread_mutex_lock(&tempname_mtx); + assert(!error); + if (tempname_pid == -1) { + tempname_pid = getpid(); + tempname_count = 0; + } + count = tempname_count++; + error = pthread_mutex_unlock(&tempname_mtx); + assert(!error); + cp = strrchr(path, '/'); + if (cp == NULL) + xasprintf(&temp, "%s-%ld.%d", TEMPNAME_PREFIX, + (long)tempname_pid, count); + else + xasprintf(&temp, "%.*s%s-%ld.%d", (int)(cp - path + 1), path, + TEMPNAME_PREFIX, (long)tempname_pid, count); + return (temp); +} + +void * +xmalloc(size_t size) +{ + void *buf; + + buf = malloc(size); + if (buf == NULL) + err(1, "malloc"); + return (buf); +} + +void * +xrealloc(void *buf, size_t size) +{ + + buf = realloc(buf, size); + if (buf == NULL) + err(1, "realloc"); + return (buf); +} + +char * +xstrdup(const char *str) +{ + char *buf; + + buf = strdup(str); + if (buf == NULL) + err(1, "strdup"); + return (buf); +} + +int +xasprintf(char **ret, const char *format, ...) +{ + va_list ap; + int rv; + + va_start(ap, format); + rv = vasprintf(ret, format, ap); + va_end(ap); + if (*ret == NULL) + err(1, "asprintf"); + return (rv); +} + +struct pattlist * +pattlist_new(void) +{ + struct pattlist *p; + + p = xmalloc(sizeof(struct pattlist)); + p->size = 4; /* Initial size. */ + p->patterns = xmalloc(p->size * sizeof(char *)); + p->in = 0; + return (p); +} + +void +pattlist_add(struct pattlist *p, const char *pattern) +{ + + if (p->in == p->size) { + p->size *= 2; + p->patterns = xrealloc(p->patterns, p->size * sizeof(char *)); + } + assert(p->in < p->size); + p->patterns[p->in++] = xstrdup(pattern); +} + +char * +pattlist_get(struct pattlist *p, size_t i) +{ + + assert(i < p->in); + return (p->patterns[i]); +} + +size_t +pattlist_size(struct pattlist *p) +{ + + return (p->in); +} + +void +pattlist_free(struct pattlist *p) +{ + size_t i; + + for (i = 0; i < p->in; i++) + free(p->patterns[i]); + free(p->patterns); + free(p); +} + +/* Creates a backoff timer. */ +struct backoff_timer * +bt_new(time_t min, time_t max, float backoff, float jitter) +{ + struct backoff_timer *bt; + + bt = xmalloc(sizeof(struct backoff_timer)); + bt->min = min; + bt->max = max; + bt->backoff = backoff; + bt->jitter = jitter; + bt->interval = min; + bt_addjitter(bt); + srandom(time(0)); + return (bt); +} + +/* Updates the backoff timer. */ +static void +bt_update(struct backoff_timer *bt) +{ + + bt->interval = (time_t)min(bt->interval * bt->backoff, bt->max); + bt_addjitter(bt); +} + +/* Adds some jitter. */ +static void +bt_addjitter(struct backoff_timer *bt) +{ + long mag; + + mag = (long)(bt->jitter * bt->interval); + /* We want a random number between -mag and mag. */ + bt->interval += (time_t)(random() % (2 * mag) - mag); +} + +/* Returns the current timer value. */ +time_t +bt_get(struct backoff_timer *bt) +{ + + return (bt->interval); +} + +/* Times out for bt->interval seconds. */ +void +bt_pause(struct backoff_timer *bt) +{ + + sleep(bt->interval); + bt_update(bt); +} + +void +bt_free(struct backoff_timer *bt) +{ + + free(bt); +} diff --git a/contrib/csup/misc.h b/contrib/csup/misc.h new file mode 100644 index 000000000000..47f976750d2d --- /dev/null +++ b/contrib/csup/misc.h @@ -0,0 +1,128 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _MISC_H_ +#define _MISC_H_ + +#include <openssl/md5.h> + +#include <sys/types.h> + +/* If we're not compiling in a C99 environment, define the C99 types. */ +#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901 + +#ifdef uint32_t +#undef uint32_t +#endif +#define uint32_t u_int32_t + +#ifdef uint16_t +#undef uint16_t +#endif +#define uint16_t u_int16_t + +#ifdef uint8_t +#undef uint8_t +#endif +#define uint8_t u_int8_t + +#else +#include <stdint.h> +#endif + +/* This is a GCC-specific keyword but some other compilers (namely icc) + understand it, and the code won't work if we can't disable padding + anyways. */ +#undef __packed +#define __packed __attribute__((__packed__)) + +/* We explicitely don't define this with icc because it defines __GNUC__ + but doesn't support it. */ +#undef __printflike +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && \ + (__GNUC__ > 2 || __GNUC__ == 2 && __GNUC__MINOR__ >= 7) +#define __printflike(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +#define __printflike(fmtarg, firstvararg) +#endif + +/* Exit codes. */ +#define STATUS_SUCCESS 0 +#define STATUS_FAILURE 1 +#define STATUS_TRANSIENTFAILURE 2 +#define STATUS_INTERRUPTED 3 + +struct config; +struct stream; + +/* Thread parameters. */ +struct thread_args { + struct config *config; + struct stream *rd; + struct stream *wr; + int status; + char *errmsg; +}; + +/* Minimum size for MD5_File() and MD5_End() buffers. */ +#define MD5_DIGEST_SIZE 33 + +#define min(a, b) ((a) > (b) ? (b) : (a)) +#define max(a, b) ((a) < (b) ? (b) : (a)) + +struct backoff_timer; +struct pattlist; +struct tm; + +int lprintf(int, const char *, ...) __printflike(2, 3); +int MD5_File(char *, char *); +void MD5_End(char *, MD5_CTX *); +int rcsdatetotm(const char *, struct tm *); +time_t rcsdatetotime(const char *); +int pathcmp(const char *, const char *); +size_t commonpathlength(const char *, size_t, const char *, size_t); +char *pathlast(char *); +char *checkoutpath(const char *, const char *); +int mkdirhier(char *, mode_t); +char *tempname(const char *); +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); +int xasprintf(char **, const char *, ...) __printflike(2, 3); + +struct pattlist *pattlist_new(void); +void pattlist_add(struct pattlist *, const char *); +char *pattlist_get(struct pattlist *, size_t); +size_t pattlist_size(struct pattlist *); +void pattlist_free(struct pattlist *); + +struct backoff_timer *bt_new(time_t, time_t, float, float); +time_t bt_get(struct backoff_timer *); +void bt_pause(struct backoff_timer *); +void bt_free(struct backoff_timer *); +#endif /* !_MISC_H_ */ diff --git a/contrib/csup/mux.c b/contrib/csup/mux.c new file mode 100644 index 000000000000..b663b5411b6f --- /dev/null +++ b/contrib/csup/mux.c @@ -0,0 +1,1201 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> + +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "misc.h" +#include "mux.h" + +/* + * Packet types. + */ +#define MUX_STARTUPREQ 0 +#define MUX_STARTUPREP 1 +#define MUX_CONNECT 2 +#define MUX_ACCEPT 3 +#define MUX_RESET 4 +#define MUX_DATA 5 +#define MUX_WINDOW 6 +#define MUX_CLOSE 7 + +/* + * Header sizes. + */ +#define MUX_STARTUPHDRSZ 3 +#define MUX_CONNECTHDRSZ 8 +#define MUX_ACCEPTHDRSZ 8 +#define MUX_RESETHDRSZ 2 +#define MUX_DATAHDRSZ 4 +#define MUX_WINDOWHDRSZ 6 +#define MUX_CLOSEHDRSZ 2 + +#define MUX_PROTOVER 0 /* Protocol version. */ + +struct mux_header { + uint8_t type; + union { + struct { + uint16_t version; + } __packed mh_startup; + struct { + uint8_t id; + uint16_t mss; + uint32_t window; + } __packed mh_connect; + struct { + uint8_t id; + uint16_t mss; + uint32_t window; + } __packed mh_accept; + struct { + uint8_t id; + } __packed mh_reset; + struct { + uint8_t id; + uint16_t len; + } __packed mh_data; + struct { + uint8_t id; + uint32_t window; + } __packed mh_window; + struct { + uint8_t id; + } __packed mh_close; + } mh_u; +} __packed; + +#define mh_startup mh_u.mh_startup +#define mh_connect mh_u.mh_connect +#define mh_accept mh_u.mh_accept +#define mh_reset mh_u.mh_reset +#define mh_data mh_u.mh_data +#define mh_window mh_u.mh_window +#define mh_close mh_u.mh_close + +#define MUX_MAXCHAN 2 + +/* Channel states. */ +#define CS_UNUSED 0 +#define CS_LISTENING 1 +#define CS_CONNECTING 2 +#define CS_ESTABLISHED 3 +#define CS_RDCLOSED 4 +#define CS_WRCLOSED 5 +#define CS_CLOSED 6 + +/* Channel flags. */ +#define CF_CONNECT 0x01 +#define CF_ACCEPT 0x02 +#define CF_RESET 0x04 +#define CF_WINDOW 0x08 +#define CF_DATA 0x10 +#define CF_CLOSE 0x20 + +#define CHAN_SBSIZE (16 * 1024) /* Send buffer size. */ +#define CHAN_RBSIZE (16 * 1024) /* Receive buffer size. */ +#define CHAN_MAXSEGSIZE 1024 /* Maximum segment size. */ + +/* Circular buffer. */ +struct buf { + uint8_t *data; + size_t size; + size_t in; + size_t out; +}; + +struct chan { + int flags; + int state; + pthread_mutex_t lock; + struct mux *mux; + + /* Receiver state variables. */ + struct buf *recvbuf; + pthread_cond_t rdready; + uint32_t recvseq; + uint16_t recvmss; + + /* Sender state variables. */ + struct buf *sendbuf; + pthread_cond_t wrready; + uint32_t sendseq; + uint32_t sendwin; + uint16_t sendmss; +}; + +struct mux { + int closed; + int status; + int socket; + pthread_mutex_t lock; + pthread_cond_t done; + struct chan *channels[MUX_MAXCHAN]; + int nchans; + + /* Sender thread data. */ + pthread_t sender; + pthread_cond_t sender_newwork; + pthread_cond_t sender_started; + int sender_waiting; + int sender_ready; + int sender_lastid; + + /* Receiver thread data. */ + pthread_t receiver; +}; + +static int sock_writev(int, struct iovec *, int); +static int sock_write(int, void *, size_t); +static ssize_t sock_read(int, void *, size_t); +static int sock_readwait(int, void *, size_t); + +static int mux_init(struct mux *); +static void mux_lock(struct mux *); +static void mux_unlock(struct mux *); + +static struct chan *chan_new(struct mux *); +static struct chan *chan_get(struct mux *, int); +static struct chan *chan_connect(struct mux *, int); +static void chan_lock(struct chan *); +static void chan_unlock(struct chan *); +static int chan_insert(struct mux *, struct chan *); +static void chan_free(struct chan *); + +static struct buf *buf_new(size_t); +static size_t buf_count(struct buf *); +static size_t buf_avail(struct buf *); +static void buf_get(struct buf *, void *, size_t); +static void buf_put(struct buf *, const void *, size_t); +static void buf_free(struct buf *); + +static void sender_wakeup(struct mux *); +static void *sender_loop(void *); +static int sender_waitforwork(struct mux *, int *); +static int sender_scan(struct mux *, int *); +static void sender_cleanup(void *); + +static void *receiver_loop(void *); + +static int +sock_writev(int s, struct iovec *iov, int iovcnt) +{ + ssize_t nbytes; + +again: + nbytes = writev(s, iov, iovcnt); + if (nbytes != -1) { + while (nbytes > 0 && (size_t)nbytes >= iov->iov_len) { + nbytes -= iov->iov_len; + iov++; + iovcnt--; + } + if (nbytes == 0) + return (0); + iov->iov_len -= nbytes; + iov->iov_base = (char *)iov->iov_base + nbytes; + } else if (errno != EINTR) { + return (-1); + } + goto again; +} + +static int +sock_write(int s, void *buf, size_t size) +{ + struct iovec iov; + int ret; + + iov.iov_base = buf; + iov.iov_len = size; + ret = sock_writev(s, &iov, 1); + return (ret); +} + +static ssize_t +sock_read(int s, void *buf, size_t size) +{ + ssize_t nbytes; + +again: + nbytes = read(s, buf, size); + if (nbytes == -1 && errno == EINTR) + goto again; + return (nbytes); +} + +static int +sock_readwait(int s, void *buf, size_t size) +{ + char *cp; + ssize_t nbytes; + size_t left; + + cp = buf; + left = size; + while (left > 0) { + nbytes = sock_read(s, cp, left); + if (nbytes == 0) { + errno = ECONNRESET; + return (-1); + } + if (nbytes < 0) + return (-1); + left -= nbytes; + cp += nbytes; + } + return (0); +} + +static void +mux_lock(struct mux *m) +{ + int error; + + error = pthread_mutex_lock(&m->lock); + assert(!error); +} + +static void +mux_unlock(struct mux *m) +{ + int error; + + error = pthread_mutex_unlock(&m->lock); + assert(!error); +} + +/* Create a TCP multiplexer on the given socket. */ +struct mux * +mux_open(int sock, struct chan **chan) +{ + struct mux *m; + struct chan *chan0; + int error; + + m = xmalloc(sizeof(struct mux)); + memset(m->channels, 0, sizeof(m->channels)); + m->nchans = 0; + m->closed = 0; + m->status = -1; + m->socket = sock; + + m->sender_waiting = 0; + m->sender_lastid = 0; + m->sender_ready = 0; + pthread_mutex_init(&m->lock, NULL); + pthread_cond_init(&m->done, NULL); + pthread_cond_init(&m->sender_newwork, NULL); + pthread_cond_init(&m->sender_started, NULL); + + error = mux_init(m); + if (error) + goto bad; + chan0 = chan_connect(m, 0); + if (chan0 == NULL) + goto bad; + *chan = chan0; + return (m); +bad: + mux_shutdown(m, NULL, STATUS_FAILURE); + (void)mux_close(m); + return (NULL); +} + +int +mux_close(struct mux *m) +{ + struct chan *chan; + int i, status; + + assert(m->closed); + for (i = 0; i < m->nchans; i++) { + chan = m->channels[i]; + if (chan != NULL) + chan_free(chan); + } + pthread_cond_destroy(&m->sender_started); + pthread_cond_destroy(&m->sender_newwork); + pthread_cond_destroy(&m->done); + pthread_mutex_destroy(&m->lock); + status = m->status; + free(m); + return (status); +} + +/* Close a channel. */ +int +chan_close(struct chan *chan) +{ + + chan_lock(chan); + if (chan->state == CS_ESTABLISHED) { + chan->state = CS_WRCLOSED; + chan->flags |= CF_CLOSE; + } else if (chan->state == CS_RDCLOSED) { + chan->state = CS_CLOSED; + chan->flags |= CF_CLOSE; + } else if (chan->state == CS_WRCLOSED || chan->state == CS_CLOSED) { + chan_unlock(chan); + return (0); + } else { + chan_unlock(chan); + return (-1); + } + chan_unlock(chan); + sender_wakeup(chan->mux); + return (0); +} + +void +chan_wait(struct chan *chan) +{ + + chan_lock(chan); + while (chan->state != CS_CLOSED) + pthread_cond_wait(&chan->rdready, &chan->lock); + chan_unlock(chan); +} + +/* Returns the ID of an available channel in the listening state. */ +int +chan_listen(struct mux *m) +{ + struct chan *chan; + int i; + + mux_lock(m); + for (i = 0; i < m->nchans; i++) { + chan = m->channels[i]; + chan_lock(chan); + if (chan->state == CS_UNUSED) { + mux_unlock(m); + chan->state = CS_LISTENING; + chan_unlock(chan); + return (i); + } + chan_unlock(chan); + } + mux_unlock(m); + chan = chan_new(m); + chan->state = CS_LISTENING; + i = chan_insert(m, chan); + if (i == -1) + chan_free(chan); + return (i); +} + +struct chan * +chan_accept(struct mux *m, int id) +{ + struct chan *chan; + + chan = chan_get(m, id); + while (chan->state == CS_LISTENING) + pthread_cond_wait(&chan->rdready, &chan->lock); + if (chan->state != CS_ESTABLISHED) { + errno = ECONNRESET; + chan_unlock(chan); + return (NULL); + } + chan_unlock(chan); + return (chan); +} + +/* Read bytes from a channel. */ +ssize_t +chan_read(struct chan *chan, void *buf, size_t size) +{ + char *cp; + size_t count, n; + + cp = buf; + chan_lock(chan); + for (;;) { + if (chan->state == CS_RDCLOSED || chan->state == CS_CLOSED) { + chan_unlock(chan); + return (0); + } + if (chan->state != CS_ESTABLISHED && + chan->state != CS_WRCLOSED) { + chan_unlock(chan); + errno = EBADF; + return (-1); + } + count = buf_count(chan->recvbuf); + if (count > 0) + break; + pthread_cond_wait(&chan->rdready, &chan->lock); + } + n = min(count, size); + buf_get(chan->recvbuf, cp, n); + chan->recvseq += n; + chan->flags |= CF_WINDOW; + chan_unlock(chan); + /* We need to wake up the sender so that it sends a window update. */ + sender_wakeup(chan->mux); + return (n); +} + +/* Write bytes to a channel. */ +ssize_t +chan_write(struct chan *chan, const void *buf, size_t size) +{ + const char *cp; + size_t avail, n, pos; + + pos = 0; + cp = buf; + chan_lock(chan); + while (pos < size) { + for (;;) { + if (chan->state != CS_ESTABLISHED && + chan->state != CS_RDCLOSED) { + chan_unlock(chan); + errno = EPIPE; + return (-1); + } + avail = buf_avail(chan->sendbuf); + if (avail > 0) + break; + pthread_cond_wait(&chan->wrready, &chan->lock); + } + n = min(avail, size - pos); + buf_put(chan->sendbuf, cp + pos, n); + pos += n; + } + chan_unlock(chan); + sender_wakeup(chan->mux); + return (size); +} + +/* + * Internal channel API. + */ + +static struct chan * +chan_connect(struct mux *m, int id) +{ + struct chan *chan; + + chan = chan_get(m, id); + if (chan->state != CS_UNUSED) { + chan_unlock(chan); + return (NULL); + } + chan->state = CS_CONNECTING; + chan->flags |= CF_CONNECT; + chan_unlock(chan); + sender_wakeup(m); + chan_lock(chan); + while (chan->state == CS_CONNECTING) + pthread_cond_wait(&chan->wrready, &chan->lock); + if (chan->state != CS_ESTABLISHED) { + chan_unlock(chan); + return (NULL); + } + chan_unlock(chan); + return (chan); +} + +/* + * Get a channel from its ID, creating it if necessary. + * The channel is returned locked. + */ +static struct chan * +chan_get(struct mux *m, int id) +{ + struct chan *chan; + + assert(id < MUX_MAXCHAN); + mux_lock(m); + chan = m->channels[id]; + if (chan == NULL) { + chan = chan_new(m); + m->channels[id] = chan; + m->nchans++; + } + chan_lock(chan); + mux_unlock(m); + return (chan); +} + +/* Lock a channel. */ +static void +chan_lock(struct chan *chan) +{ + int error; + + error = pthread_mutex_lock(&chan->lock); + assert(!error); +} + +/* Unlock a channel. */ +static void +chan_unlock(struct chan *chan) +{ + int error; + + error = pthread_mutex_unlock(&chan->lock); + assert(!error); +} + +/* + * Create a new channel. + */ +static struct chan * +chan_new(struct mux *m) +{ + struct chan *chan; + + chan = xmalloc(sizeof(struct chan)); + chan->state = CS_UNUSED; + chan->flags = 0; + chan->mux = m; + chan->sendbuf = buf_new(CHAN_SBSIZE); + chan->sendseq = 0; + chan->sendwin = 0; + chan->sendmss = 0; + chan->recvbuf = buf_new(CHAN_RBSIZE); + chan->recvseq = 0; + chan->recvmss = CHAN_MAXSEGSIZE; + pthread_mutex_init(&chan->lock, NULL); + pthread_cond_init(&chan->rdready, NULL); + pthread_cond_init(&chan->wrready, NULL); + return (chan); +} + +/* Free any resources associated with a channel. */ +static void +chan_free(struct chan *chan) +{ + + pthread_cond_destroy(&chan->rdready); + pthread_cond_destroy(&chan->wrready); + pthread_mutex_destroy(&chan->lock); + buf_free(chan->recvbuf); + buf_free(chan->sendbuf); + free(chan); +} + +/* Insert the new channel in the channel list. */ +static int +chan_insert(struct mux *m, struct chan *chan) +{ + int i; + + mux_lock(m); + for (i = 0; i < MUX_MAXCHAN; i++) { + if (m->channels[i] == NULL) { + m->channels[i] = chan; + m->nchans++; + mux_unlock(m); + return (i); + } + } + errno = ENOBUFS; + return (-1); +} + +/* + * Initialize the multiplexer protocol. + * + * This means negotiating protocol version and starting + * the receiver and sender threads. + */ +static int +mux_init(struct mux *m) +{ + struct mux_header mh; + int error; + + mh.type = MUX_STARTUPREQ; + mh.mh_startup.version = htons(MUX_PROTOVER); + error = sock_write(m->socket, &mh, MUX_STARTUPHDRSZ); + if (error) + return (-1); + error = sock_readwait(m->socket, &mh, MUX_STARTUPHDRSZ); + if (error) + return (-1); + if (mh.type != MUX_STARTUPREP || + ntohs(mh.mh_startup.version) != MUX_PROTOVER) + return (-1); + mux_lock(m); + error = pthread_create(&m->sender, NULL, sender_loop, m); + if (error) { + mux_unlock(m); + return (-1); + } + /* + * Make sure the sender thread has run and is waiting for new work + * before going on. Otherwise, it might lose the race and a + * request, which will cause a deadlock. + */ + while (!m->sender_ready) + pthread_cond_wait(&m->sender_started, &m->lock); + + mux_unlock(m); + error = pthread_create(&m->receiver, NULL, receiver_loop, m); + if (error) + return (-1); + return (0); +} + +/* + * Close all the channels, terminate the sender and receiver thread. + * This is an important function because it is used everytime we need + * to wake up all the worker threads to abort the program. + * + * This function accepts an error message that will be printed if the + * multiplexer wasn't already closed. This is useful because it ensures + * that only the first error message will be printed, and that it will + * be printed before doing the actual shutdown work. If this is a + * normal shutdown, NULL can be passed instead. + * + * The "status" parameter of the first mux_shutdown() call is retained + * and then returned by mux_close(), so that the main thread can know + * what type of error happened in the end, if any. + */ +void +mux_shutdown(struct mux *m, const char *errmsg, int status) +{ + pthread_t self, sender, receiver; + struct chan *chan; + const char *name; + void *val; + int i, ret; + + mux_lock(m); + if (m->closed) { + mux_unlock(m); + return; + } + m->closed = 1; + m->status = status; + self = pthread_self(); + sender = m->sender; + receiver = m->receiver; + if (errmsg != NULL) { + if (pthread_equal(self, receiver)) + name = "Receiver"; + else if (pthread_equal(self, sender)) + name = "Sender"; + else + name = NULL; + if (name == NULL) + lprintf(-1, "%s\n", errmsg); + else + lprintf(-1, "%s: %s\n", name, errmsg); + } + + for (i = 0; i < MUX_MAXCHAN; i++) { + if (m->channels[i] != NULL) { + chan = m->channels[i]; + chan_lock(chan); + if (chan->state != CS_UNUSED) { + chan->state = CS_CLOSED; + chan->flags = 0; + pthread_cond_broadcast(&chan->rdready); + pthread_cond_broadcast(&chan->wrready); + } + chan_unlock(chan); + } + } + mux_unlock(m); + + if (!pthread_equal(self, receiver)) { + ret = pthread_cancel(receiver); + assert(!ret); + pthread_join(receiver, &val); + assert(val == PTHREAD_CANCELED); + } + if (!pthread_equal(self, sender)) { + ret = pthread_cancel(sender); + assert(!ret); + pthread_join(sender, &val); + assert(val == PTHREAD_CANCELED); + } +} + +static void +sender_wakeup(struct mux *m) +{ + int waiting; + + mux_lock(m); + waiting = m->sender_waiting; + mux_unlock(m); + /* + * We don't care about the race here: if the sender was + * waiting and is not anymore, we'll just send a useless + * signal; if he wasn't waiting then he won't go to sleep + * before having sent what we want him to. + */ + if (waiting) + pthread_cond_signal(&m->sender_newwork); +} + +static void * +sender_loop(void *arg) +{ + struct iovec iov[3]; + struct mux_header mh; + struct mux *m; + struct chan *chan; + struct buf *buf; + uint32_t winsize; + uint16_t hdrsize, size, len; + int error, id, iovcnt, what; + + m = (struct mux *)arg; +again: + id = sender_waitforwork(m, &what); + chan = chan_get(m, id); + hdrsize = size = 0; + switch (what) { + case CF_CONNECT: + mh.type = MUX_CONNECT; + mh.mh_connect.id = id; + mh.mh_connect.mss = htons(chan->recvmss); + mh.mh_connect.window = htonl(chan->recvseq + + chan->recvbuf->size); + hdrsize = MUX_CONNECTHDRSZ; + break; + case CF_ACCEPT: + mh.type = MUX_ACCEPT; + mh.mh_accept.id = id; + mh.mh_accept.mss = htons(chan->recvmss); + mh.mh_accept.window = htonl(chan->recvseq + + chan->recvbuf->size); + hdrsize = MUX_ACCEPTHDRSZ; + break; + case CF_RESET: + mh.type = MUX_RESET; + mh.mh_reset.id = id; + hdrsize = MUX_RESETHDRSZ; + break; + case CF_WINDOW: + mh.type = MUX_WINDOW; + mh.mh_window.id = id; + mh.mh_window.window = htonl(chan->recvseq + + chan->recvbuf->size); + hdrsize = MUX_WINDOWHDRSZ; + break; + case CF_DATA: + mh.type = MUX_DATA; + mh.mh_data.id = id; + size = min(buf_count(chan->sendbuf), chan->sendmss); + winsize = chan->sendwin - chan->sendseq; + if (winsize < size) + size = winsize; + mh.mh_data.len = htons(size); + hdrsize = MUX_DATAHDRSZ; + break; + case CF_CLOSE: + mh.type = MUX_CLOSE; + mh.mh_close.id = id; + hdrsize = MUX_CLOSEHDRSZ; + break; + } + if (size > 0) { + assert(mh.type == MUX_DATA); + /* + * Older FreeBSD versions (and maybe other OSes) have the + * iov_base field defined as char *. Cast to char * to + * silence a warning in this case. + */ + iov[0].iov_base = (char *)&mh; + iov[0].iov_len = hdrsize; + iovcnt = 1; + /* We access the buffer directly to avoid some copying. */ + buf = chan->sendbuf; + len = min(size, buf->size + 1 - buf->out); + iov[iovcnt].iov_base = buf->data + buf->out; + iov[iovcnt].iov_len = len; + iovcnt++; + if (size > len) { + /* Wrapping around. */ + iov[iovcnt].iov_base = buf->data; + iov[iovcnt].iov_len = size - len; + iovcnt++; + } + /* + * Since we're the only thread sending bytes from the + * buffer and modifying buf->out, it's safe to unlock + * here during I/O. It avoids keeping the channel lock + * too long, since write() might block. + */ + chan_unlock(chan); + error = sock_writev(m->socket, iov, iovcnt); + if (error) + goto bad; + chan_lock(chan); + chan->sendseq += size; + buf->out += size; + if (buf->out > buf->size) + buf->out -= buf->size + 1; + pthread_cond_signal(&chan->wrready); + chan_unlock(chan); + } else { + chan_unlock(chan); + error = sock_write(m->socket, &mh, hdrsize); + if (error) + goto bad; + } + goto again; +bad: + if (error == EPIPE) + mux_shutdown(m, strerror(errno), STATUS_TRANSIENTFAILURE); + else + mux_shutdown(m, strerror(errno), STATUS_FAILURE); + return (NULL); +} + +static void +sender_cleanup(void *arg) +{ + struct mux *m; + + m = (struct mux *)arg; + mux_unlock(m); +} + +static int +sender_waitforwork(struct mux *m, int *what) +{ + int id; + + mux_lock(m); + pthread_cleanup_push(sender_cleanup, m); + if (!m->sender_ready) { + pthread_cond_signal(&m->sender_started); + m->sender_ready = 1; + } + while ((id = sender_scan(m, what)) == -1) { + m->sender_waiting = 1; + pthread_cond_wait(&m->sender_newwork, &m->lock); + } + m->sender_waiting = 0; + pthread_cleanup_pop(1); + return (id); +} + +/* + * Scan for work to do for the sender. Has to be called with + * the multiplexer lock held. + */ +static int +sender_scan(struct mux *m, int *what) +{ + struct chan *chan; + int id; + + if (m->nchans <= 0) + return (-1); + id = m->sender_lastid; + do { + id++; + if (id >= m->nchans) + id = 0; + chan = m->channels[id]; + chan_lock(chan); + if (chan->state != CS_UNUSED) { + if (chan->sendseq != chan->sendwin && + buf_count(chan->sendbuf) > 0) + chan->flags |= CF_DATA; + if (chan->flags) { + /* By order of importance. */ + if (chan->flags & CF_CONNECT) + *what = CF_CONNECT; + else if (chan->flags & CF_ACCEPT) + *what = CF_ACCEPT; + else if (chan->flags & CF_RESET) + *what = CF_RESET; + else if (chan->flags & CF_WINDOW) + *what = CF_WINDOW; + else if (chan->flags & CF_DATA) + *what = CF_DATA; + else if (chan->flags & CF_CLOSE) + *what = CF_CLOSE; + chan->flags &= ~*what; + chan_unlock(chan); + m->sender_lastid = id; + return (id); + } + } + chan_unlock(chan); + } while (id != m->sender_lastid); + return (-1); +} + +/* Read the rest of a packet header depending on its type. */ +#define SOCK_READREST(s, mh, hsize) \ + sock_readwait(s, (char *)&mh + sizeof(mh.type), (hsize) - sizeof(mh.type)) + +void * +receiver_loop(void *arg) +{ + struct mux_header mh; + struct mux *m; + struct chan *chan; + struct buf *buf; + uint16_t size, len; + int error; + + m = (struct mux *)arg; + while ((error = sock_readwait(m->socket, &mh.type, + sizeof(mh.type))) == 0) { + switch (mh.type) { + case MUX_CONNECT: + error = SOCK_READREST(m->socket, mh, MUX_CONNECTHDRSZ); + if (error) + goto bad; + chan = chan_get(m, mh.mh_connect.id); + if (chan->state == CS_LISTENING) { + chan->state = CS_ESTABLISHED; + chan->sendmss = ntohs(mh.mh_connect.mss); + chan->sendwin = ntohl(mh.mh_connect.window); + chan->flags |= CF_ACCEPT; + pthread_cond_signal(&chan->rdready); + } else + chan->flags |= CF_RESET; + chan_unlock(chan); + sender_wakeup(m); + break; + case MUX_ACCEPT: + error = SOCK_READREST(m->socket, mh, MUX_ACCEPTHDRSZ); + if (error) + goto bad; + chan = chan_get(m, mh.mh_accept.id); + if (chan->state == CS_CONNECTING) { + chan->sendmss = ntohs(mh.mh_accept.mss); + chan->sendwin = ntohl(mh.mh_accept.window); + chan->state = CS_ESTABLISHED; + pthread_cond_signal(&chan->wrready); + chan_unlock(chan); + } else { + chan->flags |= CF_RESET; + chan_unlock(chan); + sender_wakeup(m); + } + break; + case MUX_RESET: + error = SOCK_READREST(m->socket, mh, MUX_RESETHDRSZ); + if (error) + goto bad; + goto badproto; + case MUX_WINDOW: + error = SOCK_READREST(m->socket, mh, MUX_WINDOWHDRSZ); + if (error) + goto bad; + chan = chan_get(m, mh.mh_window.id); + if (chan->state == CS_ESTABLISHED || + chan->state == CS_RDCLOSED) { + chan->sendwin = ntohl(mh.mh_window.window); + chan_unlock(chan); + sender_wakeup(m); + } else { + chan_unlock(chan); + } + break; + case MUX_DATA: + error = SOCK_READREST(m->socket, mh, MUX_DATAHDRSZ); + if (error) + goto bad; + chan = chan_get(m, mh.mh_data.id); + len = ntohs(mh.mh_data.len); + buf = chan->recvbuf; + if ((chan->state != CS_ESTABLISHED && + chan->state != CS_WRCLOSED) || + (len > buf_avail(buf) || + len > chan->recvmss)) { + chan_unlock(chan); + goto badproto; + return (NULL); + } + /* + * Similarly to the sender code, it's safe to + * unlock the channel here. + */ + chan_unlock(chan); + size = min(buf->size + 1 - buf->in, len); + error = sock_readwait(m->socket, + buf->data + buf->in, size); + if (error) + goto bad; + if (len > size) { + /* Wrapping around. */ + error = sock_readwait(m->socket, + buf->data, len - size); + if (error) + goto bad; + } + chan_lock(chan); + buf->in += len; + if (buf->in > buf->size) + buf->in -= buf->size + 1; + pthread_cond_signal(&chan->rdready); + chan_unlock(chan); + break; + case MUX_CLOSE: + error = SOCK_READREST(m->socket, mh, MUX_CLOSEHDRSZ); + if (error) + goto bad; + chan = chan_get(m, mh.mh_close.id); + if (chan->state == CS_ESTABLISHED) + chan->state = CS_RDCLOSED; + else if (chan->state == CS_WRCLOSED) + chan->state = CS_CLOSED; + else + goto badproto; + pthread_cond_signal(&chan->rdready); + chan_unlock(chan); + break; + default: + goto badproto; + } + } +bad: + if (errno == ECONNRESET || errno == ECONNABORTED) + mux_shutdown(m, strerror(errno), STATUS_TRANSIENTFAILURE); + else + mux_shutdown(m, strerror(errno), STATUS_FAILURE); + return (NULL); +badproto: + mux_shutdown(m, "Protocol error", STATUS_FAILURE); + return (NULL); +} + +/* + * Circular buffers API. + */ + +static struct buf * +buf_new(size_t size) +{ + struct buf *buf; + + buf = xmalloc(sizeof(struct buf)); + buf->data = xmalloc(size + 1); + buf->size = size; + buf->in = 0; + buf->out = 0; + return (buf); +} + +static void +buf_free(struct buf *buf) +{ + + free(buf->data); + free(buf); +} + +/* Number of bytes stored in the buffer. */ +static size_t +buf_count(struct buf *buf) +{ + size_t count; + + if (buf->in >= buf->out) + count = buf->in - buf->out; + else + count = buf->size + 1 + buf->in - buf->out; + return (count); +} + +/* Number of bytes available in the buffer. */ +static size_t +buf_avail(struct buf *buf) +{ + size_t avail; + + if (buf->out > buf->in) + avail = buf->out - buf->in - 1; + else + avail = buf->size + buf->out - buf->in; + return (avail); +} + +static void +buf_put(struct buf *buf, const void *data, size_t size) +{ + const char *cp; + size_t len; + + assert(size > 0); + assert(buf_avail(buf) >= size); + cp = data; + len = buf->size + 1 - buf->in; + if (len < size) { + /* Wrapping around. */ + memcpy(buf->data + buf->in, cp, len); + memcpy(buf->data, cp + len, size - len); + } else { + /* Not wrapping around. */ + memcpy(buf->data + buf->in, cp, size); + } + buf->in += size; + if (buf->in > buf->size) + buf->in -= buf->size + 1; +} + +static void +buf_get(struct buf *buf, void *data, size_t size) +{ + char *cp; + size_t len; + + assert(size > 0); + assert(buf_count(buf) >= size); + cp = data; + len = buf->size + 1 - buf->out; + if (len < size) { + /* Wrapping around. */ + memcpy(cp, buf->data + buf->out, len); + memcpy(cp + len, buf->data, size - len); + } else { + /* Not wrapping around. */ + memcpy(cp, buf->data + buf->out, size); + } + buf->out += size; + if (buf->out > buf->size) + buf->out -= buf->size + 1; +} diff --git a/contrib/csup/mux.h b/contrib/csup/mux.h new file mode 100644 index 000000000000..ff36083c3573 --- /dev/null +++ b/contrib/csup/mux.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _MUX_H_ +#define _MUX_H_ + +struct mux; +struct chan; + +struct mux *mux_open(int, struct chan **); +void mux_shutdown(struct mux *, const char *, int); +int mux_close(struct mux *); + +void chan_wait(struct chan *); +int chan_listen(struct mux *); +struct chan *chan_accept(struct mux *, int); +ssize_t chan_read(struct chan *, void *, size_t); +ssize_t chan_write(struct chan *, const void *, size_t); +int chan_close(struct chan *); + +#endif /* !_MUX_H_ */ diff --git a/contrib/csup/parse.y b/contrib/csup/parse.y new file mode 100644 index 000000000000..3dcd617421a9 --- /dev/null +++ b/contrib/csup/parse.y @@ -0,0 +1,91 @@ +%{ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> + +#include "config.h" +#include "token.h" + +%} + +%union { + char *str; + int i; +} + +%token DEFAULT +%token <i> NAME +%token <i> BOOLEAN +%token EQUAL +%token <str> STRING + +%% + +config_file + : config_list + | + ; + +config_list + : config + | config_list config + ; + +config + : default_line + | collection + ; + +default_line + : DEFAULT options + { coll_setdef(); } + ; + +collection + : STRING options + { coll_add($1); } + ; + +options + : + | options option + ; + +option + : BOOLEAN + { coll_setopt($1, NULL); } + | value + ; + +value + : NAME EQUAL STRING + { coll_setopt($1, $3); } + ; + +%% diff --git a/contrib/csup/pathcomp.c b/contrib/csup/pathcomp.c new file mode 100644 index 000000000000..380d04298542 --- /dev/null +++ b/contrib/csup/pathcomp.c @@ -0,0 +1,182 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "misc.h" +#include "pathcomp.h" + +struct pathcomp { + char *target; + size_t targetlen; + char *trashed; + char *prev; + size_t prevlen; + size_t goal; + size_t curlen; +}; + +struct pathcomp * +pathcomp_new(void) +{ + struct pathcomp *pc; + + pc = xmalloc(sizeof(struct pathcomp)); + pc->curlen = 0; + pc->target = NULL; + pc->targetlen = 0; + pc->trashed = NULL; + pc->prev = NULL; + pc->prevlen = 0; + return (pc); +} + +int +pathcomp_put(struct pathcomp *pc, int type, char *path) +{ + char *cp; + + assert(pc->target == NULL); + if (*path == '/') + return (-1); + + switch (type) { + case PC_DIRDOWN: + pc->target = path; + pc->targetlen = strlen(path); + break; + case PC_FILE: + case PC_DIRUP: + cp = strrchr(path, '/'); + pc->target = path; + if (cp != NULL) + pc->targetlen = cp - path; + else + pc->targetlen = 0; + break; + } + if (pc->prev != NULL) + pc->goal = commonpathlength(pc->prev, pc->prevlen, pc->target, + pc->targetlen); + else + pc->goal = 0; + if (pc->curlen == pc->goal) /* No need to go up. */ + pc->goal = pc->targetlen; + return (0); +} + +int +pathcomp_get(struct pathcomp *pc, int *type, char **name) +{ + char *cp; + size_t slashpos, start; + + if (pc->curlen > pc->goal) { /* Going up. */ + assert(pc->prev != NULL); + pc->prev[pc->curlen] = '\0'; + cp = pc->prev + pc->curlen - 1; + while (cp >= pc->prev) { + if (*cp == '/') + break; + cp--; + } + if (cp >= pc->prev) + slashpos = cp - pc->prev; + else + slashpos = 0; + pc->curlen = slashpos; + if (pc->curlen <= pc->goal) { /* Done going up. */ + assert(pc->curlen == pc->goal); + pc->goal = pc->targetlen; + } + *type = PC_DIRUP; + *name = pc->prev; + return (1); + } else if (pc->curlen < pc->goal) { /* Going down. */ + /* Restore the previously overwritten '/' character. */ + if (pc->trashed != NULL) { + *pc->trashed = '/'; + pc->trashed = NULL; + } + if (pc->curlen == 0) + start = pc->curlen; + else + start = pc->curlen + 1; + slashpos = start; + while (slashpos < pc->goal) { + if (pc->target[slashpos] == '/') + break; + slashpos++; + } + if (pc->target[slashpos] != '\0') { + assert(pc->target[slashpos] == '/'); + pc->trashed = pc->target + slashpos; + pc->target[slashpos] = '\0'; + } + pc->curlen = slashpos; + *type = PC_DIRDOWN; + *name = pc->target; + return (1); + } else { /* Done. */ + if (pc->target != NULL) { + if (pc->trashed != NULL) { + *pc->trashed = '/'; + pc->trashed = NULL; + } + if (pc->prev != NULL) + free(pc->prev); + pc->prev = xmalloc(pc->targetlen + 1); + memcpy(pc->prev, pc->target, pc->targetlen); + pc->prev[pc->targetlen] = '\0'; + pc->prevlen = pc->targetlen; + pc->target = NULL; + pc->targetlen = 0; + } + return (0); + } +} + +void +pathcomp_finish(struct pathcomp *pc) +{ + + pc->target = NULL; + pc->targetlen = 0; + pc->goal = 0; +} + +void +pathcomp_free(struct pathcomp *pc) +{ + + if (pc->prev != NULL) + free(pc->prev); + free(pc); +} diff --git a/contrib/csup/pathcomp.h b/contrib/csup/pathcomp.h new file mode 100644 index 000000000000..3cea41052f58 --- /dev/null +++ b/contrib/csup/pathcomp.h @@ -0,0 +1,44 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _PATHCOMP_H +#define _PATHCOMP_H + +/* File types */ +#define PC_DIRDOWN 0 +#define PC_FILE 1 +#define PC_DIRUP 2 + +struct pathcomp; + +struct pathcomp *pathcomp_new(void); +int pathcomp_put(struct pathcomp *, int, char *); +int pathcomp_get(struct pathcomp *, int *, char **); +void pathcomp_finish(struct pathcomp *); +void pathcomp_free(struct pathcomp *); + +#endif /* !_PATHCOMP_H */ diff --git a/contrib/csup/proto.c b/contrib/csup/proto.c new file mode 100644 index 000000000000..04f5495095e2 --- /dev/null +++ b/contrib/csup/proto.c @@ -0,0 +1,1004 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <netdb.h> +#include <pthread.h> +#include <signal.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "detailer.h" +#include "fattr.h" +#include "fixups.h" +#include "globtree.h" +#include "keyword.h" +#include "lister.h" +#include "misc.h" +#include "mux.h" +#include "proto.h" +#include "queue.h" +#include "stream.h" +#include "threads.h" +#include "updater.h" + +struct killer { + pthread_t thread; + sigset_t sigset; + struct mux *mux; + int killedby; +}; + +static void killer_start(struct killer *, struct mux *); +static void *killer_run(void *); +static void killer_stop(struct killer *); + +static int proto_waitconnect(int); +static int proto_greet(struct config *); +static int proto_negproto(struct config *); +static int proto_login(struct config *); +static int proto_fileattr(struct config *); +static int proto_xchgcoll(struct config *); +static struct mux *proto_mux(struct config *); + +static int proto_escape(struct stream *, const char *); +static void proto_unescape(char *); + +static int +proto_waitconnect(int s) +{ + fd_set readfd; + socklen_t len; + int error, rv, soerror; + + FD_ZERO(&readfd); + FD_SET(s, &readfd); + + do { + rv = select(s + 1, &readfd, NULL, NULL, NULL); + } while (rv == -1 && errno == EINTR); + if (rv == -1) + return (-1); + /* Check that the connection was really successful. */ + len = sizeof(soerror); + error = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &len); + if (error) { + /* We have no choice but faking an error here. */ + errno = ECONNREFUSED; + return (-1); + } + if (soerror) { + errno = soerror; + return (-1); + } + return (0); +} + +/* Connect to the CVSup server. */ +int +proto_connect(struct config *config, int family, uint16_t port) +{ + char addrbuf[NI_MAXHOST]; + /* Enough to hold sizeof("cvsup") or any port number. */ + char servname[8]; + struct addrinfo *res, *ai, hints; + int error, opt, s; + + s = -1; + if (port != 0) + snprintf(servname, sizeof(servname), "%d", port); + else { + strncpy(servname, "cvsup", sizeof(servname) - 1); + servname[sizeof(servname) - 1] = '\0'; + } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(config->host, servname, &hints, &res); + /* + * Try with the hardcoded port number for OSes that don't + * have cvsup defined in the /etc/services file. + */ + if (error == EAI_SERVICE) { + strncpy(servname, "5999", sizeof(servname) - 1); + servname[sizeof(servname) - 1] = '\0'; + error = getaddrinfo(config->host, servname, &hints, &res); + } + if (error) { + lprintf(0, "Name lookup failure for \"%s\": %s\n", config->host, + gai_strerror(error)); + return (STATUS_TRANSIENTFAILURE); + } + for (ai = res; ai != NULL; ai = ai->ai_next) { + s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (s != -1) { + error = 0; + if (config->laddr != NULL) { + opt = 1; + (void)setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + &opt, sizeof(opt)); + error = bind(s, config->laddr, + config->laddrlen); + } + if (!error) { + error = connect(s, ai->ai_addr, ai->ai_addrlen); + if (error && errno == EINTR) + error = proto_waitconnect(s); + } + if (error) + close(s); + } + (void)getnameinfo(ai->ai_addr, ai->ai_addrlen, addrbuf, + sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); + if (s == -1 || error) { + lprintf(0, "Cannot connect to %s: %s\n", addrbuf, + strerror(errno)); + continue; + } + lprintf(1, "Connected to %s\n", addrbuf); + freeaddrinfo(res); + config->socket = s; + return (STATUS_SUCCESS); + } + freeaddrinfo(res); + return (STATUS_TRANSIENTFAILURE); +} + +/* Greet the server. */ +static int +proto_greet(struct config *config) +{ + char *line, *cmd, *msg, *swver; + struct stream *s; + + s = config->server; + line = stream_getln(s, NULL); + cmd = proto_get_ascii(&line); + if (cmd == NULL) + goto bad; + if (strcmp(cmd, "OK") == 0) { + (void)proto_get_ascii(&line); /* major number */ + (void)proto_get_ascii(&line); /* minor number */ + swver = proto_get_ascii(&line); + } else if (strcmp(cmd, "!") == 0) { + msg = proto_get_rest(&line); + if (msg == NULL) + goto bad; + lprintf(-1, "Rejected by server: %s\n", msg); + return (STATUS_TRANSIENTFAILURE); + } else + goto bad; + lprintf(2, "Server software version: %s\n", + swver != NULL ? swver : "."); + return (STATUS_SUCCESS); +bad: + lprintf(-1, "Invalid greeting from server\n"); + return (STATUS_FAILURE); +} + +/* Negotiate protocol version with the server. */ +static int +proto_negproto(struct config *config) +{ + struct stream *s; + char *cmd, *line, *msg; + int error, maj, min; + + s = config->server; + proto_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER); + stream_flush(s); + line = stream_getln(s, NULL); + cmd = proto_get_ascii(&line); + if (line == NULL) + goto bad; + if (strcmp(cmd, "!") == 0) { + msg = proto_get_rest(&line); + lprintf(-1, "Protocol negotiation failed: %s\n", msg); + return (1); + } else if (strcmp(cmd, "PROTO") != 0) + goto bad; + error = proto_get_int(&line, &maj, 10); + if (!error) + error = proto_get_int(&line, &min, 10); + if (error) + goto bad; + if (maj != PROTO_MAJ || min != PROTO_MIN) { + lprintf(-1, "Server protocol version %d.%d not supported " + "by client\n", maj, min); + return (STATUS_FAILURE); + } + return (STATUS_SUCCESS); +bad: + lprintf(-1, "Invalid PROTO command from server\n"); + return (STATUS_FAILURE); +} + +static int +proto_login(struct config *config) +{ + struct stream *s; + char host[MAXHOSTNAMELEN]; + char *line, *cmd, *realm, *challenge, *msg; + + s = config->server; + gethostname(host, sizeof(host)); + host[sizeof(host) - 1] = '\0'; + proto_printf(s, "USER %s %s\n", getlogin(), host); + stream_flush(s); + line = stream_getln(s, NULL); + cmd = proto_get_ascii(&line); + realm = proto_get_ascii(&line); + challenge = proto_get_ascii(&line); + if (challenge == NULL || line != NULL) + goto bad; + if (strcmp(realm, ".") != 0 || strcmp(challenge, ".") != 0) { + lprintf(-1, "Authentication required by the server and not " + "supported by client\n"); + return (STATUS_FAILURE); + } + proto_printf(s, "AUTHMD5 . . .\n"); + stream_flush(s); + line = stream_getln(s, NULL); + cmd = proto_get_ascii(&line); + if (strcmp(cmd, "OK") == 0) + return (STATUS_SUCCESS); + if (strcmp(cmd, "!") == 0) { + msg = proto_get_rest(&line); + if (msg == NULL) + goto bad; + lprintf(-1, "Server error: %s\n", msg); + return (STATUS_FAILURE); + } +bad: + lprintf(-1, "Invalid server reply to AUTHMD5\n"); + return (STATUS_FAILURE); +} + +/* + * File attribute support negotiation. + */ +static int +proto_fileattr(struct config *config) +{ + fattr_support_t support; + struct stream *s; + char *line, *cmd; + int error, i, n, attr; + + s = config->server; + lprintf(2, "Negotiating file attribute support\n"); + proto_printf(s, "ATTR %d\n", FT_NUMBER); + for (i = 0; i < FT_NUMBER; i++) + proto_printf(s, "%x\n", fattr_supported(i)); + proto_printf(s, ".\n"); + stream_flush(s); + line = stream_getln(s, NULL); + if (line == NULL) + goto bad; + cmd = proto_get_ascii(&line); + error = proto_get_int(&line, &n, 10); + if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER) + goto bad; + for (i = 0; i < n; i++) { + line = stream_getln(s, NULL); + if (line == NULL) + goto bad; + error = proto_get_int(&line, &attr, 16); + if (error) + goto bad; + support[i] = fattr_supported(i) & attr; + } + for (i = n; i < FT_NUMBER; i++) + support[i] = 0; + line = stream_getln(s, NULL); + if (line == NULL || strcmp(line, ".") != 0) + goto bad; + memcpy(config->fasupport, support, sizeof(config->fasupport)); + return (STATUS_SUCCESS); +bad: + lprintf(-1, "Protocol error negotiating attribute support\n"); + return (STATUS_FAILURE); +} + +/* + * Exchange collection information. + */ +static int +proto_xchgcoll(struct config *config) +{ + struct coll *coll; + struct stream *s; + struct globtree *diraccept, *dirrefuse; + struct globtree *fileaccept, *filerefuse; + char *line, *cmd, *collname, *pat; + char *msg, *release, *ident, *rcskey, *prefix; + size_t i, len; + int error, flags, options; + + s = config->server; + lprintf(2, "Exchanging collection information\n"); + STAILQ_FOREACH(coll, &config->colls, co_next) { + proto_printf(s, "COLL %s %s %o %d\n", coll->co_name, + coll->co_release, coll->co_umask, coll->co_options); + for (i = 0; i < pattlist_size(coll->co_accepts); i++) { + proto_printf(s, "ACC %s\n", + pattlist_get(coll->co_accepts, i)); + } + for (i = 0; i < pattlist_size(coll->co_refusals); i++) { + proto_printf(s, "REF %s\n", + pattlist_get(coll->co_refusals, i)); + } + proto_printf(s, ".\n"); + } + proto_printf(s, ".\n"); + stream_flush(s); + STAILQ_FOREACH(coll, &config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + line = stream_getln(s, NULL); + if (line == NULL) + goto bad; + cmd = proto_get_ascii(&line); + collname = proto_get_ascii(&line); + release = proto_get_ascii(&line); + error = proto_get_int(&line, &options, 10); + if (error || line != NULL) + goto bad; + if (strcmp(cmd, "COLL") != 0 || + strcmp(collname, coll->co_name) != 0 || + strcmp(release, coll->co_release) != 0) + goto bad; + coll->co_options = + (coll->co_options | (options & CO_SERVMAYSET)) & + ~(~options & CO_SERVMAYCLEAR); + while ((line = stream_getln(s, NULL)) != NULL) { + if (strcmp(line, ".") == 0) + break; + cmd = proto_get_ascii(&line); + if (cmd == NULL) + goto bad; + if (strcmp(cmd, "!") == 0) { + msg = proto_get_rest(&line); + if (msg == NULL) + goto bad; + lprintf(-1, "Server message: %s\n", msg); + } else if (strcmp(cmd, "PRFX") == 0) { + prefix = proto_get_ascii(&line); + if (prefix == NULL || line != NULL) + goto bad; + coll->co_cvsroot = xstrdup(prefix); + } else if (strcmp(cmd, "KEYALIAS") == 0) { + ident = proto_get_ascii(&line); + rcskey = proto_get_ascii(&line); + if (rcskey == NULL || line != NULL) + goto bad; + error = keyword_alias(coll->co_keyword, ident, + rcskey); + if (error) + goto bad; + } else if (strcmp(cmd, "KEYON") == 0) { + ident = proto_get_ascii(&line); + if (ident == NULL || line != NULL) + goto bad; + error = keyword_enable(coll->co_keyword, ident); + if (error) + goto bad; + } else if (strcmp(cmd, "KEYOFF") == 0) { + ident = proto_get_ascii(&line); + if (ident == NULL || line != NULL) + goto bad; + error = keyword_disable(coll->co_keyword, + ident); + if (error) + goto bad; + } + } + if (line == NULL) + goto bad; + keyword_prepare(coll->co_keyword); + + diraccept = globtree_true(); + fileaccept = globtree_true(); + dirrefuse = globtree_false(); + filerefuse = globtree_false(); + + if (pattlist_size(coll->co_accepts) > 0) { + globtree_free(diraccept); + globtree_free(fileaccept); + diraccept = globtree_false(); + fileaccept = globtree_false(); + flags = FNM_PATHNAME | FNM_LEADING_DIR | + FNM_PREFIX_DIRS; + for (i = 0; i < pattlist_size(coll->co_accepts); i++) { + pat = pattlist_get(coll->co_accepts, i); + diraccept = globtree_or(diraccept, + globtree_match(pat, flags)); + + len = strlen(pat); + if (coll->co_options & CO_CHECKOUTMODE && + (len == 0 || pat[len - 1] != '*')) { + /* We must modify the pattern so that it + refers to the RCS file, rather than + the checked-out file. */ + xasprintf(&pat, "%s,v", pat); + fileaccept = globtree_or(fileaccept, + globtree_match(pat, flags)); + free(pat); + } else { + fileaccept = globtree_or(fileaccept, + globtree_match(pat, flags)); + } + } + } + + for (i = 0; i < pattlist_size(coll->co_refusals); i++) { + pat = pattlist_get(coll->co_refusals, i); + dirrefuse = globtree_or(dirrefuse, + globtree_match(pat, 0)); + len = strlen(pat); + if (coll->co_options & CO_CHECKOUTMODE && + (len == 0 || pat[len - 1] != '*')) { + /* We must modify the pattern so that it refers + to the RCS file, rather than the checked-out + file. */ + xasprintf(&pat, "%s,v", pat); + filerefuse = globtree_or(filerefuse, + globtree_match(pat, 0)); + free(pat); + } else { + filerefuse = globtree_or(filerefuse, + globtree_match(pat, 0)); + } + } + + coll->co_dirfilter = globtree_and(diraccept, + globtree_not(dirrefuse)); + coll->co_filefilter = globtree_and(fileaccept, + globtree_not(filerefuse)); + + /* At this point we don't need the pattern lists anymore. */ + pattlist_free(coll->co_accepts); + pattlist_free(coll->co_refusals); + coll->co_accepts = NULL; + coll->co_refusals = NULL; + + /* Set up a mask of file attributes that we don't want to sync + with the server. */ + if (!(coll->co_options & CO_SETOWNER)) + coll->co_attrignore |= FA_OWNER | FA_GROUP; + if (!(coll->co_options & CO_SETMODE)) + coll->co_attrignore |= FA_MODE; + if (!(coll->co_options & CO_SETFLAGS)) + coll->co_attrignore |= FA_FLAGS; + } + return (STATUS_SUCCESS); +bad: + lprintf(-1, "Protocol error during collection exchange\n"); + return (STATUS_FAILURE); +} + +static struct mux * +proto_mux(struct config *config) +{ + struct mux *m; + struct stream *s, *wr; + struct chan *chan0, *chan1; + int id; + + s = config->server; + lprintf(2, "Establishing multiplexed-mode data connection\n"); + proto_printf(s, "MUX\n"); + stream_flush(s); + m = mux_open(config->socket, &chan0); + if (m == NULL) { + lprintf(-1, "Cannot open the multiplexer\n"); + return (NULL); + } + id = chan_listen(m); + if (id == -1) { + lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno)); + mux_close(m); + return (NULL); + } + wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL); + proto_printf(wr, "CHAN %d\n", id); + stream_close(wr); + chan1 = chan_accept(m, id); + if (chan1 == NULL) { + lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno)); + mux_close(m); + return (NULL); + } + config->chan0 = chan0; + config->chan1 = chan1; + return (m); +} + +/* + * Initializes the connection to the CVSup server, that is handle + * the protocol negotiation, logging in, exchanging file attributes + * support and collections information, and finally run the update + * session. + */ +int +proto_run(struct config *config) +{ + struct thread_args lister_args; + struct thread_args detailer_args; + struct thread_args updater_args; + struct thread_args *args; + struct killer killer; + struct threads *workers; + struct mux *m; + int i, status; + + /* + * We pass NULL for the close() function because we'll reuse + * the socket after the stream is closed. + */ + config->server = stream_open_fd(config->socket, stream_read_fd, + stream_write_fd, NULL); + status = proto_greet(config); + if (status == STATUS_SUCCESS) + status = proto_negproto(config); + if (status == STATUS_SUCCESS) + status = proto_login(config); + if (status == STATUS_SUCCESS) + status = proto_fileattr(config); + if (status == STATUS_SUCCESS) + status = proto_xchgcoll(config); + if (status != STATUS_SUCCESS) + return (status); + + /* Multi-threaded action starts here. */ + m = proto_mux(config); + if (m == NULL) + return (STATUS_FAILURE); + + stream_close(config->server); + config->server = NULL; + config->fixups = fixups_new(); + killer_start(&killer, m); + + /* Start the worker threads. */ + workers = threads_new(); + args = &lister_args; + args->config = config; + args->status = -1; + args->errmsg = NULL; + args->rd = NULL; + args->wr = stream_open(config->chan0, + NULL, (stream_writefn_t *)chan_write, NULL); + threads_create(workers, lister, args); + + args = &detailer_args; + args->config = config; + args->status = -1; + args->errmsg = NULL; + args->rd = stream_open(config->chan0, + (stream_readfn_t *)chan_read, NULL, NULL); + args->wr = stream_open(config->chan1, + NULL, (stream_writefn_t *)chan_write, NULL); + threads_create(workers, detailer, args); + + args = &updater_args; + args->config = config; + args->status = -1; + args->errmsg = NULL; + args->rd = stream_open(config->chan1, + (stream_readfn_t *)chan_read, NULL, NULL); + args->wr = NULL; + threads_create(workers, updater, args); + + lprintf(2, "Running\n"); + /* Wait for all the worker threads to finish. */ + status = STATUS_SUCCESS; + for (i = 0; i < 3; i++) { + args = threads_wait(workers); + if (args->rd != NULL) + stream_close(args->rd); + if (args->wr != NULL) + stream_close(args->wr); + if (args->status != STATUS_SUCCESS) { + assert(args->errmsg != NULL); + if (status == STATUS_SUCCESS) { + status = args->status; + /* Shutdown the multiplexer to wake up all + the other threads. */ + mux_shutdown(m, args->errmsg, status); + } + free(args->errmsg); + } + } + threads_free(workers); + if (status == STATUS_SUCCESS) { + lprintf(2, "Shutting down connection to server\n"); + chan_close(config->chan0); + chan_close(config->chan1); + chan_wait(config->chan0); + chan_wait(config->chan1); + mux_shutdown(m, NULL, STATUS_SUCCESS); + } + killer_stop(&killer); + fixups_free(config->fixups); + status = mux_close(m); + if (status == STATUS_SUCCESS) { + lprintf(1, "Finished successfully\n"); + } else if (status == STATUS_INTERRUPTED) { + lprintf(-1, "Interrupted\n"); + if (killer.killedby != -1) + kill(getpid(), killer.killedby); + } + return (status); +} + +/* + * Write a string into the stream, escaping characters as needed. + * Characters escaped: + * + * SPACE -> "\_" + * TAB -> "\t" + * NEWLINE -> "\n" + * CR -> "\r" + * \ -> "\\" + */ +static int +proto_escape(struct stream *wr, const char *s) +{ + size_t len; + ssize_t n; + char c; + + /* Handle characters that need escaping. */ + do { + len = strcspn(s, " \t\r\n\\"); + n = stream_write(wr, s, len); + if (n == -1) + return (-1); + c = s[len]; + switch (c) { + case ' ': + n = stream_write(wr, "\\_", 2); + break; + case '\t': + n = stream_write(wr, "\\t", 2); + break; + case '\r': + n = stream_write(wr, "\\r", 2); + break; + case '\n': + n = stream_write(wr, "\\n", 2); + break; + case '\\': + n = stream_write(wr, "\\\\", 2); + break; + } + if (n == -1) + return (-1); + s += len + 1; + } while (c != '\0'); + return (0); +} + +/* + * A simple printf() implementation specifically tailored for csup. + * List of the supported formats: + * + * %c Print a char. + * %d or %i Print an int as decimal. + * %x Print an int as hexadecimal. + * %o Print an int as octal. + * %t Print a time_t as decimal. + * %s Print a char * escaping some characters as needed. + * %S Print a char * without escaping. + * %f Print an encoded struct fattr *. + * %F Print an encoded struct fattr *, specifying the supported + * attributes. + */ +int +proto_printf(struct stream *wr, const char *format, ...) +{ + fattr_support_t *support; + long long longval; + struct fattr *fa; + const char *fmt; + va_list ap; + char *cp, *s, *attr; + ssize_t n; + int rv, val, ignore; + char c; + + n = 0; + rv = 0; + fmt = format; + va_start(ap, format); + while ((cp = strchr(fmt, '%')) != NULL) { + if (cp > fmt) { + n = stream_write(wr, fmt, cp - fmt); + if (n == -1) + return (-1); + } + if (*++cp == '\0') + goto done; + switch (*cp) { + case 'c': + c = va_arg(ap, int); + rv = stream_printf(wr, "%c", c); + break; + case 'd': + case 'i': + val = va_arg(ap, int); + rv = stream_printf(wr, "%d", val); + break; + case 'x': + val = va_arg(ap, int); + rv = stream_printf(wr, "%x", val); + break; + case 'o': + val = va_arg(ap, int); + rv = stream_printf(wr, "%o", val); + break; + case 'S': + s = va_arg(ap, char *); + assert(s != NULL); + rv = stream_printf(wr, "%s", s); + break; + case 's': + s = va_arg(ap, char *); + assert(s != NULL); + rv = proto_escape(wr, s); + break; + case 't': + longval = (long long)va_arg(ap, time_t); + rv = stream_printf(wr, "%lld", longval); + break; + case 'f': + fa = va_arg(ap, struct fattr *); + attr = fattr_encode(fa, NULL, 0); + rv = proto_escape(wr, attr); + free(attr); + break; + case 'F': + fa = va_arg(ap, struct fattr *); + support = va_arg(ap, fattr_support_t *); + ignore = va_arg(ap, int); + attr = fattr_encode(fa, *support, ignore); + rv = proto_escape(wr, attr); + free(attr); + break; + case '%': + n = stream_write(wr, "%", 1); + if (n == -1) + return (-1); + break; + } + if (rv == -1) + return (-1); + fmt = cp + 1; + } + if (*fmt != '\0') { + rv = stream_printf(wr, "%s", fmt); + if (rv == -1) + return (-1); + } +done: + va_end(ap); + return (0); +} + +/* + * Unescape the string, see proto_escape(). + */ +static void +proto_unescape(char *s) +{ + char *cp, *cp2; + + cp = s; + while ((cp = strchr(cp, '\\')) != NULL) { + switch (cp[1]) { + case '_': + *cp = ' '; + break; + case 't': + *cp = '\t'; + break; + case 'r': + *cp = '\r'; + break; + case 'n': + *cp = '\n'; + break; + case '\\': + *cp = '\\'; + break; + default: + *cp = *(cp + 1); + } + cp2 = ++cp; + while (*cp2 != '\0') { + *cp2 = *(cp2 + 1); + cp2++; + } + } +} + +/* + * Get an ascii token in the string. + */ +char * +proto_get_ascii(char **s) +{ + char *ret; + + ret = strsep(s, " "); + if (ret == NULL) + return (NULL); + /* Make sure we disallow 0-length fields. */ + if (*ret == '\0') { + *s = NULL; + return (NULL); + } + proto_unescape(ret); + return (ret); +} + +/* + * Get the rest of the string. + */ +char * +proto_get_rest(char **s) +{ + char *ret; + + if (s == NULL) + return (NULL); + ret = *s; + proto_unescape(ret); + *s = NULL; + return (ret); +} + +/* + * Get an int token. + */ +int +proto_get_int(char **s, int *val, int base) +{ + char *cp, *end; + long longval; + + cp = proto_get_ascii(s); + if (cp == NULL) + return (-1); + errno = 0; + longval = strtol(cp, &end, base); + if (errno || *end != '\0') + return (-1); + if (longval > INT_MAX || longval < INT_MIN) { + errno = ERANGE; + return (-1); + } + *val = longval; + return (0); +} + +/* + * Get a time_t token. + * + * Ideally, we would use an intmax_t and strtoimax() here, but strtoll() + * is more portable and 64bits should be enough for a timestamp. + */ +int +proto_get_time(char **s, time_t *val) +{ + long long tmp; + char *cp, *end; + + cp = proto_get_ascii(s); + if (cp == NULL) + return (-1); + errno = 0; + tmp = strtoll(cp, &end, 10); + if (errno || *end != '\0') + return (-1); + *val = (time_t)tmp; + return (0); +} + +/* Start the killer thread. It is used to protect against some signals + during the multi-threaded run so that we can gracefully fail. */ +static void +killer_start(struct killer *k, struct mux *m) +{ + int error; + + k->mux = m; + k->killedby = -1; + sigemptyset(&k->sigset); + sigaddset(&k->sigset, SIGINT); + sigaddset(&k->sigset, SIGHUP); + sigaddset(&k->sigset, SIGTERM); + sigaddset(&k->sigset, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &k->sigset, NULL); + error = pthread_create(&k->thread, NULL, killer_run, k); + if (error) + err(1, "pthread_create"); +} + +/* The main loop of the killer thread. */ +static void * +killer_run(void *arg) +{ + struct killer *k; + int error, sig, old; + + k = arg; +again: + error = sigwait(&k->sigset, &sig); + assert(!error); + if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) { + if (k->killedby == -1) { + k->killedby = sig; + /* Ensure we don't get canceled during the shutdown. */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); + mux_shutdown(k->mux, "Cleaning up ...", + STATUS_INTERRUPTED); + pthread_setcancelstate(old, NULL); + } + } + goto again; +} + +/* Stop the killer thread. */ +static void +killer_stop(struct killer *k) +{ + void *val; + int error; + + error = pthread_cancel(k->thread); + assert(!error); + pthread_join(k->thread, &val); + assert(val == PTHREAD_CANCELED); + pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL); +} diff --git a/contrib/csup/proto.h b/contrib/csup/proto.h new file mode 100644 index 000000000000..ea9c01230232 --- /dev/null +++ b/contrib/csup/proto.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _PROTO_H_ +#define _PROTO_H_ + +#include <time.h> + +#include "misc.h" + +#define PROTO_MAJ 17 +#define PROTO_MIN 0 +#define PROTO_SWVER "CSUP_1_0" + +struct stream; + +int proto_connect(struct config *, int, uint16_t); +int proto_run(struct config *); +int proto_printf(struct stream *, const char *, ...); +char *proto_get_ascii(char **); +char *proto_get_rest(char **); +int proto_get_int(char **, int *, int); +int proto_get_time(char **, time_t *); + +#endif /* !_PROTO_H_ */ diff --git a/contrib/csup/queue.h b/contrib/csup/queue.h new file mode 100644 index 000000000000..369c013f7546 --- /dev/null +++ b/contrib/csup/queue.h @@ -0,0 +1,227 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD$ + * + * $FreeBSD$ + */ + +#ifndef _QUEUE_H_ +#define _QUEUE_H_ + +#undef __ofsetof +#define __offsetof(type, field) ((size_t)(&((type *)0)->field)) + +/* + * Singly-linked Tail queue declarations. + */ +#undef STAILQ_HEAD +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#undef STAILQ_HEAD_INITIALIZER +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#undef STAILQ_ENTRY +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +#undef STAILQ_EMPTY +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) + +#undef STAILQ_FIRST +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#undef STAILQ_FOREACH +#define STAILQ_FOREACH(var, head, field) \ + for((var) = STAILQ_FIRST((head)); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#undef STAILQ_FOREACH_SAFE +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#undef STAILQ_INIT +#define STAILQ_INIT(head) do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#undef STAILQ_INSERT_AFTER +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_NEXT((tqelm), field) = (elm); \ +} while (0) + +#undef STAILQ_INSERT_HEAD +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_FIRST((head)) = (elm); \ +} while (0) + +#undef STAILQ_INSERT_TAIL +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#undef STAILQ_LAST +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? \ + NULL : \ + ((struct type *) \ + ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) + +#undef STAILQ_NEXT +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#undef STAILQ_REMOVE +#define STAILQ_REMOVE(head, elm, type, field) do { \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = STAILQ_FIRST((head)); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + if ((STAILQ_NEXT(curelm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\ + (head)->stqh_last = &STAILQ_NEXT((curelm), field);\ + } \ +} while (0) + +#undef STAILQ_REMOVE_HEAD +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#undef STAILQ_REMOVE_HEAD_UNTIL +#define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \ + if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +/* + * List declarations. + */ +#undef LIST_HEAD +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#undef LIST_HEAD_INITIALIZER +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#undef LIST_ENTRY +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ + +#undef LIST_EMPTY +#define LIST_EMPTY(head) ((head)->lh_first == NULL) + +#undef LIST_FIRST +#define LIST_FIRST(head) ((head)->lh_first) + +#undef LIST_FOREACH +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#undef LIST_FOREACH_SAFE +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#undef LIST_INIT +#define LIST_INIT(head) do { \ + LIST_FIRST((head)) = NULL; \ +} while (0) + +#undef LIST_INSERT_AFTER +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ + LIST_NEXT((listelm), field)->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_NEXT((listelm), field) = (elm); \ + (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ +} while (0) + +#undef LIST_INSERT_BEFORE +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + LIST_NEXT((elm), field) = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ +} while (0) + +#undef LIST_INSERT_HEAD +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ +} while (0) + +#undef LIST_NEXT +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#undef LIST_REMOVE +#define LIST_REMOVE(elm, field) do { \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ +} while (0) + +#endif /* !_QUEUE_H_ */ diff --git a/contrib/csup/status.c b/contrib/csup/status.c new file mode 100644 index 000000000000..32b6821a2fb8 --- /dev/null +++ b/contrib/csup/status.c @@ -0,0 +1,842 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "fattr.h" +#include "misc.h" +#include "pathcomp.h" +#include "proto.h" +#include "queue.h" +#include "status.h" +#include "stream.h" + +#define STATUS_VERSION 5 + +/* Internal error codes. */ +#define STATUS_ERR_READ (-1) +#define STATUS_ERR_WRITE (-2) +#define STATUS_ERR_PARSE (-3) +#define STATUS_ERR_UNSORTED (-4) +#define STATUS_ERR_TRUNC (-5) +#define STATUS_ERR_BOGUS_DIRUP (-6) +#define STATUS_ERR_BAD_TYPE (-7) +#define STATUS_ERR_RENAME (-8) + +static struct status *status_new(char *, time_t, struct stream *); +static struct statusrec *status_rd(struct status *); +static struct statusrec *status_rdraw(struct status *, char **); +static int status_wr(struct status *, struct statusrec *); +static int status_wrraw(struct status *, struct statusrec *, + char *); +static struct status *status_fromrd(char *, struct stream *); +static struct status *status_fromnull(char *); +static void status_free(struct status *); + +static void statusrec_init(struct statusrec *); +static void statusrec_fini(struct statusrec *); +static int statusrec_cook(struct statusrec *, char *); +static int statusrec_cmp(struct statusrec *, struct statusrec *); + +struct status { + char *path; + char *tempfile; + int error; + int suberror; + struct pathcomp *pc; + struct statusrec buf; + struct statusrec *previous; + struct statusrec *current; + struct stream *rd; + struct stream *wr; + time_t scantime; + int eof; + int linenum; + int depth; + int dirty; +}; + +static void +statusrec_init(struct statusrec *sr) +{ + + memset(sr, 0, sizeof(*sr)); +} + +static int +statusrec_cook(struct statusrec *sr, char *line) +{ + char *clientattr, *serverattr; + + switch (sr->sr_type) { + case SR_DIRDOWN: + /* Nothing to do. */ + if (line != NULL) + return (-1); + break; + case SR_CHECKOUTLIVE: + sr->sr_tag = proto_get_ascii(&line); + sr->sr_date = proto_get_ascii(&line); + serverattr = proto_get_ascii(&line); + sr->sr_revnum = proto_get_ascii(&line); + sr->sr_revdate = proto_get_ascii(&line); + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_serverattr = fattr_decode(serverattr); + if (sr->sr_serverattr == NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) { + fattr_free(sr->sr_serverattr); + return (-1); + } + break; + case SR_CHECKOUTDEAD: + sr->sr_tag = proto_get_ascii(&line); + sr->sr_date = proto_get_ascii(&line); + serverattr = proto_get_ascii(&line); + if (serverattr == NULL || line != NULL) + return (-1); + sr->sr_serverattr = fattr_decode(serverattr); + if (sr->sr_serverattr == NULL) + return (-1); + break; + case SR_DIRUP: + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) + return (-1); + break; + default: + return (-1); + } + return (0); +} + +static struct statusrec * +status_rd(struct status *st) +{ + struct statusrec *sr; + char *line; + int error; + + sr = status_rdraw(st, &line); + if (sr == NULL) + return (NULL); + error = statusrec_cook(sr, line); + if (error) { + st->error = STATUS_ERR_PARSE; + return (NULL); + } + return (sr); +} + +static struct statusrec * +status_rdraw(struct status *st, char **linep) +{ + struct statusrec sr; + char *cmd, *line, *file; + + if (st->rd == NULL || st->eof) + return (NULL); + line = stream_getln(st->rd, NULL); + if (line == NULL) { + if (stream_eof(st->rd)) { + if (st->depth != 0) { + st->error = STATUS_ERR_TRUNC; + return (NULL); + } + st->eof = 1; + return (NULL); + } + st->error = STATUS_ERR_READ; + st->suberror = errno; + return (NULL); + } + st->linenum++; + cmd = proto_get_ascii(&line); + file = proto_get_ascii(&line); + if (file == NULL || strlen(cmd) != 1) { + st->error = STATUS_ERR_PARSE; + return (NULL); + } + + switch (cmd[0]) { + case 'D': + sr.sr_type = SR_DIRDOWN; + st->depth++; + break; + case 'C': + sr.sr_type = SR_CHECKOUTLIVE; + break; + case 'c': + sr.sr_type = SR_CHECKOUTDEAD; + break; + case 'U': + sr.sr_type = SR_DIRUP; + if (st->depth <= 0) { + st->error = STATUS_ERR_BOGUS_DIRUP; + return (NULL); + } + st->depth--; + break; + default: + st->error = STATUS_ERR_BAD_TYPE; + st->suberror = cmd[0]; + return (NULL); + } + + sr.sr_file = xstrdup(file); + if (st->previous != NULL && + statusrec_cmp(st->previous, &sr) >= 0) { + st->error = STATUS_ERR_UNSORTED; + free(sr.sr_file); + return (NULL); + } + + if (st->previous == NULL) { + st->previous = &st->buf; + } else { + statusrec_fini(st->previous); + statusrec_init(st->previous); + } + st->previous->sr_type = sr.sr_type; + st->previous->sr_file = sr.sr_file; + *linep = line; + return (st->previous); +} + +static int +status_wr(struct status *st, struct statusrec *sr) +{ + struct pathcomp *pc; + const struct fattr *fa; + char *name; + int error, type, usedirupattr; + + pc = st->pc; + error = 0; + usedirupattr = 0; + if (sr->sr_type == SR_DIRDOWN) { + pathcomp_put(pc, PC_DIRDOWN, sr->sr_file); + } else if (sr->sr_type == SR_DIRUP) { + pathcomp_put(pc, PC_DIRUP, sr->sr_file); + usedirupattr = 1; + } else { + pathcomp_put(pc, PC_FILE, sr->sr_file); + } + + while (pathcomp_get(pc, &type, &name)) { + if (type == PC_DIRDOWN) { + error = proto_printf(st->wr, "D %s\n", name); + } else if (type == PC_DIRUP) { + if (usedirupattr) + fa = sr->sr_clientattr; + else + fa = fattr_bogus; + usedirupattr = 0; + error = proto_printf(st->wr, "U %s %f\n", name, fa); + } + if (error) + goto bad; + } + + switch (sr->sr_type) { + case SR_DIRDOWN: + case SR_DIRUP: + /* Already emitted above. */ + break; + case SR_CHECKOUTLIVE: + error = proto_printf(st->wr, "C %s %s %s %f %s %s %f\n", + sr->sr_file, sr->sr_tag, sr->sr_date, sr->sr_serverattr, + sr->sr_revnum, sr->sr_revdate, sr->sr_clientattr); + break; + case SR_CHECKOUTDEAD: + error = proto_printf(st->wr, "c %s %s %s %f\n", sr->sr_file, + sr->sr_tag, sr->sr_date, sr->sr_serverattr); + break; + } + if (error) + goto bad; + return (0); +bad: + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + return (-1); +} + +static int +status_wrraw(struct status *st, struct statusrec *sr, char *line) +{ + char *name; + char cmd; + int error, ret, type; + + if (st->wr == NULL) + return (0); + + /* + * Keep the compressor in sync. At this point, the necessary + * DirDowns and DirUps should have already been emitted, so the + * compressor should return exactly one value in the PC_DIRDOWN + * and PC_DIRUP case and none in the PC_FILE case. + */ + if (sr->sr_type == SR_DIRDOWN) + pathcomp_put(st->pc, PC_DIRDOWN, sr->sr_file); + else if (sr->sr_type == SR_DIRUP) + pathcomp_put(st->pc, PC_DIRUP, sr->sr_file); + else + pathcomp_put(st->pc, PC_FILE, sr->sr_file); + if (sr->sr_type == SR_DIRDOWN || sr->sr_type == SR_DIRUP) { + ret = pathcomp_get(st->pc, &type, &name); + assert(ret); + if (sr->sr_type == SR_DIRDOWN) + assert(type == PC_DIRDOWN); + else + assert(type == PC_DIRUP); + } + ret = pathcomp_get(st->pc, &type, &name); + assert(!ret); + + switch (sr->sr_type) { + case SR_DIRDOWN: + cmd = 'D'; + break; + case SR_DIRUP: + cmd = 'U'; + break; + case SR_CHECKOUTLIVE: + cmd = 'C'; + break; + case SR_CHECKOUTDEAD: + cmd = 'c'; + break; + default: + assert(0); + return (-1); + } + if (sr->sr_type == SR_DIRDOWN) + error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file); + else + error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file, + line); + if (error) { + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + return (-1); + } + return (0); +} + +static void +statusrec_fini(struct statusrec *sr) +{ + + fattr_free(sr->sr_serverattr); + fattr_free(sr->sr_clientattr); + free(sr->sr_file); +} + +static int +statusrec_cmp(struct statusrec *a, struct statusrec *b) +{ + size_t lena, lenb; + + if (a->sr_type == SR_DIRUP || b->sr_type == SR_DIRUP) { + lena = strlen(a->sr_file); + lenb = strlen(b->sr_file); + if (a->sr_type == SR_DIRUP && + ((lena < lenb && b->sr_file[lena] == '/') || lena == lenb) + && strncmp(a->sr_file, b->sr_file, lena) == 0) + return (1); + if (b->sr_type == SR_DIRUP && + ((lenb < lena && a->sr_file[lenb] == '/') || lenb == lena) + && strncmp(a->sr_file, b->sr_file, lenb) == 0) + return (-1); + } + return (pathcmp(a->sr_file, b->sr_file)); +} + +static struct status * +status_new(char *path, time_t scantime, struct stream *file) +{ + struct status *st; + + st = xmalloc(sizeof(struct status)); + st->path = path; + st->error = 0; + st->suberror = 0; + st->tempfile = NULL; + st->scantime = scantime; + st->rd = file; + st->wr = NULL; + st->previous = NULL; + st->current = NULL; + st->dirty = 0; + st->eof = 0; + st->linenum = 0; + st->depth = 0; + st->pc = pathcomp_new(); + statusrec_init(&st->buf); + return (st); +} + +static void +status_free(struct status *st) +{ + + if (st->previous != NULL) + statusrec_fini(st->previous); + if (st->rd != NULL) + stream_close(st->rd); + if (st->wr != NULL) + stream_close(st->wr); + if (st->tempfile != NULL) + free(st->tempfile); + free(st->path); + pathcomp_free(st->pc); + free(st); +} + +static struct status * +status_fromrd(char *path, struct stream *file) +{ + struct status *st; + char *id, *line; + time_t scantime; + int error, ver; + + /* Get the first line of the file and validate it. */ + line = stream_getln(file, NULL); + if (line == NULL) { + stream_close(file); + return (NULL); + } + id = proto_get_ascii(&line); + error = proto_get_int(&line, &ver, 10); + if (error) { + stream_close(file); + return (NULL); + } + error = proto_get_time(&line, &scantime); + if (error || line != NULL) { + stream_close(file); + return (NULL); + } + + if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) { + stream_close(file); + return (NULL); + } + + st = status_new(path, scantime, file); + st->linenum = 1; + return (st); +} + +static struct status * +status_fromnull(char *path) +{ + struct status *st; + + st = status_new(path, -1, NULL); + st->eof = 1; + return (st); +} + +/* + * Open the status file. If scantime is not -1, the file is opened + * for updating, otherwise, it is opened read-only. If the status file + * couldn't be opened, NULL is returned and errmsg is set to the error + * message. + */ +struct status * +status_open(struct coll *coll, time_t scantime, char **errmsg) +{ + struct status *st; + struct stream *file; + struct fattr *fa; + char *destpath, *path; + int error, rv; + + path = coll_statuspath(coll); + file = stream_open_file(path, O_RDONLY); + if (file == NULL) { + if (errno != ENOENT) { + xasprintf(errmsg, "Could not open \"%s\": %s\n", + path, strerror(errno)); + free(path); + return (NULL); + } + st = status_fromnull(path); + } else { + st = status_fromrd(path, file); + if (st == NULL) { + xasprintf(errmsg, "Error in \"%s\": Bad header line", + path); + free(path); + return (NULL); + } + } + + if (scantime != -1) { + /* Open for writing too. */ + xasprintf(&destpath, "%s/%s/%s/", coll->co_base, + coll->co_colldir, coll->co_name); + st->tempfile = tempname(destpath); + if (mkdirhier(destpath, coll->co_umask) != 0) { + xasprintf(errmsg, "Cannot create directories leading " + "to \"%s\": %s", destpath, strerror(errno)); + free(destpath); + status_free(st); + return (NULL); + } + free(destpath); + st->wr = stream_open_file(st->tempfile, + O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (st->wr == NULL) { + xasprintf(errmsg, "Cannot create \"%s\": %s", + st->tempfile, strerror(errno)); + status_free(st); + return (NULL); + } + fa = fattr_new(FT_FILE, -1); + fattr_mergedefault(fa); + fattr_umask(fa, coll->co_umask); + rv = fattr_install(fa, st->tempfile, NULL); + fattr_free(fa); + if (rv == -1) { + xasprintf(errmsg, + "Cannot set attributes for \"%s\": %s", + st->tempfile, strerror(errno)); + status_free(st); + return (NULL); + } + if (scantime != st->scantime) + st->dirty = 1; + error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION, + scantime); + if (error) { + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + *errmsg = status_errmsg(st); + status_free(st); + return (NULL); + } + } + return (st); +} + +/* + * Get an entry from the status file. If name is NULL, the next entry + * is returned. If name is not NULL, the entry matching this name is + * returned, or NULL if it couldn't be found. If deleteto is set to 1, + * all the entries read from the status file while looking for the + * given name are deleted. + */ +int +status_get(struct status *st, char *name, int isdirup, int deleteto, + struct statusrec **psr) +{ + struct statusrec key; + struct statusrec *sr; + char *line; + int c, error; + + if (st->eof) + return (0); + + if (st->error) + return (-1); + + if (name == NULL) { + sr = status_rd(st); + if (sr == NULL) { + if (st->error) + return (-1); + return (0); + } + *psr = sr; + return (1); + } + + if (st->current != NULL) { + sr = st->current; + st->current = NULL; + } else { + sr = status_rd(st); + if (sr == NULL) { + if (st->error) + return (-1); + return (0); + } + } + + key.sr_file = name; + if (isdirup) + key.sr_type = SR_DIRUP; + else + key.sr_type = SR_CHECKOUTLIVE; + + c = statusrec_cmp(sr, &key); + if (c < 0) { + if (st->wr != NULL && !deleteto) { + error = status_wr(st, sr); + if (error) + return (-1); + } + /* Loop until we find the good entry. */ + for (;;) { + sr = status_rdraw(st, &line); + if (sr == NULL) { + if (st->error) + return (-1); + return (0); + } + c = statusrec_cmp(sr, &key); + if (c >= 0) + break; + if (st->wr != NULL && !deleteto) { + error = status_wrraw(st, sr, line); + if (error) + return (-1); + } + } + error = statusrec_cook(sr, line); + if (error) { + st->error = STATUS_ERR_PARSE; + return (-1); + } + } + st->current = sr; + if (c != 0) + return (0); + *psr = sr; + return (1); +} + +/* + * Put this entry into the status file. If an entry with the same name + * existed in the status file, it is replaced by this one, otherwise, + * the entry is added to the status file. + */ +int +status_put(struct status *st, struct statusrec *sr) +{ + struct statusrec *old; + int error, ret; + + ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old); + if (ret == -1) + return (-1); + if (ret) { + if (old->sr_type == SR_DIRDOWN) { + /* DirUp should never match DirDown */ + assert(old->sr_type != SR_DIRUP); + if (sr->sr_type == SR_CHECKOUTLIVE || + sr->sr_type == SR_CHECKOUTDEAD) { + /* We are replacing a directory with a file. + Delete all entries inside the directory we + are replacing. */ + ret = status_get(st, sr->sr_file, 1, 1, &old); + if (ret == -1) + return (-1); + assert(ret); + } + } else + st->current = NULL; + } + st->dirty = 1; + error = status_wr(st, sr); + if (error) + return (-1); + return (0); +} + +/* + * Delete the specified entry from the status file. + */ +int +status_delete(struct status *st, char *name, int isdirup) +{ + struct statusrec *sr; + int ret; + + ret = status_get(st, name, isdirup, 0, &sr); + if (ret == -1) + return (-1); + if (ret) { + st->current = NULL; + st->dirty = 1; + } + return (0); +} + +/* + * Check whether we hit the end of file. + */ +int +status_eof(struct status *st) +{ + + return (st->eof); +} + +/* + * Returns the error message if there was an error, otherwise returns + * NULL. The error message is allocated dynamically and needs to be + * freed by the caller after use. + */ +char * +status_errmsg(struct status *st) +{ + char *errmsg; + + if (!st->error) + return (NULL); + switch (st->error) { + case STATUS_ERR_READ: + xasprintf(&errmsg, "Read failure on \"%s\": %s", + st->path, strerror(st->suberror)); + break; + case STATUS_ERR_WRITE: + xasprintf(&errmsg, "Write failure on \"%s\": %s", + st->tempfile, strerror(st->suberror)); + break; + case STATUS_ERR_PARSE: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "Could not parse status record", st->path, st->linenum); + break; + case STATUS_ERR_UNSORTED: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "File is not sorted properly", st->path, st->linenum); + break; + case STATUS_ERR_TRUNC: + xasprintf(&errmsg, "Error in \"%s\": " + "File is truncated", st->path); + break; + case STATUS_ERR_BOGUS_DIRUP: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "\"U\" entry has no matching \"D\"", st->path, st->linenum); + break; + case STATUS_ERR_BAD_TYPE: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "Invalid file type \"%c\"", st->path, st->linenum, + st->suberror); + break; + case STATUS_ERR_RENAME: + xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s", + st->tempfile, st->path, strerror(st->suberror)); + break; + default: + assert(0); + return (NULL); + } + return (errmsg); +} + +/* + * Close the status file and free any resource associated with it. + * It is OK to pass NULL for errmsg only if the status file was + * opened read-only. If it wasn't opened read-only, status_close() + * can produce an error and errmsg is not allowed to be NULL. If + * there was no errors, errmsg is set to NULL. + */ +void +status_close(struct status *st, char **errmsg) +{ + struct statusrec *sr; + char *line, *name; + int error, type; + + if (st->wr != NULL) { + if (st->dirty) { + if (st->current != NULL) { + error = status_wr(st, st->current); + if (error) { + *errmsg = status_errmsg(st); + goto bad; + } + st->current = NULL; + } + /* Copy the rest of the file. */ + while ((sr = status_rdraw(st, &line)) != NULL) { + error = status_wrraw(st, sr, line); + if (error) { + *errmsg = status_errmsg(st); + goto bad; + } + } + if (st->error) { + *errmsg = status_errmsg(st); + goto bad; + } + + /* Close off all the open directories. */ + pathcomp_finish(st->pc); + while (pathcomp_get(st->pc, &type, &name)) { + assert(type == PC_DIRUP); + error = proto_printf(st->wr, "U %s %f\n", + name, fattr_bogus); + if (error) { + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + *errmsg = status_errmsg(st); + goto bad; + } + } + + /* Rename tempfile. */ + error = rename(st->tempfile, st->path); + if (error) { + st->error = STATUS_ERR_RENAME; + st->suberror = errno; + *errmsg = status_errmsg(st); + goto bad; + } + } else { + /* Just discard the tempfile. */ + unlink(st->tempfile); + } + *errmsg = NULL; + } + status_free(st); + return; +bad: + status_free(st); +} diff --git a/contrib/csup/status.h b/contrib/csup/status.h new file mode 100644 index 000000000000..86efdda0e38c --- /dev/null +++ b/contrib/csup/status.h @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _STATUS_H_ +#define _STATUS_H_ + +#include <time.h> + +struct coll; +struct fattr; +struct status; + +#define SR_DIRDOWN 0 +#define SR_CHECKOUTLIVE 1 +#define SR_CHECKOUTDEAD 2 +#define SR_FILELIVE 3 +#define SR_FILEDEAD 4 +#define SR_DIRUP 5 + +struct statusrec { + int sr_type; + char *sr_file; + char *sr_tag; + char *sr_date; + char *sr_revnum; + char *sr_revdate; + + /* + * "clientrttr" contains the attributes of the client's file if there + * is one. "serverattr" contains the attributes of the corresponding + * file on the server. In CVS mode, these are identical. But in + * checkout mode, "clientattr" represents the checked-out file while + * "serverattr" represents the corresponding RCS file on the server. + */ + struct fattr *sr_serverattr; + struct fattr *sr_clientattr; +}; + +struct status *status_open(struct coll *, time_t, char **); +int status_get(struct status *, char *, int, int, + struct statusrec **); +int status_put(struct status *, struct statusrec *); +int status_eof(struct status *); +char *status_errmsg(struct status *); +int status_delete(struct status *, char *, int); +void status_close(struct status *, char **); + +#endif /* !_STATUS_H_ */ diff --git a/contrib/csup/stream.c b/contrib/csup/stream.c new file mode 100644 index 000000000000..34aa71e3273f --- /dev/null +++ b/contrib/csup/stream.c @@ -0,0 +1,1080 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <zlib.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "misc.h" +#include "stream.h" + +/* + * Simple stream API to make my life easier. If the fgetln() and + * funopen() functions were standard and if funopen() wasn't using + * wrong types for the function pointers, I could have just used + * stdio, but life sucks. + * + * For now, streams are always block-buffered. + */ + +/* + * Try to quiet warnings as much as possible with GCC while staying + * compatible with other compilers. + */ +#ifndef __unused +#if defined(__GNUC__) && (__GNUC__ > 2 || __GNUC__ == 2 && __GNUC_MINOR__ >= 7) +#define __unused __attribute__((__unused__)) +#else +#define __unused +#endif +#endif + +/* + * Flags passed to the flush methods. + * + * STREAM_FLUSH_CLOSING is passed during the last flush call before + * closing a stream. This allows the zlib filter to emit the EOF + * marker as appropriate. In all other cases, STREAM_FLUSH_NORMAL + * should be passed. + * + * These flags are completely unused in the default flush method, + * but they are very important for the flush method of the zlib + * filter. + */ +typedef enum { + STREAM_FLUSH_NORMAL, + STREAM_FLUSH_CLOSING +} stream_flush_t; + +/* + * This is because buf_new() will always allocate size + 1 bytes, + * so our buffer sizes will still be power of 2 values. + */ +#define STREAM_BUFSIZ 1023 + +struct buf { + char *buf; + size_t size; + size_t in; + size_t off; +}; + +struct stream { + void *cookie; + int fd; + struct buf *rdbuf; + struct buf *wrbuf; + stream_readfn_t *readfn; + stream_writefn_t *writefn; + stream_closefn_t *closefn; + int eof; + struct stream_filter *filter; + void *fdata; +}; + +typedef int stream_filter_initfn_t(struct stream *, void *); +typedef void stream_filter_finifn_t(struct stream *); +typedef int stream_filter_flushfn_t(struct stream *, struct buf *, + stream_flush_t); +typedef ssize_t stream_filter_fillfn_t(struct stream *, struct buf *); + +struct stream_filter { + stream_filter_t id; + stream_filter_initfn_t *initfn; + stream_filter_finifn_t *finifn; + stream_filter_fillfn_t *fillfn; + stream_filter_flushfn_t *flushfn; +}; + +/* Low-level buffer API. */ +#define buf_avail(buf) ((buf)->size - (buf)->off - (buf)->in) +#define buf_count(buf) ((buf)->in) +#define buf_size(buf) ((buf)->size) + +static struct buf *buf_new(size_t); +static void buf_more(struct buf *, size_t); +static void buf_less(struct buf *, size_t); +static void buf_free(struct buf *); +static void buf_grow(struct buf *, size_t); + +/* Internal stream functions. */ +static ssize_t stream_fill(struct stream *); +static ssize_t stream_fill_default(struct stream *, struct buf *); +static int stream_flush_int(struct stream *, stream_flush_t); +static int stream_flush_default(struct stream *, struct buf *, + stream_flush_t); + +/* Filters specific functions. */ +static struct stream_filter *stream_filter_lookup(stream_filter_t); +static int stream_filter_init(struct stream *, void *); +static void stream_filter_fini(struct stream *); + +/* The zlib stream filter declarations. */ +#define ZFILTER_EOF 1 /* Got Z_STREAM_END. */ + +struct zfilter { + int flags; + struct buf *rdbuf; + struct buf *wrbuf; + z_stream *rdstate; + z_stream *wrstate; +}; + +static int zfilter_init(struct stream *, void *); +static void zfilter_fini(struct stream *); +static ssize_t zfilter_fill(struct stream *, struct buf *); +static int zfilter_flush(struct stream *, struct buf *, + stream_flush_t); + +/* The MD5 stream filter. */ +struct md5filter { + MD5_CTX ctx; + char *md5; +}; + +static int md5filter_init(struct stream *, void *); +static void md5filter_fini(struct stream *); +static ssize_t md5filter_fill(struct stream *, struct buf *); +static int md5filter_flush(struct stream *, struct buf *, + stream_flush_t); + +/* The available stream filters. */ +struct stream_filter stream_filters[] = { + { + STREAM_FILTER_NULL, + NULL, + NULL, + stream_fill_default, + stream_flush_default + }, + { + STREAM_FILTER_ZLIB, + zfilter_init, + zfilter_fini, + zfilter_fill, + zfilter_flush + }, + { + STREAM_FILTER_MD5, + md5filter_init, + md5filter_fini, + md5filter_fill, + md5filter_flush + } +}; + + +/* Create a new buffer. */ +static struct buf * +buf_new(size_t size) +{ + struct buf *buf; + + buf = xmalloc(sizeof(struct buf)); + /* + * We keep one spare byte so that stream_getln() can put a '\0' + * there in case the stream doesn't have an ending newline. + */ + buf->buf = xmalloc(size + 1); + buf->size = size; + buf->in = 0; + buf->off = 0; + return (buf); +} + +/* + * Grow the size of the buffer. If "need" is 0, bump its size to the + * next power of 2 value. Otherwise, bump it to the next power of 2 + * value bigger than "need". + */ +static void +buf_grow(struct buf *buf, size_t need) +{ + + if (need == 0) + buf->size = buf->size * 2 + 1; /* Account for the spare byte. */ + else { + assert(need > buf->size); + while (buf->size < need) + buf->size = buf->size * 2 + 1; + } + buf->buf = xrealloc(buf->buf, buf->size + 1); +} + +/* Make more room in the buffer if needed. */ +static void +buf_prewrite(struct buf *buf) +{ + + if (buf_count(buf) == buf_size(buf)) + buf_grow(buf, 0); + if (buf_count(buf) > 0 && buf_avail(buf) == 0) { + memmove(buf->buf, buf->buf + buf->off, buf_count(buf)); + buf->off = 0; + } +} + +/* Account for "n" bytes being added in the buffer. */ +static void +buf_more(struct buf *buf, size_t n) +{ + + assert(n <= buf_avail(buf)); + buf->in += n; +} + +/* Account for "n" bytes having been read in the buffer. */ +static void +buf_less(struct buf *buf, size_t n) +{ + + assert(n <= buf_count(buf)); + buf->in -= n; + if (buf->in == 0) + buf->off = 0; + else + buf->off += n; +} + +/* Free a buffer. */ +static void +buf_free(struct buf *buf) +{ + + free(buf->buf); + free(buf); +} + +static struct stream * +stream_new(stream_readfn_t *readfn, stream_writefn_t *writefn, + stream_closefn_t *closefn) +{ + struct stream *stream; + + stream = xmalloc(sizeof(struct stream)); + if (readfn == NULL && writefn == NULL) { + errno = EINVAL; + return (NULL); + } + if (readfn != NULL) + stream->rdbuf = buf_new(STREAM_BUFSIZ); + else + stream->rdbuf = NULL; + if (writefn != NULL) + stream->wrbuf = buf_new(STREAM_BUFSIZ); + else + stream->wrbuf = NULL; + stream->cookie = NULL; + stream->fd = -1; + stream->readfn = readfn; + stream->writefn = writefn; + stream->closefn = closefn; + stream->filter = stream_filter_lookup(STREAM_FILTER_NULL); + stream->fdata = NULL; + stream->eof = 0; + return (stream); +} + +/* Create a new stream associated with a void *. */ +struct stream * +stream_open(void *cookie, stream_readfn_t *readfn, stream_writefn_t *writefn, + stream_closefn_t *closefn) +{ + struct stream *stream; + + stream = stream_new(readfn, writefn, closefn); + stream->cookie = cookie; + return (stream); +} + +/* Associate a file descriptor with a stream. */ +struct stream * +stream_open_fd(int fd, stream_readfn_t *readfn, stream_writefn_t *writefn, + stream_closefn_t *closefn) +{ + struct stream *stream; + + stream = stream_new(readfn, writefn, closefn); + stream->cookie = &stream->fd; + stream->fd = fd; + return (stream); +} + +/* Like open() but returns a stream. */ +struct stream * +stream_open_file(const char *path, int flags, ...) +{ + struct stream *stream; + stream_readfn_t *readfn; + stream_writefn_t *writefn; + va_list ap; + mode_t mode; + int fd; + + va_start(ap, flags); + if (flags & O_CREAT) { + /* + * GCC says I should not be using mode_t here since it's + * promoted to an int when passed through `...'. + */ + mode = va_arg(ap, int); + fd = open(path, flags, mode); + } else + fd = open(path, flags); + va_end(ap); + if (fd == -1) + return (NULL); + + flags &= O_ACCMODE; + if (flags == O_RDONLY) { + readfn = stream_read_fd; + writefn = NULL; + } else if (flags == O_WRONLY) { + readfn = NULL; + writefn = stream_write_fd; + } else if (flags == O_RDWR) { + assert(flags == O_RDWR); + readfn = stream_read_fd; + writefn = stream_write_fd; + } else { + errno = EINVAL; + close(fd); + return (NULL); + } + + stream = stream_open_fd(fd, readfn, writefn, stream_close_fd); + if (stream == NULL) + close(fd); + return (stream); +} + +/* Return the file descriptor associated with this stream, or -1. */ +int +stream_fileno(struct stream *stream) +{ + + return (stream->fd); +} + +/* Convenience read function for file descriptors. */ +ssize_t +stream_read_fd(void *cookie, void *buf, size_t size) +{ + ssize_t nbytes; + int fd; + + fd = *(int *)cookie; + nbytes = read(fd, buf, size); + return (nbytes); +} + +/* Convenience write function for file descriptors. */ +ssize_t +stream_write_fd(void *cookie, const void *buf, size_t size) +{ + ssize_t nbytes; + int fd; + + fd = *(int *)cookie; + nbytes = write(fd, buf, size); + return (nbytes); +} + +/* Convenience close function for file descriptors. */ +int +stream_close_fd(void *cookie) +{ + int fd, ret; + + fd = *(int *)cookie; + ret = close(fd); + return (ret); +} + +/* Read some bytes from the stream. */ +ssize_t +stream_read(struct stream *stream, void *buf, size_t size) +{ + struct buf *rdbuf; + ssize_t ret; + size_t n; + + rdbuf = stream->rdbuf; + if (buf_count(rdbuf) == 0) { + ret = stream_fill(stream); + if (ret <= 0) + return (-1); + } + n = min(size, buf_count(rdbuf)); + memcpy(buf, rdbuf->buf + rdbuf->off, n); + buf_less(rdbuf, n); + return (n); +} + +/* + * Read a line from the stream and return a pointer to it. + * + * If "len" is non-NULL, the length of the string will be put into it. + * The pointer is only valid until the next stream API call. The line + * can be modified by the caller, provided he doesn't write before or + * after it. + * + * This is somewhat similar to the BSD fgetln() function, except that + * "len" can be NULL here. In that case the string is terminated by + * overwriting the '\n' character with a NUL character. If it's the + * last line in the stream and it has no ending newline, we can still + * add '\0' after it, because we keep one spare byte in the buffers. + * + * However, be warned that one can't handle binary lines properly + * without knowing the size of the string since those can contain + * NUL characters. + */ +char * +stream_getln(struct stream *stream, size_t *len) +{ + struct buf *buf; + char *cp, *line; + ssize_t n; + size_t done, size; + + buf = stream->rdbuf; + if (buf_count(buf) == 0) { + n = stream_fill(stream); + if (n <= 0) + return (NULL); + } + cp = memchr(buf->buf + buf->off, '\n', buf_count(buf)); + for (done = buf_count(buf); cp == NULL; done += n) { + n = stream_fill(stream); + if (n < 0) + return (NULL); + if (n == 0) + /* Last line of the stream. */ + cp = buf->buf + buf->off + buf->in - 1; + else + cp = memchr(buf->buf + buf->off + done, '\n', + buf_count(buf) - done); + } + line = buf->buf + buf->off; + assert(cp >= line); + size = cp - line + 1; + buf_less(buf, size); + if (len != NULL) { + *len = size; + } else { + /* Terminate the string when len == NULL. */ + if (line[size - 1] == '\n') + line[size - 1] = '\0'; + else + line[size] = '\0'; + } + return (line); +} + +/* Write some bytes to a stream. */ +ssize_t +stream_write(struct stream *stream, const void *src, size_t nbytes) +{ + struct buf *buf; + int error; + + buf = stream->wrbuf; + if (nbytes > buf_size(buf)) + buf_grow(buf, nbytes); + if (nbytes > buf_avail(buf)) { + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + if (error) + return (-1); + } + memcpy(buf->buf + buf->off + buf->in, src, nbytes); + buf_more(buf, nbytes); + return (nbytes); +} + +/* Formatted output to a stream. */ +int +stream_printf(struct stream *stream, const char *fmt, ...) +{ + struct buf *buf; + va_list ap; + int error, ret; + + buf = stream->wrbuf; +again: + va_start(ap, fmt); + ret = vsnprintf(buf->buf + buf->off + buf->in, buf_avail(buf), fmt, ap); + va_end(ap); + if (ret < 0) + return (ret); + if ((unsigned)ret >= buf_avail(buf)) { + if ((unsigned)ret >= buf_size(buf)) + buf_grow(buf, ret + 1); + if ((unsigned)ret >= buf_avail(buf)) { + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + if (error) + return (-1); + } + goto again; + } + buf_more(buf, ret); + return (ret); +} + +/* Flush the entire write buffer of the stream. */ +int +stream_flush(struct stream *stream) +{ + int error; + + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + return (error); +} + +/* Internal flush API. */ +static int +stream_flush_int(struct stream *stream, stream_flush_t how) +{ + struct buf *buf; + int error; + + buf = stream->wrbuf; + error = (*stream->filter->flushfn)(stream, buf, how); + assert(buf_count(buf) == 0); + return (error); +} + +/* The default flush method. */ +static int +stream_flush_default(struct stream *stream, struct buf *buf, + stream_flush_t __unused how) +{ + ssize_t n; + + while (buf_count(buf) > 0) { + do { + n = (*stream->writefn)(stream->cookie, + buf->buf + buf->off, buf_count(buf)); + } while (n == -1 && errno == EINTR); + if (n <= 0) + return (-1); + buf_less(buf, n); + } + return (0); +} + +/* Flush the write buffer and call fsync() on the file descriptor. */ +int +stream_sync(struct stream *stream) +{ + int error; + + if (stream->fd == -1) { + errno = EINVAL; + return (-1); + } + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + if (error) + return (-1); + error = fsync(stream->fd); + return (error); +} + +/* Like truncate() but on a stream. */ +int +stream_truncate(struct stream *stream, off_t size) +{ + int error; + + if (stream->fd == -1) { + errno = EINVAL; + return (-1); + } + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + if (error) + return (-1); + error = ftruncate(stream->fd, size); + return (error); +} + +/* Like stream_truncate() except the off_t parameter is an offset. */ +int +stream_truncate_rel(struct stream *stream, off_t off) +{ + struct stat sb; + int error; + + if (stream->fd == -1) { + errno = EINVAL; + return (-1); + } + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + if (error) + return (-1); + error = fstat(stream->fd, &sb); + if (error) + return (-1); + error = stream_truncate(stream, sb.st_size + off); + return (error); +} + +/* Rewind the stream. */ +int +stream_rewind(struct stream *stream) +{ + int error; + + if (stream->fd == -1) { + errno = EINVAL; + return (-1); + } + if (stream->rdbuf != NULL) + buf_less(stream->rdbuf, buf_count(stream->rdbuf)); + if (stream->wrbuf != NULL) { + error = stream_flush_int(stream, STREAM_FLUSH_NORMAL); + if (error) + return (error); + } + error = lseek(stream->fd, 0, SEEK_SET); + return (error); +} + +/* Return EOF status. */ +int +stream_eof(struct stream *stream) +{ + + return (stream->eof); +} + +/* Close a stream and free any resources held by it. */ +int +stream_close(struct stream *stream) +{ + int error; + + if (stream == NULL) + return (0); + + error = 0; + if (stream->wrbuf != NULL) + error = stream_flush_int(stream, STREAM_FLUSH_CLOSING); + stream_filter_fini(stream); + if (stream->closefn != NULL) + /* + * We might overwrite a previous error from stream_flush(), + * but we have no choice, because wether it had worked or + * not, we need to close the file descriptor. + */ + error = (*stream->closefn)(stream->cookie); + if (stream->rdbuf != NULL) + buf_free(stream->rdbuf); + if (stream->wrbuf != NULL) + buf_free(stream->wrbuf); + free(stream); + return (error); +} + +/* The default fill method. */ +static ssize_t +stream_fill_default(struct stream *stream, struct buf *buf) +{ + ssize_t n; + + if (stream->eof) + return (0); + assert(buf_avail(buf) > 0); + n = (*stream->readfn)(stream->cookie, buf->buf + buf->off + buf->in, + buf_avail(buf)); + if (n < 0) + return (-1); + if (n == 0) { + stream->eof = 1; + return (0); + } + buf_more(buf, n); + return (n); +} + +/* + * Refill the read buffer. This function is not permitted to return + * without having made more bytes available, unless there was an error. + * Moreover, stream_fill() returns the number of bytes added. + */ +static ssize_t +stream_fill(struct stream *stream) +{ + struct stream_filter *filter; + struct buf *buf; +#ifndef NDEBUG + size_t oldcount; +#endif + ssize_t n; + + filter = stream->filter; + buf = stream->rdbuf; + buf_prewrite(buf); +#ifndef NDEBUG + oldcount = buf_count(buf); +#endif + n = (*filter->fillfn)(stream, buf); + assert((n > 0 && n == (signed)(buf_count(buf) - oldcount)) || + (n <= 0 && buf_count(buf) == oldcount)); + return (n); +} + +/* + * Lookup a stream filter. + * + * We are not supposed to get passed an invalid filter id, since + * filter ids are an enum type and we don't have invalid filter + * ids in the enum :-). Thus, we are not checking for out of + * bounds access here. If it happens, it's the caller's fault + * anyway. + */ +static struct stream_filter * +stream_filter_lookup(stream_filter_t id) +{ + struct stream_filter *filter; + + filter = stream_filters; + while (filter->id != id) + filter++; + return (filter); +} + +static int +stream_filter_init(struct stream *stream, void *data) +{ + struct stream_filter *filter; + int error; + + filter = stream->filter; + if (filter->initfn == NULL) + return (0); + error = (*filter->initfn)(stream, data); + return (error); +} + +static void +stream_filter_fini(struct stream *stream) +{ + struct stream_filter *filter; + + filter = stream->filter; + if (filter->finifn != NULL) + (*filter->finifn)(stream); +} + +/* + * Start a filter on a stream. + */ +int +stream_filter_start(struct stream *stream, stream_filter_t id, void *data) +{ + struct stream_filter *filter; + int error; + + filter = stream->filter; + if (id == filter->id) + return (0); + stream_filter_fini(stream); + stream->filter = stream_filter_lookup(id); + stream->fdata = NULL; + error = stream_filter_init(stream, data); + return (error); +} + + +/* Stop a filter, this is equivalent to setting the null filter. */ +void +stream_filter_stop(struct stream *stream) +{ + + stream_filter_start(stream, STREAM_FILTER_NULL, NULL); +} + +/* The zlib stream filter implementation. */ + +/* Take no chances with zlib... */ +static void * +zfilter_alloc(void __unused *opaque, unsigned int items, unsigned int size) +{ + + return (xmalloc(items * size)); +} + +static void +zfilter_free(void __unused *opaque, void *ptr) +{ + + free(ptr); +} + +static int +zfilter_init(struct stream *stream, void __unused *data) +{ + struct zfilter *zf; + struct buf *buf; + z_stream *state; + int rv; + + zf = xmalloc(sizeof(struct zfilter)); + memset(zf, 0, sizeof(struct zfilter)); + if (stream->rdbuf != NULL) { + state = xmalloc(sizeof(z_stream)); + state->zalloc = zfilter_alloc; + state->zfree = zfilter_free; + state->opaque = Z_NULL; + rv = inflateInit(state); + if (rv != Z_OK) + errx(1, "inflateInit: %s", state->msg); + buf = buf_new(buf_size(stream->rdbuf)); + zf->rdbuf = stream->rdbuf; + stream->rdbuf = buf; + zf->rdstate = state; + } + if (stream->wrbuf != NULL) { + state = xmalloc(sizeof(z_stream)); + state->zalloc = zfilter_alloc; + state->zfree = zfilter_free; + state->opaque = Z_NULL; + rv = deflateInit(state, Z_DEFAULT_COMPRESSION); + if (rv != Z_OK) + errx(1, "deflateInit: %s", state->msg); + buf = buf_new(buf_size(stream->wrbuf)); + zf->wrbuf = stream->wrbuf; + stream->wrbuf = buf; + zf->wrstate = state; + } + stream->fdata = zf; + return (0); +} + +static void +zfilter_fini(struct stream *stream) +{ + struct zfilter *zf; + struct buf *zbuf; + z_stream *state; + ssize_t n; + + zf = stream->fdata; + if (zf->rdbuf != NULL) { + state = zf->rdstate; + zbuf = zf->rdbuf; + /* + * Even if it has produced all the bytes, zlib sometimes + * hasn't seen the EOF marker, so we need to call inflate() + * again to make sure we have eaten all the zlib'ed bytes. + */ + if ((zf->flags & ZFILTER_EOF) == 0) { + n = zfilter_fill(stream, stream->rdbuf); + assert(n == 0 && zf->flags & ZFILTER_EOF); + } + inflateEnd(state); + free(state); + buf_free(stream->rdbuf); + stream->rdbuf = zbuf; + } + if (zf->wrbuf != NULL) { + state = zf->wrstate; + zbuf = zf->wrbuf; + /* + * Compress the remaining bytes in the buffer, if any, + * and emit an EOF marker as appropriate. We ignore + * the error because we can't do anything about it at + * this point, and it can happen if we're getting + * disconnected. + */ + (void)zfilter_flush(stream, stream->wrbuf, + STREAM_FLUSH_CLOSING); + deflateEnd(state); + free(state); + buf_free(stream->wrbuf); + stream->wrbuf = zbuf; + } + free(zf); +} + +static int +zfilter_flush(struct stream *stream, struct buf *buf, stream_flush_t how) +{ + struct zfilter *zf; + struct buf *zbuf; + z_stream *state; + size_t lastin, lastout, ate, prod; + int done, error, flags, rv; + + zf = stream->fdata; + state = zf->wrstate; + zbuf = zf->wrbuf; + + if (how == STREAM_FLUSH_NORMAL) + flags = Z_SYNC_FLUSH; + else + flags = Z_FINISH; + + done = 0; + rv = Z_OK; + +again: + /* + * According to zlib.h, we should have at least 6 bytes + * available when using deflate() with Z_SYNC_FLUSH. + */ + if ((buf_avail(zbuf) < 6 && flags == Z_SYNC_FLUSH) || + rv == Z_BUF_ERROR || buf_avail(buf) == 0) { + error = stream_flush_default(stream, zbuf, how); + if (error) + return (error); + } + + state->next_in = (Bytef *)(buf->buf + buf->off); + state->avail_in = buf_count(buf); + state->next_out = (Bytef *)(zbuf->buf + zbuf->off + zbuf->in); + state->avail_out = buf_avail(zbuf); + lastin = state->avail_in; + lastout = state->avail_out; + rv = deflate(state, flags); + if (rv != Z_BUF_ERROR && rv != Z_OK && rv != Z_STREAM_END) + errx(1, "deflate: %s", state->msg); + ate = lastin - state->avail_in; + prod = lastout - state->avail_out; + buf_less(buf, ate); + buf_more(zbuf, prod); + if ((flags == Z_SYNC_FLUSH && buf_count(buf) > 0) || + (flags == Z_FINISH && rv != Z_STREAM_END) || + (rv == Z_BUF_ERROR)) + goto again; + + assert(rv == Z_OK || (rv == Z_STREAM_END && flags == Z_FINISH)); + error = stream_flush_default(stream, zbuf, how); + return (error); +} + +static ssize_t +zfilter_fill(struct stream *stream, struct buf *buf) +{ + struct zfilter *zf; + struct buf *zbuf; + z_stream *state; + size_t lastin, lastout, new; + ssize_t n; + int rv; + + zf = stream->fdata; + state = zf->rdstate; + zbuf = zf->rdbuf; + + assert(buf_avail(buf) > 0); + if (buf_count(zbuf) == 0) { + n = stream_fill_default(stream, zbuf); + if (n <= 0) + return (n); + } +again: + assert(buf_count(zbuf) > 0); + state->next_in = (Bytef *)(zbuf->buf + zbuf->off); + state->avail_in = buf_count(zbuf); + state->next_out = (Bytef *)(buf->buf + buf->off + buf->in); + state->avail_out = buf_avail(buf); + lastin = state->avail_in; + lastout = state->avail_out; + rv = inflate(state, Z_SYNC_FLUSH); + buf_less(zbuf, lastin - state->avail_in); + new = lastout - state->avail_out; + if (new == 0 && rv != Z_STREAM_END) { + n = stream_fill_default(stream, zbuf); + if (n == -1) + return (-1); + if (n == 0) + return (0); + goto again; + } + if (rv != Z_STREAM_END && rv != Z_OK) + errx(1, "inflate: %s", state->msg); + if (rv == Z_STREAM_END) + zf->flags |= ZFILTER_EOF; + buf_more(buf, new); + return (new); +} + +/* The MD5 stream filter implementation. */ +static int +md5filter_init(struct stream *stream, void *data) +{ + struct md5filter *mf; + + mf = xmalloc(sizeof(struct md5filter)); + MD5_Init(&mf->ctx); + mf->md5 = data; + stream->fdata = mf; + return (0); +} + +static void +md5filter_fini(struct stream *stream) +{ + struct md5filter *mf; + + mf = stream->fdata; + MD5_End(mf->md5, &mf->ctx); + free(stream->fdata); +} + +static ssize_t +md5filter_fill(struct stream *stream, struct buf *buf) +{ + ssize_t n; + + assert(buf_avail(buf) > 0); + n = stream_fill_default(stream, buf); + return (n); +} + +static int +md5filter_flush(struct stream *stream, struct buf *buf, stream_flush_t how) +{ + struct md5filter *mf; + int error; + + mf = stream->fdata; + MD5_Update(&mf->ctx, buf->buf + buf->off, buf->in); + error = stream_flush_default(stream, buf, how); + return (error); +} diff --git a/contrib/csup/stream.h b/contrib/csup/stream.h new file mode 100644 index 000000000000..062fd34e3fa4 --- /dev/null +++ b/contrib/csup/stream.h @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _STREAM_H_ +#define _STREAM_H_ + +#include "misc.h" + +/* Stream filters. */ +typedef enum { + STREAM_FILTER_NULL, + STREAM_FILTER_ZLIB, + STREAM_FILTER_MD5 +} stream_filter_t; + +struct stream; + +typedef ssize_t stream_readfn_t(void *, void *, size_t); +typedef ssize_t stream_writefn_t(void *, const void *, size_t); +typedef int stream_closefn_t(void *); + +/* Convenience functions for handling file descriptors. */ +stream_readfn_t stream_read_fd; +stream_writefn_t stream_write_fd; +stream_closefn_t stream_close_fd; + +struct stream *stream_open(void *, stream_readfn_t *, stream_writefn_t *, + stream_closefn_t *); +struct stream *stream_open_fd(int, stream_readfn_t *, stream_writefn_t *, + stream_closefn_t *); +struct stream *stream_open_file(const char *, int, ...); +int stream_fileno(struct stream *); +ssize_t stream_read(struct stream *, void *, size_t); +ssize_t stream_write(struct stream *, const void *, size_t); +char *stream_getln(struct stream *, size_t *); +int stream_printf(struct stream *, const char *, ...) + __printflike(2, 3); +int stream_flush(struct stream *); +int stream_sync(struct stream *); +int stream_truncate(struct stream *, off_t); +int stream_truncate_rel(struct stream *, off_t); +int stream_rewind(struct stream *); +int stream_eof(struct stream *); +int stream_close(struct stream *); +int stream_filter_start(struct stream *, stream_filter_t, void *); +void stream_filter_stop(struct stream *); + +#endif /* !_STREAM_H_ */ diff --git a/contrib/csup/threads.c b/contrib/csup/threads.c new file mode 100644 index 000000000000..46a98606f282 --- /dev/null +++ b/contrib/csup/threads.c @@ -0,0 +1,176 @@ +/*- + * Copyright (c) 2004-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include <err.h> +#include <pthread.h> +#include <stdlib.h> + +#include "misc.h" +#include "queue.h" +#include "threads.h" + +/* + * This API is a wrapper around the pthread(3) API, which mainly + * allows me to wait for multiple threads to exit. We use a + * condition variable to signal a thread's death. All threads + * created with this API have a common entry/exit point, so we + * don't need to add any code in the threads themselves. + */ + +/* Structure describing a thread. */ +struct thread { + pthread_t thread; + void *(*start)(void *); + void *data; + struct threads *threads; + LIST_ENTRY(thread) runlist; + STAILQ_ENTRY(thread) deadlist; +}; + +/* A set of threads. */ +struct threads { + pthread_mutex_t threads_mtx; + pthread_cond_t thread_exited; + LIST_HEAD(, thread) threads_running; + STAILQ_HEAD(, thread) threads_dead; +}; + +static void *thread_start(void *); /* Common entry point for threads. */ + +static void threads_lock(struct threads *); +static void threads_unlock(struct threads *); + +static void +threads_lock(struct threads *tds) +{ + int error; + + error = pthread_mutex_lock(&tds->threads_mtx); + assert(!error); +} + +static void +threads_unlock(struct threads *tds) +{ + int error; + + error = pthread_mutex_unlock(&tds->threads_mtx); + assert(!error); +} + +/* Create a new set of threads. */ +struct threads * +threads_new(void) +{ + struct threads *tds; + + tds = xmalloc(sizeof(struct threads)); + pthread_mutex_init(&tds->threads_mtx, NULL); + pthread_cond_init(&tds->thread_exited, NULL); + LIST_INIT(&tds->threads_running); + STAILQ_INIT(&tds->threads_dead); + return (tds); +} + +/* Create a new thread in this set. */ +void +threads_create(struct threads *tds, void *(*start)(void *), void *data) +{ + pthread_attr_t attr; + struct thread *td; + int error; + + td = xmalloc(sizeof(struct thread)); + td->threads = tds; + td->start = start; + td->data = data; + /* We don't use pthread_join() to wait for the threads to finish. */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + threads_lock(tds); + error = pthread_create(&td->thread, &attr, thread_start, td); + if (error) + err(1, "pthread_create"); + LIST_INSERT_HEAD(&tds->threads_running, td, runlist); + threads_unlock(tds); +} + +/* Wait for a thread in the set to exit, and return its data pointer. */ +void * +threads_wait(struct threads *tds) +{ + struct thread *td; + void *data; + + threads_lock(tds); + while (STAILQ_EMPTY(&tds->threads_dead)) { + assert(!LIST_EMPTY(&tds->threads_running)); + pthread_cond_wait(&tds->thread_exited, &tds->threads_mtx); + } + td = STAILQ_FIRST(&tds->threads_dead); + STAILQ_REMOVE_HEAD(&tds->threads_dead, deadlist); + threads_unlock(tds); + data = td->data; + free(td); + return (data); +} + +/* Free a threads set. */ +void +threads_free(struct threads *tds) +{ + + assert(LIST_EMPTY(&tds->threads_running)); + assert(STAILQ_EMPTY(&tds->threads_dead)); + pthread_cond_destroy(&tds->thread_exited); + pthread_mutex_destroy(&tds->threads_mtx); + free(tds); +} + +/* + * Common entry point for threads. This just calls the real start + * routine, and then signals the thread's death, after having + * removed the thread from the list. + */ +static void * +thread_start(void *data) +{ + struct threads *tds; + struct thread *td; + + td = data; + tds = td->threads; + td->start(td->data); + threads_lock(tds); + LIST_REMOVE(td, runlist); + STAILQ_INSERT_TAIL(&tds->threads_dead, td, deadlist); + pthread_cond_signal(&tds->thread_exited); + threads_unlock(tds); + return (NULL); +} diff --git a/contrib/csup/threads.h b/contrib/csup/threads.h new file mode 100644 index 000000000000..66153ce5589d --- /dev/null +++ b/contrib/csup/threads.h @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _THREADS_H_ +#define _THREADS_H_ + +struct threads; + +struct threads *threads_new(void); +void threads_create(struct threads *, void *(*)(void *), void *); +void *threads_wait(struct threads *); +void threads_free(struct threads *); + +#endif /* !_THREADS_H_ */ diff --git a/contrib/csup/token.h b/contrib/csup/token.h new file mode 100644 index 000000000000..45d4ed780148 --- /dev/null +++ b/contrib/csup/token.h @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _TOKEN_H_ +#define _TOKEN_H_ + +void yyerror(const char *); +int yylex(void); +int yyparse(void); + +/* Parsing tokens. */ +#define PT_BASE 0 +#define PT_DATE 1 +#define PT_HOST 2 +#define PT_PREFIX 3 +#define PT_RELEASE 4 +#define PT_TAG 5 +#define PT_UMASK 6 +#define PT_COMPRESS 7 +#define PT_DELETE 8 +#define PT_USE_REL_SUFFIX 9 +#define PT_LIST 10 + +#endif /* !_TOKEN_H_ */ diff --git a/contrib/csup/token.l b/contrib/csup/token.l new file mode 100644 index 000000000000..0def82dedd18 --- /dev/null +++ b/contrib/csup/token.l @@ -0,0 +1,79 @@ +%{ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <err.h> +#include <stdlib.h> +#include <string.h> + +#include "parse.h" +#include "misc.h" +#include "token.h" + +#define YY_NO_UNPUT + +int lineno = 1; + +%} + +%option noyywrap + +%% + +[ \t]+ ; +#.* ; +\*default { return DEFAULT; } +base { yylval.i = PT_BASE; return NAME; } +date { yylval.i = PT_DATE; return NAME; } +host { yylval.i = PT_HOST; return NAME; } +prefix { yylval.i = PT_PREFIX; return NAME; } +release { yylval.i = PT_RELEASE; return NAME; } +tag { yylval.i = PT_TAG; return NAME; } +umask { yylval.i = PT_UMASK; return NAME; } +list { yylval.i = PT_LIST; return NAME; } += { return EQUAL; } +compress { yylval.i = PT_COMPRESS; return BOOLEAN; } +delete { yylval.i = PT_DELETE; return BOOLEAN; } +use-rel-suffix { yylval.i = PT_USE_REL_SUFFIX; return BOOLEAN; } +[a-zA-Z0-9./_-]+ { + yylval.str = strdup(yytext); + if (yylval.str == NULL) + err(1, "strdup"); + return STRING; + } +\n lineno++; + +%% + +void +yyerror(const char *s) +{ + + lprintf(-1, "Parse error line %d: %s: %s\n", lineno, s, yytext); + exit(1); +} diff --git a/contrib/csup/updater.c b/contrib/csup/updater.c new file mode 100644 index 000000000000..235bfcaf7b52 --- /dev/null +++ b/contrib/csup/updater.c @@ -0,0 +1,1012 @@ +/*- + * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "diff.h" +#include "fattr.h" +#include "fixups.h" +#include "keyword.h" +#include "updater.h" +#include "misc.h" +#include "mux.h" +#include "proto.h" +#include "status.h" +#include "stream.h" + +/* Internal error codes. */ +#define UPDATER_ERR_PROTO (-1) /* Protocol error. */ +#define UPDATER_ERR_MSG (-2) /* Error is in updater->errmsg. */ +#define UPDATER_ERR_READ (-3) /* Error reading from server. */ + +/* Everything needed to update a file. */ +struct file_update { + struct statusrec srbuf; + char *destpath; + char *coname; /* Points somewhere in destpath. */ + char *wantmd5; + struct coll *coll; + struct status *st; + /* Those are only used for diff updating. */ + char *author; + struct stream *orig; + struct stream *to; + int expand; +}; + +struct updater { + struct config *config; + struct stream *rd; + char *errmsg; +}; + +static struct file_update *fup_new(struct coll *, struct status *); +static int fup_prepare(struct file_update *, char *); +static void fup_cleanup(struct file_update *); +static void fup_free(struct file_update *); + +static void updater_prunedirs(char *, char *); +static int updater_batch(struct updater *, int); +static int updater_docoll(struct updater *, struct file_update *, int); +static void updater_delete(struct file_update *); +static int updater_checkout(struct updater *, struct file_update *, int); +static int updater_setattrs(struct updater *, struct file_update *, + char *, char *, char *, char *, char *, struct fattr *); +static void updater_checkmd5(struct updater *, struct file_update *, + const char *, int); +static int updater_updatefile(struct updater *, struct file_update *fup, + const char *, const char *); +static int updater_diff(struct updater *, struct file_update *); +static int updater_diff_batch(struct updater *, struct file_update *); +static int updater_diff_apply(struct updater *, struct file_update *, + char *); + +static struct file_update * +fup_new(struct coll *coll, struct status *st) +{ + struct file_update *fup; + + fup = xmalloc(sizeof(struct file_update)); + memset(fup, 0, sizeof(*fup)); + fup->coll = coll; + fup->st = st; + return (fup); +} + +static int +fup_prepare(struct file_update *fup, char *name) +{ + struct coll *coll; + + coll = fup->coll; + fup->destpath = checkoutpath(coll->co_prefix, name); + if (fup->destpath == NULL) + return (-1); + fup->coname = fup->destpath + coll->co_prefixlen + 1; + return (0); +} + +/* Called after each file update to reinit the structure. */ +static void +fup_cleanup(struct file_update *fup) +{ + struct statusrec *sr; + + sr = &fup->srbuf; + + if (fup->destpath != NULL) { + free(fup->destpath); + fup->destpath = NULL; + } + fup->coname = NULL; + if (fup->author != NULL) { + free(fup->author); + fup->author = NULL; + } + fup->expand = 0; + if (fup->wantmd5 != NULL) { + free(fup->wantmd5); + fup->wantmd5 = NULL; + } + if (fup->orig != NULL) { + stream_close(fup->orig); + fup->orig = NULL; + } + if (fup->to != NULL) { + stream_close(fup->to); + fup->to = NULL; + } + if (sr->sr_file != NULL) + free(sr->sr_file); + if (sr->sr_tag != NULL) + free(sr->sr_tag); + if (sr->sr_date != NULL) + free(sr->sr_date); + if (sr->sr_revnum != NULL) + free(sr->sr_revnum); + if (sr->sr_revdate != NULL) + free(sr->sr_revdate); + fattr_free(sr->sr_clientattr); + fattr_free(sr->sr_serverattr); + memset(sr, 0, sizeof(*sr)); +} + +static void +fup_free(struct file_update *fup) +{ + + fup_cleanup(fup); + free(fup); +} + +void * +updater(void *arg) +{ + struct thread_args *args; + struct updater upbuf, *up; + int error; + + args = arg; + + up = &upbuf; + up->config = args->config; + up->rd = args->rd; + up->errmsg = NULL; + + error = updater_batch(up, 0); + + /* + * Make sure to close the fixups even in case of an error, + * so that the lister thread doesn't block indefinitely. + */ + fixups_close(up->config->fixups); + if (!error) + error = updater_batch(up, 1); + switch (error) { + case UPDATER_ERR_PROTO: + xasprintf(&args->errmsg, "Updater failed: Protocol error"); + args->status = STATUS_FAILURE; + break; + case UPDATER_ERR_MSG: + xasprintf(&args->errmsg, "Updater failed: %s", up->errmsg); + free(up->errmsg); + args->status = STATUS_FAILURE; + break; + case UPDATER_ERR_READ: + if (stream_eof(up->rd)) { + xasprintf(&args->errmsg, "Updater failed: " + "Premature EOF from server"); + } else { + xasprintf(&args->errmsg, "Updater failed: " + "Network read failure: %s", strerror(errno)); + } + args->status = STATUS_TRANSIENTFAILURE; + break; + default: + assert(error == 0); + args->status = STATUS_SUCCESS; + }; + return (NULL); +} + +static int +updater_batch(struct updater *up, int isfixups) +{ + struct stream *rd; + struct coll *coll; + struct status *st; + struct file_update *fup; + char *line, *cmd, *errmsg, *collname, *release; + int error; + + rd = up->rd; + STAILQ_FOREACH(coll, &up->config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + umask(coll->co_umask); + line = stream_getln(rd, NULL); + if (line == NULL) + return (UPDATER_ERR_READ); + cmd = proto_get_ascii(&line); + collname = proto_get_ascii(&line); + release = proto_get_ascii(&line); + if (release == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + if (strcmp(cmd, "COLL") != 0 || + strcmp(collname, coll->co_name) != 0 || + strcmp(release, coll->co_release) != 0) + return (UPDATER_ERR_PROTO); + + if (!isfixups) + lprintf(1, "Updating collection %s/%s\n", coll->co_name, + coll->co_release); + + if (coll->co_options & CO_COMPRESS) + stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); + + st = status_open(coll, coll->co_scantime, &errmsg); + fup = fup_new(coll, st); + if (st == NULL) { + fup_free(fup); + up->errmsg = errmsg; + return (UPDATER_ERR_MSG); + } + error = updater_docoll(up, fup, isfixups); + status_close(st, &errmsg); + fup_free(fup); + if (errmsg != NULL) { + /* Discard previous error. */ + if (up->errmsg != NULL) + free(up->errmsg); + up->errmsg = errmsg; + return (UPDATER_ERR_MSG); + } + if (error) + return (error); + + if (coll->co_options & CO_COMPRESS) + stream_filter_stop(rd); + } + line = stream_getln(rd, NULL); + if (line == NULL) + return (UPDATER_ERR_READ); + if (strcmp(line, ".") != 0) + return (UPDATER_ERR_PROTO); + return (0); +} + +static int +updater_docoll(struct updater *up, struct file_update *fup, int isfixups) +{ + struct stream *rd; + struct coll *coll; + struct statusrec srbuf, *sr; + struct fattr *rcsattr, *tmp; + char *cmd, *line, *msg, *attr; + char *name, *tag, *date, *revdate; + char *expand, *wantmd5, *revnum; + time_t t; + int error, needfixupmsg; + + error = 0; + rd = up->rd; + coll = fup->coll; + needfixupmsg = isfixups; + while ((line = stream_getln(rd, NULL)) != NULL) { + if (strcmp(line, ".") == 0) + break; + memset(&srbuf, 0, sizeof(srbuf)); + if (needfixupmsg) { + lprintf(1, "Applying fixups for collection %s/%s\n", + coll->co_name, coll->co_release); + needfixupmsg = 0; + } + cmd = proto_get_ascii(&line); + if (cmd == NULL || strlen(cmd) != 1) + return (UPDATER_ERR_PROTO); + switch (cmd[0]) { + case 'T': + /* Update recorded information for checked-out file. */ + name = proto_get_ascii(&line); + tag = proto_get_ascii(&line); + date = proto_get_ascii(&line); + revnum = proto_get_ascii(&line); + revdate = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (attr == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + + rcsattr = fattr_decode(attr); + if (rcsattr == NULL) + return (UPDATER_ERR_PROTO); + + error = fup_prepare(fup, name); + if (error) + return (UPDATER_ERR_PROTO); + error = updater_setattrs(up, fup, name, tag, date, + revnum, revdate, rcsattr); + fattr_free(rcsattr); + if (error) + return (error); + break; + case 'c': + /* Checkout dead file. */ + name = proto_get_ascii(&line); + tag = proto_get_ascii(&line); + date = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (attr == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + + error = fup_prepare(fup, name); + if (error) + return (UPDATER_ERR_PROTO); + /* Theoritically, the file does not exist on the client. + Just to make sure, we'll delete it here, if it + exists. */ + if (access(fup->destpath, F_OK) == 0) + updater_delete(fup); + + sr = &srbuf; + sr->sr_type = SR_CHECKOUTDEAD; + sr->sr_file = name; + sr->sr_tag = tag; + sr->sr_date = date; + sr->sr_serverattr = fattr_decode(attr); + if (sr->sr_serverattr == NULL) + return (UPDATER_ERR_PROTO); + + error = status_put(fup->st, sr); + fattr_free(sr->sr_serverattr); + if (error) { + up->errmsg = status_errmsg(fup->st); + return (UPDATER_ERR_MSG); + } + break; + case 'U': + /* Update live checked-out file. */ + name = proto_get_ascii(&line); + tag = proto_get_ascii(&line); + date = proto_get_ascii(&line); + proto_get_ascii(&line); /* XXX - oldRevNum */ + proto_get_ascii(&line); /* XXX - fromAttic */ + proto_get_ascii(&line); /* XXX - logLines */ + expand = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + wantmd5 = proto_get_ascii(&line); + if (wantmd5 == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + + sr = &fup->srbuf; + sr->sr_type = SR_CHECKOUTLIVE; + sr->sr_file = xstrdup(name); + sr->sr_date = xstrdup(date); + sr->sr_tag = xstrdup(tag); + sr->sr_serverattr = fattr_decode(attr); + if (sr->sr_serverattr == NULL) + return (UPDATER_ERR_PROTO); + + fup->expand = keyword_decode_expand(expand); + if (fup->expand == -1) + return (UPDATER_ERR_PROTO); + error = fup_prepare(fup, name); + if (error) + return (UPDATER_ERR_PROTO); + + fup->wantmd5 = xstrdup(wantmd5); + error = updater_diff(up, fup); + if (error) + return (error); + break; + case 'u': + /* Update dead checked-out file. */ + name = proto_get_ascii(&line); + tag = proto_get_ascii(&line); + date = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (attr == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + + error = fup_prepare(fup, name); + if (error) + return (UPDATER_ERR_PROTO); + updater_delete(fup); + sr = &srbuf; + sr->sr_type = SR_CHECKOUTDEAD; + sr->sr_file = name; + sr->sr_tag = tag; + sr->sr_date = date; + sr->sr_serverattr = fattr_decode(attr); + if (sr->sr_serverattr == NULL) + return (UPDATER_ERR_PROTO); + error = status_put(fup->st, sr); + fattr_free(sr->sr_serverattr); + if (error) { + up->errmsg = status_errmsg(fup->st); + return (UPDATER_ERR_MSG); + } + break; + case 'C': + case 'Y': + /* Checkout file. */ + name = proto_get_ascii(&line); + tag = proto_get_ascii(&line); + date = proto_get_ascii(&line); + revnum = proto_get_ascii(&line); + revdate = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (attr == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + + sr = &fup->srbuf; + sr->sr_type = SR_CHECKOUTLIVE; + sr->sr_file = xstrdup(name); + sr->sr_tag = xstrdup(tag); + sr->sr_date = xstrdup(date); + sr->sr_revnum = xstrdup(revnum); + sr->sr_revdate = xstrdup(revdate); + sr->sr_serverattr = fattr_decode(attr); + if (sr->sr_serverattr == NULL) + return (UPDATER_ERR_PROTO); + + t = rcsdatetotime(revdate); + if (t == -1) + return (UPDATER_ERR_PROTO); + + sr->sr_clientattr = fattr_new(FT_FILE, t); + tmp = fattr_forcheckout(sr->sr_serverattr, + coll->co_umask); + fattr_override(sr->sr_clientattr, tmp, FA_MASK); + fattr_free(tmp); + fattr_mergedefault(sr->sr_clientattr); + error = fup_prepare(fup, name); + if (error) + return (UPDATER_ERR_PROTO); + if (*cmd == 'Y') + error = updater_checkout(up, fup, 1); + else + error = updater_checkout(up, fup, 0); + if (error) + return (error); + break; + case 'D': + /* Delete file. */ + name = proto_get_ascii(&line); + if (name == NULL || line != NULL) + return (UPDATER_ERR_PROTO); + error = fup_prepare(fup, name); + if (error) + return (UPDATER_ERR_PROTO); + updater_delete(fup); + error = status_delete(fup->st, name, 0); + if (error) { + up->errmsg = status_errmsg(fup->st); + return (UPDATER_ERR_MSG); + } + break; + case '!': + /* Warning from server. */ + msg = proto_get_rest(&line); + if (msg == NULL) + return (UPDATER_ERR_PROTO); + lprintf(-1, "Server warning: %s\n", msg); + break; + default: + return (UPDATER_ERR_PROTO); + } + fup_cleanup(fup); + } + if (line == NULL) + return (UPDATER_ERR_READ); + return (0); +} + +/* Delete file. */ +static void +updater_delete(struct file_update *fup) +{ + struct coll *coll; + int error; + + /* XXX - delete limit handling */ + coll = fup->coll; + if (coll->co_options & CO_DELETE) { + lprintf(1, " Delete %s\n", fup->coname); + error = fattr_delete(fup->destpath); + if (error) { + lprintf(-1, "Cannot delete \"%s\": %s\n", + fup->destpath, strerror(errno)); + return; + } + if (coll->co_options & CO_CHECKOUTMODE) + updater_prunedirs(coll->co_prefix, fup->destpath); + } else { + lprintf(1," NoDelete %s\n", fup->coname); + } +} + +static int +updater_setattrs(struct updater *up, struct file_update *fup, char *name, + char *tag, char *date, char *revnum, char *revdate, struct fattr *rcsattr) +{ + struct statusrec sr; + struct status *st; + struct coll *coll; + struct fattr *fileattr, *fa; + char *path; + int error, rv; + + coll = fup->coll; + st = fup->st; + path = fup->destpath; + + fileattr = fattr_frompath(path, FATTR_NOFOLLOW); + if (fileattr == NULL) { + /* The file has vanished. */ + error = status_delete(st, name, 0); + if (error) { + up->errmsg = status_errmsg(st); + return (UPDATER_ERR_MSG); + } + return (0); + } + fa = fattr_forcheckout(rcsattr, coll->co_umask); + fattr_override(fileattr, fa, FA_MASK); + fattr_free(fa); + + rv = fattr_install(fileattr, path, NULL); + if (rv == -1) { + lprintf(1, " SetAttrs %s\n", fup->coname); + fattr_free(fileattr); + xasprintf(&up->errmsg, "Cannot set attributes for \"%s\": %s", + path, strerror(errno)); + return (UPDATER_ERR_MSG); + } + if (rv == 1) { + lprintf(1, " SetAttrs %s\n", fup->coname); + fattr_free(fileattr); + fileattr = fattr_frompath(path, FATTR_NOFOLLOW); + if (fileattr == NULL) { + /* We're being very unlucky. */ + error = status_delete(st, name, 0); + if (error) { + up->errmsg = status_errmsg(st); + return (UPDATER_ERR_MSG); + } + return (0); + } + } + + fattr_maskout(fileattr, FA_COIGNORE); + + sr.sr_type = SR_CHECKOUTLIVE; + sr.sr_file = name; + sr.sr_tag = tag; + sr.sr_date = date; + sr.sr_revnum = revnum; + sr.sr_revdate = revdate; + sr.sr_clientattr = fileattr; + sr.sr_serverattr = rcsattr; + + error = status_put(st, &sr); + fattr_free(fileattr); + if (error) { + up->errmsg = status_errmsg(st); + return (UPDATER_ERR_MSG); + } + return (0); +} + +/* + * Check that the file we created/updated has a correct MD5 checksum. + * If it doesn't and that this is not a fixup update, add a fixup + * request to checkout the whole file. If it's already a fixup update, + * we just fail. + */ +static void +updater_checkmd5(struct updater *up, struct file_update *fup, const char *md5, + int isfixup) +{ + struct statusrec *sr; + + sr = &fup->srbuf; + if (strcmp(fup->wantmd5, md5) == 0) + return; + if (isfixup) { + lprintf(-1, "%s: Checksum mismatch -- file not updated\n", + fup->destpath); + return; + } + lprintf(-1, "%s: Checksum mismatch -- will transfer entire file\n", + fup->destpath); + fixups_put(up->config->fixups, fup->coll, sr->sr_file); +} + +static int +updater_updatefile(struct updater *up, struct file_update *fup, const char *to, + const char *from) +{ + struct coll *coll; + struct status *st; + struct statusrec *sr; + struct fattr *fileattr; + int error, rv; + + coll = fup->coll; + sr = &fup->srbuf; + st = fup->st; + + fattr_umask(sr->sr_clientattr, coll->co_umask); + rv = fattr_install(sr->sr_clientattr, to, from); + if (rv == -1) { + if (from == NULL) + xasprintf(&up->errmsg, "Cannot install \"%s\": %s", + to, strerror(errno)); + else + xasprintf(&up->errmsg, + "Cannot install \"%s\" to \"%s\": %s", + from, to, strerror(errno)); + return (UPDATER_ERR_MSG); + } + + /* XXX Executes */ + /* + * We weren't necessarily able to set all the file attributes to the + * desired values, and any executes may have altered the attributes. + * To make sure we record the actual attribute values, we fetch + * them from the file. + * + * However, we preserve the link count as received from the + * server. This is important for preserving hard links in mirror + * mode. + */ + fileattr = fattr_frompath(to, FATTR_NOFOLLOW); + if (fileattr == NULL) { + xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", to, + strerror(errno)); + return (UPDATER_ERR_MSG); + } + fattr_override(fileattr, sr->sr_clientattr, FA_LINKCOUNT); + fattr_free(sr->sr_clientattr); + sr->sr_clientattr = fileattr; + + /* + * To save space, don't write out the device and inode unless + * the link count is greater than 1. These attributes are used + * only for detecting hard links. If the link count is 1 then we + * know there aren't any hard links. + */ + if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) || + fattr_getlinkcount(sr->sr_clientattr) <= 1) + fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE); + + if (coll->co_options & CO_CHECKOUTMODE) + fattr_maskout(sr->sr_clientattr, FA_COIGNORE); + + error = status_put(st, sr); + if (error) { + up->errmsg = status_errmsg(st); + return (UPDATER_ERR_MSG); + } + return (0); +} + +static int +updater_diff(struct updater *up, struct file_update *fup) +{ + char md5[MD5_DIGEST_SIZE]; + struct coll *coll; + struct statusrec *sr; + struct fattr *fa, *tmp; + char *author, *path, *revnum, *revdate; + char *line, *cmd, *temppath; + int error; + + temppath = NULL; + coll = fup->coll; + sr = &fup->srbuf; + path = fup->destpath; + + lprintf(1, " Edit %s\n", fup->coname); + while ((line = stream_getln(up->rd, NULL)) != NULL) { + if (strcmp(line, ".") == 0) + break; + cmd = proto_get_ascii(&line); + if (cmd == NULL || strcmp(cmd, "D") != 0) { + error = UPDATER_ERR_PROTO; + goto bad; + } + revnum = proto_get_ascii(&line); + proto_get_ascii(&line); /* XXX - diffbase */ + revdate = proto_get_ascii(&line); + author = proto_get_ascii(&line); + if (author == NULL || line != NULL) { + error = UPDATER_ERR_PROTO; + goto bad; + } + if (sr->sr_revnum != NULL) + free(sr->sr_revnum); + if (sr->sr_revdate != NULL) + free(sr->sr_revdate); + if (fup->author != NULL) + free(fup->author); + sr->sr_revnum = xstrdup(revnum); + sr->sr_revdate = xstrdup(revdate); + fup->author = xstrdup(author); + if (fup->orig == NULL) { + /* First patch, the "origin" file is the one we have. */ + fup->orig = stream_open_file(path, O_RDONLY); + if (fup->orig == NULL) { + xasprintf(&up->errmsg, "%s: Cannot open: %s", + path, strerror(errno)); + error = UPDATER_ERR_MSG; + goto bad; + } + } else { + /* Subsequent patches. */ + stream_close(fup->orig); + fup->orig = fup->to; + stream_rewind(fup->orig); + unlink(temppath); + free(temppath); + } + temppath = tempname(path); + fup->to = stream_open_file(temppath, + O_RDWR | O_CREAT | O_EXCL, 0600); + if (fup->to == NULL) { + xasprintf(&up->errmsg, "%s: Cannot open: %s", + temppath, strerror(errno)); + error = UPDATER_ERR_MSG; + goto bad; + } + lprintf(2, " Add delta %s %s %s\n", sr->sr_revnum, + sr->sr_revdate, fup->author); + error = updater_diff_batch(up, fup); + if (error) + goto bad; + } + if (line == NULL) { + error = UPDATER_ERR_READ; + goto bad; + } + + fa = fattr_frompath(path, FATTR_FOLLOW); + tmp = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); + fattr_override(fa, tmp, FA_MASK); + fattr_free(tmp); + fattr_maskout(fa, FA_MODTIME); + sr->sr_clientattr = fa; + + error = updater_updatefile(up, fup, path, temppath); + if (error) + goto bad; + + if (MD5_File(path, md5) == -1) { + xasprintf(&up->errmsg, + "Cannot calculate checksum for \"%s\": %s", + path, strerror(errno)); + error = UPDATER_ERR_MSG; + goto bad; + } + updater_checkmd5(up, fup, md5, 0); + free(temppath); + return (0); +bad: + assert(error); + if (temppath != NULL) + free(temppath); + return (error); +} + +static int +updater_diff_batch(struct updater *up, struct file_update *fup) +{ + struct stream *rd; + char *cmd, *line, *state, *tok; + int error; + + state = NULL; + rd = up->rd; + while ((line = stream_getln(rd, NULL)) != NULL) { + if (strcmp(line, ".") == 0) + break; + cmd = proto_get_ascii(&line); + if (cmd == NULL || strlen(cmd) != 1) { + error = UPDATER_ERR_PROTO; + goto bad; + } + switch (cmd[0]) { + case 'L': + line = stream_getln(rd, NULL); + /* XXX - We're just eating the log for now. */ + while (line != NULL && strcmp(line, ".") != 0 && + strcmp(line, ".+") != 0) + line = stream_getln(rd, NULL); + if (line == NULL) { + error = UPDATER_ERR_READ; + goto bad; + } + break; + case 'S': + tok = proto_get_ascii(&line); + if (tok == NULL || line != NULL) { + error = UPDATER_ERR_PROTO; + goto bad; + } + if (state != NULL) + free(state); + state = xstrdup(tok); + break; + case 'T': + error = updater_diff_apply(up, fup, state); + if (error) + goto bad; + break; + default: + error = UPDATER_ERR_PROTO; + goto bad; + } + } + if (line == NULL) { + error = UPDATER_ERR_READ; + goto bad; + } + if (state != NULL) + free(state); + return (0); +bad: + if (state != NULL) + free(state); + return (error); +} + +int +updater_diff_apply(struct updater *up, struct file_update *fup, char *state) +{ + struct diffinfo dibuf, *di; + struct coll *coll; + struct statusrec *sr; + int error; + + coll = fup->coll; + sr = &fup->srbuf; + di = &dibuf; + + di->di_rcsfile = sr->sr_file; + di->di_cvsroot = coll->co_cvsroot; + di->di_revnum = sr->sr_revnum; + di->di_revdate = sr->sr_revdate; + di->di_author = fup->author; + di->di_tag = sr->sr_tag; + di->di_state = state; + di->di_expand = fup->expand; + + error = diff_apply(up->rd, fup->orig, fup->to, coll->co_keyword, di); + if (error) { + /* XXX Bad error message */ + xasprintf(&up->errmsg, "Bad diff from server"); + return (UPDATER_ERR_MSG); + } + return (0); +} + +static int +updater_checkout(struct updater *up, struct file_update *fup, int isfixup) +{ + char md5[MD5_DIGEST_SIZE]; + struct statusrec *sr; + struct coll *coll; + struct stream *to; + char *cmd, *path, *line; + size_t size; + ssize_t nbytes; + int error, first; + + coll = fup->coll; + sr = &fup->srbuf; + path = fup->destpath; + + if (isfixup) + lprintf(1, " Fixup %s\n", fup->coname); + else + lprintf(1, " Checkout %s\n", fup->coname); + error = mkdirhier(path, coll->co_umask); + if (error) { + xasprintf(&up->errmsg, + "Cannot create directories leading to \"%s\": %s", + path, strerror(errno)); + return (UPDATER_ERR_MSG); + } + + to = stream_open_file(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (to == NULL) { + xasprintf(&up->errmsg, "%s: Cannot create: %s", + path, strerror(errno)); + return (UPDATER_ERR_MSG); + } + stream_filter_start(to, STREAM_FILTER_MD5, md5); + line = stream_getln(up->rd, &size); + first = 1; + while (line != NULL) { + if (line[size - 1] == '\n') + size--; + if ((size == 1 && *line == '.') || + (size == 2 && memcmp(line, ".+", 2) == 0)) + break; + if (size >= 2 && memcmp(line, "..", 2) == 0) { + size--; + line++; + } + if (!first) { + nbytes = stream_write(to, "\n", 1); + if (nbytes == -1) + goto bad; + } + stream_write(to, line, size); + line = stream_getln(up->rd, &size); + first = 0; + } + if (line == NULL) { + stream_close(to); + return (UPDATER_ERR_READ); + } + if (size == 1 && *line == '.') { + nbytes = stream_write(to, "\n", 1); + if (nbytes == -1) + goto bad; + } + stream_close(to); + /* Get the checksum line. */ + line = stream_getln(up->rd, NULL); + if (line == NULL) + return (UPDATER_ERR_READ); + cmd = proto_get_ascii(&line); + fup->wantmd5 = proto_get_ascii(&line); + if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0) + return (UPDATER_ERR_PROTO); + updater_checkmd5(up, fup, md5, isfixup); + fup->wantmd5 = NULL; /* So that it doesn't get freed. */ + error = updater_updatefile(up, fup, path, NULL); + if (error) + return (error); + return (0); +bad: + xasprintf(&up->errmsg, "%s: Cannot write: %s", path, strerror(errno)); + return (UPDATER_ERR_MSG); +} + +/* + * Remove all empty directories below file. + * This function will trash the path passed to it. + */ +static void +updater_prunedirs(char *base, char *file) +{ + char *cp; + int error; + + while ((cp = strrchr(file, '/')) != NULL) { + *cp = '\0'; + if (strcmp(base, file) == 0) + return; + error = rmdir(file); + if (error) + return; + } +} diff --git a/contrib/csup/updater.h b/contrib/csup/updater.h new file mode 100644 index 000000000000..9ec9ed7c0945 --- /dev/null +++ b/contrib/csup/updater.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#ifndef _UPDATER_H_ +#define _UPDATER_H + +void *updater(void *); + +#endif /* !_UPDATER_H_ */ |