diff -Naur src.orig/Makefile.inc1 src/Makefile.inc1
--- src.orig/Makefile.inc1	2007-08-07 18:28:09 +0300
+++ src/Makefile.inc1	2007-08-07 22:42:09 +0300
@@ -105,7 +105,7 @@
 CLEANDIR=	cleandir
 .endif
 
-SUP?=		/usr/local/bin/cvsup
+SUP?=		/usr/bin/csup
 SUPFLAGS?=	-g -L 2 -P -
 .if defined(SUPHOST)
 SUPFLAGS+=	-h ${SUPHOST}
diff -Naur src.orig/contrib/csup/GNUmakefile src/contrib/csup/GNUmakefile
--- src.orig/contrib/csup/GNUmakefile	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/GNUmakefile	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/GNUmakefile,v 1.8 2006/03/13 22:16:27 mux Exp $
+#
+
+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 idcache.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 -Naur src.orig/contrib/csup/Makefile src/contrib/csup/Makefile
--- src.orig/contrib/csup/Makefile	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/Makefile	2007-08-07 22:54:06 +0300
@@ -0,0 +1,44 @@
+# $FreeBSD: projects/csup/Makefile,v 1.45 2006/03/07 19:10:25 mux Exp $
+
+PREFIX?=	/usr/local
+BINDIR?=	${PREFIX}/bin
+MANDIR?=	${PREFIX}/man/man
+
+UNAME!=		/usr/bin/uname -s
+
+PROG=	csup
+SRCS=	attrstack.c config.c detailer.c diff.c fattr.c fixups.c fnmatch.c \
+	globtree.c idcache.c keyword.c lister.c main.c misc.c mux.c parse.y \
+	pathcomp.c proto.c status.c stream.c threads.c token.l updater.c
+
+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 -Naur src.orig/contrib/csup/README src/contrib/csup/README
--- src.orig/contrib/csup/README	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/README	2007-08-07 22:54:06 +0300
@@ -0,0 +1,39 @@
+$FreeBSD: projects/csup/README,v 1.4 2006/02/18 11:56:27 mux Exp $
+
+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 -Naur src.orig/contrib/csup/TODO src/contrib/csup/TODO
--- src.orig/contrib/csup/TODO	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/TODO	2007-08-07 22:54:06 +0300
@@ -0,0 +1,31 @@
+$FreeBSD: projects/csup/TODO,v 1.44 2006/03/07 12:02:13 mux Exp $
+
+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.
+- 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 : -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 -Naur src.orig/contrib/csup/attrstack.c src/contrib/csup/attrstack.c
--- src.orig/contrib/csup/attrstack.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/attrstack.c	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/attrstack.h src/contrib/csup/attrstack.h
--- src.orig/contrib/csup/attrstack.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/attrstack.h	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/config.c src/contrib/csup/config.c
--- src.orig/contrib/csup/config.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/config.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,583 @@
+/*-
+ * 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: projects/csup/config.c,v 1.55 2006/03/07 11:14:50 mux Exp $
+ */
+
+#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 struct coll *ovcoll;
+static int ovmask;
+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;
+	ovcoll = override;
+	ovmask = overridemask;
+
+	/* 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) {
+ 		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 *cp, *line, *pat;
+
+	rd = stream_open_file(path, O_RDONLY);
+	if (rd == NULL)
+		return (0);
+	while ((line = stream_getln(rd, NULL)) != NULL) {
+		pat = line;
+		for (;;) {
+			/* Trim leading whitespace. */
+			pat += strspn(pat, " \t");
+			if (pat[0] == '\0')
+				break;
+			cp = strpbrk(pat, " \t");
+			if (cp != NULL)
+				*cp = '\0';
+			pattlist_add(coll->co_refusals, pat);
+			if (cp == NULL)
+				break;
+			pat = cp + 1;
+		}
+	}
+	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;
+	coll_override(cur_coll, ovcoll, ovmask);
+	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_norsync != NULL)
+		globtree_free(coll->co_norsync);
+	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;
+	int error, mask;
+
+	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:
+		error = asciitoint(value, &mask, 8);
+		free(value);
+		if (error) {
+			lprintf(-1, "Parse error in \"%s\": Invalid "
+			    "umask value\n", cfgfile);
+			exit(1);
+		}
+		coll->co_umask = mask;
+		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;
+	case PT_NORSYNC:
+		coll->co_options |= CO_NORSYNC;
+		break;
+	}
+}
+
+/* Set "coll" as being the default collection. */
+void
+coll_setdef(void)
+{
+
+	coll_free(defaults);
+	defaults = cur_coll;
+	cur_coll = coll_new(defaults);
+}
diff -Naur src.orig/contrib/csup/config.h src/contrib/csup/config.h
--- src.orig/contrib/csup/config.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/config.h	2007-08-07 22:54:06 +0300
@@ -0,0 +1,126 @@
+/*-
+ * 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: projects/csup/config.h,v 1.38 2006/03/07 12:02:13 mux Exp $
+ */
+#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;
+	struct globtree *co_norsync;
+	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 deletelim;
+	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 -Naur src.orig/contrib/csup/csup.1 src/contrib/csup/csup.1
--- src.orig/contrib/csup/csup.1	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/csup.1	2007-08-07 22:54:06 +0300
@@ -0,0 +1,899 @@
+.\" 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: projects/csup/csup.1,v 1.9 2006/03/07 12:11:38 mux Exp $
+.\"
+.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 146ksvzZ
+.Op Fl A Ar addr
+.Op Fl b Ar base
+.Op Fl c Ar collDir
+.Op Fl d Ar delLimit
+.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 d Ar delLimit
+Specifies the maximum number of files that may be deleted in a
+single update run.
+Any attempt to exceed the limit results in a fatal error.
+This can provide some protection against temporary configuration
+mistakes on the server.
+The default limit is infinity.
+.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 k
+Causes
+.Nm
+to keep the temporary copies of any incorrectly edited files, in the
+event of checksum mismatches.
+This option is for debugging, to help determine why the files were
+edited incorrectly.
+Regardless of whether this option is specified, the permanent versions
+of faulty files are replaced with correct versions obtained by
+transferring the files in their entirety.
+Such transfers are called fixups.
+.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 -Naur src.orig/contrib/csup/detailer.c src/contrib/csup/detailer.c
--- src.orig/contrib/csup/detailer.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/detailer.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/detailer.c,v 1.40 2006/02/22 21:27:01 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/detailer.h src/contrib/csup/detailer.h
--- src.orig/contrib/csup/detailer.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/detailer.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/detailer.h,v 1.6 2006/01/27 17:13:49 mux Exp $
+ */
+#ifndef _DETAILER_H_
+#define _DETAILER_H_
+
+void	*detailer(void *);
+
+#endif /* !_DETAILER_H_ */
diff -Naur src.orig/contrib/csup/diff.c src/contrib/csup/diff.c
--- src.orig/contrib/csup/diff.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/diff.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/diff.c,v 1.21 2006/02/18 10:44:45 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/diff.h src/contrib/csup/diff.h
--- src.orig/contrib/csup/diff.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/diff.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/diff.h,v 1.10 2006/02/18 10:44:45 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/fattr.c src/contrib/csup/fattr.c
--- src.orig/contrib/csup/fattr.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fattr.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,938 @@
+/*-
+ * 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: projects/csup/fattr.c,v 1.42 2006/03/13 21:56:42 mux Exp $
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fattr.h"
+#include "idcache.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;
+	}
+	/* Initialize the uid/gid lookup cache. */
+	idcache_init();
+}
+
+void
+fattr_fini(void)
+{
+	int i;
+
+	idcache_fini();
+	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;
+	char *cp, *s, *username, *groupname;
+	size_t len, vallen;
+	mode_t mode, modemask;
+	int mask, n, i;
+
+	username = NULL;
+	groupname = NULL;
+	if (support == NULL)
+		mask = fa->mask;
+	else
+		mask = fa->mask & support[fa->type];
+	mask &= ~ignore;
+	if (fa->mask & FA_OWNER) {
+		username = getuserbyid(fa->uid);
+		if (username == NULL)
+			mask &= ~FA_OWNER;
+	}
+	if (fa->mask & FA_GROUP) {
+		groupname = getgroupbyid(fa->gid);
+		if (groupname == 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(username);
+		piece->extval = 1;
+		piece->ext = username;
+		len += snprintf(piece->len, sizeof(piece->len), "%lld",
+		    (long long)vallen) + vallen + 1;
+		piece++;
+	}
+	if (mask & FA_GROUP) {
+		vallen = strlen(groupname);
+		piece->extval = 1;
+		piece->ext = groupname;
+		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)
+{
+	char *attrend, *attrstart, *end;
+	size_t len;
+	unsigned long attrlen;
+	int error;
+	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:
+		error = getuidbyname(attrstart, &fa->uid);
+		if (error)
+			fa->mask &= ~FA_OWNER;
+		break;
+	case FA_GROUP:
+		error = getgidbyname(attrstart, &fa->gid);
+		if (error)
+			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);
+	}
+
+#ifdef HAVE_FFLAGS
+	/* Clear flags. */
+	if (fa->mask & FA_FLAGS && fa->flags != 0) {
+		fa->flags = 0;
+		(void)chflags(path, fa->flags);
+	}
+#endif
+
+	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) {
+		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. */
+		if (old != NULL && 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 -Naur src.orig/contrib/csup/fattr.h src/contrib/csup/fattr.h
--- src.orig/contrib/csup/fattr.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fattr.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/fattr.h,v 1.24 2006/02/25 22:46:53 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/fattr_bsd.h src/contrib/csup/fattr_bsd.h
--- src.orig/contrib/csup/fattr_bsd.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fattr_bsd.h	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/fattr_posix.h src/contrib/csup/fattr_posix.h
--- src.orig/contrib/csup/fattr_posix.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fattr_posix.h	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/fixups.c src/contrib/csup/fixups.c
--- src.orig/contrib/csup/fixups.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fixups.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/fixups.c,v 1.1 2006/02/17 17:05:02 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/fixups.h src/contrib/csup/fixups.h
--- src.orig/contrib/csup/fixups.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fixups.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/fixups.h,v 1.1 2006/02/17 17:05:02 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/fnmatch.c src/contrib/csup/fnmatch.c
--- src.orig/contrib/csup/fnmatch.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fnmatch.c	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/fnmatch.h src/contrib/csup/fnmatch.h
--- src.orig/contrib/csup/fnmatch.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/fnmatch.h	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/globtree.c src/contrib/csup/globtree.c
--- src.orig/contrib/csup/globtree.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/globtree.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/globtree.c,v 1.5 2006/03/13 22:02:20 mux Exp $
+ */
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <regex.h>
+#include <stdlib.h>
+
+#include "fnmatch.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 -Naur src.orig/contrib/csup/globtree.h src/contrib/csup/globtree.h
--- src.orig/contrib/csup/globtree.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/globtree.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/globtree.h,v 1.3 2006/03/01 03:11:50 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/idcache.c src/contrib/csup/idcache.c
--- src.orig/contrib/csup/idcache.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/idcache.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,421 @@
+/*-
+ * 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: projects/csup/idcache.c,v 1.2 2006/03/07 19:04:34 mux Exp $
+ */
+#include <sys/types.h>
+
+#include <assert.h>
+#include <grp.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "idcache.h"
+#include "misc.h"
+
+/*
+ * Constants and data structures used to implement the thread-safe
+ * group and password file caches.  Cache sizes must be prime.
+ */
+#define	UIDTONAME_SZ		317	/* Size of uid -> user name cache */
+#define	NAMETOUID_SZ		317	/* Size of user name -> uid cache */
+#define	GIDTONAME_SZ		317	/* Size of gid -> group name cache */
+#define	NAMETOGID_SZ		317	/* Size of group name -> gid cache */
+
+/* Node structures used to cache lookups. */
+struct uidc {
+	char *name;		/* user name */
+	uid_t uid;		/* cached uid */
+	int valid;		/* is this a valid or a miss entry */
+	struct uidc *next;	/* for collisions */
+};
+
+struct gidc {
+	char *name;		/* group name */
+	gid_t gid;		/* cached gid */
+	int valid;		/* is this a valid or a miss entry */
+	struct gidc *next;	/* for collisions */
+};
+
+static struct uidc **uidtoname;	/* uid to user name cache */
+static struct gidc **gidtoname;	/* gid to group name cache */
+static struct uidc **nametouid;	/* user name to uid cache */
+static struct gidc **nametogid;	/* group name to gid cache */
+
+static pthread_mutex_t uid_mtx;
+static pthread_mutex_t gid_mtx;
+
+static void		uid_lock(void);
+static void		uid_unlock(void);
+static void		gid_lock(void);
+static void		gid_unlock(void);
+
+static uint32_t		hash(const char *);
+
+/* A 32-bit version of Peter Weinberger's (PJW) hash algorithm,
+    as used by ELF for hashing function names. */
+static uint32_t
+hash(const char *name)
+{
+	uint32_t g, h;
+
+	h = 0;
+	while(*name != '\0') {
+		h = (h << 4) + *name++;
+		if ((g = h & 0xF0000000)) {
+			h ^= g >> 24;
+			h &= 0x0FFFFFFF;
+		}
+	}
+	return (h);
+}
+
+static void
+uid_lock(void)
+{
+	int error;
+
+	error = pthread_mutex_lock(&uid_mtx);
+	assert(!error);
+}
+
+static void
+uid_unlock(void)
+{
+	int error;
+
+	error = pthread_mutex_unlock(&uid_mtx);
+	assert(!error);
+}
+
+static void
+gid_lock(void)
+{
+	int error;
+
+	error = pthread_mutex_lock(&gid_mtx);
+	assert(!error);
+}
+
+static void
+gid_unlock(void)
+{
+	int error;
+
+	error = pthread_mutex_unlock(&gid_mtx);
+	assert(!error);
+}
+
+static void
+uidc_insert(struct uidc **tbl, struct uidc *uidc, uint32_t key)
+{
+
+	uidc->next = tbl[key];
+	tbl[key] = uidc;
+}
+
+static void
+gidc_insert(struct gidc **tbl, struct gidc *gidc, uint32_t key)
+{
+
+	gidc->next = tbl[key];
+	tbl[key] = gidc;
+}
+
+/* Return the user name for this uid, or NULL if it's not found. */
+char *
+getuserbyid(uid_t uid)
+{
+	struct passwd *pw;
+	struct uidc *uidc, *uidc2;
+	uint32_t key, key2;
+
+	key = uid % UIDTONAME_SZ;
+	uid_lock();
+	uidc = uidtoname[key];
+	while (uidc != NULL) {
+		if (uidc->uid == uid)
+			break;
+		uidc = uidc->next;
+	}
+
+	if (uidc == NULL) {
+		/* We didn't find this uid, look it up and add it. */
+		uidc = xmalloc(sizeof(struct uidc));
+		uidc->uid = uid;
+		pw = getpwuid(uid);
+		if (pw != NULL) {
+			/* This uid is in the password file. */
+			uidc->name = xstrdup(pw->pw_name);
+			uidc->valid = 1;
+			/* Also add it to the name -> gid table. */
+			uidc2 = xmalloc(sizeof(struct uidc));
+			uidc2->uid = uid;
+			uidc2->name = uidc->name; /* We reuse the pointer. */
+			uidc2->valid = 1;
+			key2 = hash(uidc->name) % NAMETOUID_SZ;
+			uidc_insert(nametouid, uidc2, key2);
+		} else {
+			/* Add a miss entry for this uid. */
+			uidc->name = NULL;
+			uidc->valid = 0;
+		}
+		uidc_insert(uidtoname, uidc, key);
+	}
+	/* It is safe to unlock here since the cache structure
+	   is not going to get freed or changed. */
+	uid_unlock();
+	return (uidc->name);
+}
+
+/* Return the group name for this gid, or NULL if it's not found. */
+char *
+getgroupbyid(gid_t gid)
+{
+	struct group *gr;
+	struct gidc *gidc, *gidc2;
+	uint32_t key, key2;
+
+	key = gid % GIDTONAME_SZ;
+	gid_lock();
+	gidc = gidtoname[key];
+	while (gidc != NULL) {
+		if (gidc->gid == gid)
+			break;
+		gidc = gidc->next;
+	}
+
+	if (gidc == NULL) {
+		/* We didn't find this gid, look it up and add it. */
+		gidc = xmalloc(sizeof(struct gidc));
+		gidc->gid = gid;
+		gr = getgrgid(gid);
+		if (gr != NULL) {
+			/* This gid is in the group file. */
+			gidc->name = xstrdup(gr->gr_name);
+			gidc->valid = 1;
+			/* Also add it to the name -> gid table. */
+			gidc2 = xmalloc(sizeof(struct gidc));
+			gidc2->gid = gid;
+			gidc2->name = gidc->name; /* We reuse the pointer. */
+			gidc2->valid = 1;
+			key2 = hash(gidc->name) % NAMETOGID_SZ;
+			gidc_insert(nametogid, gidc2, key2);
+		} else {
+			/* Add a miss entry for this gid. */
+			gidc->name = NULL;
+			gidc->valid = 0;
+		}
+		gidc_insert(gidtoname, gidc, key);
+	}
+	/* It is safe to unlock here since the cache structure
+	   is not going to get freed or changed. */
+	gid_unlock();
+	return (gidc->name);
+}
+
+/* Finds the uid for this user name.  If it's found, the gid is stored
+   in *uid and 0 is returned.  Otherwise, -1 is returned. */
+int
+getuidbyname(const char *name, uid_t *uid)
+{
+	struct passwd *pw;
+	struct uidc *uidc, *uidc2;
+	uint32_t key, key2;
+
+	uid_lock();
+	key = hash(name) % NAMETOUID_SZ;
+	uidc = nametouid[key];
+	while (uidc != NULL) {
+		if (strcmp(uidc->name, name) == 0)
+			break;
+		uidc = uidc->next;
+	}
+
+	if (uidc == NULL) {
+		uidc = xmalloc(sizeof(struct uidc));
+		uidc->name = xstrdup(name);
+		pw = getpwnam(name);
+		if (pw != NULL) {
+			/* This user name is in the password file. */
+			uidc->valid = 1;
+			uidc->uid = pw->pw_uid;
+			/* Also add it to the uid -> name table. */
+			uidc2 = xmalloc(sizeof(struct uidc));
+			uidc2->name = uidc->name; /* We reuse the pointer. */
+			uidc2->uid = uidc->uid;
+			uidc2->valid = 1;
+			key2 = uidc2->uid % UIDTONAME_SZ;
+			uidc_insert(uidtoname, uidc2, key2);
+		} else {
+			/* Add a miss entry for this user name. */
+			uidc->valid = 0;
+			uidc->uid = (uid_t)-1; /* Should not be accessed. */
+		}
+		uidc_insert(nametouid, uidc, key);
+	}
+	/* It is safe to unlock here since the cache structure
+	   is not going to get freed or changed. */
+	uid_unlock();
+	if (!uidc->valid)
+		return (-1);
+	*uid = uidc->uid;
+	return (0);
+}
+
+/* Finds the gid for this group name.  If it's found, the gid is stored
+   in *gid and 0 is returned.  Otherwise, -1 is returned. */
+int
+getgidbyname(const char *name, gid_t *gid)
+{
+	struct group *gr;
+	struct gidc *gidc, *gidc2;
+	uint32_t key, key2;
+
+	gid_lock();
+	key = hash(name) % NAMETOGID_SZ;
+	gidc = nametogid[key];
+	while (gidc != NULL) {
+		if (strcmp(gidc->name, name) == 0)
+			break;
+		gidc = gidc->next;
+	}
+
+	if (gidc == NULL) {
+		gidc = xmalloc(sizeof(struct gidc));
+		gidc->name = xstrdup(name);
+		gr = getgrnam(name);
+		if (gr != NULL) {
+			/* This group name is in the group file. */
+			gidc->gid = gr->gr_gid;
+			gidc->valid = 1;
+			/* Also add it to the gid -> name table. */
+			gidc2 = xmalloc(sizeof(struct gidc));
+			gidc2->name = gidc->name; /* We reuse the pointer. */
+			gidc2->gid = gidc->gid;
+			gidc2->valid = 1;
+			key2 = gidc2->gid % GIDTONAME_SZ;
+			gidc_insert(gidtoname, gidc2, key2);
+		} else {
+			/* Add a miss entry for this group name. */
+			gidc->gid = (gid_t)-1; /* Should not be accessed. */
+			gidc->valid = 0;
+		}
+		gidc_insert(nametogid, gidc, key);
+	}
+	/* It is safe to unlock here since the cache structure
+	   is not going to get freed or changed. */
+	gid_unlock();
+	if (!gidc->valid)
+		return (-1);
+	*gid = gidc->gid;
+	return (0);
+}
+
+/* Initialize the cache structures. */
+void
+idcache_init(void)
+{
+
+	pthread_mutex_init(&uid_mtx, NULL);
+	pthread_mutex_init(&gid_mtx, NULL);
+	uidtoname = xmalloc(UIDTONAME_SZ * sizeof(struct uidc *));
+	gidtoname = xmalloc(GIDTONAME_SZ * sizeof(struct gidc *));
+	nametouid = xmalloc(NAMETOUID_SZ * sizeof(struct uidc *));
+	nametogid = xmalloc(NAMETOGID_SZ * sizeof(struct gidc *));
+	memset(uidtoname, 0, UIDTONAME_SZ * sizeof(struct uidc *));
+	memset(gidtoname, 0, GIDTONAME_SZ * sizeof(struct gidc *));
+	memset(nametouid, 0, NAMETOUID_SZ * sizeof(struct uidc *));
+	memset(nametogid, 0, NAMETOGID_SZ * sizeof(struct gidc *));
+}
+
+/* Cleanup the cache structures. */
+void
+idcache_fini(void)
+{
+	struct uidc *uidc, *uidc2;
+	struct gidc *gidc, *gidc2;
+	size_t i;
+
+	for (i = 0; i < UIDTONAME_SZ; i++) {
+		uidc = uidtoname[i];
+		while (uidc != NULL) {
+			if (uidc->name != NULL) {
+				assert(uidc->valid);
+				free(uidc->name);
+			}
+			uidc2 = uidc->next;
+			free(uidc);
+			uidc = uidc2;
+		}
+	}
+	free(uidtoname);
+	for (i = 0; i < NAMETOUID_SZ; i++) {
+		uidc = nametouid[i];
+		while (uidc != NULL) {
+			assert(uidc->name != NULL);
+			/* If it's a valid entry, it has been added to both the
+			   uidtoname and nametouid tables, and the name pointer
+			   has been reused for both entries.  Thus, the name
+			   pointer has already been freed in the loop above. */
+			if (!uidc->valid)
+				free(uidc->name);
+			uidc2 = uidc->next;
+			free(uidc);
+			uidc = uidc2;
+		}
+	}
+	free(nametouid);
+	for (i = 0; i < GIDTONAME_SZ; i++) {
+		gidc = gidtoname[i];
+		while (gidc != NULL) {
+			if (gidc->name != NULL) {
+				assert(gidc->valid);
+				free(gidc->name);
+			}
+			gidc2 = gidc->next;
+			free(gidc);
+			gidc = gidc2;
+		}
+	}
+	free(gidtoname);
+	for (i = 0; i < NAMETOGID_SZ; i++) {
+		gidc = nametogid[i];
+		while (gidc != NULL) {
+			assert(gidc->name != NULL);
+			/* See above comment. */
+			if (!gidc->valid)
+				free(gidc->name);
+			gidc2 = gidc->next;
+			free(gidc);
+			gidc = gidc2;
+		}
+	}
+	free(nametogid);
+	pthread_mutex_destroy(&uid_mtx);
+	pthread_mutex_destroy(&gid_mtx);
+}
diff -Naur src.orig/contrib/csup/idcache.h src/contrib/csup/idcache.h
--- src.orig/contrib/csup/idcache.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/idcache.h	2007-08-07 22:54:06 +0300
@@ -0,0 +1,41 @@
+/*-
+ * 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: projects/csup/idcache.h,v 1.1 2006/03/06 00:32:53 mux Exp $
+ */
+#ifndef _IDCACHE_H_
+#define _IDCACHE_H_
+
+#include <sys/types.h>
+
+void	 idcache_init(void);
+void	 idcache_fini(void);
+
+char	*getuserbyid(uid_t);
+char	*getgroupbyid(gid_t);
+int	 getuidbyname(const char *, uid_t *);
+int	 getgidbyname(const char *, gid_t *);
+
+#endif /* !_IDCACHE_H_ */
diff -Naur src.orig/contrib/csup/keyword.c src/contrib/csup/keyword.c
--- src.orig/contrib/csup/keyword.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/keyword.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/keyword.c,v 1.32 2006/03/02 17:40:04 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/keyword.h src/contrib/csup/keyword.h
--- src.orig/contrib/csup/keyword.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/keyword.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/keyword.h,v 1.11 2006/02/18 10:41:08 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/lister.c src/contrib/csup/lister.c
--- src.orig/contrib/csup/lister.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/lister.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,439 @@
+/*-
+ * 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: projects/csup/lister.c,v 1.28 2006/03/09 16:34:28 mux Exp $
+ */
+
+#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;
+	size_t i;
+	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:
+	for (i = 0; i < attrstack_size(as); i++) {
+		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 -Naur src.orig/contrib/csup/lister.h src/contrib/csup/lister.h
--- src.orig/contrib/csup/lister.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/lister.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/lister.h,v 1.6 2006/01/27 17:13:49 mux Exp $
+ */
+#ifndef _LISTER_H_
+#define _LISTER_H_
+
+void	*lister(void *);
+
+#endif /* !_LISTER_H_ */
diff -Naur src.orig/contrib/csup/main.c src/contrib/csup/main.c
--- src.orig/contrib/csup/main.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/main.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/main.c,v 1.39 2006/03/07 12:02:13 mux Exp $
+ */
+
+#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, "-d delLimit",
+	    "Allow at most \"delLimit\" file deletions (default unlimited)");
+	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, "-k",
+	    "Keep bad temporary files when fixups are required");
+	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;
+	int family, error, lockfd, lflag, overridemask;
+	int c, i, deletelim, port, retries, status;
+	time_t nexttry;
+
+	error = 0;
+	family = PF_UNSPEC;
+	deletelim = -1;
+	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:d:gh:i:kl: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 'd':
+			error = asciitoint(optarg, &deletelim, 0);
+			if (error || deletelim < 0) {
+				lprintf(-1, "Invalid deletion limit\n");
+				usage(argv0);
+				return (1);
+			}
+			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 'k':
+			override->co_options |= CO_KEEPBADFILES;
+			overridemask |= CO_KEEPBADFILES;
+			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':
+			error = asciitoint(optarg, &verbose, 0);
+			if (error) {
+				lprintf(-1, "Invalid verbosity\n");
+				usage(argv0);
+				return (1);
+			}
+			break;
+		case 'p':
+			/* Use specified server port. */
+			error = asciitoint(optarg, &port, 0);
+			if (error) {
+				lprintf(-1, "Invalid server port\n");
+				usage(argv0);
+				return (1);
+			}
+			if (port <= 0 || port >= 65536) {
+				lprintf(-1, "Invalid port %d\n", port);
+				return (1);
+			}
+			if (port < 1024) {
+				lprintf(-1, "Reserved port %d not permitted\n",
+				    port);
+				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':
+			error = asciitoint(optarg, &retries, 0);
+			if (error || 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 (config_checkcolls(config) == 0) {
+		lprintf(-1, "No collections selected\n");
+		return (1);
+	}
+
+	if (laddr != NULL) {
+		config->laddr = laddr;
+		config->laddrlen = laddrlen;
+	}
+	config->deletelim = deletelim;
+	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 -Naur src.orig/contrib/csup/main.h src/contrib/csup/main.h
--- src.orig/contrib/csup/main.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/main.h	2007-08-07 22:54:06 +0300
@@ -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 -Naur src.orig/contrib/csup/misc.c src/contrib/csup/misc.c
--- src.orig/contrib/csup/misc.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/misc.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,522 @@
+/*-
+ * 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: projects/csup/misc.c,v 1.30 2006/03/07 01:43:01 mux Exp $
+ */
+
+#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 <limits.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
+asciitoint(const char *s, int *val, int base)
+{
+	char *end;
+	long longval;
+
+	errno = 0;
+	longval = strtol(s, &end, base);
+	if (errno || *end != '\0')
+		return (-1);
+	if (longval > INT_MAX || longval < INT_MIN) {
+		errno = ERANGE;
+		return (-1);
+	}
+	*val = longval;
+	return (0);
+}
+
+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 -Naur src.orig/contrib/csup/misc.h src/contrib/csup/misc.h
--- src.orig/contrib/csup/misc.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/misc.h	2007-08-07 22:54:06 +0300
@@ -0,0 +1,130 @@
+/*-
+ * 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: projects/csup/misc.h,v 1.27 2006/03/07 01:43:01 mux Exp $
+ */
+#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	 asciitoint(const char *, int *, int);
+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 -Naur src.orig/contrib/csup/mux.c src/contrib/csup/mux.c
--- src.orig/contrib/csup/mux.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/mux.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/mux.c,v 1.71 2006/02/24 23:01:32 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/mux.h src/contrib/csup/mux.h
--- src.orig/contrib/csup/mux.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/mux.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/mux.h,v 1.25 2006/02/22 21:27:01 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/parse.y src/contrib/csup/parse.y
--- src.orig/contrib/csup/parse.y	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/parse.y	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/parse.y,v 1.15 2006/03/01 02:29:56 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/pathcomp.c src/contrib/csup/pathcomp.c
--- src.orig/contrib/csup/pathcomp.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/pathcomp.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/pathcomp.c,v 1.5 2006/02/26 04:31:13 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/pathcomp.h src/contrib/csup/pathcomp.h
--- src.orig/contrib/csup/pathcomp.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/pathcomp.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/pathcomp.h,v 1.2 2006/02/26 04:31:13 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/proto.c src/contrib/csup/proto.c
--- src.orig/contrib/csup/proto.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/proto.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,1029 @@
+/*-
+ * 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: projects/csup/proto.c,v 1.91 2006/03/17 20:19:43 mux Exp $
+ */
+
+#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 <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 *, sigset_t *set,
+			     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 (cmd == NULL || 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 hostbuf[MAXHOSTNAMELEN];
+	char *line, *login, *host, *cmd, *realm, *challenge, *msg;
+	int error;
+
+	s = config->server;
+	error = gethostname(hostbuf, sizeof(hostbuf));
+	hostbuf[sizeof(hostbuf) - 1] = '\0';
+	if (error)
+		host = NULL;
+	else
+		host = hostbuf;
+	login = getlogin();
+	proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
+	    host != NULL ? 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 (cmd == NULL || line == NULL)
+		goto bad;
+	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;
+		coll->co_norsync = globtree_false();
+		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;
+			} else if (strcmp(cmd, "NORS") == 0) {
+				pat = proto_get_ascii(&line);
+				if (pat == NULL || line != NULL)
+					goto bad;
+				coll->co_norsync = globtree_or(coll->co_norsync,
+				    globtree_match(pat, FNM_PATHNAME));
+			} else if (strcmp(cmd, "RNORS") == 0) {
+				pat = proto_get_ascii(&line);
+				if (pat == NULL || line != NULL)
+					goto bad;
+				coll->co_norsync = globtree_or(coll->co_norsync,
+				    globtree_match(pat, FNM_PATHNAME |
+				    FNM_LEADING_DIR));
+			} else
+				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;
+	sigset_t set;
+	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);
+
+	/*
+	 * Call pthread_sigmask() prior to creating any thread, or the
+	 * sigwait() of the killer thread won't work properly with some
+	 * libpthread implementations.
+	 */
+	sigemptyset(&set);
+	sigaddset(&set, SIGINT);
+	sigaddset(&set, SIGHUP);
+	sigaddset(&set, SIGTERM);
+	sigaddset(&set, SIGPIPE);
+	pthread_sigmask(SIG_BLOCK, &set, NULL);
+
+	/* 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, &set, 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);
+	pthread_sigmask(SIG_UNBLOCK, &set, NULL);
+	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;
+	int error;
+
+	cp = proto_get_ascii(s);
+	if (cp == NULL)
+		return (-1);
+	error = asciitoint(cp, val, base);
+	return (error);
+}
+
+/*
+ * 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, sigset_t *set, struct mux *m)
+{
+	int error;
+
+	k->mux = m;
+	k->killedby = -1;
+	k->sigset = set;
+	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);
+}
diff -Naur src.orig/contrib/csup/proto.h src/contrib/csup/proto.h
--- src.orig/contrib/csup/proto.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/proto.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/proto.h,v 1.14 2006/03/01 04:33:17 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/queue.h src/contrib/csup/queue.h
--- src.orig/contrib/csup/queue.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/queue.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/queue.h,v 1.5 2006/03/03 18:41:59 mux Exp $
+ *
+ * $FreeBSD: projects/csup/queue.h,v 1.5 2006/03/03 18:41:59 mux Exp $
+ */
+
+#ifndef _QUEUE_H_
+#define	_QUEUE_H_
+
+#undef __offsetof
+#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 *)(void *)				\
+		((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 -Naur src.orig/contrib/csup/status.c src/contrib/csup/status.c
--- src.orig/contrib/csup/status.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/status.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/status.c,v 1.15 2006/02/26 20:34:43 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/status.h src/contrib/csup/status.h
--- src.orig/contrib/csup/status.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/status.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/status.h,v 1.4 2006/02/26 04:31:13 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/stream.c src/contrib/csup/stream.c
--- src.orig/contrib/csup/stream.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/stream.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/stream.c,v 1.53 2006/02/11 01:17:04 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/stream.h src/contrib/csup/stream.h
--- src.orig/contrib/csup/stream.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/stream.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/stream.h,v 1.21 2006/02/10 18:18:47 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/threads.c src/contrib/csup/threads.c
--- src.orig/contrib/csup/threads.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/threads.c	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/threads.c,v 1.8 2006/02/22 21:27:01 mux Exp $
+ */
+
+#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 -Naur src.orig/contrib/csup/threads.h src/contrib/csup/threads.h
--- src.orig/contrib/csup/threads.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/threads.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/threads.h,v 1.6 2006/02/22 21:27:01 mux Exp $
+ */
+#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 -Naur src.orig/contrib/csup/token.h src/contrib/csup/token.h
--- src.orig/contrib/csup/token.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/token.h	2007-08-07 22:54:06 +0300
@@ -0,0 +1,49 @@
+/*-
+ * 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: projects/csup/token.h,v 1.10 2006/03/07 02:13:55 mux Exp $
+ */
+#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
+#define PT_NORSYNC		11
+
+#endif /* !_TOKEN_H_ */
diff -Naur src.orig/contrib/csup/token.l src/contrib/csup/token.l
--- src.orig/contrib/csup/token.l	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/token.l	2007-08-07 22:54:06 +0300
@@ -0,0 +1,80 @@
+%{
+/*-
+ * 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: projects/csup/token.l,v 1.18 2006/03/07 02:13:55 mux Exp $
+ */
+
+#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; }
+norsync			{ yylval.i = PT_NORSYNC; 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 -Naur src.orig/contrib/csup/updater.c src/contrib/csup/updater.c
--- src.orig/contrib/csup/updater.c	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/updater.c	2007-08-07 22:54:06 +0300
@@ -0,0 +1,1011 @@
+/*-
+ * 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: projects/csup/updater.c,v 1.78 2006/03/07 12:02:13 mux Exp $
+ */
+
+#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. */
+#define	UPDATER_ERR_DELETELIM	(-4)	/* File deletion limit exceeded. */
+
+/* Everything needed to update a file. */
+struct file_update {
+	struct statusrec srbuf;
+	char *destpath;
+	char *temppath;
+	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;
+	int deletecount;
+};
+
+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 int	 updater_delete(struct updater *, struct file_update *);
+static void	 updater_deletefile(const char *);
+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 int	 updater_updatefile(struct updater *, struct file_update *fup,
+		     const char *, int);
+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;
+	}
+	if (fup->temppath != NULL) {
+		free(fup->temppath);
+		fup->temppath = 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;
+	up->deletecount = 0;
+
+	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;
+	case UPDATER_ERR_DELETELIM:
+		xasprintf(&args->errmsg, "Updater failed: "
+		    "File deletion limit exceeded");
+		args->status = STATUS_FAILURE;
+		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);
+		if (st == NULL) {
+			up->errmsg = errmsg;
+			return (UPDATER_ERR_MSG);
+		}
+		fup = fup_new(coll, st);
+		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) {
+				error = updater_delete(up, fup);
+				if (error)
+					return (error);
+			}
+
+			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);
+			fup->temppath = tempname(fup->destpath);
+			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);
+			error = updater_delete(up, fup);
+			if (error)
+				return (error);
+			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);
+			fup->temppath = tempname(fup->destpath);
+			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);
+			error = updater_delete(up, fup);
+			if (error)
+				return (error);
+			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 int
+updater_delete(struct updater *up, struct file_update *fup)
+{
+	struct config *config;
+	struct coll *coll;
+
+	config = up->config;
+	coll = fup->coll;
+	if (coll->co_options & CO_DELETE) {
+		lprintf(1, " Delete %s\n", fup->coname);
+		if (config->deletelim >= 0 &&
+		    up->deletecount >= config->deletelim)
+			return (UPDATER_ERR_DELETELIM);
+		up->deletecount++;
+		updater_deletefile(fup->destpath);
+		if (coll->co_options & CO_CHECKOUTMODE)
+			updater_prunedirs(coll->co_prefix, fup->destpath);
+	} else {
+		lprintf(1," NoDelete %s\n", fup->coname);
+	}
+	return (0);
+}
+
+static void
+updater_deletefile(const char *path)
+{
+	int error;
+
+	error = fattr_delete(path);
+	if (error && errno != ENOENT) {
+		lprintf(-1, "Cannot delete \"%s\": %s\n",
+		    path, strerror(errno));
+	}
+}
+
+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);
+}
+
+static int
+updater_updatefile(struct updater *up, struct file_update *fup,
+    const char *md5, int isfixup)
+{
+	struct coll *coll;
+	struct status *st;
+	struct statusrec *sr;
+	struct fattr *fileattr;
+	int error, rv;
+
+	coll = fup->coll;
+	sr = &fup->srbuf;
+	st = fup->st;
+
+	if (strcmp(fup->wantmd5, md5) != 0) {
+		if (isfixup) {
+			lprintf(-1, "%s: Checksum mismatch -- "
+			    "file not updated\n", fup->destpath);
+		} else {
+			lprintf(-1, "%s: Checksum mismatch -- "
+			    "will transfer entire file\n", fup->destpath);
+			fixups_put(up->config->fixups, fup->coll, sr->sr_file);
+		}
+		if (coll->co_options & CO_KEEPBADFILES)
+			lprintf(-1, "Bad version saved in %s\n", fup->temppath);
+		else
+			updater_deletefile(fup->temppath);
+		return (0);
+	}
+
+	fattr_umask(sr->sr_clientattr, coll->co_umask);
+	rv = fattr_install(sr->sr_clientattr, fup->destpath, fup->temppath);
+	if (rv == -1) {
+		xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s",
+		    fup->temppath, fup->destpath, 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(fup->destpath, FATTR_NOFOLLOW);
+	if (fileattr == NULL) {
+		xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", fup->destpath,
+		    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;
+	int error;
+
+	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)
+			return (UPDATER_ERR_PROTO);
+		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)
+			return (UPDATER_ERR_PROTO);
+		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));
+				return (UPDATER_ERR_MSG);
+			}
+		} else {
+			/* Subsequent patches. */
+			stream_close(fup->orig);
+			fup->orig = fup->to;
+			stream_rewind(fup->orig);
+			unlink(fup->temppath);
+			free(fup->temppath);
+			fup->temppath = tempname(path);
+		}
+		fup->to = stream_open_file(fup->temppath,
+		    O_RDWR | O_CREAT | O_TRUNC, 0600);
+		if (fup->to == NULL) {
+			xasprintf(&up->errmsg, "%s: Cannot open: %s",
+			    fup->temppath, strerror(errno));
+			return (UPDATER_ERR_MSG);
+		}
+		lprintf(2, "  Add delta %s %s %s\n", sr->sr_revnum,
+		    sr->sr_revdate, fup->author);
+		error = updater_diff_batch(up, fup);
+		if (error)
+			return (error);
+	}
+	if (line == NULL)
+		return (UPDATER_ERR_READ);
+
+	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;
+
+	if (MD5_File(fup->temppath, md5) == -1) {
+		xasprintf(&up->errmsg,
+		    "Cannot calculate checksum for \"%s\": %s",
+		    path, strerror(errno));
+		return (UPDATER_ERR_MSG);
+	}
+	error = updater_updatefile(up, fup, md5, 0);
+	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(fup->temppath,
+	    O_WRONLY | O_CREAT | O_TRUNC, 0600);
+	if (to == NULL) {
+		xasprintf(&up->errmsg, "%s: Cannot create: %s",
+		    fup->temppath, 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);
+	error = updater_updatefile(up, fup, md5, isfixup);
+	fup->wantmd5 = NULL;	/* So that it doesn't get freed. */
+	if (error)
+		return (error);
+	return (0);
+bad:
+	xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
+	    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 -Naur src.orig/contrib/csup/updater.h src/contrib/csup/updater.h
--- src.orig/contrib/csup/updater.h	1970-01-01 03:00:00 +0300
+++ src/contrib/csup/updater.h	2007-08-07 22:54:06 +0300
@@ -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: projects/csup/updater.h,v 1.6 2006/01/27 17:13:50 mux Exp $
+ */
+#ifndef _UPDATER_H_
+#define _UPDATER_H
+
+void	*updater(void *);
+
+#endif /* !_UPDATER_H_ */
diff -Naur src.orig/usr.bin/csup/Makefile src/usr.bin/csup/Makefile
--- src.orig/usr.bin/csup/Makefile	1970-01-01 03:00:00 +0300
+++ src/usr.bin/csup/Makefile	2007-08-07 18:33:59 +0300
@@ -0,0 +1,36 @@
+# $FreeBSD: src/usr.bin/csup/Makefile,v 1.3.2.1 2006/05/15 13:47:39 mux Exp $
+
+.PATH: ${.CURDIR}/../../contrib/csup
+
+PROG=	csup
+SRCS=	attrstack.c \
+	config.c \
+	detailer.c \
+	diff.c \
+	fattr.c \
+	fixups.c \
+	fnmatch.c \
+	globtree.c \
+	idcache.c \
+	keyword.c \
+	lister.c \
+	main.c \
+	misc.c \
+	mux.c \
+	parse.y \
+	pathcomp.c \
+	proto.c \
+	status.c \
+	stream.c \
+	threads.c \
+	token.l \
+	updater.c
+
+CFLAGS+= -I. -I${.CURDIR}/../../contrib/csup
+CFLAGS+= -DHAVE_FFLAGS -DNDEBUG
+WARNS?=	6
+
+DPADD=	${LIBCRYPTO} ${LIBZ} ${LIBPTHREAD}
+LDADD=	-lcrypto -lz -lpthread
+
+.include <bsd.prog.mk>
