summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authormux <mux@FreeBSD.org>2006-03-03 04:11:29 +0000
committermux <mux@FreeBSD.org>2006-03-03 04:11:29 +0000
commitf574897c5ec9c7e09737378d27d96ef13e9a7605 (patch)
treebe7a4aa5f2f42a13afa44c087eef02ec7d819727 /contrib
parent80106a837d1f74780854b066109eeb3cb710a62c (diff)
parenteee3ac1bf0ee019f28ea75a572423e5ac7772e84 (diff)
downloadFreeBSD-src-f574897c5ec9c7e09737378d27d96ef13e9a7605.zip
FreeBSD-src-f574897c5ec9c7e09737378d27d96ef13e9a7605.tar.gz
This commit was generated by cvs2svn to compensate for changes in r156230,
which included commits to RCS files with non-trunk default branches.
Diffstat (limited to 'contrib')
-rw-r--r--contrib/csup/GNUmakefile63
-rw-r--r--contrib/csup/Makefile62
-rw-r--r--contrib/csup/README39
-rw-r--r--contrib/csup/TODO34
-rw-r--r--contrib/csup/attrstack.c90
-rw-r--r--contrib/csup/attrstack.h40
-rw-r--r--contrib/csup/config.c561
-rw-r--r--contrib/csup/config.h124
-rw-r--r--contrib/csup/csup.1880
-rw-r--r--contrib/csup/detailer.c339
-rw-r--r--contrib/csup/detailer.h33
-rw-r--r--contrib/csup/diff.c214
-rw-r--r--contrib/csup/diff.h50
-rw-r--r--contrib/csup/fattr.c946
-rw-r--r--contrib/csup/fattr.h115
-rw-r--r--contrib/csup/fattr_bsd.h52
-rw-r--r--contrib/csup/fattr_posix.h48
-rw-r--r--contrib/csup/fixups.c198
-rw-r--r--contrib/csup/fixups.h48
-rw-r--r--contrib/csup/fnmatch.c199
-rw-r--r--contrib/csup/fnmatch.h58
-rw-r--r--contrib/csup/globtree.c393
-rw-r--r--contrib/csup/globtree.h45
-rw-r--r--contrib/csup/keyword.c502
-rw-r--r--contrib/csup/keyword.h53
-rw-r--r--contrib/csup/lister.c438
-rw-r--r--contrib/csup/lister.h33
-rw-r--r--contrib/csup/main.c315
-rw-r--r--contrib/csup/main.h29
-rw-r--r--contrib/csup/misc.c503
-rw-r--r--contrib/csup/misc.h128
-rw-r--r--contrib/csup/mux.c1201
-rw-r--r--contrib/csup/mux.h45
-rw-r--r--contrib/csup/parse.y91
-rw-r--r--contrib/csup/pathcomp.c182
-rw-r--r--contrib/csup/pathcomp.h44
-rw-r--r--contrib/csup/proto.c1004
-rw-r--r--contrib/csup/proto.h49
-rw-r--r--contrib/csup/queue.h227
-rw-r--r--contrib/csup/status.c842
-rw-r--r--contrib/csup/status.h72
-rw-r--r--contrib/csup/stream.c1080
-rw-r--r--contrib/csup/stream.h72
-rw-r--r--contrib/csup/threads.c176
-rw-r--r--contrib/csup/threads.h38
-rw-r--r--contrib/csup/token.h48
-rw-r--r--contrib/csup/token.l79
-rw-r--r--contrib/csup/updater.c1012
-rw-r--r--contrib/csup/updater.h33
49 files changed, 12927 insertions, 0 deletions
diff --git a/contrib/csup/GNUmakefile b/contrib/csup/GNUmakefile
new file mode 100644
index 0000000..441a6b8
--- /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 0000000..06f7b92
--- /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 0000000..879c481
--- /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 0000000..5f67831
--- /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 0000000..f5f56b3
--- /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 0000000..721313c
--- /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 0000000..73be865
--- /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 0000000..aa12cb6
--- /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 0000000..7a80c01
--- /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 0000000..bf2ddb7
--- /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 0000000..fe82b27
--- /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 0000000..ea53c36
--- /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 0000000..cbd9e50
--- /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 0000000..59b6607
--- /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 0000000..6015fbb
--- /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 0000000..112a824
--- /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 0000000..c4650e4
--- /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 0000000..b105a8f
--- /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 0000000..0dddc90
--- /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 0000000..a63f016
--- /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 0000000..21d2f64
--- /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 0000000..520931a
--- /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, &gt, &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 0000000..43882e3
--- /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 0000000..dab44f0
--- /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 0000000..3f152c1
--- /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 0000000..a048d25
--- /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 0000000..a0a9bbe
--- /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 0000000..33f5915
--- /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 0000000..217c7eb
--- /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 0000000..ff9403e
--- /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 0000000..47f9767
--- /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 0000000..b663b54
--- /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 0000000..ff36083
--- /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 0000000..3dcd617
--- /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 0000000..380d042
--- /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 0000000..3cea410
--- /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 0000000..04f5495
--- /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 0000000..ea9c012
--- /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 0000000..369c013
--- /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 0000000..32b6821
--- /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 0000000..86efdda
--- /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 0000000..34aa71e
--- /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 0000000..062fd34
--- /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 0000000..46a9860
--- /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 0000000..66153ce
--- /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 0000000..45d4ed7
--- /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 0000000..0def82d
--- /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 0000000..235bfca
--- /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 0000000..9ec9ed7
--- /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_ */
OpenPOWER on IntegriCloud