Index: Makefile
===================================================================
RCS file: /cvs/csup/csup/Makefile,v
retrieving revision 1.31
diff -u -p -r1.31 Makefile
--- Makefile	22 May 2005 22:43:07 -0000	1.31
+++ Makefile	29 May 2005 15:04:11 -0000
@@ -5,7 +5,8 @@ BINDIR?=	/usr/local/bin
 UNAME!=		/usr/bin/uname -s
 
 PROG=	csup
-SRCS=	config.c config.h \
+SRCS=	attrstack.c attrstack.h \
+	config.c config.h \
 	detailer.c detailer.h \
 	diff.c diff.h \
 	fattr.c fattr.h fattr_os.h \
@@ -16,6 +17,7 @@ SRCS=	config.c config.h \
 	mux.c mux.h \
 	parse.h parse.y \
 	proto.c proto.h \
+	status.c status.h \
 	stream.c stream.h \
 	threads.c threads.h \
 	token.h token.l \
Index: attrstack.c
===================================================================
RCS file: attrstack.c
diff -N attrstack.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ attrstack.c	21 May 2005 20:22:30 -0000
@@ -0,0 +1,97 @@
+/*-
+ * Copyright (c) 2003-2005, 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 <sys/types.h>
+
+#include <assert.h>
+#include <err.h>
+#include <stdlib.h>
+
+#include "attrstack.h"
+#include "fattr.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 = malloc(sizeof(struct attrstack));
+	if (as == NULL)
+		err(1, "malloc");
+	as->stack = malloc(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)
+{
+	struct fattr **new;
+
+	if (as->cur >= as->size) {
+		as->size *= 2;
+		new = realloc(as->stack, sizeof(struct fattr *) * as->size);
+		if (new == NULL)
+			err(1, "realloc");
+		as->stack = new;
+	}
+	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);
+}
Index: attrstack.h
===================================================================
RCS file: attrstack.h
diff -N attrstack.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ attrstack.h	22 May 2005 17:38:43 -0000
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2003-2005, 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_ */
Index: config.c
===================================================================
RCS file: /cvs/csup/csup/config.c,v
retrieving revision 1.31
diff -u -p -r1.31 config.c
--- config.c	9 Jul 2005 18:54:17 -0000	1.31
+++ config.c	3 Dec 2005 03:50:49 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -36,6 +36,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "fattr.h"
 #include "config.h"
 #include "keyword.h"
 #include "misc.h"
@@ -58,7 +59,7 @@ static const char *cfgfile;
  */
 struct config *
 config_init(const char *file, char *host, char *base, char *colldir,
-    in_port_t port, int compress)
+    in_port_t port, int compress, int truststatus)
 {
 	struct coll *cur;
 	mode_t mask;
@@ -69,10 +70,6 @@ config_init(const char *file, char *host
 		err(1, "malloc");
 	config->host = NULL;
 	STAILQ_INIT(&config->colls);
-	if (colldir != NULL)
-		config->colldir = colldir;
-	else
-		config->colldir = "sup";
 
 	/* Set default collection options. */
 	defaults = coll_alloc();
@@ -130,6 +127,12 @@ config_init(const char *file, char *host
 			cur->co_options |= CO_COMPRESS;
 		else if (compress < 0)
 			cur->co_options &= ~CO_COMPRESS;
+		if (truststatus)
+			cur->co_options |= CO_TRUSTSTATUSFILE;
+		if (colldir)
+			cur->co_colldir = colldir;
+		else
+			cur->co_colldir = "sup";
 	}
 
 	/* Override host if necessary. */
@@ -204,6 +207,24 @@ coll_new(void)
 	return (new);
 }
 
+char *
+coll_statuspath(struct coll *coll)
+{
+	char *path;
+
+	if (coll->co_options & CO_USERELSUFFIX) {
+		asprintf(&path, "%s/%s/%s/checkouts.%s:%s", coll->co_base,
+		    coll->co_colldir, coll->co_name, coll->co_release,
+		    coll->co_tag);
+	} else {
+		asprintf(&path, "%s/%s/%s/checkouts", coll->co_base,
+		    coll->co_colldir, coll->co_name);
+	}
+	if (path == NULL)
+		err(1, "asprintf");
+	return (path);
+}
+
 void
 coll_add(char *name)
 {
@@ -217,13 +238,22 @@ void
 coll_free(struct coll *coll)
 {
 
-	free(coll->co_base);
-	free(coll->co_date);
-	free(coll->co_prefix);
-	free(coll->co_release);
-	free(coll->co_tag);
-	free(coll->co_cvsroot);
-	free(coll->co_name);
+	if (coll == NULL)
+		return;
+	if (coll->co_base != NULL)
+		free(coll->co_base);
+	if (coll->co_base != NULL)
+		free(coll->co_date);
+	if (coll->co_base != NULL)
+		free(coll->co_prefix);
+	if (coll->co_base != NULL)
+		free(coll->co_release);
+	if (coll->co_base != NULL)
+		free(coll->co_tag);
+	if (coll->co_base != NULL)
+		free(coll->co_cvsroot);
+	if (coll->co_base != NULL)
+		free(coll->co_name);
 	keyword_free(coll->co_keyword);
 	free(coll);
 }
@@ -236,23 +266,28 @@ coll_setopt(int opt, char *value)
 	coll = cur_coll;
 	switch (opt) {
 	case PT_BASE:
-		free(coll->co_base);
+		if (coll->co_base != NULL)
+			free(coll->co_base);
 		coll->co_base = value;
 		break;
 	case PT_DATE:
-		free(coll->co_date);
+		if (coll->co_base != NULL)
+			free(coll->co_date);
 		coll->co_date = value;
 		break;
 	case PT_PREFIX:
-		free(coll->co_prefix);
+		if (coll->co_base != NULL)
+			free(coll->co_prefix);
 		coll->co_prefix = value;
 		break;
 	case PT_RELEASE:
-		free(coll->co_release);
+		if (coll->co_base != NULL)
+			free(coll->co_release);
 		coll->co_release = value;
 		break;
 	case PT_TAG:
-		free(coll->co_tag);
+		if (coll->co_base != NULL)
+			free(coll->co_tag);
 		coll->co_tag = value;
 		break;
 	case PT_UMASK:
Index: config.h
===================================================================
RCS file: /cvs/csup/csup/config.h,v
retrieving revision 1.22
diff -u -p -r1.22 config.h
--- config.h	22 May 2005 17:38:00 -0000	1.22
+++ config.h	12 Dec 2005 21:17:24 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,9 @@
 #include "fattr.h"
 #include "queue.h"
 
+#include <stdint.h>
+#include <time.h>
+
 /*
  * Collection options.
  */
@@ -71,6 +74,8 @@ struct coll {
 	char *co_release;
 	char *co_tag;
 	char *co_cvsroot;
+	const char *co_colldir;
+	time_t co_scantime;		/* Set by the detailer thread. */
 	int co_options;
 	mode_t co_umask;
 	struct keyword *co_keyword;
@@ -79,23 +84,23 @@ struct coll {
 
 struct config {
 	STAILQ_HEAD(, coll) colls;
-	const char *colldir;
 	char *host;
-	in_port_t port;
+	uint16_t port;
 	int socket;
 	int id0, id1;
 	struct stream *server;
 	fattr_support_t fasupport;
 };
 
-struct config	*config_init(const char *, char *, char *, char *, in_port_t,
-		     int);
+struct config	*config_init(const char *, char *, char *, char *, uint16_t,
+		     int, int);
 int		 config_sethost(char *);
 
 struct coll	*coll_new(void);
-void		coll_add(char *);
-void		coll_free(struct coll *);
-void		coll_setdef(void);
-void		coll_setopt(int, char *);
+char		*coll_statuspath(struct coll *);
+void		 coll_add(char *);
+void		 coll_free(struct coll *);
+void		 coll_setdef(void);
+void		 coll_setopt(int, char *);
 
 #endif /* !_CONFIG_H_ */
Index: detailer.c
===================================================================
RCS file: /cvs/csup/csup/detailer.c,v
retrieving revision 1.30
diff -u -p -r1.30 detailer.c
--- detailer.c	9 Jul 2005 18:37:14 -0000	1.30
+++ detailer.c	12 Dec 2005 21:19:43 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -26,9 +26,6 @@
  * $Id: detailer.c,v 1.30 2005/07/09 18:37:14 mux Exp $
  */
 
-#include <sys/types.h>
-#include <sys/stat.h>
-
 #include <stdlib.h>
 #include <string.h>
 
@@ -37,17 +34,22 @@
 #include "misc.h"
 #include "mux.h"
 #include "proto.h"
+#include "status.h"
 #include "stream.h"
 
+static int	detailer_coll(struct coll *, struct status *);
+static int	detailer_dofile(struct coll *, struct status *, char *);
+
+static struct stream *rd, *wr;
+
 void *
 detailer(void *arg)
 {
-	char md5[MD5_DIGEST_SIZE];
-	struct stat sb;
 	struct config *config;
 	struct coll *coll;
-	struct stream *rd, *wr;
-	char *cmd, *collname, *file, *line, *path, *release;
+	struct status *st;
+	char *errmsg;
+	char *cmd, *collname, *line, *release;
 	int error;
 
 	config = arg;
@@ -57,10 +59,11 @@ detailer(void *arg)
 		if (coll->co_options & CO_SKIP)
 			continue;
 		line = stream_getln(rd, NULL);
-		cmd = proto_getstr(&line);
-		collname = proto_getstr(&line);
-		release = proto_getstr(&line);
-		if (release == NULL || strcmp(cmd, "COLL") != 0 ||
+		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)
 			goto bad;
@@ -70,31 +73,17 @@ detailer(void *arg)
 			stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
 			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
 		}
-		line = stream_getln(rd, NULL);
-		if (line == NULL)
-			goto bad;
-		while (strcmp(line, ".") != 0) {
-			cmd = proto_getstr(&line);
-			file = proto_getstr(&line);
-			if (file == NULL || strcmp(cmd, "U") != 0)
-				goto bad;
-			path = checkoutpath(coll->co_prefix, file);
-			if (path == NULL)
-				goto bad;
-			error = stat(path, &sb);
-			if (!error && MD5file(path, md5) == 0)
-				proto_printf(wr, "S %s %s %s %s\n", file,
-				    coll->co_tag, coll->co_date, md5);
-			else
-				proto_printf(wr, "C %s %s %s\n", file,
-				    coll->co_tag, coll->co_date);
-			free(path);
-			stream_flush(wr);
-			line = stream_getln(rd, NULL);
-			if (line == NULL)
-				goto bad;
+		st = status_open(coll, -1, &errmsg);
+		if (st == NULL) {
+			lprintf(-1, "Detailer: %s\n", errmsg);
+			stream_close(rd);
+			stream_close(wr);
+			return (NULL);
 		}
-		proto_printf(wr, ".\n");
+		error = detailer_coll(coll, st);
+		status_close(st, NULL);
+		if (error)
+			goto bad;
 		if (coll->co_options & CO_COMPRESS) {
 			stream_filter_stop(rd);
 			stream_filter_stop(wr);
@@ -130,3 +119,88 @@ bad:
 	stream_close(rd);
 	return (NULL);
 }
+
+static int
+detailer_coll(struct coll *coll, struct status *st)
+{
+	char *cmd, *file, *line;
+	int error;
+
+	line = stream_getln(rd, NULL);
+	if (line == NULL)
+		return (-1);
+	while (strcmp(line, ".") != 0) {
+		cmd = proto_get_ascii(&line);
+		file = proto_get_ascii(&line);
+		if (file == NULL || line != NULL)
+			return (-1);
+		if (strcmp(cmd, "D") == 0) {
+			/* XXX: status file? */
+			stream_printf(wr, "D %s\n", file);
+		} else {
+			if (strcmp(cmd, "U") != 0)
+				return (-1);
+			error = detailer_dofile(coll, st, file);
+			if (error)
+				return (-1);
+		}
+		stream_flush(wr);
+		line = stream_getln(rd, NULL);
+		if (line == NULL)
+			return (-1);
+	}
+	stream_printf(wr, ".\n");
+	return (0);
+}
+
+static int
+detailer_dofile(struct coll *coll, struct status *st, char *file)
+{
+	char md5[MD5_DIGEST_SIZE];
+	struct fattr *fa;
+	struct statusrec *sr;
+	char *path;
+	int error;
+
+	path = checkoutpath(coll->co_prefix, file);
+	if (path == NULL)
+		return (-1);
+	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. */
+		stream_printf(wr, "C %s %s %s\n",
+		    file, coll->co_tag, coll->co_date);
+		free(path);
+		return (0);
+	}
+	sr = status_get(st, file, 0, 0);
+
+	/* 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) {
+		stream_printf(wr, "U %s %s %s %s %s\n", file, coll->co_tag,
+		    coll->co_date, sr->sr_revnum, sr->sr_revdate);
+		free(path);
+		return (0);
+	}
+
+	error = MD5file(path, md5);
+	if (error)
+		return (-1);
+	free(path);
+	if (sr == NULL) {
+		stream_printf(wr, "S %s %s %s %s\n", file, coll->co_tag,
+		    coll->co_date, md5);
+	} else {
+		stream_printf(wr, "s %s %s %s %s %s\n", file, coll->co_tag,
+		    coll->co_date, sr->sr_revnum, md5);
+	}
+	return (0);
+}
Index: fattr.c
===================================================================
RCS file: /cvs/csup/csup/fattr.c,v
retrieving revision 1.22
diff -u -p -r1.22 fattr.c
--- fattr.c	9 Jul 2005 18:54:17 -0000	1.22
+++ fattr.c	12 Nov 2005 00:05:26 -0000
@@ -127,6 +127,20 @@ fattr_new(int type)
 	return (new);
 }
 
+struct fattr *
+fattr_default(int type)
+{
+	struct fattr *fa;
+
+	fa = fattr_new(type);
+	if (fa->type == FT_DIRECTORY)
+		fa->mode = 0777 | FA_PERMMASK;
+	else
+		fa->mode = 0666 | FA_PERMMASK;
+	fa->mask |= FA_MODE;
+	return (fa);
+}
+
 /* Returns a new file attribute structure based on a stat structure. */
 struct fattr *
 fattr_fromstat(struct stat *sb)
@@ -284,7 +298,10 @@ fattr_encode(const struct fattr *fa, fat
 
 	pw = NULL;
 	gr = NULL;
-	mask = fa->mask & support[fa->type];
+	if (support == NULL)
+		mask = fa->mask;
+	else
+		mask = fa->mask & support[fa->type];
 	/* XXX - Use getpwuid_r() and getgrgid_r(). */
 	if (fa->mask & FA_OWNER) {
 		pw = getpwuid(fa->uid);
@@ -439,6 +456,14 @@ fattr_free(struct fattr *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)
 {
 
@@ -649,7 +674,7 @@ fattr_override(struct fattr *fa, const s
  * it has been applied successfully.
  */
 int
-fattr_install(struct fattr *fa, const char *frompath, const char *topath)
+fattr_install(struct fattr *fa, const char *topath, const char *frompath)
 {
 	struct timeval tv[2];
 	struct fattr *old;
@@ -685,7 +710,7 @@ fattr_install(struct fattr *fa, const ch
 	 * since as far as I know that's the way things are.
 	 */
 	if ((old->mask & FA_FLAGS) && old->flags > 0) {
-		chflags(topath, 0);
+		(void)chflags(topath, 0);
 		old->flags = 0;
 	}
 
@@ -693,9 +718,11 @@ fattr_install(struct fattr *fa, const ch
 	if (!inplace && (fa->type == FT_DIRECTORY) !=
 	    (old->type == FT_DIRECTORY)) {
 		if (old->type == FT_DIRECTORY)
-			rmdir(topath);
+			error = rmdir(topath);
 		else
-			unlink(topath);
+			error = unlink(topath);
+		if (error)
+			goto bad;
 	}
 
 	/* Change those attributes that we can before moving the file
Index: fattr.h
===================================================================
RCS file: /cvs/csup/csup/fattr.h,v
retrieving revision 1.17
diff -u -p -r1.17 fattr.h
--- fattr.h	16 Jun 2005 22:21:47 -0000	1.17
+++ fattr.h	11 Nov 2005 00:46:11 -0000
@@ -28,6 +28,8 @@
 #ifndef _FATTR_H_
 #define _FATTR_H_
 
+#include <fcntl.h>
+
 /*
  * File types.
  */
@@ -81,6 +83,7 @@ typedef int	fattr_support_t[FT_NUMBER];
 extern const struct fattr *fattr_bogus;
 
 struct fattr	*fattr_new(int);
+struct fattr	*fattr_default(int);
 struct fattr	*fattr_fromstat(struct stat *);
 struct fattr	*fattr_frompath(const char *, int);
 struct fattr	*fattr_fromfd(int);
@@ -90,6 +93,7 @@ struct fattr	*fattr_dup(const struct fat
 char		*fattr_encode(const struct fattr *, fattr_support_t);
 int		 fattr_type(const struct fattr *);
 void		 fattr_maskout(struct fattr *, int);
+void		 fattr_umask(struct fattr *, mode_t);
 void		 fattr_merge(struct fattr *, const struct fattr *);
 void		 fattr_override(struct fattr *, const struct fattr *, int);
 int		 fattr_install(struct fattr *, const char *, const char *);
Index: lister.c
===================================================================
RCS file: /cvs/csup/csup/lister.c,v
retrieving revision 1.15
diff -u -p -r1.15 lister.c
--- lister.c	22 May 2005 17:33:26 -0000	1.15
+++ lister.c	17 Dec 2005 02:38:49 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -28,35 +28,307 @@
 
 #include <sys/types.h>
 
-#include <stddef.h>
+#include <assert.h>
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 
+#include "attrstack.h"
 #include "config.h"
+#include "fattr.h"
 #include "lister.h"
+#include "misc.h"
 #include "mux.h"
+#include "status.h"
 #include "stream.h"
 
+static int	lister_coll(struct config *, struct stream *, struct coll *,
+		    struct status *);
+static void	lister_sendbogus(struct config *, struct stream *,
+		    struct statusrec *);
+static int	lister_dodirdown(struct config *, struct stream *,
+		    struct coll *, struct statusrec *, struct attrstack *as);
+static int	lister_dodirup(struct config *, struct stream *, struct coll *,
+    		    struct statusrec *, struct attrstack *as);
+static int	lister_dofile(struct config *, struct stream *, struct coll *,
+		    struct statusrec *);
+static int	lister_dodead(struct config *, struct stream *, struct coll *,
+		    struct statusrec *);
+
 void *
 lister(void *arg)
 {
 	struct config *config;
 	struct coll *coll;
 	struct stream *wr;
+	struct status *st;
+	char *errmsg;
+	int error;
 
 	config = arg;
 	wr = stream_fdopen(config->id0, NULL, chan_write, NULL);
 	STAILQ_FOREACH(coll, &config->colls, co_next) {
 		if (coll->co_options & CO_SKIP)
 			continue;
+		st = status_open(coll, -1, &errmsg);
+		if (st == NULL) {
+			lprintf(-1, "Lister: %s\n", errmsg);
+			stream_close(wr);
+			return (NULL);
+		}
 		stream_printf(wr, "COLL %s %s\n", coll->co_name,
 		    coll->co_release);
 		if (coll->co_options & CO_COMPRESS)
 			stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
-		stream_printf(wr, ".\n");
+		error = lister_coll(config, wr, coll, st);
+		if (error) {
+			status_close(st, NULL);
+			stream_close(wr);
+			return (NULL);
+		}
 		if (coll->co_options & CO_COMPRESS)
 			stream_filter_stop(wr);
 		stream_flush(wr);
+		status_close(st, NULL);
 	}
 	stream_printf(wr, ".\n");
 	stream_close(wr);
 	return (NULL);
 }
+
+/* List a single collection based on the status file. */
+static int
+lister_coll(struct config *config, struct stream *wr, struct coll *coll,
+    struct status *st)
+{
+	struct attrstack *as;
+	struct statusrec *sr;
+	struct fattr *fa;
+	int depth, error, prunedepth;
+
+	depth = 0;
+	prunedepth = INT_MAX;
+	as = attrstack_new();
+	while ((sr = status_get(st, NULL, 0, 0)) != NULL) {
+		switch (sr->sr_type) {
+		case SR_DIRDOWN:
+			depth++;
+			if (depth < prunedepth) {
+				error = lister_dodirdown(config, wr, coll, sr,
+				    as);
+				if (error) {
+					prunedepth = depth;
+					break;
+				}
+			}
+			break;
+		case SR_DIRUP:
+			if (depth < prunedepth) {
+				error = lister_dodirup(config, wr, coll, sr,
+				    as);
+			} else if (depth == prunedepth) {
+				/* Finished pruning. */
+				prunedepth = INT_MAX;
+			}
+			depth--;
+			continue;
+		case SR_CHECKOUTLIVE:
+			if (depth < prunedepth)
+				error = lister_dofile(config, wr, coll, sr);
+			break;
+		case SR_CHECKOUTDEAD:
+			if (depth < prunedepth)
+				error = lister_dodead(config, wr, coll, sr);
+			break;
+		default:
+			goto bad;
+		}
+	}
+	if (!status_eof(st) || depth != 0)
+		goto bad;
+	stream_printf(wr, ".\n");
+	attrstack_free(as);
+	return (0);
+bad:
+	lprintf(-1, "Lister: Bad status file for collection \"%s\".  "
+	    "Delete it and try again.\n", coll->co_name);
+	while (depth-- > 0) {
+		fa = attrstack_pop(as);
+		fattr_free(fa);
+	}
+	attrstack_free(as);
+	return (-1);
+}
+
+/* Handle a directory up entry found in the status file. */
+static int
+lister_dodirdown(struct config *config, struct stream *wr, struct coll *coll,
+    struct statusrec *sr, struct attrstack *as)
+{
+	struct fattr *fa, *fa2;
+	char *path;
+
+	if (coll->co_options & CO_TRUSTSTATUSFILE) {
+		fa = fattr_new(FT_DIRECTORY);
+	} else {
+		asprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file);
+		if (path == NULL)
+			err(1, "asprintf");
+		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. */
+		lister_sendbogus(config, wr, sr);
+		return (-1);
+	}
+
+	/* It really is a directory. */
+	attrstack_push(as, fa);
+	stream_printf(wr, "D %s\n", pathlast(sr->sr_file));
+	return (0);
+}
+
+/* Handle a directory up entry found in the status file. */
+static int
+lister_dodirup(struct config *config, struct stream *wr, struct coll *coll,
+    struct statusrec *sr, struct attrstack *as)
+{
+	struct fattr *fa, *fa2;
+	char *attrs;
+
+	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))
+		attrs = fattr_encode(fa, config->fasupport);
+	else
+		attrs = fattr_encode(fattr_bogus, config->fasupport);
+	stream_printf(wr, "U %s\n", attrs);
+	free(attrs);
+	if (!(coll->co_options & CO_TRUSTSTATUSFILE))
+		fattr_free(fa);
+	stream_flush(wr);	/* XXX - CVSup flushes here.*/
+	return (0);
+}
+
+/* Handle a checkout live entry found in the status file. */
+static int
+lister_dofile(struct config *config, struct stream *wr, struct coll *coll,
+    struct statusrec *sr)
+{
+	struct fattr *fa, *fa2, *cfa, *sfa, *rfa;
+	char *attrs, *path;
+	int error;
+
+	fa = NULL;
+	rfa = NULL;
+	error = 0;
+	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
+		path = checkoutpath(coll->co_prefix, sr->sr_file);
+		if (path == NULL)
+			goto bad;
+		rfa = fattr_frompath(path, FATTR_NOFOLLOW);
+		free(path);
+		if (rfa == NULL)
+			goto bad;
+		fa = rfa;
+	}
+	cfa = sr->sr_clientattr;
+	if (fa == NULL)
+		fa = cfa;
+	sfa = sr->sr_serverattr;
+	fa2 = fattr_forcheckout(sfa, coll->co_umask);
+	if (!fattr_equal(fa, cfa) || !fattr_equal(fa, fa2) ||
+	    strcmp(coll->co_tag, sr->sr_tag) != 0 ||
+	    strcmp(coll->co_date, sr->sr_date) != 0) {
+		fattr_free(fa2);
+		fattr_free(rfa);
+		goto bad;
+	}
+	attrs = fattr_encode(sfa, config->fasupport);
+	stream_printf(wr, "F %s %s\n", pathlast(sr->sr_file), attrs);
+	free(attrs);
+	fattr_free(fa2);
+	fattr_free(rfa);
+	return (0);
+bad:
+	lister_sendbogus(config, wr, sr);
+	return (-1);
+}
+
+/* Handle a checkout dead entry found in the status file. */
+static int
+lister_dodead(struct config *config, struct stream *wr, struct coll *coll,
+    struct statusrec *sr)
+{
+	struct fattr *fa;
+	char *attrs, *path;
+
+	if (!(coll->co_options & CO_TRUSTSTATUSFILE)) {
+		path = checkoutpath(coll->co_prefix, sr->sr_file);
+		if (path == NULL)
+			goto bad;
+		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);
+			goto bad;
+		}
+		fattr_free(fa);
+	}
+	if (strcmp(coll->co_tag, sr->sr_tag) != 0 ||
+	    strcmp(coll->co_date, sr->sr_date) != 0)
+		attrs = fattr_encode(fattr_bogus, config->fasupport);
+	else
+		attrs = fattr_encode(sr->sr_serverattr, config->fasupport);
+	stream_printf(wr, "f %s %s\n", pathlast(sr->sr_file), attrs);
+	free(attrs);
+	return (0);
+bad:
+	lister_sendbogus(config, wr, sr);
+	return (-1);
+}
+
+static void
+lister_sendbogus(struct config *config, struct stream *wr, struct statusrec *sr)
+{
+	char *attrs;
+
+	attrs = fattr_encode(fattr_bogus, config->fasupport);
+	stream_printf(wr, "F %s %s\n", pathlast(sr->sr_file), attrs);
+	free(attrs);
+}
Index: main.c
===================================================================
RCS file: /cvs/csup/csup/main.c,v
retrieving revision 1.20
diff -u -p -r1.20 main.c
--- main.c	9 Jul 2005 18:54:17 -0000	1.20
+++ main.c	3 Dec 2005 03:57:04 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -61,6 +61,8 @@ usage(char *argv0)
 	    "Verbosity level (0..2, default 1)");
 	lprintf(-1, USAGE_OPTFMT, "-p port",
 	    "Alternate server port (default 5999)");
+	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");
@@ -74,15 +76,16 @@ main(int argc, char *argv[])
 	struct config *config;
 	char *argv0, *base, *colldir, *host, *file, *lockfile;
 	in_port_t port;
-	int c, compress, error, lockfd, lflag;
+	int c, compress, error, lockfd, lflag, truststatus;
 
 	port = 0;
 	compress = 0;
+	truststatus = 0;
 	lflag = 0;
 	lockfd = 0;
 	argv0 = argv[0];
 	base = colldir = host = lockfile = NULL;
-	while ((c = getopt(argc, argv, "b:c:gh:l:L:p:P:vzZ")) != -1) {
+	while ((c = getopt(argc, argv, "b:c:gh:l:L:p:P:svzZ")) != -1) {
 		switch (c) {
 		case 'b':
 			base = optarg;
@@ -139,6 +142,9 @@ main(int argc, char *argv[])
 				return (1);
 			}
 			break;
+		case 's':
+			truststatus = 1;
+			break;
 		case 'v':
 			lprintf(-1, "Csup version 0.1\n");
 			return (0);
@@ -168,7 +174,8 @@ main(int argc, char *argv[])
 
 	file = argv[0];
 	lprintf(2, "Parsing supfile \"%s\"\n", file);
-	config = config_init(file, host, base, colldir, port, compress);
+	config = config_init(file, host, base, colldir, port, compress,
+	    truststatus);
 	lprintf(2, "Connecting to %s\n", config->host);
 	error = proto_connect(config);
 	if (error)
Index: misc.c
===================================================================
RCS file: /cvs/csup/csup/misc.c,v
retrieving revision 1.14
diff -u -p -r1.14 misc.c
--- misc.c	9 Jul 2005 18:54:17 -0000	1.14
+++ misc.c	17 Dec 2005 01:45:24 -0000
@@ -26,10 +26,12 @@
  * $Id: misc.c,v 1.14 2005/07/09 18:54:17 mux Exp $
  */
 
+#include <sys/types.h>
 #include <openssl/md5.h>
 
 #include <err.h>
 #include <fcntl.h>
+#include <pthread.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
@@ -111,23 +113,15 @@ md5tostr(unsigned char *md5, char *s)
 int
 pathcmp(const char *s1, const char *s2)
 {
-	int c1, c2;
+	char c1, c2;
 
 	do {
-		c1 = *s1++ & 0xff;
-		if (c1 == '/') {
-			if (*s1 != '\0')
-				c1 = 0x100;
-			else
-				c1 = 0;
-		}
-		c2 = *s2++ & 0xff;
-		if (c2 == '/') {
-			if (*s2 != '\0')
-				c2 = 0x100;
-			else
-				c2 = 0;
-		}
+		c1 = *s1++;
+		if (c1 == '/')
+			c1 = 1;
+		c2 = *s2++;
+		if (c2 == '/')
+			c2 = 1;
 	} while (c1 == c2 && c1 != '\0');
 
 	return (c1 - c2);
@@ -148,7 +142,7 @@ pathlast(char *path)
 
 /*
  * Returns a buffer allocated with malloc() containing the absolute
- * pathname to the checkout file made from the prefix, and the path
+ * 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.
  */
@@ -176,3 +170,38 @@ checkoutpath(const char *prefix, const c
 		err(1, "asprintf");
 	return (path);
 }
+
+/*
+ * 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;
+
+	pthread_mutex_lock(&tempname_mtx);
+	if (tempname_pid == -1) {
+		tempname_pid = getpid();
+		tempname_count = 0;
+	}
+	count = tempname_count++;
+	pthread_mutex_unlock(&tempname_mtx);
+	cp = strrchr(path, '/');
+	if (cp == NULL)
+		asprintf(&temp, "%s-%ld.%d", TEMPNAME_PREFIX,
+		    (long)tempname_pid, count);
+	else
+		asprintf(&temp, "%.*s%s-%ld.%d", (int)(cp - path + 1), path,
+		    TEMPNAME_PREFIX, (long)tempname_pid, count);
+	if (temp == NULL)
+		err(1, "asprintf");
+	return (temp);
+}
Index: misc.h
===================================================================
RCS file: /cvs/csup/csup/misc.h,v
retrieving revision 1.14
diff -u -p -r1.14 misc.h
--- misc.h	5 Jun 2005 02:36:10 -0000	1.14
+++ misc.h	8 Nov 2005 19:08:46 -0000
@@ -55,5 +55,6 @@ void	md5tostr(unsigned char *, char *);
 int	pathcmp(const char *, const char *);
 char	*pathlast(char *);
 char	*checkoutpath(const char *, const char *);
+char	*tempname(const char *);
 
 #endif /* !_MISC_H_ */
Index: mux.c
===================================================================
RCS file: /cvs/csup/csup/mux.c,v
retrieving revision 1.52
diff -u -p -r1.52 mux.c
--- mux.c	9 Jul 2005 18:54:17 -0000	1.52
+++ mux.c	9 Jul 2005 18:54:24 -0000
@@ -869,10 +869,13 @@ receiver_loop(void *arg)
 				goto bad;
 			chan = chan_get(mh.mh_window.id);
 			if (chan->state == CS_ESTABLISHED ||
-			    chan->state == CS_RDCLOSED)
+			    chan->state == CS_RDCLOSED) {
 				chan->sendwin = ntohl(mh.mh_window.window);
-			chan_unlock(chan);
-			sender_wakeup();
+				chan_unlock(chan);
+				sender_wakeup();
+			} else {
+				chan_unlock(chan);
+			}
 			break;
 		case MUX_DATA:
 			error = sock_readwait(s, (char *)&mh + sizeof(mh.type),
Index: proto.c
===================================================================
RCS file: /cvs/csup/csup/proto.c,v
retrieving revision 1.48
diff -u -p -r1.48 proto.c
--- proto.c	9 Jul 2005 18:54:17 -0000	1.48
+++ proto.c	17 Dec 2005 02:36:31 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -140,13 +140,13 @@ proto_greet(struct config *config)
 
 	s = config->server;
 	line = stream_getln(s, NULL);
-	tok = proto_getstr(&line);
+	tok = proto_get_ascii(&line);
 	if (tok == NULL)
 		goto bad;
 	if (strcmp(tok, "OK") == 0) {
-		proto_getstr(&line);	/* XXX major number */
-		proto_getstr(&line);	/* XXX minor number */
-		tok = proto_getstr(&line);
+		proto_get_ascii(&line);	/* XXX major number */
+		proto_get_ascii(&line);	/* XXX minor number */
+		tok = proto_get_ascii(&line);
 	} else if (strcmp(tok, "!") == 0) {
 		lprintf(-1, "Rejected by server: %s\n", line);
 		return (-1);
@@ -164,33 +164,26 @@ static int
 proto_negproto(struct config *config)
 {
 	struct stream *s;
-	char *cmd, *line, *maj, *min;
-	int pmaj, pmin;
+	char *cmd, *line;
+	int error, maj, min;
 
 	s = config->server;
 	stream_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER);
 	stream_flush(s);
 	line = stream_getln(s, NULL);
-	cmd = proto_getstr(&line);
+	cmd = proto_get_ascii(&line);
 	if (strcmp(cmd, "!") == 0) {
 		lprintf(-1, "Protocol negotiation failed: %s\n", line);
 		return (1);
 	} else if (strcmp(cmd, "PROTO") != 0)
 		goto bad;
-	maj = proto_getstr(&line);
-	min = proto_getstr(&line);
-	if (min == NULL || line != NULL)
-		goto bad;
-	errno = 0;
-	pmaj = strtol(maj, NULL, 10);
-	if (errno == EINVAL)
-		goto bad;
-	errno = 0;
-	pmin = strtol(min, NULL, 10);
-	if (errno == EINVAL)
+	error = proto_get_int(&line, &maj);
+	if (!error)
+		error = proto_get_int(&line, &min);
+	if (error)
 		goto bad;
-	if (pmaj != PROTO_MAJ || pmin != PROTO_MIN) {
-		lprintf(-1, "Server protocol version %s.%s not supported "
+	if (maj != PROTO_MAJ || min != PROTO_MIN) {
+		lprintf(-1, "Server protocol version %d.%d not supported "
 		    "by client\n", maj, min);
 		return (1);
 	}
@@ -213,9 +206,9 @@ proto_login(struct config *config)
 	stream_printf(s, "USER %s %s\n", getlogin(), host);
 	stream_flush(s);
 	line = stream_getln(s, NULL);
-	cmd = proto_getstr(&line);
-	realm = proto_getstr(&line);
-	challenge = proto_getstr(&line);
+	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) {
@@ -226,7 +219,7 @@ proto_login(struct config *config)
 	stream_printf(s, "AUTHMD5 . . .\n");
 	stream_flush(s);
 	line = stream_getln(s, NULL);
-	cmd = proto_getstr(&line);
+	cmd = proto_get_ascii(&line);
 	if (strcmp(cmd, "OK") == 0)
 		return (0);
 	if (strcmp(cmd, "!") == 0 && line != NULL) {
@@ -257,7 +250,7 @@ proto_fileattr(struct config *config)
 	stream_printf(s, ".\n");
 	stream_flush(s);
 	line = stream_getln(s, NULL);
-	cmd = proto_getstr(&line);
+	cmd = proto_get_ascii(&line);
 	if (line == NULL || strcmp(cmd, "ATTR") != 0)
 		goto bad;
 	errno = 0;
@@ -311,10 +304,10 @@ proto_xchgcoll(struct config *config)
 		line = stream_getln(s, NULL);
 		if (line == NULL)
 			goto bad;
-		cmd = proto_getstr(&line);
-		coll = proto_getstr(&line);
-		release = proto_getstr(&line);
-		options = proto_getstr(&line);
+		cmd = proto_get_ascii(&line);
+		coll = proto_get_ascii(&line);
+		release = proto_get_ascii(&line);
+		options = proto_get_ascii(&line);
 		if (options == NULL || line != NULL)
 			goto bad;
 		if (strcmp(cmd, "COLL") != 0)
@@ -333,11 +326,11 @@ proto_xchgcoll(struct config *config)
 		while ((line = stream_getln(s, NULL)) != NULL) {
 		 	if (strcmp(line, ".") == 0)
 				break;
-			cmd = proto_getstr(&line);
+			cmd = proto_get_ascii(&line);
 			if (cmd == NULL)
 				goto bad;
 			if (strcmp(cmd, "!") == 0) {
-				msg = proto_getstr(&line);
+				msg = proto_get_ascii(&line);
 				if (err == NULL)
 					goto bad;
 				lprintf(-1, "Server message: %s\n", msg);
@@ -346,8 +339,8 @@ proto_xchgcoll(struct config *config)
 				if (cur->co_cvsroot == NULL)
 					err(1, "strdup");
 			} else if (strcmp(cmd, "KEYALIAS") == 0) {
-				ident = proto_getstr(&line);
-				rcskey = proto_getstr(&line);
+				ident = proto_get_ascii(&line);
+				rcskey = proto_get_ascii(&line);
 				if (rcskey == NULL || line != NULL)
 					goto bad;
 				error = keyword_alias(cur->co_keyword, ident,
@@ -355,14 +348,14 @@ proto_xchgcoll(struct config *config)
 				if (error)
 					goto bad;
 			} else if (strcmp(cmd, "KEYON") == 0) {
-				ident = proto_getstr(&line);
+				ident = proto_get_ascii(&line);
 				if (ident == NULL || line != NULL)
 					goto bad;
 				error = keyword_enable(cur->co_keyword, ident);
 				if (error)
 					goto bad;
 			} else if (strcmp(cmd, "KEYOFF") == 0) {
-				ident = proto_getstr(&line);
+				ident = proto_get_ascii(&line);
 				if (ident == NULL || line != NULL)
 					goto bad;
 				error = keyword_disable(cur->co_keyword, ident);
@@ -472,9 +465,10 @@ proto_init(struct config *config)
 }
 
 /*
- * Very simple printf() implementation that only understands %d
- * and %s formats.  For the %s format, some characters in the
- * string need to be encoded.
+ * Very simple printf() implementation that only understands standard
+ * format specifiers %d and %s, plus an additional %t format for printing
+ * time_t integers (unlike the C99 %t length modifier for ptrdiff_t).
+ * For the %s format, some characters in the string may get escaped.
  */
 int
 proto_printf(struct stream *wr, const char *format, ...)
@@ -484,14 +478,18 @@ proto_printf(struct stream *wr, const ch
 	va_list ap;
 	char *cp, *s;
 	size_t len;
+	ssize_t n;
 	int val, ret;
 	char c;
 
+	n = 0;
 	fmt = format;
 	va_start(ap, format);
 	while ((cp = strchr(fmt, '%')) != NULL) {
 		if (cp > fmt)
-			stream_write(wr, fmt, cp - fmt);
+			n = stream_write(wr, fmt, cp - fmt);
+		if (n == -1)
+			return (-1);
 		if (*++cp == '\0')
 			goto done;
 		switch (*cp) {
@@ -503,7 +501,7 @@ proto_printf(struct stream *wr, const ch
 			   with 128-bit ints some day... */
 			if ((unsigned)ret > sizeof(buf) + 1)
 				errx(1, "%s: increase buffer size", __func__);
-			stream_write(wr, buf, ret);
+			n = stream_write(wr, buf, ret);
 			break;
 		case 's':
 			s = va_arg(ap, char *);
@@ -513,42 +511,59 @@ proto_printf(struct stream *wr, const ch
 			do {
 				len = strcspn(s, " \t\n\\");
 				c = s[len];
-				stream_write(wr, s, len);
+				n = stream_write(wr, s, len);
+				if (n == -1)
+					return (-1);
 				s += len + 1;
 				switch (c) {
 				case ' ':
-					stream_write(wr, "\\_", 2);
+					n = stream_write(wr, "\\_", 2);
 					break;
 				case '\t':
-					stream_write(wr, "\\t", 2);
+					n = stream_write(wr, "\\t", 2);
 					break;
 				case '\n':
-					stream_write(wr, "\\n", 2);
+					n = stream_write(wr, "\\n", 2);
 					break;
 				case '\\':
-					stream_write(wr, "\\\\", 2);
+					n = stream_write(wr, "\\\\", 2);
 					break;
 				}
+				if (n == -1)
+					return (-1);
 			} while (c != '\0');
 			break;
+		case 't':
+			val = va_arg(ap, time_t);
+			ret = snprintf(buf, sizeof(buf), "%lld",
+			    (long long)val);
+			if ((unsigned)ret > sizeof(buf) + 1)
+				errx(1, "%s: increase buffer size", __func__);
+			n = stream_write(wr, buf, ret);
+			break;
 		case '%':
-			stream_write(wr, "%", 1);
+			n = stream_write(wr, "%", 1);
 			break;
 		}
 		fmt = cp + 1;
 	}
-	if (*fmt != '\0')
-		stream_write(wr, fmt, strlen(fmt));
+	if (n == -1)
+		return (-1);
+	if (*fmt != '\0') {
+		n = stream_write(wr, fmt, strlen(fmt));
+		if (n == -1)
+			return (-1);
+	}
 done:
 	va_end(ap);
 	return (0);
 }
 
 /*
- * Eat a token in the string.
+ * Get an ascii token in the string.
  */
 char *
-proto_getstr(char **s)
+proto_get_ascii(char **s)
 {
 	char *cp, *cp2, *ret;
 
@@ -582,3 +597,44 @@ proto_getstr(char **s)
 	}
 	return (ret);
 }
+
+/*
+ * Get an int token.
+ */
+int
+proto_get_int(char **s, int *val)
+{
+	char *cp, *end;
+
+	cp = proto_get_ascii(s);
+	if (cp == NULL)
+		return (-1);
+	errno = 0;
+	*val = strtol(cp, &end, 10);
+	if (errno || *end != '\0')
+		return (-1);
+	return (0);
+}
+
+/*
+ * Get a time_t token.
+ *
+ * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
+ * is more portable and 64bits should be enough for a timestamp.
+ */
+int
+proto_get_time(char **s, time_t *val)
+{
+	long long tmp;
+	char *cp, *end;
+
+	cp = proto_get_ascii(s);
+	if (cp == NULL)
+		return (-1);
+	errno = 0;
+	tmp = strtoll(cp, &end, 10);
+	if (errno || *end != '\0')
+		return (-1);
+	*val = (time_t)tmp;
+	return (0);
+}
Index: proto.h
===================================================================
RCS file: /cvs/csup/csup/proto.h,v
retrieving revision 1.7
diff -u -p -r1.7 proto.h
--- proto.h	9 Jul 2005 18:37:14 -0000	1.7
+++ proto.h	17 Dec 2005 16:09:55 -0000
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * Copyright (c) 2003-2005, Maxime Henrion <mux@FreeBSD.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -28,11 +28,15 @@
 #ifndef _PROTO_H_
 #define _PROTO_H_
 
+#include <time.h>
+
 struct stream;
 
 int	 proto_connect(struct config *);
 int	 proto_init(struct config *);
 int	 proto_printf(struct stream *, const char *, ...);
-char	*proto_getstr(char **);
+char	*proto_get_ascii(char **);
+int	 proto_get_int(char **, int *);
+int	 proto_get_time(char **, time_t *);
 
 #endif /* !_PROTO_H_ */
Index: status.c
===================================================================
RCS file: status.c
diff -N status.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ status.c	17 Dec 2005 16:09:50 -0000
@@ -0,0 +1,681 @@
+/*-
+ * Copyright (c) 2003-2005, 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 <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <err.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 "proto.h"
+#include "status.h"
+#include "stream.h"
+
+#define	STATUS_VERSION	5
+
+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;
+	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. */
+		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_type = 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)
+		return (NULL);
+	return (sr);
+}
+
+static struct statusrec *
+status_rdraw(struct status *st, char **linep)
+{
+	struct statusrec sr;
+	char *cmd, *line, *file;
+
+	line = stream_getln(st->rd, NULL);
+	if (line == NULL) {
+		if (stream_eof(st->rd)) {
+			st->eof = 1;
+			return (NULL);
+		}
+		lprintf(-1, "Error reading status file\n");
+		return (NULL);
+	}
+	st->linenum++;
+	cmd = proto_get_ascii(&line);
+	file = proto_get_ascii(&line);
+	if (file == NULL)
+		return (NULL);
+
+	if (strcmp(cmd, "D") == 0) {
+		sr.sr_type = SR_DIRDOWN;
+		st->depth++;
+	} else if (strcmp(cmd, "C") == 0) {
+		sr.sr_type = SR_CHECKOUTLIVE;
+	} else if (strcmp(cmd, "c") == 0) {
+		sr.sr_type = SR_CHECKOUTDEAD;
+	} else if (strcmp(cmd, "U") == 0) {
+		sr.sr_type = SR_DIRUP;
+		if (st->depth <= 0) {
+			lprintf(-1, "\"U\" entry has no matching \"D\"\n");
+			return (NULL);
+		}
+		st->depth--;
+	} else {
+		lprintf(-1, "Invalid file type \"%s\"\n", cmd);
+		return (NULL);
+	}
+
+	sr.sr_file = strdup(file);
+	if (sr.sr_file == NULL)
+		err(1, "strdup");
+	if (st->previous != NULL &&
+	    statusrec_cmp(st->previous, &sr) >= 0) {
+		lprintf(-1, "File is not sorted properly\n");
+		lprintf(-1, "\"%s\" \"%s\" (%d)\n", st->previous->sr_file,
+		    sr.sr_file, statusrec_cmp(st->previous, &sr));
+		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)
+{
+	char *clientattr, *serverattr, *attr;
+	int error;
+
+	switch (sr->sr_type) {
+	case SR_DIRDOWN:
+		error = proto_printf(st->wr, "D %s\n", sr->sr_file);
+		break;
+	case SR_DIRUP:
+		attr = fattr_encode(fattr_bogus, NULL);
+		error = proto_printf(st->wr, "U %s %s\n", sr->sr_file, attr);
+		free(attr);
+		break;
+	case SR_CHECKOUTLIVE:
+		clientattr = fattr_encode(sr->sr_clientattr, NULL);
+		serverattr = fattr_encode(sr->sr_serverattr, NULL);
+		error = proto_printf(st->wr, "C %s %s %s %s %s %s %s\n",
+		    sr->sr_file, sr->sr_tag, sr->sr_date, serverattr,
+		    sr->sr_revnum, sr->sr_revdate, clientattr);
+		free(clientattr);
+		free(serverattr);
+		break;
+	case SR_CHECKOUTDEAD:
+		serverattr = fattr_encode(sr->sr_serverattr, NULL);
+		error = proto_printf(st->wr, "c %s %s %s %s\n", sr->sr_file,
+		    sr->sr_tag, sr->sr_date, serverattr);
+		free(serverattr);
+		break;
+	default:
+		return (-1);
+	}
+	if (error)
+		return (-1);
+	return (0);
+}
+
+static int
+status_wrraw(struct status *st, struct statusrec *sr, char *line)
+{
+	char type;
+	int ret;
+
+	if (st->wr == NULL)
+		return (0);
+	switch (sr->sr_type) {
+	case SR_DIRDOWN:
+		type = 'D';
+		break;
+	case SR_DIRUP:
+		type = 'U';
+		break;
+	case SR_CHECKOUTLIVE:
+		type = 'C';
+		break;
+	case SR_CHECKOUTDEAD:
+		type = 'c';
+		break;
+	default:
+		return (-1);
+	}
+	/* XXX - We should probably use proto_printf() here. */
+	if (sr->sr_type == SR_DIRDOWN)
+		ret = stream_printf(st->wr, "%c %s\n", type, sr->sr_file);
+	else
+		ret = stream_printf(st->wr, "%c %s %s\n", type, sr->sr_file,
+		    line);
+	if (ret == -1)
+		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 = malloc(sizeof(struct status));
+	if (st == NULL)
+		err(1, "malloc");
+	st->path = path;
+	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;
+	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);
+	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);
+	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);
+	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) {
+			/* XXX */
+		}
+		lprintf(-1, "Couldn't open \"%s\"\n", path);
+		st = status_fromnull(path);
+	} else
+		st = status_fromrd(path, file);
+	if (st == NULL) {
+		asprintf(errmsg, "Error in status file \"%s\".  "
+		    "Delete it and try again.\n", path);
+		if (*errmsg == NULL)
+			err(1, "asprintf");
+		free(path);
+		return (NULL);
+	}
+
+	if (scantime != -1) {
+		/* Open for writing too. */
+		asprintf(&destpath, "%s/%s/%s/", coll->co_base,
+		    coll->co_colldir, coll->co_name);
+		if (destpath == NULL)
+			err(1, "asprintf");
+		st->tempfile = tempname(destpath);
+		free(destpath);
+		st->wr = stream_open_file(st->tempfile,
+		    O_CREAT | O_TRUNC | O_WRONLY, 0644);
+		/* XXX Create directories leading to tempfile. */
+		if (st->wr == NULL) {
+			asprintf(errmsg, "Cannot create \"%s\": %s",
+			    st->tempfile, strerror(errno));
+			if (*errmsg == NULL)
+				err(1, "asprintf");
+			status_free(st);
+			return (NULL);
+		}
+		fa = fattr_default(FT_FILE);
+		fattr_umask(fa, coll->co_umask);
+		rv = fattr_install(fa, st->tempfile, NULL);
+		fattr_free(fa);
+		if (rv == -1) {
+			asprintf(errmsg, "Cannot set attributes for \"%s\": %s",
+			    st->tempfile, strerror(errno));
+			if (*errmsg == NULL)
+				err(1, "asprintf");
+			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) {
+			/* XXX */
+		}
+	}
+	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.
+ */
+struct statusrec *
+status_get(struct status *st, char *name, int isdirup, int deleteto)
+{
+	struct statusrec key;
+	struct statusrec *sr;
+	char *line;
+	int c, error;
+
+	if (st->rd == NULL || st->eof)
+		return (NULL);
+
+	if (name == NULL) {
+		sr = status_rd(st);
+		return (sr);
+	}
+
+	if (st->current != NULL) {
+		sr = st->current;
+		st->current = NULL;
+	} else {
+		sr = status_rd(st);
+		if (sr == NULL)
+			return (NULL);
+	}
+
+	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)
+				/* XXX */
+				return (NULL);
+		}
+		/* Loop until we find the good entry. */
+		for (;;) {
+			sr = status_rdraw(st, &line);
+			if (sr == NULL)
+				return (NULL);
+			c = statusrec_cmp(sr, &key);
+			if (c >= 0)
+				break;
+			if (st->wr != NULL && !deleteto) {
+				error = status_wrraw(st, sr, line);
+				if (error)
+					return (NULL);
+			}
+		}
+		error = statusrec_cook(sr, line);
+		if (error) {
+			lprintf(-1, "Error in status file\n");
+			return (NULL);
+		}
+	}
+	if (c != 0) {
+		st->current = sr;
+		return (NULL);
+	}
+	st->current = sr;
+	return (sr);
+}
+
+/*
+ * 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;
+
+	old = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0);
+	if (old != NULL) {
+		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. */
+				old = status_get(st, sr->sr_file, 1, 1);
+				assert(old != NULL);
+			}
+		} else
+			st->current = NULL;
+	} else {
+		error = status_wr(st, old);
+		if (error)
+			/* XXX */
+			return (-1);
+	}
+	st->dirty = 1;
+	error = status_wr(st, sr);
+	if (error)
+		/* XXX */
+		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;
+
+	sr = status_get(st, name, isdirup, 0);
+	if (sr != NULL) {
+		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);
+}
+
+/*
+ * 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;
+	int error;
+
+	if (st->wr != NULL) {
+		if (st->dirty) {
+			if (st->current != NULL) {
+				error = status_wr(st, st->current);
+				if (error) {
+					asprintf(errmsg, "Write failure on "
+					    "\"%s\": %s", st->tempfile,
+					    strerror(errno));
+					if (*errmsg == NULL)
+						err(1, "asprintf");
+					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) {
+					asprintf(errmsg, "Write failure on "
+					    "\"%s\": %s", st->tempfile,
+					    strerror(errno));
+					if (*errmsg == NULL)
+						err(1, "asprintf");
+					goto bad;
+				}
+			}
+			/* Rename tempfile. */
+			error = rename(st->tempfile, st->path);
+			if (error) {
+				asprintf(errmsg, "Cannot rename \"%s\" to "
+				    "\"%s\": %s", st->tempfile, st->path,
+				    strerror(errno));
+				if (*errmsg == NULL)
+					err(1, "asprintf");
+				goto bad;
+			}
+		} else {
+			/* Just discard the tempfile. */
+			unlink(st->tempfile);
+		}
+		*errmsg = NULL;
+	}
+	status_free(st);
+	return;
+bad:
+	status_free(st);
+}
Index: status.h
===================================================================
RCS file: status.h
diff -N status.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ status.h	7 Dec 2005 01:00:35 -0000
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2003-2005, 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 _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 **);
+struct statusrec	*status_get(struct status *, char *, int, int);
+int			 status_put(struct status *, struct statusrec *);
+int			 status_eof(struct status *);
+int			 status_delete(struct status *, char *, int);
+void			 status_close(struct status *, char **);
+
+#endif /* !_STATUS_H_ */
Index: stream.c
===================================================================
RCS file: /cvs/csup/csup/stream.c,v
retrieving revision 1.42
diff -u -p -r1.42 stream.c
--- stream.c	9 Jul 2005 18:54:17 -0000	1.42
+++ stream.c	17 Dec 2005 13:54:48 -0000
@@ -103,6 +103,7 @@ struct stream {
 	stream_readfn_t readfn;
 	stream_writefn_t writefn;
 	stream_closefn_t closefn;
+	int eof;
 	struct stream_filter *filter;
 	void *fdata;
 };
@@ -318,6 +319,8 @@ stream_fdopen(int id, stream_readfn_t re
 	stream->writefn = writefn;
 	stream->closefn = closefn;
 	stream->filter = stream_filter_lookup(STREAM_FILTER_NULL);
+	stream->fdata = NULL;
+	stream->eof = 0;
 	return (stream);
 }
 
@@ -580,6 +583,14 @@ stream_rewind(struct stream *stream)
 	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)
@@ -614,11 +625,17 @@ stream_fill_default(struct stream *strea
 {
 	ssize_t n;
 
+	if (stream->eof)
+		return (0);
 	assert(buf_avail(buf) > 0);
 	n = (*stream->readfn)(stream->id, 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);
 }
@@ -689,6 +706,9 @@ stream_filter_fini(struct stream *stream
 	struct stream_filter *filter;
 
 	filter = stream->filter;
+	if (filter == NULL) {
+		fprintf(stderr, "ARG!\n");
+	}
 	if (filter->finifn != NULL)
 		(*filter->finifn)(stream);
 }
Index: stream.h
===================================================================
RCS file: /cvs/csup/csup/stream.h,v
retrieving revision 1.16
diff -u -p -r1.16 stream.h
--- stream.h	5 Jun 2005 02:27:13 -0000	1.16
+++ stream.h	7 Nov 2005 00:00:27 -0000
@@ -54,6 +54,7 @@ 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 *);
Index: updater.c
===================================================================
RCS file: /cvs/csup/csup/updater.c,v
retrieving revision 1.56
diff -u -p -r1.56 updater.c
--- updater.c	9 Jul 2005 18:54:17 -0000	1.56
+++ updater.c	17 Dec 2005 16:18:24 -0000
@@ -47,44 +47,52 @@
 #include "misc.h"
 #include "mux.h"
 #include "proto.h"
+#include "status.h"
 #include "stream.h"
 
-/* Everything we need to update a file. */
-struct update {
+/* Everything we need to context a file. */
+struct context {
 	struct coll *coll;
+	struct stream *rd;
+	struct status *st;
+
+	struct statusrec sr;
 	char *author;
-	char *rcsfile;
-	char *revnum;
-	char *revdate;
 	char *state;
 	char *tag;
+	char *cksum;
 	struct stream *from;
 	struct stream *to;
-	struct stream *stream;
-	struct fattr *rcsattr;
 	int expand;
 };
 
+static void	 context_fini(struct context *);
+
 static int	 updater_makedirs(char *);
 static void	 updater_prunedirs(char *, char *);
 static char	*updater_maketmp(const char *, const char *);
-static int	 updater_checkout(struct coll *, struct stream *, char *);
-static int	 updater_delete(struct coll *, char *);
-static int	 updater_diff(struct coll *, struct stream *, char *);
-static int	 updater_diff_batch(struct update *);
-static int	 updater_diff_apply(struct update *);
+static int	 updater_docoll(struct context *);
+static int	 updater_checkout(struct context *, char *);
+static int	 updater_delete(struct context *, char *);
+static int	 updater_diff(struct context *, char *);
+static int	 updater_diff_parseln(char *, struct context *);
+static int	 updater_diff_batch(struct context *);
+static int	 updater_diff_apply(struct context *);
 static int	 updater_install(struct coll *, struct fattr *, const char *,
 		     const char *);
 
 void *
 updater(void *arg)
 {
+	struct context ctx;
 	struct config *config;
 	struct coll *coll;
+	struct status *st;
 	struct stream *rd;
-	char *line, *cmd, *collname, *release;
+	char *line, *cmd, *errmsg, *collname, *release;
 	int error;
 
+	memset(&ctx, 0, sizeof(ctx));
 	config = arg;
 	rd = stream_fdopen(config->id1, chan_read, NULL, NULL);
 	error = 0;
@@ -93,48 +101,43 @@ updater(void *arg)
 			continue;
 		umask(coll->co_umask);
 		line = stream_getln(rd, NULL);
-		cmd = proto_getstr(&line);
-		collname = proto_getstr(&line);
-		release = proto_getstr(&line);
+		cmd = proto_get_ascii(&line);
+		collname = proto_get_ascii(&line);
+		release = proto_get_ascii(&line);
 		if (release == NULL || line != NULL)
 			goto bad;
 		if (strcmp(cmd, "COLL") != 0 ||
 		    strcmp(collname, coll->co_name) != 0 ||
 		    strcmp(release, coll->co_release) != 0)
 			goto bad;
+
+		st = status_open(coll, coll->co_scantime, &errmsg);
+		if (st == NULL) {
+			lprintf(-1, "Updater: %s\n", errmsg);
+			stream_close(rd);
+			return (NULL);
+		}
+
 		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);
-		while ((line = stream_getln(rd, NULL)) != NULL) {
-			if (strcmp(line, ".") == 0)
-				break;
-			cmd = proto_getstr(&line);
-			if (cmd == NULL)
-				goto bad;
-			if (strcmp(cmd, "T") == 0)
-				/* XXX */;
-			else if (strcmp(cmd, "c") == 0)
-				/* XXX */;
-			else if (strcmp(cmd, "U") == 0)
-				error = updater_diff(coll, rd, line);
-			else if (strcmp(cmd, "u") == 0)
-				error = updater_delete(coll, line);
-			else if (strcmp(cmd, "C") == 0)
-				error = updater_checkout(coll, rd, line);
-			else if (strcmp(cmd, "D") == 0)
-				error = updater_delete(coll, line);
-			else {
-				lprintf(-1, "Updater: Unknown command: "
-				    "\"%s\"\n", cmd);
-				goto bad;
-			}
-			if (error)
-				goto bad;
-		}
-		if (line == NULL)
+
+		ctx.coll = coll;
+		ctx.rd = rd;
+		ctx.st = st;
+		error = updater_docoll(&ctx);
+		if (error)
 			goto bad;
+
+		status_close(st, &errmsg);
+		if (errmsg != NULL) {
+			lprintf(-1, "Updater: %s\n", errmsg);
+			stream_close(rd);
+			return (NULL);
+		}
+
 		if (coll->co_options & CO_COMPRESS)
 			stream_filter_stop(rd);
 	}
@@ -150,192 +153,284 @@ bad:
 }
 
 static int
-updater_delete(struct coll *coll, char *line)
+updater_docoll(struct context *ctx)
 {
-	char *file, *rcsfile;
+	struct coll *coll;
+	char *cmd, *line;
 	int error;
 
-	rcsfile = proto_getstr(&line);
-	file = checkoutpath(coll->co_prefix, rcsfile);
-	if (file == NULL) {
-		lprintf(-1, "Updater: Bad filename \"%s\"\n", rcsfile);
+	error = 0;
+	coll = ctx->coll;
+	while ((line = stream_getln(ctx->rd, NULL)) != NULL) {
+		if (strcmp(line, ".") == 0)
+			break;
+		cmd = proto_get_ascii(&line);
+		if (cmd == NULL)
+			return (-1);
+		if (strcmp(cmd, "T") == 0)
+			;
+			//lprintf(-1, "T %s\n", line);
+		else if (strcmp(cmd, "c") == 0)
+			;
+			//lprintf(-1, "c %s\n", line);
+		else if (strcmp(cmd, "U") == 0)
+			error = updater_diff(ctx, line);
+		else if (strcmp(cmd, "u") == 0)
+			error = updater_delete(ctx, line);
+		else if (strcmp(cmd, "C") == 0)
+			error = updater_checkout(ctx, line);
+		else if (strcmp(cmd, "D") == 0)
+			error = updater_delete(ctx, line);
+		else {
+			lprintf(-1, "Updater: Unknown command: "
+			    "\"%s\"\n", cmd);
+			return (-1);
+		}
+		if (error)
+			return (-1);
+	}
+	if (line == NULL)
 		return (-1);
+	return (0);
+}
+
+static int
+updater_delete(struct context *ctx, char *line)
+{
+	struct coll *coll;
+	char *file, *name;
+	int error;
+
+	coll = ctx->coll;
+	name = proto_get_ascii(&line);
+	/* XXX - destDir handling */
+	if (coll->co_options & CO_DELETE) {
+		file = checkoutpath(coll->co_prefix, name);
+		if (file == NULL) {
+			lprintf(-1, "Updater: Bad filename \"%s\"\n", name);
+			return (-1);
+		}
+		lprintf(1, " Delete %s\n", file + strlen(coll->co_prefix) + 1);
+		error = unlink(file);
+		if (error) {
+			free(file);
+			return (error);
+		}
+		updater_prunedirs(coll->co_prefix, file);
+		free(file);
 	}
-	lprintf(1, " Delete %s\n", file + strlen(coll->co_prefix) + 1);
-	error = unlink(file);
+	error = status_delete(ctx->st, name, 0);
 	if (error) {
-		free(file);
-		return (error);
+		/* XXX */
+		return (-1);
 	}
-	updater_prunedirs(coll->co_prefix, file);
-	free(file);
 	return (0);
 }
 
 static int
-updater_diff(struct coll *coll, struct stream *rd, char *line)
+updater_diff_parseln(char *line, struct context *ctx)
+{
+	struct statusrec *sr;
+	char *cp, *name, *tag, *date;
+	char *expand, *attr, *cksum;
+
+	cp = line;
+	name = proto_get_ascii(&cp);
+	tag = proto_get_ascii(&cp);
+	date = proto_get_ascii(&cp);
+	proto_get_ascii(&cp);	/* XXX - oldRevNum */
+	proto_get_ascii(&cp);	/* XXX - fromAttic */
+	proto_get_ascii(&cp);	/* XXX - logLines */
+	expand = proto_get_ascii(&cp);
+	attr = proto_get_ascii(&cp);
+	cksum = proto_get_ascii(&cp);
+	if (cksum == NULL || cp != NULL)
+		return (-1);
+
+	sr = &ctx->sr;
+	sr->sr_type = SR_CHECKOUTLIVE;
+	sr->sr_file = strdup(name);
+	sr->sr_date = strdup(date);
+	sr->sr_tag = strdup(tag);
+	if (sr->sr_file == NULL || sr->sr_date == NULL || sr->sr_tag == NULL)
+		err(1, "strdup");
+	sr->sr_serverattr = fattr_decode(attr);
+
+	if (strcmp(expand, ".") == 0)
+		ctx->expand = EXPAND_DEFAULT;
+	else if (strcmp(expand, "kv") == 0)
+		ctx->expand = EXPAND_KEYVALUE;
+	else if (strcmp(expand, "kvl") == 0)
+		ctx->expand = EXPAND_KEYVALUELOCKER;
+	else if (strcmp(expand, "k") == 0)
+		ctx->expand = EXPAND_KEY;
+	else if (strcmp(expand, "o") == 0)
+		ctx->expand = EXPAND_OLD;
+	else if (strcmp(expand, "b") == 0)
+		ctx->expand = EXPAND_BINARY;
+	else if (strcmp(expand, "v") == 0)
+		ctx->expand = EXPAND_VALUE;
+	else
+		return (-1);
+
+	ctx->tag = strdup(tag);
+	ctx->cksum = strdup(cksum);
+	if (ctx->cksum == NULL || ctx->tag == NULL)
+		err(1, "strdup");
+	return (0);
+}
+
+static void
+context_fini(struct context *ctx)
+{
+	struct statusrec *sr;
+
+	sr = &ctx->sr;
+	free(ctx->author);
+	ctx->author = NULL;
+	free(ctx->cksum);
+	ctx->cksum = NULL;
+	free(ctx->tag);
+	ctx->tag = NULL;
+	stream_close(ctx->from);
+	ctx->from = NULL;
+	stream_close(ctx->to);
+	ctx->to = NULL;
+	free(sr->sr_file);
+	free(sr->sr_tag);
+	free(sr->sr_date);
+	free(sr->sr_revnum);
+	free(sr->sr_revdate);
+	fattr_free(sr->sr_clientattr);
+	fattr_free(sr->sr_serverattr);
+	memset(sr, 0, sizeof(*sr));
+}
+
+static int
+updater_diff(struct context *ctx, char *line)
 {
 	char md5[MD5_DIGEST_SIZE];
-	struct update up;
-	struct fattr *rcsattr, *fa;
-	char *author, *expand, *tag, *rcsfile, *revnum, *revdate, *path;
-	char *attr, *cp, *cksum, *line2, *line3, *tok, *topath;
+	struct coll *coll;
+	struct statusrec *sr;
+	struct fattr *fa;
+	char *author, *path, *revnum, *revdate;
+	char *tok, *topath;
 	int error;
 
-	line3 = NULL;
-	rcsattr = NULL;
 	path = NULL;
 	topath = NULL;
-	memset(&up, 0, sizeof(struct update));
-	up.coll = coll;
+	sr = &ctx->sr;
+	coll = ctx->coll;
 
-	line2 = strdup(line);
-	if (line2 == NULL)
-		err(1, "strdup");
-	cp = line2;
-	rcsfile = proto_getstr(&cp);
-	tag = proto_getstr(&cp);
-	proto_getstr(&cp); /* XXX - date */
-	proto_getstr(&cp); /* XXX - orig revnum */
-	proto_getstr(&cp); /* XXX - from attic */
-	proto_getstr(&cp); /* XXX - loglines */
-	expand = proto_getstr(&cp);
-	attr = proto_getstr(&cp);
-	cksum = proto_getstr(&cp);
-	if (cksum == NULL || cp != NULL)
+	error = updater_diff_parseln(line, ctx);
+	if (error) {
+		lprintf(-1, "Updater: Parse error\n");
 		goto bad;
-	path = checkoutpath(coll->co_prefix, rcsfile);
+	}
+
+	path = checkoutpath(coll->co_prefix, sr->sr_file);
 	if (path == NULL) {
-		lprintf(-1, "Updater: bad filename %s\n", rcsfile);
+		lprintf(-1, "Updater: Bad filename \"%s\"\n", sr->sr_file);
 		goto bad;
 	}
-	if (strcmp(expand, ".") == 0)
-		up.expand = EXPAND_DEFAULT;
-	else if (strcmp(expand, "kv") == 0)
-		up.expand = EXPAND_KEYVALUE;
-	else if (strcmp(expand, "kvl") == 0)
-		up.expand = EXPAND_KEYVALUELOCKER;
-	else if (strcmp(expand, "k") == 0)
-		up.expand = EXPAND_KEY;
-	else if (strcmp(expand, "o") == 0)
-		up.expand = EXPAND_OLD;
-	else if (strcmp(expand, "b") == 0)
-		up.expand = EXPAND_BINARY;
-	else if (strcmp(expand, "v") == 0)
-		up.expand = EXPAND_VALUE;
-	else {
-		lprintf(-1, "Updater: invalid expansion mode \"%s\"\n",
-		    expand);
-		goto bad;
-	}
-	rcsattr = fattr_decode(attr);
-	if (rcsattr == NULL) {
-		lprintf(-1, "Updater: invalid file attributes \"%s\"\n",
-		    attr);
-		goto bad;
-	}
-	up.rcsattr = rcsattr;
-	up.rcsfile = rcsfile;
-	if (strcmp(tag, ".") != 0)
-		up.tag = tag;
 
 	lprintf(1, " Edit %s\n", path + strlen(coll->co_prefix) + 1);
-	while ((line = stream_getln(rd, NULL)) != NULL) {
+	while ((line = stream_getln(ctx->rd, NULL)) != NULL) {
 		if (strcmp(line, ".") == 0)
 			break;
-		tok = proto_getstr(&line);
+		tok = proto_get_ascii(&line);
 		if (strcmp(tok, "D") != 0)
 			goto bad;
-		free(line3);
-		line3 = strdup(line);
-		if (line3 == NULL)
-			err(1, "strdup");
-		cp = line3;
-		revnum = proto_getstr(&cp);
-		proto_getstr(&cp); /* XXX - diffbase */
-		revdate = proto_getstr(&cp);
-		author = proto_getstr(&cp);
-		if (author == NULL || cp != NULL)
+		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)
 			goto bad;
-		up.revnum = revnum;
-		up.revdate = revdate;
-		up.author = author;
-		up.stream = rd;
-		if (up.from == NULL) {
+		if (sr->sr_revnum != NULL)
+			free(sr->sr_revnum);
+		if (sr->sr_revdate != NULL)
+			free(sr->sr_revdate);
+		if (ctx->author != NULL)
+			free(ctx->author);
+		ctx->author = strdup(author);
+		sr->sr_revnum = strdup(revnum);
+		sr->sr_revdate = strdup(revdate);
+		if (ctx->author == NULL || sr->sr_revnum == NULL ||
+		    sr->sr_revdate == NULL)
+			err(1, "strdup");
+		if (ctx->from == NULL) {
 			/* First patch, the "origin" file is the one we have. */
-			up.from = stream_open_file(path, O_RDONLY);
-			if (up.from == NULL)
+			ctx->from = stream_open_file(path, O_RDONLY);
+			if (ctx->from == NULL)
 				goto bad;
 		} else {
 			/* Subsequent patches. */
-			stream_close(up.from);
-			up.from = up.to;
-			stream_rewind(up.from);
+			stream_close(ctx->from);
+			ctx->from = ctx->to;
+			stream_rewind(ctx->from);
 			/* XXX */
 			if (unlink(topath) == -1)
 				warn("unlink");
 			free(topath);
 		}
-		topath = updater_maketmp(coll->co_prefix, up.rcsfile);
+		topath = updater_maketmp(coll->co_prefix, sr->sr_file);
 		if (topath == NULL) {
 			perror("Cannot create temporary file");
 			goto bad;
 		}
-		up.to = stream_open_file(topath, O_RDWR | O_CREAT | O_EXCL,
+		ctx->to = stream_open_file(topath,O_RDWR | O_CREAT | O_EXCL,
 		    0600);
-		if (up.to == NULL)
+		if (ctx->to == NULL)
 			goto bad;
 		lprintf(2, "  Add delta %s %s %s\n", revnum, revdate, author);
-		error = updater_diff_batch(&up);
+		error = updater_diff_batch(ctx);
 		if (error)
 			goto bad;
 	}
 	if (line == NULL)
 		goto bad;
-	fa = fattr_dup(rcsattr);
+	fa = fattr_dup(sr->sr_serverattr);
 	fattr_maskout(fa, FA_MODTIME);
 	updater_install(coll, fa, topath, path);
-	fattr_free(fa);
-	/* XXX - Compute MD5 while writing the file. */
+	sr->sr_clientattr = fa;
+	error = status_put(ctx->st, sr);
+	if (error) {
+		lprintf(-1, "Updater: status_put() failed!\n");
+		goto bad;
+	}
+
 	if (MD5file(path, md5) == -1) {
 		lprintf(-1, "%s: MD5file() failed\n", __func__);
 		goto bad;
 	}
-	if (strcmp(cksum, md5) != 0) {
+	if (strcmp(ctx->cksum, md5) != 0) {
 		lprintf(-1, "Updater: Bad MD5 checksum for \"%s\"\n", path);
 		goto bad;
 	}
-	stream_close(up.from);
-	stream_close(up.to);
+	context_fini(ctx);
 	free(topath);
-	fattr_free(rcsattr);
 	free(path);
-	free(line3);
-	free(line2);
 	return (0);
 bad:
-	stream_close(up.from);
-	stream_close(up.to);
+	context_fini(ctx);
 	free(topath);
-	fattr_free(rcsattr);
 	free(path);
-	free(line3);
-	free(line2);
 	return (-1);
 }
 
 static int
-updater_diff_batch(struct update *up)
+updater_diff_batch(struct context *ctx)
 {
 	struct stream *rd;
 	char *tok, *line;
 	int error;
 
-	rd = up->stream;
+	rd = ctx->rd;
 	while ((line = stream_getln(rd, NULL)) != NULL) {
 		if (strcmp(line, ".") == 0)
 			break;
-		tok = proto_getstr(&line);
+		tok = proto_get_ascii(&line);
 		if (tok == NULL)
 			goto bad;
 		if (strcmp(tok, "L") == 0) {
@@ -347,53 +442,57 @@ updater_diff_batch(struct update *up)
 			if (line == NULL)
 				goto bad;
 		} else if (strcmp(tok, "S") == 0) {
-			tok = proto_getstr(&line);
+			tok = proto_get_ascii(&line);
 			if (tok == NULL || line != NULL)
 				goto bad;
-			free(up->state);
-			up->state = strdup(tok);
-			if (up->state == NULL)
+			free(ctx->state);
+			ctx->state = strdup(tok);
+			if (ctx->state == NULL)
 				err(1, "strdup");
 		} else if (strcmp(tok, "T") == 0) {
-			error = updater_diff_apply(up);
+			error = updater_diff_apply(ctx);
 			if (error) {
-				free(up->state);
-				up->state = NULL;
+				free(ctx->state);
+				ctx->state = NULL;
 				return (error);
 			}
 		}
 	}
 	if (line == NULL)
 		goto bad;
-	free(up->state);
-	up->state = NULL;
+	free(ctx->state);
+	ctx->state = NULL;
 	return (0);
 bad:
 	lprintf(-1, "Updater: Protocol error\n");
-	free(up->state);
-	up->state = NULL;
+	free(ctx->state);
+	ctx->state = NULL;
 	return (-1);
 }
 
 int
-updater_diff_apply(struct update *up)
+updater_diff_apply(struct context *ctx)
 {
 	struct diff diff;
+	struct coll *coll;
+	struct statusrec *sr;
 	int error;
 
-	/* XXX - This is stupid, both structs should be merged. */
-	diff.d_orig = up->from;
-	diff.d_to = up->to;
-	diff.d_diff = up->stream;
-	diff.d_author = up->author;
-	diff.d_cvsroot = up->coll->co_cvsroot;
-	diff.d_rcsfile = up->rcsfile;
-	diff.d_revnum = up->revnum;
-	diff.d_revdate = up->revdate;
-	diff.d_state = up->state;
-	diff.d_tag = up->tag;
-	diff.d_expand = up->expand;
-	error = diff_apply(&diff, up->coll->co_keyword);
+	sr = &ctx->sr;
+	coll = ctx->coll;
+
+	diff.d_orig = ctx->from;
+	diff.d_to = ctx->to;
+	diff.d_diff = ctx->rd;
+	diff.d_author = ctx->author;
+	diff.d_cvsroot = coll->co_cvsroot;
+	diff.d_rcsfile = sr->sr_file;
+	diff.d_revnum = sr->sr_revnum;
+	diff.d_revdate = sr->sr_revdate;
+	diff.d_state = ctx->state;
+	diff.d_tag = ctx->tag;
+	diff.d_expand = ctx->expand;
+	error = diff_apply(&diff, coll->co_keyword);
 	if (error) {
 		lprintf(-1, "Updater: Bad diff from server\n");
 		return (-1);
@@ -402,22 +501,26 @@ updater_diff_apply(struct update *up)
 }
 
 static int
-updater_checkout(struct coll *coll, struct stream *rd, char *line)
+updater_checkout(struct context *ctx, char *line)
 {
 	char md5[MD5_DIGEST_SIZE];
+	struct stream *rd;
+	struct coll *coll;
 	struct fattr *fa;
 	struct stream *to;
 	char *attr, *cksum, *cmd, *file, *rcsfile;
+	char *tag, *date, *revnum, *revdate;
 	size_t size;
 	int error, first;
 
-	rcsfile = proto_getstr(&line);
-	/* I'll need those when the status file is supported. */
-	proto_getstr(&line);	/* tag */
-	proto_getstr(&line);	/* date */
-	proto_getstr(&line);	/* revnum */
-	proto_getstr(&line);	/* revdate */
-	attr = proto_getstr(&line);
+	rd = ctx->rd;
+	coll = ctx->coll;
+	rcsfile = 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 (-1);
 
@@ -436,7 +539,7 @@ updater_checkout(struct coll *coll, stru
 	lprintf(1, " Checkout %s\n", file + strlen(coll->co_prefix) + 1);
 	error = updater_makedirs(file);
 	if (error) {
-		lprintf(-1, "Updater: Can't create directory hierarchy: %s\n",
+		lprintf(-1, "Updater: Cannot create directory hierarchy: %s\n",
 		    strerror(errno));
 		goto bad;
 	}
@@ -474,8 +577,8 @@ updater_checkout(struct coll *coll, stru
 	stream_close(to);
 	/* Get the checksum line. */
 	line = stream_getln(rd, NULL);
-	cmd = proto_getstr(&line);
-	cksum = proto_getstr(&line);
+	cmd = proto_get_ascii(&line);
+	cksum = proto_get_ascii(&line);
 	if (cksum == NULL || line != NULL ||
 	    strcmp(cmd, "5") != 0) {
 		goto bad;
@@ -566,6 +669,6 @@ updater_install(struct coll *coll, struc
 	tmp = fattr_forcheckout(fa, coll->co_umask);
 	fattr_override(fa, tmp, FA_MASK);
 	fattr_free(tmp);
-	fattr_install(fa, from, to);
+	fattr_install(fa, to, from);
 	return (0);
 }
