summaryrefslogtreecommitdiffstats
path: root/usr.bin/csup
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/csup')
-rw-r--r--usr.bin/csup/Makefile46
-rw-r--r--usr.bin/csup/README39
-rw-r--r--usr.bin/csup/TODO29
-rw-r--r--usr.bin/csup/attrstack.c90
-rw-r--r--usr.bin/csup/attrstack.h40
-rw-r--r--usr.bin/csup/auth.c331
-rw-r--r--usr.bin/csup/auth.h38
-rw-r--r--usr.bin/csup/config.c579
-rw-r--r--usr.bin/csup/config.h127
-rw-r--r--usr.bin/csup/cpasswd.1120
-rwxr-xr-xusr.bin/csup/cpasswd.sh135
-rw-r--r--usr.bin/csup/csup.11000
-rw-r--r--usr.bin/csup/detailer.c603
-rw-r--r--usr.bin/csup/detailer.h33
-rw-r--r--usr.bin/csup/diff.c438
-rw-r--r--usr.bin/csup/diff.h52
-rw-r--r--usr.bin/csup/fattr.c981
-rw-r--r--usr.bin/csup/fattr.h118
-rw-r--r--usr.bin/csup/fattr_bsd.h52
-rw-r--r--usr.bin/csup/fattr_posix.h48
-rw-r--r--usr.bin/csup/fixups.c198
-rw-r--r--usr.bin/csup/fixups.h48
-rw-r--r--usr.bin/csup/fnmatch.c199
-rw-r--r--usr.bin/csup/fnmatch.h58
-rw-r--r--usr.bin/csup/globtree.c393
-rw-r--r--usr.bin/csup/globtree.h45
-rw-r--r--usr.bin/csup/idcache.c421
-rw-r--r--usr.bin/csup/idcache.h41
-rw-r--r--usr.bin/csup/keyword.c525
-rw-r--r--usr.bin/csup/keyword.h54
-rw-r--r--usr.bin/csup/lex.rcs.c2094
-rw-r--r--usr.bin/csup/lister.c569
-rw-r--r--usr.bin/csup/lister.h33
-rw-r--r--usr.bin/csup/main.c347
-rw-r--r--usr.bin/csup/main.h29
-rw-r--r--usr.bin/csup/misc.c645
-rw-r--r--usr.bin/csup/misc.h138
-rw-r--r--usr.bin/csup/mux.c1202
-rw-r--r--usr.bin/csup/mux.h45
-rw-r--r--usr.bin/csup/parse.y91
-rw-r--r--usr.bin/csup/pathcomp.c182
-rw-r--r--usr.bin/csup/pathcomp.h44
-rw-r--r--usr.bin/csup/proto.c997
-rw-r--r--usr.bin/csup/proto.h50
-rw-r--r--usr.bin/csup/queue.h227
-rw-r--r--usr.bin/csup/rcsfile.c1412
-rw-r--r--usr.bin/csup/rcsfile.h73
-rw-r--r--usr.bin/csup/rcsparse.c357
-rw-r--r--usr.bin/csup/rcsparse.h41
-rw-r--r--usr.bin/csup/rcstokenizer.h333
-rw-r--r--usr.bin/csup/rcstokenizer.l73
-rw-r--r--usr.bin/csup/rsyncfile.c223
-rw-r--r--usr.bin/csup/rsyncfile.h41
-rw-r--r--usr.bin/csup/status.c874
-rw-r--r--usr.bin/csup/status.h72
-rw-r--r--usr.bin/csup/stream.c1303
-rw-r--r--usr.bin/csup/stream.h84
-rw-r--r--usr.bin/csup/threads.c176
-rw-r--r--usr.bin/csup/threads.h38
-rw-r--r--usr.bin/csup/token.h49
-rw-r--r--usr.bin/csup/token.l80
-rw-r--r--usr.bin/csup/updater.c2044
-rw-r--r--usr.bin/csup/updater.h33
63 files changed, 20847 insertions, 33 deletions
diff --git a/usr.bin/csup/Makefile b/usr.bin/csup/Makefile
index 417de54..af1815c 100644
--- a/usr.bin/csup/Makefile
+++ b/usr.bin/csup/Makefile
@@ -1,42 +1,22 @@
# $FreeBSD$
-.PATH: ${.CURDIR}/../../contrib/csup
+PREFIX?= /usr/local
+BINDIR?= ${PREFIX}/bin
+MANDIR?= ${PREFIX}/man/man
+
+UNAME!= /usr/bin/uname -s
PROG= csup
-SRCS= attrstack.c \
- auth.c \
- config.c \
- detailer.c \
- diff.c \
- fattr.c \
- fixups.c \
- fnmatch.c \
- globtree.c \
- idcache.c \
- keyword.c \
- lex.rcs.c \
- lister.c \
- main.c \
- misc.c \
- mux.c \
- parse.y \
- pathcomp.c \
- proto.c \
- rcsfile.c \
- rcsparse.c \
- rsyncfile.c \
- status.c \
- stream.c \
- threads.c \
- token.l \
- updater.c
+SRCS= attrstack.c auth.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 \
+ rcsfile.c rcsparse.c lex.rcs.c rsyncfile.c
-CFLAGS+= -I. -I${.CURDIR}/../../contrib/csup
-CFLAGS+= -DHAVE_FFLAGS -DNDEBUG
-WARNS?= 1
+CFLAGS+= -I. -I${.CURDIR} -g -pthread -DHAVE_FFLAGS -DNDEBUG
+WARNS?= 1
-DPADD= ${LIBCRYPTO} ${LIBZ} ${LIBPTHREAD}
-LDADD= -lcrypto -lz -lpthread
+DPADD= ${LIBCRYPTO} ${LIBZ}
+LDADD= -lcrypto -lz
SCRIPTS= cpasswd.sh
MAN= csup.1 cpasswd.1
diff --git a/usr.bin/csup/README b/usr.bin/csup/README
new file mode 100644
index 0000000..879c481
--- /dev/null
+++ b/usr.bin/csup/README
@@ -0,0 +1,39 @@
+$FreeBSD$
+
+Authors
+-------
+
+CVSup was originally written in Modula-3 by
+ John Polstra <jdp@polstra.com>.
+
+Csup is a rewrite of CVSup in C. It has been mostly written by
+ Maxime Henrion <mux@FreeBSD.org>.
+
+A few contributors have helped him in his task and they are listed here in
+alphabetical order :
+
+ Olivier Houchard <cognet@FreeBSD.org>
+ Ulf Lilleengen <lulf@kerneled.org>
+ Christoph Mathys <cmathys@bluewin.ch> (Google SoC Project)
+ Etienne Vidal <etienne.vidal@gmail.com>
+
+
+Building & Installing
+---------------------
+
+Csup should build and run fine under any *BSD OS (that includes FreeBSD,
+NetBSD, OpenBSD and DragonFlyBSD), as well as Linux and Darwin. If you
+have a problem building from source, drop me a mail!
+
+There is one Makefile specifically tailored for *BSD systems named
+Makefile and another one that is gmake-specific for Darwin and Linux
+users named GNUmakefile. You don't really need to worry about that
+since whatever your "make" command is, it should pick up the correct
+Makefile.
+
+As usual, to build the source code, just run "make". Once this is done,
+just run "make install" to install the binary and manual page.
+
+Be warned however that if the packaging system of your OS knows about
+csup, it is certainly better to install it from there rather than by
+hand, so that it can then be properly deinstalled.
diff --git a/usr.bin/csup/TODO b/usr.bin/csup/TODO
new file mode 100644
index 0000000..f4f0b31
--- /dev/null
+++ b/usr.bin/csup/TODO
@@ -0,0 +1,29 @@
+$FreeBSD$
+
+BUGS:
+
+- Fix every XXX in the code :-).
+- The stream API needs some polishing. It needs proper error numbers
+ and a stream_error() function similar to the ferror() function.
+- The yacc/lex code to parse the configuration file is sub-optimal. It
+ has global variables because of yacc, but I think it should be possible
+ to do it better by using YYFUNC_PROTOTYPE or something. I think it
+ should also be possible to completely get rid of the lex file.
+- 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 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.
diff --git a/usr.bin/csup/attrstack.c b/usr.bin/csup/attrstack.c
new file mode 100644
index 0000000..f5f56b3
--- /dev/null
+++ b/usr.bin/csup/attrstack.c
@@ -0,0 +1,90 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id$
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "attrstack.h"
+#include "fattr.h"
+#include "misc.h"
+
+#define ATTRSTACK_DEFSIZE 16 /* Initial size of the stack. */
+
+struct attrstack {
+ struct fattr **stack;
+ size_t cur;
+ size_t size;
+};
+
+struct attrstack *
+attrstack_new(void)
+{
+ struct attrstack *as;
+
+ as = xmalloc(sizeof(struct attrstack));
+ as->stack = xmalloc(sizeof(struct fattr *) * ATTRSTACK_DEFSIZE);
+ as->size = ATTRSTACK_DEFSIZE;
+ as->cur = 0;
+ return (as);
+}
+
+struct fattr *
+attrstack_pop(struct attrstack *as)
+{
+
+ assert(as->cur > 0);
+ return (as->stack[--as->cur]);
+}
+
+void
+attrstack_push(struct attrstack *as, struct fattr *fa)
+{
+
+ if (as->cur >= as->size) {
+ as->size *= 2;
+ as->stack = xrealloc(as->stack,
+ sizeof(struct fattr *) * as->size);
+ }
+ as->stack[as->cur++] = fa;
+}
+
+size_t
+attrstack_size(struct attrstack *as)
+{
+
+ return (as->cur);
+}
+
+void
+attrstack_free(struct attrstack *as)
+{
+
+ assert(as->cur == 0);
+ free(as->stack);
+ free(as);
+}
diff --git a/usr.bin/csup/attrstack.h b/usr.bin/csup/attrstack.h
new file mode 100644
index 0000000..721313c
--- /dev/null
+++ b/usr.bin/csup/attrstack.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id$
+ */
+#ifndef _ATTRSTACK_H_
+#define _ATTRSTACK_H_
+
+struct fattr;
+struct attrstack;
+
+struct attrstack *attrstack_new(void);
+void attrstack_push(struct attrstack *, struct fattr *);
+struct fattr *attrstack_pop(struct attrstack *);
+size_t attrstack_size(struct attrstack *);
+void attrstack_free(struct attrstack *);
+
+#endif /* !_ATTRSTACK_H_ */
diff --git a/usr.bin/csup/auth.c b/usr.bin/csup/auth.c
new file mode 100644
index 0000000..4e79bc5
--- /dev/null
+++ b/usr.bin/csup/auth.c
@@ -0,0 +1,331 @@
+/*-
+ * Copyright (c) 2003-2007, Petar Zhivkov Petrov <pesho.petrov@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <openssl/md5.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "auth.h"
+#include "config.h"
+#include "misc.h"
+#include "proto.h"
+#include "stream.h"
+
+#define MD5_BYTES 16
+
+/* This should be at least 2 * MD5_BYTES + 6 (length of "$md5$" + 1) */
+#define MD5_CHARS_MAX (2*(MD5_BYTES)+6)
+
+struct srvrecord {
+ char server[MAXHOSTNAMELEN];
+ char client[256];
+ char password[256];
+};
+
+static int auth_domd5auth(struct config *);
+static int auth_lookuprecord(char *, struct srvrecord *);
+static int auth_parsetoken(char **, char *, int);
+static void auth_makesecret(struct srvrecord *, char *);
+static void auth_makeresponse(char *, char *, char *);
+static void auth_readablesum(unsigned char *, char *);
+static void auth_makechallenge(struct config *, char *);
+static int auth_checkresponse(char *, char *, char *);
+
+int auth_login(struct config *config)
+{
+ struct stream *s;
+ char hostbuf[MAXHOSTNAMELEN];
+ char *login, *host;
+ 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);
+ error = auth_domd5auth(config);
+ return (error);
+}
+
+static int
+auth_domd5auth(struct config *config)
+{
+ struct stream *s;
+ char *line, *cmd, *challenge, *realm, *client, *srvresponse, *msg;
+ char shrdsecret[MD5_CHARS_MAX], response[MD5_CHARS_MAX];
+ char clichallenge[MD5_CHARS_MAX];
+ struct srvrecord auth;
+ int error;
+
+ lprintf(2, "MD5 authentication started\n");
+ s = config->server;
+ 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 ||
+ (strcmp(cmd, "AUTHMD5") != 0)) {
+ lprintf(-1, "Invalid server reply to USER\n");
+ return (STATUS_FAILURE);
+ }
+
+ client = NULL;
+ response[0] = clichallenge[0] = '.';
+ response[1] = clichallenge[1] = 0;
+ if (config->reqauth || (strcmp(challenge, ".") != 0)) {
+ if (strcmp(realm, ".") == 0) {
+ lprintf(-1, "Authentication required, but not enabled on server\n");
+ return (STATUS_FAILURE);
+ }
+ error = auth_lookuprecord(realm, &auth);
+ if (error != STATUS_SUCCESS)
+ return (error);
+ client = auth.client;
+ auth_makesecret(&auth, shrdsecret);
+ }
+
+ if (strcmp(challenge, ".") != 0)
+ auth_makeresponse(challenge, shrdsecret, response);
+ if (config->reqauth)
+ auth_makechallenge(config, clichallenge);
+ proto_printf(s, "AUTHMD5 %s %s %s\n",
+ client == NULL ? "." : client, response, clichallenge);
+ 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) {
+ srvresponse = proto_get_ascii(&line);
+ if (srvresponse == NULL)
+ goto bad;
+ if (config->reqauth &&
+ !auth_checkresponse(srvresponse, clichallenge, shrdsecret)) {
+ lprintf(-1, "Server failed to authenticate itself to client\n");
+ return (STATUS_FAILURE);
+ }
+ lprintf(2, "MD5 authentication successfull\n");
+ 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);
+}
+
+static int
+auth_lookuprecord(char *server, struct srvrecord *auth)
+{
+ char *home, *line, authfile[FILENAME_MAX];
+ struct stream *s;
+ int linenum = 0, error;
+
+ home = getenv("HOME");
+ if (home == NULL) {
+ lprintf(-1, "Environment variable \"HOME\" is not set\n");
+ return (STATUS_FAILURE);
+ }
+ snprintf(authfile, sizeof(authfile), "%s/%s", home, AUTHFILE);
+ s = stream_open_file(authfile, O_RDONLY);
+ if (s == NULL) {
+ lprintf(-1, "Could not open file %s\n", authfile);
+ return (STATUS_FAILURE);
+ }
+
+ while ((line = stream_getln(s, NULL)) != NULL) {
+ linenum++;
+ if (line[0] == '#' || line[0] == '\0')
+ continue;
+ error = auth_parsetoken(&line, auth->server,
+ sizeof(auth->server));
+ if (error != STATUS_SUCCESS) {
+ lprintf(-1, "%s:%d Missng client name\n", authfile, linenum);
+ goto close;
+ }
+ /* Skip the rest of this line, it isn't what we are looking for. */
+ if (strcmp(auth->server, server) != 0)
+ continue;
+ error = auth_parsetoken(&line, auth->client,
+ sizeof(auth->client));
+ if (error != STATUS_SUCCESS) {
+ lprintf(-1, "%s:%d Missng password\n", authfile, linenum);
+ goto close;
+ }
+ error = auth_parsetoken(&line, auth->password,
+ sizeof(auth->password));
+ if (error != STATUS_SUCCESS) {
+ lprintf(-1, "%s:%d Missng comment\n", authfile, linenum);
+ goto close;
+ }
+ stream_close(s);
+ lprintf(2, "Found authentication record for server \"%s\"\n",
+ server);
+ return (STATUS_SUCCESS);
+ }
+ lprintf(-1, "Unknown server \"%s\". Fix your %s\n", server , authfile);
+ memset(auth->password, 0, sizeof(auth->password));
+close:
+ stream_close(s);
+ return (STATUS_FAILURE);
+}
+
+static int
+auth_parsetoken(char **line, char *buf, int len)
+{
+ char *colon;
+
+ colon = strchr(*line, ':');
+ if (colon == NULL)
+ return (STATUS_FAILURE);
+ *colon = 0;
+ buf[len - 1] = 0;
+ strncpy(buf, *line, len - 1);
+ *line = colon + 1;
+ return (STATUS_SUCCESS);
+}
+
+static void
+auth_makesecret(struct srvrecord *auth, char *secret)
+{
+ char *s, ch;
+ const char *md5salt = "$md5$";
+ unsigned char md5sum[MD5_BYTES];
+ MD5_CTX md5;
+
+ MD5_Init(&md5);
+ for (s = auth->client; *s != 0; ++s) {
+ ch = tolower(*s);
+ MD5_Update(&md5, &ch, 1);
+ }
+ MD5_Update(&md5, ":", 1);
+ for (s = auth->server; *s != 0; ++s) {
+ ch = tolower(*s);
+ MD5_Update(&md5, &ch, 1);
+ }
+ MD5_Update(&md5, ":", 1);
+ MD5_Update(&md5, auth->password, strlen(auth->password));
+ MD5_Final(md5sum, &md5);
+ memset(secret, 0, sizeof(secret));
+ strcpy(secret, md5salt);
+ auth_readablesum(md5sum, secret + strlen(md5salt));
+}
+
+static void
+auth_makeresponse(char *challenge, char *sharedsecret, char *response)
+{
+ MD5_CTX md5;
+ unsigned char md5sum[MD5_BYTES];
+
+ MD5_Init(&md5);
+ MD5_Update(&md5, sharedsecret, strlen(sharedsecret));
+ MD5_Update(&md5, ":", 1);
+ MD5_Update(&md5, challenge, strlen(challenge));
+ MD5_Final(md5sum, &md5);
+ auth_readablesum(md5sum, response);
+}
+
+/*
+ * Generates a challenge string which is an MD5 sum
+ * of a fairly random string. The purpose is to decrease
+ * the possibility of generating the same challenge
+ * string (even by different clients) more then once
+ * for the same server.
+ */
+static void
+auth_makechallenge(struct config *config, char *challenge)
+{
+ MD5_CTX md5;
+ unsigned char md5sum[MD5_BYTES];
+ char buf[128];
+ struct timeval tv;
+ struct sockaddr_in laddr;
+ pid_t pid, ppid;
+ int error, addrlen;
+
+ gettimeofday(&tv, NULL);
+ pid = getpid();
+ ppid = getppid();
+ srand(tv.tv_usec ^ tv.tv_sec ^ pid);
+ addrlen = sizeof(laddr);
+ error = getsockname(config->socket, (struct sockaddr *)&laddr, &addrlen);
+ if (error < 0) {
+ memset(&laddr, 0, sizeof(laddr));
+ }
+ gettimeofday(&tv, NULL);
+ MD5_Init(&md5);
+ snprintf(buf, sizeof(buf), "%s:%ld:%ld:%ld:%d:%d",
+ inet_ntoa(laddr.sin_addr), tv.tv_sec, tv.tv_usec, random(), pid, ppid);
+ MD5_Update(&md5, buf, strlen(buf));
+ MD5_Final(md5sum, &md5);
+ auth_readablesum(md5sum, challenge);
+}
+
+static int
+auth_checkresponse(char *response, char *challenge, char *secret)
+{
+ char correctresponse[MD5_CHARS_MAX];
+
+ auth_makeresponse(challenge, secret, correctresponse);
+ return (strcmp(response, correctresponse) == 0);
+}
+
+static void
+auth_readablesum(unsigned char *md5sum, char *readable)
+{
+ unsigned int i;
+ char *s = readable;
+
+ for (i = 0; i < MD5_BYTES; ++i, s+=2) {
+ sprintf(s, "%.2x", md5sum[i]);
+ }
+}
+
diff --git a/usr.bin/csup/auth.h b/usr.bin/csup/auth.h
new file mode 100644
index 0000000..61052e3
--- /dev/null
+++ b/usr.bin/csup/auth.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2003-2007, Petar Zhivkov Petrov <pesho.petrov@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _AUTH_H_
+#define _AUTH_H_
+
+#define AUTHFILE ".csup/auth" /* user home relative */
+
+struct config;
+
+int auth_login(struct config *);
+
+#endif /* !_AUTH_H_ */
+
diff --git a/usr.bin/csup/config.c b/usr.bin/csup/config.c
new file mode 100644
index 0000000..46ae6cd
--- /dev/null
+++ b/usr.bin/csup/config.c
@@ -0,0 +1,579 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "globtree.h"
+#include "keyword.h"
+#include "misc.h"
+#include "parse.h"
+#include "stream.h"
+#include "token.h"
+
+static int config_parse_refusefiles(struct coll *);
+static int config_parse_refusefile(struct coll *, char *);
+
+extern FILE *yyin;
+
+/* These are globals because I can't think of a better way with yacc. */
+static STAILQ_HEAD(, coll) colls;
+static struct coll *cur_coll;
+static struct coll *defaults;
+static 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;
+ 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 (!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 --git a/usr.bin/csup/config.h b/usr.bin/csup/config.h
new file mode 100644
index 0000000..859013c
--- /dev/null
+++ b/usr.bin/csup/config.h
@@ -0,0 +1,127 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <time.h>
+
+#include "fattr.h"
+#include "queue.h"
+#include "misc.h"
+
+/*
+ * Collection options.
+ */
+#define CO_BACKUP 0x00000001
+#define CO_DELETE 0x00000002
+#define CO_KEEP 0x00000004
+#define CO_OLD 0x00000008
+#define CO_UNLINKBUSY 0x00000010
+#define CO_NOUPDATE 0x00000020
+#define CO_COMPRESS 0x00000040
+#define CO_USERELSUFFIX 0x00000080
+#define CO_EXACTRCS 0x00000100
+#define CO_CHECKRCS 0x00000200
+#define CO_SKIP 0x00000400
+#define CO_CHECKOUTMODE 0x00000800
+#define CO_NORSYNC 0x00001000
+#define CO_KEEPBADFILES 0x00002000
+#define CO_EXECUTE 0x00004000
+#define CO_SETOWNER 0x00008000
+#define CO_SETMODE 0x00010000
+#define CO_SETFLAGS 0x00020000
+#define CO_NORCS 0x00040000
+#define CO_STRICTCHECKRCS 0x00080000
+#define CO_TRUSTSTATUSFILE 0x00100000
+#define CO_DODELETESONLY 0x00200000
+#define CO_DETAILALLRCSFILES 0x00400000
+
+#define CO_MASK 0x007fffff
+
+/* Options that the server is allowed to set. */
+#define CO_SERVMAYSET (CO_SKIP | CO_NORSYNC | CO_NORCS)
+/* Options that the server is allowed to clear. */
+#define CO_SERVMAYCLEAR CO_CHECKRCS
+
+struct coll {
+ char *co_name;
+ char *co_host;
+ char *co_base;
+ char *co_date;
+ char *co_prefix;
+ size_t co_prefixlen;
+ char *co_release;
+ char *co_tag;
+ char *co_cvsroot;
+ int co_attrignore;
+ struct pattlist *co_accepts;
+ struct pattlist *co_refusals;
+ struct globtree *co_dirfilter;
+ struct globtree *co_filefilter;
+ 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;
+ int reqauth;
+};
+
+struct config *config_init(const char *, struct coll *, int);
+int config_checkcolls(struct config *);
+void config_free(struct config *);
+
+struct coll *coll_new(struct coll *);
+void coll_override(struct coll *, struct coll *, int);
+char *coll_statuspath(struct coll *);
+char *coll_statussuffix(struct coll *);
+void coll_add(char *);
+void coll_free(struct coll *);
+void coll_setdef(void);
+void coll_setopt(int, char *);
+
+#endif /* !_CONFIG_H_ */
diff --git a/usr.bin/csup/cpasswd.1 b/usr.bin/csup/cpasswd.1
new file mode 100644
index 0000000..946783f
--- /dev/null
+++ b/usr.bin/csup/cpasswd.1
@@ -0,0 +1,120 @@
+.\" Copyright 1999-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.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgment:
+.\" This product includes software developed by John D. Polstra.
+.\" 4. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" 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: cvpasswd.1,v 1.4 2003/03/04 18:24:42 jdp Exp $
+.\" $FreeBSD $
+.\"
+.Dd June 27, 2007
+.Os FreeBSD
+.Dt CPASSWD 1
+.Sh NAME
+.Nm cpasswd
+.Nd scramble passwords for csup authentication
+.Sh SYNOPSIS
+.Nm
+.Ar clientName
+.Ar serverName
+.Sh DESCRIPTION
+The
+.Nm
+utility creates scrambled passwords for the
+.Nm CVSup
+server's authentication database. It is invoked with a client name
+and a server name.
+.Ar ClientName
+is the name the client uses to gain access to the
+server. By convention, e-mail addresses are used for all client
+names, e.g.,
+.Ql BillyJoe@FreeBSD.ORG .
+Client names are case-insensitive.
+.Pp
+.Ar ServerName
+is the name of the
+.Nm CVSup
+server which the client wishes to access. By convention,
+it is the canonical fully-qualified domain name of the server, e.g.,
+.Ql CVSup.FreeBSD.ORG .
+This must agree with the server's own idea of its name. The name is
+case-insensitive.
+.Pp
+To set up authentication for a given server, one must perform the
+following steps:
+.Bl -enum
+.It
+Obtain the official
+.Ar serverName
+from the administrator of the server or from some other source.
+.It
+Choose an appropriate
+.Ar clientName .
+It should be in the form of a valid e-mail address, to make it easy
+for the server administrator to contact the user if necessary.
+.It
+Choose an arbitrary secret
+.Ar password .
+.It
+Run
+.Nm cpasswd ,
+and type in the
+.Ar password
+when prompted for it. The utility will print out a line to send
+to the server administrator, and instruct you how to modify your
+.Li $ Ns Ev HOME Ns Pa /.csup/auth
+file. You should use a secure channel to send the line to the
+server administrator.
+.El
+.Pp
+Since
+.Li $ Ns Ev HOME Ns Pa /.csup/auth
+contains passwords, you should ensure that it is not readable by
+anyone except yourself.
+.Sh FILES
+.Bl -tag -width $HOME/.csup/authxx -compact
+.It Li $ Ns Ev HOME Ns Pa /.csup/auth
+Authentication password file.
+.El
+.Sh SEE ALSO
+.Xr csup 1 ,
+.Xr cvsup 1 ,
+.Xr cvsupd 8 .
+.Pp
+.Bd -literal
+http://www.cvsup.org/
+.Ed
+.Sh AUTHORS
+.An -nosplit
+.An Petar Zhivkov Petrov Aq pesho.petrov@gmail.com
+is the author of
+.Nm ,
+the rewrite of
+.Nm cvpasswd .
+.An John Polstra Aq jdp@polstra.com
+is the author of
+.Nm CVSup .
+.Sh LEGALITIES
+CVSup is a registered trademark of John D. Polstra.
diff --git a/usr.bin/csup/cpasswd.sh b/usr.bin/csup/cpasswd.sh
new file mode 100755
index 0000000..71e17c5
--- /dev/null
+++ b/usr.bin/csup/cpasswd.sh
@@ -0,0 +1,135 @@
+#! /bin/sh
+#
+# Copyright 2007. Petar Zhivkov Petrov
+# pesho.petrov@gmail.com
+#
+# $FreeBSD$
+
+usage() {
+ echo "Usage: $0 clientName serverName"
+ echo " $0 -v"
+}
+
+countChars() {
+ _count="`echo "$1" | sed -e "s/[^$2]//g" | tr -d "\n" | wc -c`"
+ return 0
+}
+
+readPassword() {
+ while [ true ]; do
+ stty -echo
+ read -p "$1" _password
+ stty echo
+ echo ""
+ countChars "$_password" ":"
+ if [ $_count != 0 ]; then
+ echo "Sorry, password must not contain \":\" characters"
+ echo ""
+ else
+ break
+ fi
+ done
+ return 0
+}
+
+makeSecret() {
+ local clientLower="`echo "$1" | tr "[:upper:]" "[:lower:]"`"
+ local serverLower="`echo "$2" | tr "[:upper:]" "[:lower:]"`"
+ local secret="`md5 -qs "$clientLower:$serverLower:$3"`"
+ _secret="\$md5\$$secret"
+}
+
+if [ $# -eq 1 -a "X$1" = "X-v" ]; then
+ echo "Csup authentication key generator"
+ usage
+ exit
+elif [ $# -ne 2 ]; then
+ usage
+ exit
+fi
+
+clientName=$1
+serverName=$2
+
+#
+# Client name must contain exactly one '@' and at least one '.'.
+# It must not contain a ':'.
+#
+
+countChars "$clientName" "@"
+aCount=$_count
+
+countChars "$clientName" "."
+dotCount=$_count
+if [ $aCount -ne 1 -o $dotCount -eq 0 ]; then
+ echo "Client name must have the form of an e-mail address,"
+ echo "e.g., \"user@domain.com\""
+ exit
+fi
+
+countChars "$clientName" ":"
+colonCount=$_count
+if [ $colonCount -gt 0 ]; then
+ echo "Client name must not contain \":\" characters"
+ exit
+fi
+
+#
+# Server name must not contain '@' and must have at least one '.'.
+# It also must not contain a ':'.
+#
+
+countChars "$serverName" "@"
+aCount=$_count
+
+countChars "$serverName" "."
+dotCount=$_count
+if [ $aCount != 0 -o $dotCount = 0 ]; then
+ echo "Server name must be a fully-qualified domain name."
+ echo "e.g., \"host.domain.com\""
+ exit
+fi
+
+countChars "$serverName" ":"
+colonCount=$_count
+if [ $colonCount -gt 0 ]; then
+ echo "Server name must not contain \":\" characters"
+ exit
+fi
+
+#
+# Ask for password and generate secret.
+#
+
+while [ true ]; do
+ readPassword "Enter password: "
+ makeSecret "$clientName" "$serverName" "$_password"
+ secret=$_secret
+
+ readPassword "Enter same password again: "
+ makeSecret "$clientName" "$serverName" "$_password"
+ secret2=$_secret
+
+ if [ "X$secret" = "X$secret2" ]; then
+ break
+ else
+ echo "Passwords did not match. Try again."
+ echo ""
+ fi
+done
+
+echo ""
+echo "Send this line to the server administrator at $serverName:"
+echo "-------------------------------------------------------------------------------"
+echo "$clientName:$secret::"
+echo "-------------------------------------------------------------------------------"
+echo "Be sure to send it using a secure channel!"
+echo ""
+echo "Add this line to your file \"$HOME/.csup/auth\", replacing \"XXX\""
+echo "with the password you typed in:"
+echo "-------------------------------------------------------------------------------"
+echo "$serverName:$clientName:XXX:"
+echo "-------------------------------------------------------------------------------"
+echo "Make sure the file is readable and writable only by you!"
+echo ""
+
diff --git a/usr.bin/csup/csup.1 b/usr.bin/csup/csup.1
new file mode 100644
index 0000000..2690863
--- /dev/null
+++ b/usr.bin/csup/csup.1
@@ -0,0 +1,1000 @@
+.\" Copyright 1996-2003 John D. Polstra.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $Id: cvsup.1,v 1.70 2003/03/04 18:23:46 jdp Exp $
+.\" $FreeBSD$
+.\"
+.Dd February 1, 2006
+.Os FreeBSD
+.Dt CSUP 1
+.Sh NAME
+.Nm csup
+.Nd network distribution package for CVS repositories
+.Sh SYNOPSIS
+.Nm
+.Op Fl 146aksvzZ
+.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
+Requires the server to authenticate itself (prove its identity) to
+the client. If authentication of the server fails, the update is
+canceled. See
+.Sx AUTHENTICATION ,
+below.
+.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.
+.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 AUTHENTICATION
+.Nm
+implements an optional authentication mechanism which can be used by the
+client and server to verify each other's identities.
+Public CVSup servers normally do not enable authentication.
+.Nm
+users may ignore this section unless they have been informed
+that authentication is required by the administrator of their server.
+.Pp
+The authentication subsystem uses a
+challenge-response protocol which is immune to packet sniffing and
+replay attacks. No passwords are sent over the network in either
+direction. Both the client and the server can independently verify
+the identities of each other.
+.Pp
+The file
+.Li $ Ns Ev HOME Ns Pa /.csup/auth
+holds the information used for authentication. This file contains a
+record for each server that the client is allowed to access. Each
+record occupies one line in the file. Lines beginning with
+.Ql #
+are ignored, as are lines containing only white space. White space is
+significant everywhere else in the file. Fields are separated by
+.Ql \&:
+characters.
+.Pp
+Each record of the file has the following form:
+.Bd -literal -offset indent
+.Sm off
+.Xo Ar serverName No : Ar clientName No :
+.Ar password No : Ar comment
+.Xc
+.Sm on
+.Ed
+.Pp
+All fields must be present even if some of them are empty.
+.Ar ServerName
+is the name of the server to which the record applies. By convention,
+it is the canonical fully-qualified domain name of the server, e.g.,
+.Ql CVSup177.FreeBSD.ORG .
+This must agree with the server's own idea of its name. The name is
+case-insensitive.
+.Pp
+.Ar ClientName
+is the name the client uses to gain access to the server. By
+convention, e-mail addresses are used for all client names, e.g.,
+.Ql BillyJoe@FreeBSD.ORG .
+Client names are case-insensitive.
+.Pp
+.Ar Password
+is a secret string of characters that the client uses to prove its
+identity. It may not contain any
+.Ql \&:
+or newline characters.
+.Pp
+.Ar Comment
+may contain any additional information to identify the record. It
+is not interpreted by the program.
+.Pp
+To set up authentication for a given server, one must perform the
+following steps:
+.Bl -enum
+.It
+Obtain the official
+.Ar serverName
+from the administrator of the server or from some other source.
+.It
+Choose an appropriate
+.Ar clientName .
+It should be in the form of a valid e-mail address, to make it easy
+for the server administrator to contact the user if necessary.
+.It
+Choose an arbitrary secret
+.Ar password .
+.It
+Run the
+.Nm cpasswd
+utility, and type in the
+.Ar password
+when prompted for it. The utility will print out a line to send
+to the server administrator, and instruct you how to modify your
+.Li $ Ns Ev HOME Ns Pa /.csup/auth
+file. You should use a secure channel to send the line to the
+server administrator.
+.El
+.Pp
+Since
+.Li $ Ns Ev HOME Ns Pa /.csup/auth
+contains passwords, you should ensure that it is not readable by
+anyone except yourself.
+.Pp
+Authentication works independently in both directions. The server
+administrator controls whether you must prove your identity.
+You control whether to check the server's identity, by means of the
+.Fl a
+command line option.
+.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 cpasswd 1 ,
+.Xr cvs 1 ,
+.Xr rcsintro 1 ,
+.Xr ssh 1 .
+.Pp
+.Bd -literal
+http://mu.org/~mux/csup.html
+.Ed
+.Sh AUTHORS
+.An -nosplit
+.An Maxime Henrion Aq mux@FreeBSD.org
+is the author of
+.Nm ,
+the rewrite of
+.Nm CVSup
+in C.
+.An John Polstra Aq jdp@polstra.com
+is the author of
+.Nm CVSup .
+.Sh LEGALITIES
+CVSup is a registered trademark of John D. Polstra.
+.Pp
+.Nm
+is released under a 2-clauses BSD license.
+.Sh BUGS
+An RCS file is not recognized as such unless its name ends with
+.Ql \&,v .
+.Pp
+Any directory named
+.Ql Attic
+is assumed to be a CVS Attic, and is treated specially.
diff --git a/usr.bin/csup/detailer.c b/usr.bin/csup/detailer.c
new file mode 100644
index 0000000..5592655
--- /dev/null
+++ b/usr.bin/csup/detailer.c
@@ -0,0 +1,603 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "detailer.h"
+#include "fixups.h"
+#include "globtree.h"
+#include "misc.h"
+#include "mux.h"
+#include "proto.h"
+#include "rcsfile.h"
+#include "rsyncfile.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_co(struct detailer *, struct coll *,
+ struct status *, char *);
+static int detailer_dofile_rcs(struct detailer *, struct coll *,
+ char *, char *);
+static int detailer_dofile_regular(struct detailer *, char *, char *);
+static int detailer_dofile_rsync(struct detailer *, char *, char *);
+static int detailer_checkrcsattr(struct detailer *, struct coll *, char *,
+ struct fattr *, int);
+int detailer_send_details(struct detailer *, struct coll *, char *,
+ char *, struct fattr *);
+
+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;
+ if (coll->co_options & CO_CHECKOUTMODE)
+ error = proto_printf(wr, "Y %s %s %s\n",
+ fixup->f_name, coll->co_tag, coll->co_date);
+ else {
+ error = proto_printf(wr, "A %s\n",
+ fixup->f_name);
+ }
+ 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 fattr *rcsattr;
+ struct stream *rd, *wr;
+ char *attr, *cmd, *file, *line, *msg, *path, *target;
+ int error, attic;
+
+ rd = d->rd;
+ wr = d->wr;
+ attic = 0;
+ 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 'I':
+ case 'i':
+ case 'j':
+ /* Directory operations. */
+ file = proto_get_ascii(&line);
+ if (file == NULL || line != NULL)
+ return (DETAILER_ERR_PROTO);
+ error = proto_printf(wr, "%s %s\n", cmd, file);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ break;
+ case 'J':
+ /* Set directory attributes. */
+ file = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ if (file == NULL || line != NULL || attr == NULL)
+ return (DETAILER_ERR_PROTO);
+ error = proto_printf(wr, "%s %s %s\n", cmd, file, attr);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ break;
+ case 'H':
+ case 'h':
+ /* Create a hard link. */
+ file = proto_get_ascii(&line);
+ target = proto_get_ascii(&line);
+ if (file == NULL || target == NULL)
+ return (DETAILER_ERR_PROTO);
+ error = proto_printf(wr, "%s %s %s\n", cmd, file,
+ target);
+ break;
+ case 't':
+ file = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ if (file == NULL || attr == NULL || line != NULL) {
+ return (DETAILER_ERR_PROTO);
+ }
+ rcsattr = fattr_decode(attr);
+ if (rcsattr == NULL) {
+ return (DETAILER_ERR_PROTO);
+ }
+ error = detailer_checkrcsattr(d, coll, file, rcsattr,
+ 1);
+ break;
+
+ case 'T':
+ file = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ if (file == NULL || attr == NULL || line != NULL)
+ return (DETAILER_ERR_PROTO);
+ rcsattr = fattr_decode(attr);
+ if (rcsattr == NULL)
+ return (DETAILER_ERR_PROTO);
+ error = detailer_checkrcsattr(d, coll, file, rcsattr,
+ 0);
+ break;
+
+ case 'U':
+ /* Add or update file. */
+ file = proto_get_ascii(&line);
+ if (file == NULL || line != NULL)
+ return (DETAILER_ERR_PROTO);
+ if (coll->co_options & CO_CHECKOUTMODE) {
+ error = detailer_dofile_co(d, coll, st, file);
+ } else {
+ path = cvspath(coll->co_prefix, file, 0);
+ rcsattr = fattr_frompath(path, FATTR_NOFOLLOW);
+ error = detailer_send_details(d, coll, file,
+ path, rcsattr);
+ if (rcsattr != NULL)
+ fattr_free(rcsattr);
+ free(path);
+ }
+ 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);
+}
+
+/*
+ * Tell the server to update a regular file.
+ */
+static int
+detailer_dofile_regular(struct detailer *d, char *name, char *path)
+{
+ struct stream *wr;
+ struct stat st;
+ char md5[MD5_DIGEST_SIZE];
+ int error;
+
+ wr = d->wr;
+ error = stat(path, &st);
+ /* If we don't have it or it's unaccessible, we want it again. */
+ if (error) {
+ proto_printf(wr, "A %s\n", name);
+ return (0);
+ }
+
+ /* If not, we want the file to be updated. */
+ error = MD5_File(path, md5);
+ if (error) {
+ lprintf(-1, "Error reading \"%s\"\n", name);
+ return (error);
+ }
+ error = proto_printf(wr, "R %s %O %s\n", name, st.st_size, md5);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ return (0);
+}
+
+/*
+ * Tell the server to update a file with the rsync algorithm.
+ */
+static int
+detailer_dofile_rsync(struct detailer *d, char *name, char *path)
+{
+ struct stream *wr;
+ struct rsyncfile *rf;
+
+ wr = d->wr;
+ rf = rsync_open(path, 0, 1);
+ if (rf == NULL) {
+ /* Fallback if we fail in opening it. */
+ proto_printf(wr, "A %s\n", name);
+ return (0);
+ }
+ proto_printf(wr, "r %s %z %z\n", name, rsync_filesize(rf),
+ rsync_blocksize(rf));
+ /* Detail the blocks. */
+ while (rsync_nextblock(rf) != 0)
+ proto_printf(wr, "%s %s\n", rsync_rsum(rf), rsync_blockmd5(rf));
+ proto_printf(wr, ".\n");
+ rsync_close(rf);
+ return (0);
+}
+
+/*
+ * Tell the server to update an RCS file that we have, or send it if we don't.
+ */
+static int
+detailer_dofile_rcs(struct detailer *d, struct coll *coll, char *name,
+ char *path)
+{
+ struct stream *wr;
+ struct fattr *fa;
+ struct rcsfile *rf;
+ int error;
+
+ wr = d->wr;
+ path = atticpath(coll->co_prefix, name);
+ fa = fattr_frompath(path, FATTR_NOFOLLOW);
+ if (fa == NULL) {
+ /* We don't have it, so send request to get it. */
+ error = proto_printf(wr, "A %s\n", name);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ free(path);
+ return (0);
+ }
+
+ rf = rcsfile_frompath(path, name, coll->co_cvsroot, coll->co_tag, 1);
+ free(path);
+ if (rf == NULL) {
+ error = proto_printf(wr, "A %s\n", name);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ return (0);
+ }
+ /* Tell to update the RCS file. The client version details follow. */
+ rcsfile_send_details(rf, wr);
+ rcsfile_free(rf);
+ fattr_free(fa);
+ return (0);
+}
+
+static int
+detailer_dofile_co(struct detailer *d, struct coll *coll, struct status *st,
+ char *file)
+{
+ struct stream *wr;
+ struct fattr *fa;
+ struct statusrec *sr;
+ char md5[MD5_DIGEST_SIZE];
+ 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);
+}
+
+int
+detailer_checkrcsattr(struct detailer *d, struct coll *coll, char *name,
+ struct fattr *server_attr, int attic)
+{
+ struct fattr *client_attr;
+ char *attr, *path;
+ int error;
+
+ /*
+ * I don't think we can use the status file, since it only records file
+ * attributes in cvsmode.
+ */
+ client_attr = NULL;
+ path = cvspath(coll->co_prefix, name, attic);
+ if (path == NULL) {
+ return (DETAILER_ERR_PROTO);
+ }
+
+ if (access(path, F_OK) == 0 &&
+ ((client_attr = fattr_frompath(path, FATTR_NOFOLLOW)) != NULL) &&
+ fattr_equal(client_attr, server_attr)) {
+ attr = fattr_encode(client_attr, NULL, 0);
+ if (attic) {
+ error = proto_printf(d->wr, "l %s %s\n", name, attr);
+ } else {
+ error = proto_printf(d->wr, "L %s %s\n", name, attr);
+ }
+ free(attr);
+ free(path);
+ fattr_free(client_attr);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ return (0);
+ }
+ /* We don't have it, so tell the server to send it. */
+ error = detailer_send_details(d, coll, name, path, client_attr);
+ fattr_free(client_attr);
+ free(path);
+ return (error);
+}
+
+int
+detailer_send_details(struct detailer *d, struct coll *coll, char *name,
+ char *path, struct fattr *fa)
+{
+ int error;
+ size_t len;
+
+ /*
+ * Try to check if the file exists either live or dead to see if we can
+ * edit it and put it live or dead, rather than receiving the entire
+ * file.
+ */
+ if (fa == NULL) {
+ path = atticpath(coll->co_prefix, name);
+ fa = fattr_frompath(path, FATTR_NOFOLLOW);
+ }
+ if (fa == NULL) {
+ error = proto_printf(d->wr, "A %s\n", name);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ } else if (fattr_type(fa) == FT_FILE) {
+ if (isrcs(name, &len) && !(coll->co_options & CO_NORCS)) {
+ detailer_dofile_rcs(d, coll, name, path);
+ } else if (!(coll->co_options & CO_NORSYNC) &&
+ !globtree_test(coll->co_norsync, name)) {
+ detailer_dofile_rsync(d, name, path);
+ } else {
+ detailer_dofile_regular(d, name, path);
+ }
+ } else {
+ error = proto_printf(d->wr, "N %s\n", name);
+ if (error)
+ return (DETAILER_ERR_WRITE);
+ }
+ return (0);
+}
diff --git a/usr.bin/csup/detailer.h b/usr.bin/csup/detailer.h
new file mode 100644
index 0000000..fe82b27
--- /dev/null
+++ b/usr.bin/csup/detailer.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _DETAILER_H_
+#define _DETAILER_H_
+
+void *detailer(void *);
+
+#endif /* !_DETAILER_H_ */
diff --git a/usr.bin/csup/diff.c b/usr.bin/csup/diff.c
new file mode 100644
index 0000000..8059676
--- /dev/null
+++ b/usr.bin/csup/diff.c
@@ -0,0 +1,438 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/limits.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "diff.h"
+#include "keyword.h"
+#include "misc.h"
+#include "stream.h"
+#include "queue.h"
+
+typedef long lineno_t;
+
+#define EC_ADD 0
+#define EC_DEL 1
+#define MAXKEY LONG_MAX
+
+/* Editing command and state. */
+struct editcmd {
+ int cmd;
+ long key;
+ int havetext;
+ int offset;
+ 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;
+ LIST_ENTRY(editcmd) next;
+};
+
+struct diffstart {
+ LIST_HEAD(, editcmd) dhead;
+};
+
+static int diff_geteditcmd(struct editcmd *, char *);
+static int diff_copyln(struct editcmd *, lineno_t);
+static int diff_ignoreln(struct editcmd *, lineno_t);
+static void diff_write(struct editcmd *, void *, size_t);
+static int diff_insert_edit(struct diffstart *, struct editcmd *);
+static void diff_free(struct diffstart *);
+
+int
+diff_apply(struct stream *rd, struct stream *orig, struct stream *dest,
+ struct keyword *keyword, struct diffinfo *di, int comode)
+{
+ struct editcmd ec;
+ lineno_t i;
+ size_t size;
+ char *line;
+ 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 (comode && 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 (comode && line == NULL)
+ return (-1);
+ /* If we got ".+", there's no ending newline. */
+ if (comode && 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);
+}
+
+/*
+ * Reverse a diff using the same algorithm as in cvsup.
+ */
+static int
+diff_write_reverse(struct stream *dest, struct diffstart *ds)
+{
+ struct editcmd *ec, *nextec;
+ long editline, endline, firstoutputlinedeleted;
+ long num_added, num_deleted, startline;
+ int num;
+
+ nextec = LIST_FIRST(&ds->dhead);
+ editline = 0;
+ num = 0;
+ while (nextec != NULL) {
+ ec = nextec;
+ nextec = LIST_NEXT(nextec, next);
+ if (nextec == NULL)
+ break;
+ num++;
+ num_deleted = 0;
+ if (ec->havetext)
+ num_deleted = ec->count;
+ num_added = num_deleted + nextec->offset - ec->offset;
+ if (num_deleted > 0) {
+ firstoutputlinedeleted = ec->key - num_deleted + 1;
+ stream_printf(dest, "d%ld %ld\n", firstoutputlinedeleted,
+ num_deleted);
+ if (num_added <= 0)
+ continue;
+ }
+ if (num_added > 0) {
+ stream_printf(dest, "a%ld %ld\n", ec->key, num_added);
+ startline = ec->key - num_deleted + 1 + ec->offset;
+ endline = startline + num_added - 1;
+
+ /* Copy lines from original file. First ignore some. */
+ ec->editline = editline;
+ diff_ignoreln(ec, startline - 1);
+ diff_copyln(ec, endline);
+ editline = ec->editline;
+ }
+ }
+ return (0);
+}
+
+/*
+ * Insert a diff into the list sorted on key. Should perhaps use quicker
+ * algorithms than insertion sort, but do this for now.
+ */
+static int
+diff_insert_edit(struct diffstart *ds, struct editcmd *ec)
+{
+ struct editcmd *curec;
+
+ if (ec == NULL)
+ return (0);
+
+ if (LIST_EMPTY(&ds->dhead)) {
+ LIST_INSERT_HEAD(&ds->dhead, ec, next);
+ return (0);
+ }
+
+ /* Insertion sort based on key. */
+ LIST_FOREACH(curec, &ds->dhead, next) {
+ if (ec->key < curec->key) {
+ LIST_INSERT_BEFORE(curec, ec, next);
+ return (0);
+ }
+ if (LIST_NEXT(curec, next) == NULL)
+ break;
+ }
+ /* Just insert it after. */
+ LIST_INSERT_AFTER(curec, ec, next);
+ return (0);
+}
+
+static void
+diff_free(struct diffstart *ds)
+{
+ struct editcmd *ec;
+
+ while(!LIST_EMPTY(&ds->dhead)) {
+ ec = LIST_FIRST(&ds->dhead);
+ LIST_REMOVE(ec, next);
+ free(ec);
+ }
+}
+
+/*
+ * Write the reverse diff from the diff in rd, and original file into
+ * destination. This algorithm is the same as used in cvsup.
+ */
+int
+diff_reverse(struct stream *rd, struct stream *orig, struct stream *dest,
+ struct keyword *keyword, struct diffinfo *di)
+{
+ struct diffstart ds;
+ struct editcmd ec, *addec, *delec;
+ lineno_t i;
+ char *line;
+ int error, offset;
+
+ memset(&ec, 0, sizeof(ec));
+ ec.orig = orig;
+ ec.dest = dest;
+ ec.keyword = keyword;
+ ec.di = di;
+ addec = NULL;
+ delec = NULL;
+ ec.havetext = 0;
+ offset = 0;
+ LIST_INIT(&ds.dhead);
+
+ /* Start with next since we need it. */
+ line = stream_getln(rd, NULL);
+ /* First we build up the list of diffs from input. */
+ while (line != NULL) {
+ error = diff_geteditcmd(&ec, line);
+ if (error)
+ break;
+ if (ec.cmd == EC_ADD) {
+ addec = xmalloc(sizeof(struct editcmd));
+ *addec = ec;
+ addec->havetext = 1;
+ /* Ignore the lines we was supposed to add. */
+ for (i = 0; i < ec.count; i++) {
+ line = stream_getln(rd, NULL);
+ if (line == NULL)
+ return (-1);
+ }
+
+ /* Get the next diff command if we have one. */
+ addec->key = addec->where + addec->count - offset;
+ if (delec != NULL &&
+ delec->key == addec->key - addec->count) {
+ delec->key = addec->key;
+ delec->havetext = addec->havetext;
+ delec->count = addec->count;
+ diff_insert_edit(&ds, delec);
+ free(addec);
+ delec = NULL;
+ addec = NULL;
+ } else {
+ if (delec != NULL) {
+ diff_insert_edit(&ds, delec);
+ }
+ delec = NULL;
+ addec->offset = offset;
+ diff_insert_edit(&ds, addec);
+ addec = NULL;
+ }
+ offset -= ec.count;
+ } else if (ec.cmd == EC_DEL) {
+ if (delec != NULL) {
+ /* Update offset to our next. */
+ diff_insert_edit(&ds, delec);
+ delec = NULL;
+ }
+ delec = xmalloc(sizeof(struct editcmd));
+ *delec = ec;
+ delec->key = delec->where - 1 - offset;
+ delec->offset = offset;
+ delec->count = 0;
+ delec->havetext = 0;
+ /* Important to use the count we had before reset.*/
+ offset += ec.count;
+ }
+ line = stream_getln(rd, NULL);
+ }
+
+ while (line != NULL)
+ line = stream_getln(rd, NULL);
+ if (delec != NULL) {
+ diff_insert_edit(&ds, delec);
+ delec = NULL;
+ }
+
+ addec = xmalloc(sizeof(struct editcmd));
+ /* Should be filesize, but we set it to max value. */
+ addec->key = MAXKEY;
+ addec->offset = offset;
+ addec->havetext = 0;
+ addec->count = 0;
+ diff_insert_edit(&ds, addec);
+ addec = NULL;
+ diff_write_reverse(dest, &ds);
+ diff_free(&ds);
+ stream_flush(dest);
+ 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)
+{
+ size_t size;
+ char *line;
+
+ while (ec->editline < to) {
+ line = stream_getln(ec->orig, &size);
+ if (line == NULL)
+ return (-1);
+ ec->editline++;
+ diff_write(ec, line, size);
+ }
+ return (0);
+}
+
+/* Ignore lines from the original version of the file up to line "to". */
+static int
+diff_ignoreln(struct editcmd *ec, lineno_t to)
+{
+ size_t size;
+ char *line;
+
+ while (ec->editline < to) {
+ line = stream_getln(ec->orig, &size);
+ if (line == NULL)
+ return (-1);
+ ec->editline++;
+ }
+ 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)
+{
+ size_t newsize;
+ char *line, *newline;
+ int ret;
+
+ line = buf;
+ ret = keyword_expand(ec->keyword, ec->di, line, size,
+ &newline, &newsize);
+ if (ret) {
+ stream_write(ec->dest, newline, newsize);
+ free(newline);
+ } else {
+ stream_write(ec->dest, buf, size);
+ }
+}
diff --git a/usr.bin/csup/diff.h b/usr.bin/csup/diff.h
new file mode 100644
index 0000000..b0c8c97
--- /dev/null
+++ b/usr.bin/csup/diff.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _DIFF_H_
+#define _DIFF_H_
+
+struct stream;
+struct keyword;
+struct file_update;
+
+/* Description of an RCS delta. */
+struct diffinfo {
+ char *di_rcsfile; /* RCS filename */
+ char *di_cvsroot; /* CVS root prefix */
+ char *di_revnum; /* Revision number */
+ char *di_revdate; /* Revision date */
+ char *di_author; /* Author of the delta */
+ char *di_tag; /* CVS tag, if any */
+ char *di_state; /* State of the branch */
+ int di_expand; /* CVS expansion mode */
+};
+
+int diff_apply(struct stream *, struct stream *, struct stream *,
+ struct keyword *, struct diffinfo *, int);
+int diff_reverse(struct stream *, struct stream *,
+ struct stream *, struct keyword *, struct diffinfo *);
+
+#endif /* !_DIFF_H_ */
diff --git a/usr.bin/csup/fattr.c b/usr.bin/csup/fattr.c
new file mode 100644
index 0000000..b141c2c
--- /dev/null
+++ b/usr.bin/csup/fattr.c
@@ -0,0 +1,981 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <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 a 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), "%llx",
+ (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);
+}
+
+char *
+fattr_getlinktarget(const struct fattr *fa)
+{
+
+ return (fa->linktarget);
+}
+
+/*
+ * 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;
+
+ error = 0;
+
+ 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. */
+ if (fa->mask & FA_MODE)
+ mode = fa->mode & modemask;
+ else
+ mode = 0700;
+
+ if (fa->type == FT_DIRECTORY)
+ error = mkdir(path, mode);
+ else if (fa->type == FT_SYMLINK) {
+ error = symlink(fa->linktarget, path);
+ } else if (fa->type == FT_CDEV) {
+ lprintf(-1, "Character devices not supported!\n");
+ } else if (fa->type == FT_BDEV) {
+ lprintf(-1, "Block devices not supported!\n");
+ }
+ 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
+
+ /*
+ * If it is changed from a file to a symlink, remove the file
+ * and create the symlink.
+ */
+ if (inplace && (fa->type == FT_SYMLINK) &&
+ (old->type == FT_FILE)) {
+ error = unlink(topath);
+ if (error)
+ goto bad;
+ error = symlink(fa->linktarget, topath);
+ if (error)
+ goto bad;
+ }
+ /* 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_FILETYPE)
+ if (fa1->type != fa2->type)
+ 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);
+}
+
+/*
+ * Must have to get the correct filesize sendt by the server.
+ */
+off_t
+fattr_filesize(const struct fattr *fa)
+{
+ return (fa->size);
+}
diff --git a/usr.bin/csup/fattr.h b/usr.bin/csup/fattr.h
new file mode 100644
index 0000000..bd4e649
--- /dev/null
+++ b/usr.bin/csup/fattr.h
@@ -0,0 +1,118 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _FATTR_H_
+#define _FATTR_H_
+
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <time.h>
+
+/*
+ * File types.
+ */
+#define FT_UNKNOWN 0 /* Unknown file type. */
+#define FT_FILE 1 /* Regular file. */
+#define FT_DIRECTORY 2 /* Directory. */
+#define FT_CDEV 3 /* Character device. */
+#define FT_BDEV 4 /* Block device. */
+#define FT_SYMLINK 5 /* Symbolic link. */
+#define FT_MAX FT_SYMLINK /* Maximum file type number. */
+#define FT_NUMBER (FT_MAX + 1) /* Number of file types. */
+
+/*
+ * File attributes.
+ */
+#define FA_FILETYPE 0x0001 /* True for all supported file types. */
+#define FA_MODTIME 0x0002 /* Last file modification time. */
+#define FA_SIZE 0x0004 /* Size of the file. */
+#define FA_LINKTARGET 0x0008 /* Target of a symbolic link. */
+#define FA_RDEV 0x0010 /* Device for a device node. */
+#define FA_OWNER 0x0020 /* Owner of the file. */
+#define FA_GROUP 0x0040 /* Group of the file. */
+#define FA_MODE 0x0080 /* File permissions. */
+#define FA_FLAGS 0x0100 /* 4.4BSD flags, a la chflags(2). */
+#define FA_LINKCOUNT 0x0200 /* Hard link count. */
+#define FA_DEV 0x0400 /* Device holding the inode. */
+#define FA_INODE 0x0800 /* Inode number. */
+
+#define FA_MASK 0x0fff
+
+#define FA_NUMBER 12 /* Number of file attributes. */
+
+/* Attributes that we might be able to change. */
+#define FA_CHANGEABLE (FA_MODTIME | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS)
+
+/*
+ * Attributes that we don't want to save in the "checkouts" file
+ * when in checkout mode.
+ */
+#define FA_COIGNORE (FA_MASK & ~(FA_FILETYPE|FA_MODTIME|FA_SIZE|FA_MODE))
+
+/* These are for fattr_frompath(). */
+#define FATTR_FOLLOW 0
+#define FATTR_NOFOLLOW 1
+
+struct stat;
+struct fattr;
+
+typedef int fattr_support_t[FT_NUMBER];
+
+extern const struct fattr *fattr_bogus;
+
+void fattr_init(void);
+void fattr_fini(void);
+
+struct fattr *fattr_new(int, time_t);
+struct fattr *fattr_default(int);
+struct fattr *fattr_fromstat(struct stat *);
+struct fattr *fattr_frompath(const char *, int);
+struct fattr *fattr_fromfd(int);
+struct fattr *fattr_decode(char *);
+struct fattr *fattr_forcheckout(const struct fattr *, mode_t);
+struct fattr *fattr_dup(const struct fattr *);
+char *fattr_encode(const struct fattr *, fattr_support_t, int);
+int fattr_type(const struct fattr *);
+void fattr_maskout(struct fattr *, int);
+int fattr_getmask(const struct fattr *);
+nlink_t fattr_getlinkcount(const struct fattr *);
+char *fattr_getlinktarget(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);
+off_t fattr_filesize(const struct fattr *);
+
+
+#endif /* !_FATTR_H_ */
diff --git a/usr.bin/csup/fattr_bsd.h b/usr.bin/csup/fattr_bsd.h
new file mode 100644
index 0000000..112a824
--- /dev/null
+++ b/usr.bin/csup/fattr_bsd.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id$
+ */
+
+/*
+ * The file attributes we support in a BSD environment.
+ *
+ * This is similar to fattr_posix.h, except that we support the FA_FLAGS
+ * attribute when it makes sense. The FA_FLAGS attribute is for the
+ * extended BSD file flags, see chflags(2).
+ */
+fattr_support_t fattr_support = {
+ /* FT_UNKNOWN */
+ 0,
+ /* FT_FILE */
+ FA_FILETYPE | FA_MODTIME | FA_SIZE | FA_OWNER | FA_GROUP | FA_MODE |
+ FA_FLAGS | FA_LINKCOUNT | FA_INODE | FA_DEV,
+ /* FT_DIRECTORY */
+ FA_FILETYPE | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS,
+ /* FT_CDEV */
+ FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS |
+ FA_LINKCOUNT | FA_DEV | FA_INODE,
+ /* FT_BDEV */
+ FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_FLAGS |
+ FA_LINKCOUNT | FA_DEV | FA_INODE,
+ /* FT_SYMLINK */
+ FA_FILETYPE | FA_LINKTARGET
+};
diff --git a/usr.bin/csup/fattr_posix.h b/usr.bin/csup/fattr_posix.h
new file mode 100644
index 0000000..c4650e4
--- /dev/null
+++ b/usr.bin/csup/fattr_posix.h
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id$
+ */
+
+/*
+ * The file attributes we support in a POSIX environment.
+ */
+fattr_support_t fattr_support = {
+ /* FT_UNKNOWN */
+ 0,
+ /* FT_FILE */
+ FA_FILETYPE | FA_MODTIME | FA_SIZE | FA_OWNER | FA_GROUP | FA_MODE |
+ FA_LINKCOUNT | FA_INODE | FA_DEV,
+ /* FT_DIRECTORY */
+ FA_FILETYPE | FA_OWNER | FA_GROUP | FA_MODE,
+ /* FT_CDEV */
+ FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_LINKCOUNT |
+ FA_DEV | FA_INODE,
+ /* FT_BDEV */
+ FA_FILETYPE | FA_RDEV | FA_OWNER | FA_GROUP | FA_MODE | FA_LINKCOUNT |
+ FA_DEV | FA_INODE,
+ /* FT_SYMLINK */
+ FA_FILETYPE | FA_LINKTARGET
+};
diff --git a/usr.bin/csup/fixups.c b/usr.bin/csup/fixups.c
new file mode 100644
index 0000000..b105a8f
--- /dev/null
+++ b/usr.bin/csup/fixups.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fixups.h"
+#include "misc.h"
+#include "queue.h"
+
+/*
+ * A synchronized queue to implement fixups. The updater thread adds
+ * fixup requests to the queue with fixups_put() when a checksum
+ * mismatch error occured. It then calls fixups_close() when he's
+ * done requesting fixups. The detailer thread gets the fixups with
+ * fixups_get() and then send the requests to the server.
+ *
+ * The queue is synchronized with a mutex and a condition variable.
+ */
+
+struct fixups {
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ STAILQ_HEAD(, fixup) fixupq;
+ struct fixup *cur;
+ size_t size;
+ int closed;
+};
+
+static void fixups_lock(struct fixups *);
+static void fixups_unlock(struct fixups *);
+
+static struct fixup *fixup_new(struct coll *, const char *);
+static void fixup_free(struct fixup *);
+
+static void
+fixups_lock(struct fixups *f)
+{
+ int error;
+
+ error = pthread_mutex_lock(&f->lock);
+ assert(!error);
+}
+
+static void
+fixups_unlock(struct fixups *f)
+{
+ int error;
+
+ error = pthread_mutex_unlock(&f->lock);
+ assert(!error);
+}
+
+static struct fixup *
+fixup_new(struct coll *coll, const char *name)
+{
+ struct fixup *fixup;
+
+ fixup = xmalloc(sizeof(struct fixup));
+ fixup->f_name = xstrdup(name);
+ fixup->f_coll = coll;
+ return (fixup);
+}
+
+static void
+fixup_free(struct fixup *fixup)
+{
+
+ free(fixup->f_name);
+ free(fixup);
+}
+
+/* Create a new fixup queue. */
+struct fixups *
+fixups_new(void)
+{
+ struct fixups *f;
+
+ f = xmalloc(sizeof(struct fixups));
+ f->size = 0;
+ f->closed = 0;
+ f->cur = NULL;
+ STAILQ_INIT(&f->fixupq);
+ pthread_mutex_init(&f->lock, NULL);
+ pthread_cond_init(&f->cond, NULL);
+ return (f);
+}
+
+/* Add a fixup request to the queue. */
+void
+fixups_put(struct fixups *f, struct coll *coll, const char *name)
+{
+ struct fixup *fixup;
+ int dosignal;
+
+ dosignal = 0;
+ fixup = fixup_new(coll, name);
+ fixups_lock(f);
+ assert(!f->closed);
+ STAILQ_INSERT_TAIL(&f->fixupq, fixup, f_link);
+ if (f->size++ == 0)
+ dosignal = 1;
+ fixups_unlock(f);
+ if (dosignal)
+ pthread_cond_signal(&f->cond);
+}
+
+/* Get a fixup request from the queue. */
+struct fixup *
+fixups_get(struct fixups *f)
+{
+ struct fixup *fixup, *tofree;
+
+ fixups_lock(f);
+ while (f->size == 0 && !f->closed)
+ pthread_cond_wait(&f->cond, &f->lock);
+ if (f->closed) {
+ fixups_unlock(f);
+ return (NULL);
+ }
+ assert(f->size > 0);
+ fixup = STAILQ_FIRST(&f->fixupq);
+ tofree = f->cur;
+ f->cur = fixup;
+ STAILQ_REMOVE_HEAD(&f->fixupq, f_link);
+ f->size--;
+ fixups_unlock(f);
+ if (tofree != NULL)
+ fixup_free(tofree);
+ return (fixup);
+}
+
+/* Close the writing end of the queue. */
+void
+fixups_close(struct fixups *f)
+{
+ int dosignal;
+
+ dosignal = 0;
+ fixups_lock(f);
+ if (f->size == 0 && !f->closed)
+ dosignal = 1;
+ f->closed = 1;
+ fixups_unlock(f);
+ if (dosignal)
+ pthread_cond_signal(&f->cond);
+}
+
+/* Free a fixups queue. */
+void
+fixups_free(struct fixups *f)
+{
+ struct fixup *fixup, *fixup2;
+
+ assert(f->closed);
+ /*
+ * Free any fixup that has been left on the queue.
+ * This can happen if we have been aborted prematurely.
+ */
+ fixup = STAILQ_FIRST(&f->fixupq);
+ while (fixup != NULL) {
+ fixup2 = STAILQ_NEXT(fixup, f_link);
+ fixup_free(fixup);
+ fixup = fixup2;
+ }
+ if (f->cur != NULL)
+ fixup_free(f->cur);
+ pthread_cond_destroy(&f->cond);
+ pthread_mutex_destroy(&f->lock);
+ free(f);
+}
diff --git a/usr.bin/csup/fixups.h b/usr.bin/csup/fixups.h
new file mode 100644
index 0000000..0dddc90
--- /dev/null
+++ b/usr.bin/csup/fixups.h
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _FIXUPS_H_
+#define _FIXUPS_H_
+
+#include "queue.h"
+
+struct coll;
+struct fixups;
+
+struct fixup {
+ struct coll *f_coll;
+ char *f_name;
+ STAILQ_ENTRY(fixup) f_link; /* Not for consumers. */
+};
+
+struct fixups *fixups_new(void);
+void fixups_put(struct fixups *, struct coll *, const char *);
+struct fixup *fixups_get(struct fixups *);
+void fixups_close(struct fixups *);
+void fixups_free(struct fixups *);
+
+#endif /* !_FIXUPS_H_ */
diff --git a/usr.bin/csup/fnmatch.c b/usr.bin/csup/fnmatch.c
new file mode 100644
index 0000000..a63f016
--- /dev/null
+++ b/usr.bin/csup/fnmatch.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * From FreeBSD fnmatch.c 1.11
+ * $Id: fnmatch.c,v 1.3 1997/08/19 02:34:30 jdp Exp $
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94";
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
+ * Compares a filename or pathname to a pattern.
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "fnmatch.h"
+
+#define EOS '\0'
+
+static const char *rangematch(const char *, char, int);
+
+int
+fnmatch(const char *pattern, const char *string, int flags)
+{
+ const char *stringstart;
+ char c, test;
+
+ for (stringstart = string;;)
+ switch (c = *pattern++) {
+ case EOS:
+ if ((flags & FNM_LEADING_DIR) && *string == '/')
+ return (0);
+ return (*string == EOS ? 0 : FNM_NOMATCH);
+ case '?':
+ if (*string == EOS)
+ return (FNM_NOMATCH);
+ if (*string == '/' && (flags & FNM_PATHNAME))
+ return (FNM_NOMATCH);
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+ ++string;
+ break;
+ case '*':
+ c = *pattern;
+ /* Collapse multiple stars. */
+ while (c == '*')
+ c = *++pattern;
+
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+
+ /* Optimize for pattern with * at end or before /. */
+ if (c == EOS)
+ if (flags & FNM_PATHNAME)
+ return ((flags & FNM_LEADING_DIR) ||
+ strchr(string, '/') == NULL ?
+ 0 : FNM_NOMATCH);
+ else
+ return (0);
+ else if (c == '/' && flags & FNM_PATHNAME) {
+ if ((string = strchr(string, '/')) == NULL)
+ return (FNM_NOMATCH);
+ break;
+ }
+
+ /* General case, use recursion. */
+ while ((test = *string) != EOS) {
+ if (!fnmatch(pattern, string, flags & ~FNM_PERIOD))
+ return (0);
+ if (test == '/' && flags & FNM_PATHNAME)
+ break;
+ ++string;
+ }
+ return (FNM_NOMATCH);
+ case '[':
+ if (*string == EOS)
+ return (FNM_NOMATCH);
+ if (*string == '/' && flags & FNM_PATHNAME)
+ return (FNM_NOMATCH);
+ if ((pattern =
+ rangematch(pattern, *string, flags)) == NULL)
+ return (FNM_NOMATCH);
+ ++string;
+ break;
+ case '\\':
+ if (!(flags & FNM_NOESCAPE)) {
+ if ((c = *pattern++) == EOS) {
+ c = '\\';
+ --pattern;
+ }
+ }
+ /* FALLTHROUGH */
+ default:
+ if (c == *string)
+ ;
+ else if ((flags & FNM_CASEFOLD) &&
+ (tolower((unsigned char)c) ==
+ tolower((unsigned char)*string)))
+ ;
+ else if ((flags & FNM_PREFIX_DIRS) && *string == EOS &&
+ ((c == '/' && string != stringstart) ||
+ (string == stringstart+1 && *stringstart == '/')))
+ return (0);
+ else
+ return (FNM_NOMATCH);
+ string++;
+ break;
+ }
+ /* NOTREACHED */
+}
+
+static const char *
+rangematch(const char *pattern, char test, int flags)
+{
+ int negate, ok;
+ char c, c2;
+
+ /*
+ * A bracket expression starting with an unquoted circumflex
+ * character produces unspecified results (IEEE 1003.2-1992,
+ * 3.13.2). This implementation treats it like '!', for
+ * consistency with the regular expression syntax.
+ * J.T. Conklin (conklin@ngai.kaleida.com)
+ */
+ if ( (negate = (*pattern == '!' || *pattern == '^')) )
+ ++pattern;
+
+ if (flags & FNM_CASEFOLD)
+ test = tolower((unsigned char)test);
+
+ for (ok = 0; (c = *pattern++) != ']';) {
+ if (c == '\\' && !(flags & FNM_NOESCAPE))
+ c = *pattern++;
+ if (c == EOS)
+ return (NULL);
+
+ if (flags & FNM_CASEFOLD)
+ c = tolower((unsigned char)c);
+
+ if (*pattern == '-'
+ && (c2 = *(pattern+1)) != EOS && c2 != ']') {
+ pattern += 2;
+ if (c2 == '\\' && !(flags & FNM_NOESCAPE))
+ c2 = *pattern++;
+ if (c2 == EOS)
+ return (NULL);
+
+ if (flags & FNM_CASEFOLD)
+ c2 = tolower((unsigned char)c2);
+
+ if ((unsigned char)c <= (unsigned char)test &&
+ (unsigned char)test <= (unsigned char)c2)
+ ok = 1;
+ } else if (c == test)
+ ok = 1;
+ }
+ return (ok == negate ? NULL : pattern);
+}
diff --git a/usr.bin/csup/fnmatch.h b/usr.bin/csup/fnmatch.h
new file mode 100644
index 0000000..21d2f64
--- /dev/null
+++ b/usr.bin/csup/fnmatch.h
@@ -0,0 +1,58 @@
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)fnmatch.h 8.1 (Berkeley) 6/2/93
+ *
+ * From FreeBSD fnmatch.h 1.7
+ * $Id: fnmatch.h,v 1.4 2001/10/04 02:46:21 jdp Exp $
+ */
+
+#ifndef _FNMATCH_H_
+#define _FNMATCH_H_
+
+#define FNM_NOMATCH 1 /* Match failed. */
+
+#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
+#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
+#define FNM_PERIOD 0x04 /* Period must be matched by period. */
+#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */
+#define FNM_CASEFOLD 0x10 /* Case insensitive search. */
+#define FNM_PREFIX_DIRS 0x20 /* Directory prefixes of pattern match too. */
+
+/* Make this compile successfully with "gcc -traditional" */
+#ifndef __STDC__
+#define const /* empty */
+#endif
+
+int fnmatch(const char *, const char *, int);
+
+#endif /* !_FNMATCH_H_ */
diff --git a/usr.bin/csup/globtree.c b/usr.bin/csup/globtree.c
new file mode 100644
index 0000000..74ac2c1
--- /dev/null
+++ b/usr.bin/csup/globtree.c
@@ -0,0 +1,393 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <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 --git a/usr.bin/csup/globtree.h b/usr.bin/csup/globtree.h
new file mode 100644
index 0000000..43882e3
--- /dev/null
+++ b/usr.bin/csup/globtree.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _GLOBTREE_H_
+#define _GLOBTREE_H_
+
+#include "fnmatch.h"
+
+struct globtree;
+
+struct globtree *globtree_true(void);
+struct globtree *globtree_false(void);
+struct globtree *globtree_match(const char *, int);
+struct globtree *globtree_regex(const char *);
+struct globtree *globtree_and(struct globtree *, struct globtree *);
+struct globtree *globtree_or(struct globtree *, struct globtree *);
+struct globtree *globtree_not(struct globtree *);
+int globtree_test(struct globtree *, const char *);
+void globtree_free(struct globtree *);
+
+#endif /* !_GLOBTREE_H_ */
diff --git a/usr.bin/csup/idcache.c b/usr.bin/csup/idcache.c
new file mode 100644
index 0000000..47a3e71
--- /dev/null
+++ b/usr.bin/csup/idcache.c
@@ -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$
+ */
+#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 --git a/usr.bin/csup/idcache.h b/usr.bin/csup/idcache.h
new file mode 100644
index 0000000..558af0c
--- /dev/null
+++ b/usr.bin/csup/idcache.h
@@ -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$
+ */
+#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 --git a/usr.bin/csup/keyword.c b/usr.bin/csup/keyword.c
new file mode 100644
index 0000000..049a011
--- /dev/null
+++ b/usr.bin/csup/keyword.c
@@ -0,0 +1,525 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "diff.h"
+#include "keyword.h"
+#include "misc.h"
+#include "queue.h"
+#include "stream.h"
+
+/*
+ * The keyword API is used to expand the CVS/RCS keywords in files,
+ * such as $Id$, $Revision$, etc. The server does it for us when it
+ * sends us entire files, but we need to handle the expansion when
+ * applying a diff update.
+ */
+
+enum rcskey {
+ RCSKEY_AUTHOR,
+ RCSKEY_CVSHEADER,
+ RCSKEY_DATE,
+ RCSKEY_HEADER,
+ RCSKEY_ID,
+ RCSKEY_LOCKER,
+ RCSKEY_LOG,
+ RCSKEY_NAME,
+ RCSKEY_RCSFILE,
+ RCSKEY_REVISION,
+ RCSKEY_SOURCE,
+ RCSKEY_STATE
+};
+
+typedef enum rcskey rcskey_t;
+
+struct tag {
+ char *ident;
+ rcskey_t key;
+ int enabled;
+ STAILQ_ENTRY(tag) next;
+};
+
+static struct tag *tag_new(const char *, rcskey_t);
+static char *tag_expand(struct tag *, struct diffinfo *);
+static void tag_free(struct tag *);
+
+struct keyword {
+ STAILQ_HEAD(, tag) keywords; /* Enabled keywords. */
+ size_t minkeylen;
+ size_t maxkeylen;
+};
+
+/* Default CVS keywords. */
+static struct {
+ const char *ident;
+ rcskey_t key;
+} tag_defaults[] = {
+ { "Author", RCSKEY_AUTHOR },
+ { "CVSHeader", RCSKEY_CVSHEADER },
+ { "Date", RCSKEY_DATE },
+ { "Header", RCSKEY_HEADER },
+ { "Id", RCSKEY_ID },
+ { "Locker", RCSKEY_LOCKER },
+ { "Log", RCSKEY_LOG },
+ { "Name", RCSKEY_NAME },
+ { "RCSfile", RCSKEY_RCSFILE },
+ { "Revision", RCSKEY_REVISION },
+ { "Source", RCSKEY_SOURCE },
+ { "State", RCSKEY_STATE },
+ { NULL, 0, }
+};
+
+struct keyword *
+keyword_new(void)
+{
+ struct keyword *new;
+ struct tag *tag;
+ size_t len;
+ int i;
+
+ new = xmalloc(sizeof(struct keyword));
+ STAILQ_INIT(&new->keywords);
+ new->minkeylen = ~0;
+ new->maxkeylen = 0;
+ for (i = 0; tag_defaults[i].ident != NULL; i++) {
+ tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key);
+ STAILQ_INSERT_TAIL(&new->keywords, tag, next);
+ len = strlen(tag->ident);
+ /*
+ * These values are only computed here and not updated when
+ * adding an alias. This is a bug, but CVSup has it and we
+ * need to be bug-to-bug compatible since the server will
+ * expect us to do the same, and we will fail with an MD5
+ * checksum mismatch if we don't.
+ */
+ new->minkeylen = min(new->minkeylen, len);
+ new->maxkeylen = max(new->maxkeylen, len);
+ }
+ return (new);
+}
+
+int
+keyword_decode_expand(const char *expand)
+{
+
+ if (strcmp(expand, ".") == 0)
+ return (EXPAND_DEFAULT);
+ else if (strcmp(expand, "kv") == 0)
+ return (EXPAND_KEYVALUE);
+ else if (strcmp(expand, "kvl") == 0)
+ return (EXPAND_KEYVALUELOCKER);
+ else if (strcmp(expand, "k") == 0)
+ return (EXPAND_KEY);
+ else if (strcmp(expand, "o") == 0)
+ return (EXPAND_OLD);
+ else if (strcmp(expand, "b") == 0)
+ return (EXPAND_BINARY);
+ else if (strcmp(expand, "v") == 0)
+ return (EXPAND_VALUE);
+ else
+ return (-1);
+}
+
+const char *
+keyword_encode_expand(int expand)
+{
+
+ switch (expand) {
+ case EXPAND_DEFAULT:
+ return (".");
+ case EXPAND_KEYVALUE:
+ return ("kv");
+ case EXPAND_KEYVALUELOCKER:
+ return ("kvl");
+ case EXPAND_KEY:
+ return ("k");
+ case EXPAND_OLD:
+ return ("o");
+ case EXPAND_BINARY:
+ return ("b");
+ case EXPAND_VALUE:
+ return ("v");
+ }
+ return (NULL);
+}
+
+void
+keyword_free(struct keyword *keyword)
+{
+ struct tag *tag;
+
+ if (keyword == NULL)
+ return;
+ while (!STAILQ_EMPTY(&keyword->keywords)) {
+ tag = STAILQ_FIRST(&keyword->keywords);
+ STAILQ_REMOVE_HEAD(&keyword->keywords, next);
+ tag_free(tag);
+ }
+ free(keyword);
+}
+
+int
+keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
+{
+ struct tag *new, *tag;
+
+ STAILQ_FOREACH(tag, &keyword->keywords, next) {
+ if (strcmp(tag->ident, rcskey) == 0) {
+ new = tag_new(ident, tag->key);
+ STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
+ return (0);
+ }
+ }
+ errno = ENOENT;
+ return (-1);
+}
+
+int
+keyword_enable(struct keyword *keyword, const char *ident)
+{
+ struct tag *tag;
+ int all;
+
+ all = 0;
+ if (strcmp(ident, ".") == 0)
+ all = 1;
+
+ STAILQ_FOREACH(tag, &keyword->keywords, next) {
+ if (!all && strcmp(tag->ident, ident) != 0)
+ continue;
+ tag->enabled = 1;
+ if (!all)
+ return (0);
+ }
+ if (!all) {
+ errno = ENOENT;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+keyword_disable(struct keyword *keyword, const char *ident)
+{
+ struct tag *tag;
+ int all;
+
+ all = 0;
+ if (strcmp(ident, ".") == 0)
+ all = 1;
+
+ STAILQ_FOREACH(tag, &keyword->keywords, next) {
+ if (!all && strcmp(tag->ident, ident) != 0)
+ continue;
+ tag->enabled = 0;
+ if (!all)
+ return (0);
+ }
+
+ if (!all) {
+ errno = ENOENT;
+ return (-1);
+ }
+ return (0);
+}
+
+void
+keyword_prepare(struct keyword *keyword)
+{
+ struct tag *tag, *temp;
+
+ STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
+ if (!tag->enabled) {
+ STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
+ tag_free(tag);
+ continue;
+ }
+ }
+}
+
+/*
+ * Expand appropriate RCS keywords. If there's no tag to expand,
+ * keyword_expand() returns 0, otherwise it returns 1 and writes a
+ * pointer to the new line in *buf and the new len in *len. The
+ * new line is allocated with malloc() and needs to be freed by the
+ * caller after use.
+ */
+int
+keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
+ size_t size, char **buf, size_t *len)
+{
+ struct tag *tag;
+ char *dollar, *keystart, *valstart, *vallim, *next;
+ char *linestart, *newline, *newval, *cp, *tmp;
+ size_t left, newsize, vallen;
+
+ if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
+ return (0);
+ newline = NULL;
+ newsize = 0;
+ left = size;
+ linestart = cp = line;
+again:
+ dollar = memchr(cp, '$', left);
+ if (dollar == NULL) {
+ if (newline != NULL) {
+ *buf = newline;
+ *len = newsize;
+ return (1);
+ }
+ return (0);
+ }
+ keystart = dollar + 1;
+ left -= keystart - cp;
+ vallim = memchr(keystart, '$', left);
+ if (vallim == NULL) {
+ if (newline != NULL) {
+ *buf = newline;
+ *len = newsize;
+ return (1);
+ }
+ return (0);
+ }
+ if (vallim == keystart) {
+ cp = keystart;
+ goto again;
+ }
+ valstart = memchr(keystart, ':', left);
+ if (valstart == keystart) {
+ cp = vallim;
+ left -= vallim - keystart;
+ goto again;
+ }
+ if (valstart == NULL || valstart > vallim)
+ valstart = vallim;
+
+ if (valstart < keystart + keyword->minkeylen ||
+ valstart > keystart + keyword->maxkeylen) {
+ cp = vallim;
+ left -= vallim -keystart;
+ goto again;
+ }
+ STAILQ_FOREACH(tag, &keyword->keywords, next) {
+ if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
+ tag->ident[valstart - keystart] == '\0') {
+ if (newline != NULL)
+ tmp = newline;
+ else
+ tmp = NULL;
+ newval = NULL;
+ if (di->di_expand == EXPAND_KEY) {
+ newsize = dollar - linestart + 1 +
+ valstart - keystart + 1 +
+ size - (vallim + 1 - linestart);
+ newline = xmalloc(newsize);
+ cp = newline;
+ memcpy(cp, linestart, dollar - linestart);
+ cp += dollar - linestart;
+ *cp++ = '$';
+ memcpy(cp, keystart, valstart - keystart);
+ cp += valstart - keystart;
+ *cp++ = '$';
+ next = cp;
+ memcpy(cp, vallim + 1,
+ size - (vallim + 1 - linestart));
+ } else if (di->di_expand == EXPAND_VALUE) {
+ newval = tag_expand(tag, di);
+ if (newval == NULL)
+ vallen = 0;
+ else
+ vallen = strlen(newval);
+ newsize = dollar - linestart +
+ vallen +
+ size - (vallim + 1 - linestart);
+ newline = xmalloc(newsize);
+ cp = newline;
+ memcpy(cp, linestart, dollar - linestart);
+ cp += dollar - linestart;
+ if (newval != NULL) {
+ memcpy(cp, newval, vallen);
+ cp += vallen;
+ }
+ next = cp;
+ memcpy(cp, vallim + 1,
+ size - (vallim + 1 - linestart));
+ } else {
+ assert(di->di_expand == EXPAND_DEFAULT ||
+ di->di_expand == EXPAND_KEYVALUE ||
+ di->di_expand == EXPAND_KEYVALUELOCKER);
+ newval = tag_expand(tag, di);
+ if (newval == NULL)
+ vallen = 0;
+ else
+ vallen = strlen(newval);
+ newsize = dollar - linestart + 1 +
+ valstart - keystart + 2 +
+ vallen + 2 +
+ size - (vallim + 1 - linestart);
+ newline = xmalloc(newsize);
+ cp = newline;
+ memcpy(cp, linestart, dollar - linestart);
+ cp += dollar - linestart;
+ *cp++ = '$';
+ memcpy(cp, keystart, valstart - keystart);
+ cp += valstart - keystart;
+ *cp++ = ':';
+ *cp++ = ' ';
+ if (newval != NULL) {
+ memcpy(cp, newval, vallen);
+ cp += vallen;
+ }
+ *cp++ = ' ';
+ *cp++ = '$';
+ next = cp;
+ memcpy(cp, vallim + 1,
+ size - (vallim + 1 - linestart));
+ }
+ if (newval != NULL)
+ free(newval);
+ if (tmp != NULL)
+ free(tmp);
+ /*
+ * Continue looking for tags in the rest of the line.
+ */
+ cp = next;
+ size = newsize;
+ left = size - (cp - newline);
+ linestart = newline;
+ goto again;
+ }
+ }
+ cp = vallim;
+ left = size - (cp - linestart);
+ goto again;
+}
+
+static struct tag *
+tag_new(const char *ident, rcskey_t key)
+{
+ struct tag *new;
+
+ new = xmalloc(sizeof(struct tag));
+ new->ident = xstrdup(ident);
+ new->key = key;
+ new->enabled = 1;
+ return (new);
+}
+
+static void
+tag_free(struct tag *tag)
+{
+
+ free(tag->ident);
+ free(tag);
+}
+
+/*
+ * Expand a specific tag and return the new value. If NULL
+ * is returned, the tag is empty.
+ */
+static char *
+tag_expand(struct tag *tag, struct diffinfo *di)
+{
+ /*
+ * CVS formats dates as "XXXX/XX/XX XX:XX:XX". 32 bytes
+ * is big enough until year 10,000,000,000,000,000 :-).
+ */
+ char cvsdate[32];
+ struct tm tm;
+ char *filename, *val;
+ int error;
+
+ error = rcsdatetotm(di->di_revdate, &tm);
+ if (error)
+ err(1, "strptime");
+ if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
+ err(1, "strftime");
+ filename = strrchr(di->di_rcsfile, '/');
+ if (filename == NULL)
+ filename = di->di_rcsfile;
+ else
+ filename++;
+
+ switch (tag->key) {
+ case RCSKEY_AUTHOR:
+ xasprintf(&val, "%s", di->di_author);
+ break;
+ case RCSKEY_CVSHEADER:
+ xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
+ di->di_revnum, cvsdate, di->di_author, di->di_state);
+ break;
+ case RCSKEY_DATE:
+ xasprintf(&val, "%s", cvsdate);
+ break;
+ case RCSKEY_HEADER:
+ xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
+ di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
+ di->di_state);
+ break;
+ case RCSKEY_ID:
+ xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
+ cvsdate, di->di_author, di->di_state);
+ break;
+ case RCSKEY_LOCKER:
+ /*
+ * Unimplemented even in CVSup sources. It seems we don't
+ * even have this information sent by the server.
+ */
+ return (NULL);
+ case RCSKEY_LOG:
+ /* XXX */
+ printf("%s: Implement Log keyword expansion\n", __func__);
+ return (NULL);
+ case RCSKEY_NAME:
+ if (di->di_tag != NULL)
+ xasprintf(&val, "%s", di->di_tag);
+ else
+ return (NULL);
+ break;
+ case RCSKEY_RCSFILE:
+ xasprintf(&val, "%s", filename);
+ break;
+ case RCSKEY_REVISION:
+ xasprintf(&val, "%s", di->di_revnum);
+ break;
+ case RCSKEY_SOURCE:
+ xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
+ break;
+ case RCSKEY_STATE:
+ xasprintf(&val, "%s", di->di_state);
+ break;
+ }
+ return (val);
+}
diff --git a/usr.bin/csup/keyword.h b/usr.bin/csup/keyword.h
new file mode 100644
index 0000000..033cb0a
--- /dev/null
+++ b/usr.bin/csup/keyword.h
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _KEYWORD_H_
+#define _KEYWORD_H_
+
+/* CVS expansion modes. */
+#define EXPAND_DEFAULT 0
+#define EXPAND_KEYVALUE 1
+#define EXPAND_KEYVALUELOCKER 2
+#define EXPAND_KEY 3
+#define EXPAND_OLD 4
+#define EXPAND_BINARY 5
+#define EXPAND_VALUE 6
+
+struct diffinfo;
+struct keyword;
+
+struct keyword *keyword_new(void);
+int keyword_decode_expand(const char *);
+const char *keyword_encode_expand(int);
+int keyword_alias(struct keyword *, const char *, const char *);
+int keyword_enable(struct keyword *, const char *);
+int keyword_disable(struct keyword *, const char *);
+void keyword_prepare(struct keyword *);
+int keyword_expand(struct keyword *, struct diffinfo *, char *,
+ size_t, char **, size_t *);
+void keyword_free(struct keyword *);
+
+#endif /* !_KEYWORD_H_ */
diff --git a/usr.bin/csup/lex.rcs.c b/usr.bin/csup/lex.rcs.c
new file mode 100644
index 0000000..5db7a7b
--- /dev/null
+++ b/usr.bin/csup/lex.rcs.c
@@ -0,0 +1,2094 @@
+
+#line 3 "lex.rcs.c"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 35
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else /* ! __cplusplus */
+
+/* C99 requires __STDC__ to be defined as 1. */
+#if defined (__STDC__)
+
+#define YY_USE_CONST
+
+#endif /* defined (__STDC__) */
+#endif /* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index. If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+ are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yyg->yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yyg->yy_start - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE rcsrestart(yyin ,yyscanner )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+ #define YY_LESS_LINENO(n)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ *yy_cp = yyg->yy_hold_char; \
+ YY_RESTORE_YY_MORE_OFFSET \
+ yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+
+#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner )
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ yy_size_t yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via rcsrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \
+ ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \
+ : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]
+
+void rcsrestart (FILE *input_file ,yyscan_t yyscanner );
+void rcs_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+YY_BUFFER_STATE rcs_create_buffer (FILE *file,int size ,yyscan_t yyscanner );
+void rcs_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void rcs_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void rcspush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+void rcspop_buffer_state (yyscan_t yyscanner );
+
+static void rcsensure_buffer_stack (yyscan_t yyscanner );
+static void rcs_load_buffer_state (yyscan_t yyscanner );
+static void rcs_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner );
+
+#define YY_FLUSH_BUFFER rcs_flush_buffer(YY_CURRENT_BUFFER ,yyscanner)
+
+YY_BUFFER_STATE rcs_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner );
+YY_BUFFER_STATE rcs_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
+YY_BUFFER_STATE rcs_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner );
+
+void *rcsalloc (yy_size_t ,yyscan_t yyscanner );
+void *rcsrealloc (void *,yy_size_t ,yyscan_t yyscanner );
+void rcsfree (void * ,yyscan_t yyscanner );
+
+#define yy_new_buffer rcs_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){ \
+ rcsensure_buffer_stack (yyscanner); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ rcs_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+ }
+
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){\
+ rcsensure_buffer_stack (yyscanner); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ rcs_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+ }
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define rcswrap(n) 1
+#define YY_SKIP_YYWRAP
+
+typedef unsigned char YY_CHAR;
+
+typedef int yy_state_type;
+
+#define yytext_ptr yytext_r
+
+static yy_state_type yy_get_previous_state (yyscan_t yyscanner );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner);
+static int yy_get_next_buffer (yyscan_t yyscanner );
+static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ yyg->yytext_ptr = yy_bp; \
+ yyleng = (size_t) (yy_cp - yy_bp); \
+ yyg->yy_hold_char = *yy_cp; \
+ *yy_cp = '\0'; \
+ yyg->yy_c_buf_p = yy_cp;
+
+#define YY_NUM_RULES 10
+#define YY_END_OF_BUFFER 11
+/* This struct is not used in this scanner,
+ but its presence is necessary. */
+struct yy_trans_info
+ {
+ flex_int32_t yy_verify;
+ flex_int32_t yy_nxt;
+ };
+static yyconst flex_int16_t yy_accept[89] =
+ { 0,
+ 0, 0, 11, 5, 9, 8, 10, 4, 4, 7,
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 9, 5, 4, 4, 5,
+ 4, 4, 5, 0, 0, 5, 5, 3, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 3, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 2, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 1, 5, 5, 5, 0
+ } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 1, 1, 1, 4, 1, 1, 1, 1,
+ 1, 1, 1, 4, 1, 5, 1, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 7, 8, 1,
+ 1, 1, 1, 9, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 10, 11, 12, 13,
+
+ 14, 1, 15, 16, 17, 1, 18, 19, 20, 21,
+ 22, 23, 1, 24, 25, 26, 27, 1, 1, 28,
+ 29, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1
+ } ;
+
+static yyconst flex_int32_t yy_meta[30] =
+ { 0,
+ 1, 2, 2, 2, 1, 1, 2, 2, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1
+ } ;
+
+static yyconst flex_int16_t yy_base[94] =
+ { 0,
+ 0, 0, 136, 25, 127, 297, 297, 27, 29, 297,
+ 297, 34, 39, 41, 47, 44, 50, 54, 57, 66,
+ 68, 70, 76, 80, 82, 91, 84, 86, 90, 93,
+ 95, 97, 102, 58, 61, 0, 0, 107, 109, 112,
+ 114, 117, 120, 122, 125, 129, 137, 127, 135, 145,
+ 148, 74, 152, 155, 157, 162, 167, 174, 164, 178,
+ 182, 184, 187, 189, 191, 193, 196, 200, 204, 206,
+ 214, 211, 218, 224, 228, 230, 236, 239, 241, 243,
+ 245, 248, 250, 254, 260, 265, 267, 297, 76, 56,
+ 47, 292, 294
+
+ } ;
+
+static yyconst flex_int16_t yy_def[94] =
+ { 0,
+ 88, 1, 88, 89, 88, 88, 88, 90, 91, 88,
+ 88, 92, 89, 89, 89, 89, 89, 89, 89, 89,
+ 89, 89, 89, 89, 89, 88, 89, 90, 91, 89,
+ 91, 91, 92, 93, 93, 33, 33, 89, 89, 89,
+ 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,
+ 89, 88, 89, 89, 89, 89, 89, 89, 89, 89,
+ 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,
+ 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,
+ 89, 89, 89, 89, 89, 89, 89, 0, 88, 88,
+ 88, 88, 88
+
+ } ;
+
+static yyconst flex_int16_t yy_nxt[327] =
+ { 0,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 4, 18, 4, 4, 19, 4,
+ 20, 4, 4, 4, 21, 22, 4, 4, 4, 24,
+ 25, 28, 29, 31, 32, 34, 35, 34, 36, 37,
+ 34, 34, 38, 24, 25, 24, 25, 30, 24, 25,
+ 39, 24, 25, 43, 24, 25, 27, 44, 24, 25,
+ 35, 24, 25, 35, 41, 40, 52, 46, 42, 52,
+ 24, 25, 24, 25, 24, 25, 23, 45, 47, 48,
+ 24, 25, 34, 51, 24, 25, 24, 25, 24, 25,
+ 28, 29, 26, 49, 31, 32, 50, 24, 25, 31,
+
+ 32, 31, 32, 34, 35, 34, 36, 37, 34, 34,
+ 38, 24, 25, 24, 25, 33, 24, 25, 24, 25,
+ 53, 24, 25, 55, 24, 25, 24, 25, 26, 24,
+ 25, 24, 25, 24, 25, 88, 56, 54, 60, 24,
+ 25, 24, 25, 88, 64, 57, 58, 59, 61, 24,
+ 25, 62, 24, 25, 63, 88, 24, 25, 65, 24,
+ 25, 24, 25, 88, 66, 68, 24, 25, 24, 25,
+ 69, 24, 25, 72, 88, 67, 88, 70, 24, 25,
+ 62, 71, 24, 25, 88, 62, 24, 25, 24, 25,
+ 62, 24, 25, 24, 25, 24, 25, 24, 25, 73,
+
+ 24, 25, 88, 76, 24, 25, 88, 75, 24, 25,
+ 24, 25, 62, 88, 74, 24, 25, 79, 24, 25,
+ 88, 62, 24, 25, 77, 78, 88, 80, 24, 25,
+ 88, 81, 24, 25, 24, 25, 88, 62, 88, 82,
+ 24, 25, 62, 24, 25, 24, 25, 24, 25, 24,
+ 25, 83, 24, 25, 24, 25, 84, 62, 24, 25,
+ 62, 88, 62, 85, 24, 25, 88, 87, 86, 24,
+ 25, 24, 25, 62, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 62, 88, 88, 88, 62,
+ 88, 62, 33, 33, 34, 34, 3, 88, 88, 88,
+
+ 88, 88, 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88
+ } ;
+
+static yyconst flex_int16_t yy_chk[327] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 4,
+ 4, 8, 8, 9, 9, 12, 12, 12, 12, 12,
+ 12, 12, 12, 13, 13, 14, 14, 91, 16, 16,
+ 13, 15, 15, 16, 17, 17, 90, 16, 18, 18,
+ 34, 19, 19, 35, 14, 13, 34, 18, 15, 35,
+ 20, 20, 21, 21, 22, 22, 89, 17, 19, 20,
+ 23, 23, 52, 22, 24, 24, 25, 25, 27, 27,
+ 28, 28, 26, 21, 29, 29, 21, 30, 30, 31,
+
+ 31, 32, 32, 33, 33, 33, 33, 33, 33, 33,
+ 33, 38, 38, 39, 39, 38, 40, 40, 41, 41,
+ 39, 42, 42, 41, 43, 43, 44, 44, 5, 45,
+ 45, 48, 48, 46, 46, 3, 42, 40, 46, 49,
+ 49, 47, 47, 0, 49, 43, 44, 45, 47, 50,
+ 50, 47, 51, 51, 48, 0, 53, 53, 49, 54,
+ 54, 55, 55, 0, 50, 53, 56, 56, 59, 59,
+ 54, 57, 57, 59, 0, 51, 0, 55, 58, 58,
+ 57, 56, 60, 60, 0, 58, 61, 61, 62, 62,
+ 60, 63, 63, 64, 64, 65, 65, 66, 66, 61,
+
+ 67, 67, 0, 66, 68, 68, 0, 65, 69, 69,
+ 70, 70, 63, 0, 64, 72, 72, 70, 71, 71,
+ 0, 67, 73, 73, 68, 69, 0, 71, 74, 74,
+ 0, 72, 75, 75, 76, 76, 0, 74, 0, 75,
+ 77, 77, 73, 78, 78, 79, 79, 80, 80, 81,
+ 81, 76, 82, 82, 83, 83, 79, 81, 84, 84,
+ 77, 0, 78, 80, 85, 85, 0, 84, 83, 86,
+ 86, 87, 87, 82, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 85, 0, 0, 0, 86,
+ 0, 87, 92, 92, 93, 93, 88, 88, 88, 88,
+
+ 88, 88, 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88
+ } ;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+#line 1 "rcstokenizer.l"
+/*-
+ * Copyright (c) 2007-2008, Ulf Lilleengen <lulf@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$
+ *
+ */
+/*
+ * This tokenizer must be generated by a lexxer with support for reentrancy.
+ */
+#line 34 "rcstokenizer.l"
+#include <string.h>
+#include "misc.h"
+#include "rcsparse.h"
+
+#line 567 "lex.rcs.c"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Holds the entire state of the reentrant scanner. */
+struct yyguts_t
+ {
+
+ /* User-defined. Not touched by flex. */
+ YY_EXTRA_TYPE yyextra_r;
+
+ /* The rest are the same as the globals declared in the non-reentrant scanner. */
+ FILE *yyin_r, *yyout_r;
+ size_t yy_buffer_stack_top; /**< index of top of stack. */
+ size_t yy_buffer_stack_max; /**< capacity of stack. */
+ YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */
+ char yy_hold_char;
+ int yy_n_chars;
+ int yyleng_r;
+ char *yy_c_buf_p;
+ int yy_init;
+ int yy_start;
+ int yy_did_buffer_switch_on_eof;
+ int yy_start_stack_ptr;
+ int yy_start_stack_depth;
+ int *yy_start_stack;
+ yy_state_type yy_last_accepting_state;
+ char* yy_last_accepting_cpos;
+
+ int yylineno_r;
+ int yy_flex_debug_r;
+
+ char *yytext_r;
+ int yy_more_flag;
+ int yy_more_len;
+
+ }; /* end struct yyguts_t */
+
+static int yy_init_globals (yyscan_t yyscanner );
+
+int rcslex_init (yyscan_t* scanner);
+
+int rcslex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
+
+/* Accessor methods to globals.
+ These are made visible to non-reentrant scanners for convenience. */
+
+int rcslex_destroy (yyscan_t yyscanner );
+
+int rcsget_debug (yyscan_t yyscanner );
+
+void rcsset_debug (int debug_flag ,yyscan_t yyscanner );
+
+YY_EXTRA_TYPE rcsget_extra (yyscan_t yyscanner );
+
+void rcsset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner );
+
+FILE *rcsget_in (yyscan_t yyscanner );
+
+void rcsset_in (FILE * in_str ,yyscan_t yyscanner );
+
+FILE *rcsget_out (yyscan_t yyscanner );
+
+void rcsset_out (FILE * out_str ,yyscan_t yyscanner );
+
+int rcsget_leng (yyscan_t yyscanner );
+
+char *rcsget_text (yyscan_t yyscanner );
+
+int rcsget_lineno (yyscan_t yyscanner );
+
+void rcsset_lineno (int line_number ,yyscan_t yyscanner );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int rcswrap (yyscan_t yyscanner );
+#else
+extern int rcswrap (yyscan_t yyscanner );
+#endif
+#endif
+
+ static void yyunput (int c,char *buf_ptr ,yyscan_t yyscanner);
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (yyscan_t yyscanner );
+#else
+static int input (yyscan_t yyscanner );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+ { \
+ int c = '*'; \
+ int n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner)
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int rcslex (yyscan_t yyscanner);
+
+#define YY_DECL int rcslex (yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+ register yy_state_type yy_current_state;
+ register char *yy_cp, *yy_bp;
+ register int yy_act;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+#line 51 "rcstokenizer.l"
+
+
+#line 791 "lex.rcs.c"
+
+ if ( !yyg->yy_init )
+ {
+ yyg->yy_init = 1;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! yyg->yy_start )
+ yyg->yy_start = 1; /* first start state */
+
+ if ( ! yyin )
+ yyin = stdin;
+
+ if ( ! yyout )
+ yyout = stdout;
+
+ if ( ! YY_CURRENT_BUFFER ) {
+ rcsensure_buffer_stack (yyscanner);
+ YY_CURRENT_BUFFER_LVALUE =
+ rcs_create_buffer(yyin,YY_BUF_SIZE ,yyscanner);
+ }
+
+ rcs_load_buffer_state(yyscanner );
+ }
+
+ while ( 1 ) /* loops until end-of-file is reached */
+ {
+ yy_cp = yyg->yy_c_buf_p;
+
+ /* Support of yytext. */
+ *yy_cp = yyg->yy_hold_char;
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+ yy_current_state = yyg->yy_start;
+yy_match:
+ do
+ {
+ register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+ if ( yy_accept[yy_current_state] )
+ {
+ yyg->yy_last_accepting_state = yy_current_state;
+ yyg->yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 89 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ ++yy_cp;
+ }
+ while ( yy_base[yy_current_state] != 297 );
+
+yy_find_action:
+ yy_act = yy_accept[yy_current_state];
+ if ( yy_act == 0 )
+ { /* have to back up */
+ yy_cp = yyg->yy_last_accepting_cpos;
+ yy_current_state = yyg->yy_last_accepting_state;
+ yy_act = yy_accept[yy_current_state];
+ }
+
+ YY_DO_BEFORE_ACTION;
+
+do_action: /* This label is used only to access EOF actions. */
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = yyg->yy_hold_char;
+ yy_cp = yyg->yy_last_accepting_cpos;
+ yy_current_state = yyg->yy_last_accepting_state;
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 53 "rcstokenizer.l"
+{
+ return (KEYWORD_TWO);
+}
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 56 "rcstokenizer.l"
+{
+ return (KEYWORD);
+}
+ YY_BREAK
+case 3:
+/* rule 3 can match eol */
+YY_RULE_SETUP
+#line 59 "rcstokenizer.l"
+{
+ return (STRING);
+}
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 62 "rcstokenizer.l"
+{
+ return (NUM);
+}
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 65 "rcstokenizer.l"
+{
+/* This will use ID as both ID and SYM. Do extra checking elsewhere.*/
+ return (ID);
+}
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 69 "rcstokenizer.l"
+{ return (SEMIC); }
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 70 "rcstokenizer.l"
+{ return (COLON); }
+ YY_BREAK
+case 8:
+/* rule 8 can match eol */
+YY_RULE_SETUP
+#line 71 "rcstokenizer.l"
+;
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 72 "rcstokenizer.l"
+;
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 73 "rcstokenizer.l"
+ECHO;
+ YY_BREAK
+#line 937 "lex.rcs.c"
+case YY_STATE_EOF(INITIAL):
+ yyterminate();
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = yyg->yy_hold_char;
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * rcslex(). If so, then we have to assure
+ * consistency between YY_CURRENT_BUFFER and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( yyscanner );
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner);
+
+ yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++yyg->yy_c_buf_p;
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+ yy_cp = yyg->yy_c_buf_p;
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer( yyscanner ) )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ yyg->yy_did_buffer_switch_on_eof = 0;
+
+ if ( rcswrap(yyscanner ) )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! yyg->yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yyg->yy_c_buf_p =
+ yyg->yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( yyscanner );
+
+ yy_cp = yyg->yy_c_buf_p;
+ yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ yyg->yy_c_buf_p =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars];
+
+ yy_current_state = yy_get_previous_state( yyscanner );
+
+ yy_cp = yyg->yy_c_buf_p;
+ yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+} /* end of rcslex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+ register char *source = yyg->yytext_ptr;
+ register int number_to_move, i;
+ int ret_val;
+
+ if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1;
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0;
+
+ else
+ {
+ int num_to_read =
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = YY_CURRENT_BUFFER;
+
+ int yy_c_buf_p_offset =
+ (int) (yyg->yy_c_buf_p - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ rcsrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = 0;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+ number_to_move - 1;
+
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+ yyg->yy_n_chars, (size_t) num_to_read );
+
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+ }
+
+ if ( yyg->yy_n_chars == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ rcsrestart(yyin ,yyscanner);
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+ /* Extend the array by 50%, plus the number we really need. */
+ yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1);
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) rcsrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner );
+ if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+ }
+
+ yyg->yy_n_chars += number_to_move;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+ yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+ return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+ static yy_state_type yy_get_previous_state (yyscan_t yyscanner)
+{
+ register yy_state_type yy_current_state;
+ register char *yy_cp;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ yy_current_state = yyg->yy_start;
+
+ for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp )
+ {
+ register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ yyg->yy_last_accepting_state = yy_current_state;
+ yyg->yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 89 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ }
+
+ return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner)
+{
+ register int yy_is_jam;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
+ register char *yy_cp = yyg->yy_c_buf_p;
+
+ register YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ yyg->yy_last_accepting_state = yy_current_state;
+ yyg->yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 89 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ yy_is_jam = (yy_current_state == 88);
+
+ return yy_is_jam ? 0 : yy_current_state;
+}
+
+ static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner)
+{
+ register char *yy_cp;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ yy_cp = yyg->yy_c_buf_p;
+
+ /* undo effects of setting up yytext */
+ *yy_cp = yyg->yy_hold_char;
+
+ if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+ { /* need to shift things up to make room */
+ /* +2 for EOB chars. */
+ register int number_to_move = yyg->yy_n_chars + 2;
+ register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2];
+ register char *source =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move];
+
+ while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+ *--dest = *--source;
+
+ yy_cp += (int) (dest - source);
+ yy_bp += (int) (dest - source);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars =
+ yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size;
+
+ if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+ YY_FATAL_ERROR( "flex scanner push-back overflow" );
+ }
+
+ *--yy_cp = (char) c;
+
+ yyg->yytext_ptr = yy_bp;
+ yyg->yy_hold_char = *yy_cp;
+ yyg->yy_c_buf_p = yy_cp;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+ static int yyinput (yyscan_t yyscanner)
+#else
+ static int input (yyscan_t yyscanner)
+#endif
+
+{
+ int c;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ *yyg->yy_c_buf_p = yyg->yy_hold_char;
+
+ if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+ /* This was really a NUL. */
+ *yyg->yy_c_buf_p = '\0';
+
+ else
+ { /* need more input */
+ int offset = yyg->yy_c_buf_p - yyg->yytext_ptr;
+ ++yyg->yy_c_buf_p;
+
+ switch ( yy_get_next_buffer( yyscanner ) )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ rcsrestart(yyin ,yyscanner);
+
+ /*FALLTHROUGH*/
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( rcswrap(yyscanner ) )
+ return EOF;
+
+ if ( ! yyg->yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput(yyscanner);
+#else
+ return input(yyscanner);
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yyg->yy_c_buf_p = yyg->yytext_ptr + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */
+ *yyg->yy_c_buf_p = '\0'; /* preserve yytext */
+ yyg->yy_hold_char = *++yyg->yy_c_buf_p;
+
+ return c;
+}
+#endif /* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * @param yyscanner The scanner object.
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+ void rcsrestart (FILE * input_file , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ if ( ! YY_CURRENT_BUFFER ){
+ rcsensure_buffer_stack (yyscanner);
+ YY_CURRENT_BUFFER_LVALUE =
+ rcs_create_buffer(yyin,YY_BUF_SIZE ,yyscanner);
+ }
+
+ rcs_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner);
+ rcs_load_buffer_state(yyscanner );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * @param yyscanner The scanner object.
+ */
+ void rcs_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ /* TODO. We should be able to replace this entire function body
+ * with
+ * rcspop_buffer_state();
+ * rcspush_buffer_state(new_buffer);
+ */
+ rcsensure_buffer_stack (yyscanner);
+ if ( YY_CURRENT_BUFFER == new_buffer )
+ return;
+
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *yyg->yy_c_buf_p = yyg->yy_hold_char;
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+ }
+
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+ rcs_load_buffer_state(yyscanner );
+
+ /* We don't actually know whether we did this switch during
+ * EOF (rcswrap()) processing, but the only time this flag
+ * is looked at is after rcswrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+static void rcs_load_buffer_state (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+ yyg->yy_hold_char = *yyg->yy_c_buf_p;
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * @param yyscanner The scanner object.
+ * @return the allocated buffer state.
+ */
+ YY_BUFFER_STATE rcs_create_buffer (FILE * file, int size , yyscan_t yyscanner)
+{
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) rcsalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in rcs_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) rcsalloc(b->yy_buf_size + 2 ,yyscanner );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in rcs_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ rcs_init_buffer(b,file ,yyscanner);
+
+ return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with rcs_create_buffer()
+ * @param yyscanner The scanner object.
+ */
+ void rcs_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ if ( ! b )
+ return;
+
+ if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ rcsfree((void *) b->yy_ch_buf ,yyscanner );
+
+ rcsfree((void *) b ,yyscanner );
+}
+
+#ifndef __cplusplus
+extern int isatty (int );
+#endif /* __cplusplus */
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a rcsrestart() or at EOF.
+ */
+ static void rcs_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner)
+
+{
+ int oerrno = errno;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ rcs_flush_buffer(b ,yyscanner);
+
+ b->yy_input_file = file;
+ b->yy_fill_buffer = 1;
+
+ /* If b is the current buffer, then rcs_init_buffer was _probably_
+ * called from rcsrestart() or through yy_get_next_buffer.
+ * In that case, we don't want to reset the lineno or column.
+ */
+ if (b != YY_CURRENT_BUFFER){
+ b->yy_bs_lineno = 1;
+ b->yy_bs_column = 0;
+ }
+
+ b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+
+ errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * @param yyscanner The scanner object.
+ */
+ void rcs_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == YY_CURRENT_BUFFER )
+ rcs_load_buffer_state(yyscanner );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ * the current state. This function will allocate the stack
+ * if necessary.
+ * @param new_buffer The new state.
+ * @param yyscanner The scanner object.
+ */
+void rcspush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ if (new_buffer == NULL)
+ return;
+
+ rcsensure_buffer_stack(yyscanner);
+
+ /* This block is copied from rcs_switch_to_buffer. */
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *yyg->yy_c_buf_p = yyg->yy_hold_char;
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+ }
+
+ /* Only push if top exists. Otherwise, replace top. */
+ if (YY_CURRENT_BUFFER)
+ yyg->yy_buffer_stack_top++;
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+ /* copied from rcs_switch_to_buffer. */
+ rcs_load_buffer_state(yyscanner );
+ yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ * The next element becomes the new top.
+ * @param yyscanner The scanner object.
+ */
+void rcspop_buffer_state (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ if (!YY_CURRENT_BUFFER)
+ return;
+
+ rcs_delete_buffer(YY_CURRENT_BUFFER ,yyscanner);
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ if (yyg->yy_buffer_stack_top > 0)
+ --yyg->yy_buffer_stack_top;
+
+ if (YY_CURRENT_BUFFER) {
+ rcs_load_buffer_state(yyscanner );
+ yyg->yy_did_buffer_switch_on_eof = 1;
+ }
+}
+
+/* Allocates the stack if it does not exist.
+ * Guarantees space for at least one push.
+ */
+static void rcsensure_buffer_stack (yyscan_t yyscanner)
+{
+ int num_to_alloc;
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ if (!yyg->yy_buffer_stack) {
+
+ /* First allocation is just for 2 elements, since we don't know if this
+ * scanner will even need a stack. We use 2 instead of 1 to avoid an
+ * immediate realloc on the next call.
+ */
+ num_to_alloc = 1;
+ yyg->yy_buffer_stack = (struct yy_buffer_state**)rcsalloc
+ (num_to_alloc * sizeof(struct yy_buffer_state*)
+ , yyscanner);
+ if ( ! yyg->yy_buffer_stack )
+ YY_FATAL_ERROR( "out of dynamic memory in rcsensure_buffer_stack()" );
+
+ memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+ yyg->yy_buffer_stack_max = num_to_alloc;
+ yyg->yy_buffer_stack_top = 0;
+ return;
+ }
+
+ if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){
+
+ /* Increase the buffer to prepare for a possible push. */
+ int grow_size = 8 /* arbitrary grow size */;
+
+ num_to_alloc = yyg->yy_buffer_stack_max + grow_size;
+ yyg->yy_buffer_stack = (struct yy_buffer_state**)rcsrealloc
+ (yyg->yy_buffer_stack,
+ num_to_alloc * sizeof(struct yy_buffer_state*)
+ , yyscanner);
+ if ( ! yyg->yy_buffer_stack )
+ YY_FATAL_ERROR( "out of dynamic memory in rcsensure_buffer_stack()" );
+
+ /* zero only the new slots.*/
+ memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*));
+ yyg->yy_buffer_stack_max = num_to_alloc;
+ }
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE rcs_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner)
+{
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return 0;
+
+ b = (YY_BUFFER_STATE) rcsalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in rcs_scan_buffer()" );
+
+ b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = 0;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ rcs_switch_to_buffer(b ,yyscanner );
+
+ return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to rcslex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ * rcs_scan_bytes() instead.
+ */
+YY_BUFFER_STATE rcs_scan_string (yyconst char * yystr , yyscan_t yyscanner)
+{
+
+ return rcs_scan_bytes(yystr,strlen(yystr) ,yyscanner);
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to rcslex() will
+ * scan from a @e copy of @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE rcs_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner)
+{
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = _yybytes_len + 2;
+ buf = (char *) rcsalloc(n ,yyscanner );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in rcs_scan_bytes()" );
+
+ for ( i = 0; i < _yybytes_len; ++i )
+ buf[i] = yybytes[i];
+
+ buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = rcs_scan_buffer(buf,n ,yyscanner);
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in rcs_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner)
+{
+ (void) fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ yytext[yyleng] = yyg->yy_hold_char; \
+ yyg->yy_c_buf_p = yytext + yyless_macro_arg; \
+ yyg->yy_hold_char = *yyg->yy_c_buf_p; \
+ *yyg->yy_c_buf_p = '\0'; \
+ yyleng = yyless_macro_arg; \
+ } \
+ while ( 0 )
+
+/* Accessor methods (get/set functions) to struct members. */
+
+/** Get the user-defined data for this scanner.
+ * @param yyscanner The scanner object.
+ */
+YY_EXTRA_TYPE rcsget_extra (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ return yyextra;
+}
+
+/** Get the current line number.
+ * @param yyscanner The scanner object.
+ */
+int rcsget_lineno (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ if (! YY_CURRENT_BUFFER)
+ return 0;
+
+ return yylineno;
+}
+
+/** Get the current column number.
+ * @param yyscanner The scanner object.
+ */
+int rcsget_column (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ if (! YY_CURRENT_BUFFER)
+ return 0;
+
+ return yycolumn;
+}
+
+/** Get the input stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *rcsget_in (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ return yyin;
+}
+
+/** Get the output stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *rcsget_out (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ return yyout;
+}
+
+/** Get the length of the current token.
+ * @param yyscanner The scanner object.
+ */
+int rcsget_leng (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ return yyleng;
+}
+
+/** Get the current token.
+ * @param yyscanner The scanner object.
+ */
+
+char *rcsget_text (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ return yytext;
+}
+
+/** Set the user-defined data. This data is never touched by the scanner.
+ * @param user_defined The data to be associated with this scanner.
+ * @param yyscanner The scanner object.
+ */
+void rcsset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ yyextra = user_defined ;
+}
+
+/** Set the current line number.
+ * @param line_number
+ * @param yyscanner The scanner object.
+ */
+void rcsset_lineno (int line_number , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ /* lineno is only valid if an input buffer exists. */
+ if (! YY_CURRENT_BUFFER )
+ yy_fatal_error( "rcsset_lineno called with no buffer" , yyscanner);
+
+ yylineno = line_number;
+}
+
+/** Set the current column.
+ * @param line_number
+ * @param yyscanner The scanner object.
+ */
+void rcsset_column (int column_no , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ /* column is only valid if an input buffer exists. */
+ if (! YY_CURRENT_BUFFER )
+ yy_fatal_error( "rcsset_column called with no buffer" , yyscanner);
+
+ yycolumn = column_no;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param in_str A readable stream.
+ * @param yyscanner The scanner object.
+ * @see rcs_switch_to_buffer
+ */
+void rcsset_in (FILE * in_str , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ yyin = in_str ;
+}
+
+void rcsset_out (FILE * out_str , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ yyout = out_str ;
+}
+
+int rcsget_debug (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ return yy_flex_debug;
+}
+
+void rcsset_debug (int bdebug , yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ yy_flex_debug = bdebug ;
+}
+
+/* Accessor methods for yylval and yylloc */
+
+/* User-visible API */
+
+/* rcslex_init is special because it creates the scanner itself, so it is
+ * the ONLY reentrant function that doesn't take the scanner as the last argument.
+ * That's why we explicitly handle the declaration, instead of using our macros.
+ */
+
+int rcslex_init(yyscan_t* ptr_yy_globals)
+
+{
+ if (ptr_yy_globals == NULL){
+ errno = EINVAL;
+ return 1;
+ }
+
+ *ptr_yy_globals = (yyscan_t) rcsalloc ( sizeof( struct yyguts_t ), NULL );
+
+ if (*ptr_yy_globals == NULL){
+ errno = ENOMEM;
+ return 1;
+ }
+
+ /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */
+ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+
+ return yy_init_globals ( *ptr_yy_globals );
+}
+
+/* rcslex_init_extra has the same functionality as rcslex_init, but follows the
+ * convention of taking the scanner as the last argument. Note however, that
+ * this is a *pointer* to a scanner, as it will be allocated by this call (and
+ * is the reason, too, why this function also must handle its own declaration).
+ * The user defined value in the first argument will be available to rcsalloc in
+ * the yyextra field.
+ */
+
+int rcslex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals )
+
+{
+ struct yyguts_t dummy_yyguts;
+
+ rcsset_extra (yy_user_defined, &dummy_yyguts);
+
+ if (ptr_yy_globals == NULL){
+ errno = EINVAL;
+ return 1;
+ }
+
+ *ptr_yy_globals = (yyscan_t) rcsalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
+
+ if (*ptr_yy_globals == NULL){
+ errno = ENOMEM;
+ return 1;
+ }
+
+ /* By setting to 0xAA, we expose bugs in
+ yy_init_globals. Leave at 0x00 for releases. */
+ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+
+ rcsset_extra (yy_user_defined, *ptr_yy_globals);
+
+ return yy_init_globals ( *ptr_yy_globals );
+}
+
+static int yy_init_globals (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+ /* Initialization is the same as for the non-reentrant scanner.
+ * This function is called from rcslex_destroy(), so don't allocate here.
+ */
+
+ yyg->yy_buffer_stack = 0;
+ yyg->yy_buffer_stack_top = 0;
+ yyg->yy_buffer_stack_max = 0;
+ yyg->yy_c_buf_p = (char *) 0;
+ yyg->yy_init = 0;
+ yyg->yy_start = 0;
+
+ yyg->yy_start_stack_ptr = 0;
+ yyg->yy_start_stack_depth = 0;
+ yyg->yy_start_stack = NULL;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+ yyin = stdin;
+ yyout = stdout;
+#else
+ yyin = (FILE *) 0;
+ yyout = (FILE *) 0;
+#endif
+
+ /* For future reference: Set errno on error, since we are called by
+ * rcslex_init()
+ */
+ return 0;
+}
+
+/* rcslex_destroy is for both reentrant and non-reentrant scanners. */
+int rcslex_destroy (yyscan_t yyscanner)
+{
+ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+ /* Pop the buffer stack, destroying each element. */
+ while(YY_CURRENT_BUFFER){
+ rcs_delete_buffer(YY_CURRENT_BUFFER ,yyscanner );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ rcspop_buffer_state(yyscanner);
+ }
+
+ /* Destroy the stack itself. */
+ rcsfree(yyg->yy_buffer_stack ,yyscanner);
+ yyg->yy_buffer_stack = NULL;
+
+ /* Destroy the start condition stack. */
+ rcsfree(yyg->yy_start_stack ,yyscanner );
+ yyg->yy_start_stack = NULL;
+
+ /* Reset the globals. This is important in a non-reentrant scanner so the next time
+ * rcslex() is called, initialization will occur. */
+ yy_init_globals( yyscanner);
+
+ /* Destroy the main struct (reentrant only). */
+ rcsfree ( yyscanner , yyscanner );
+ yyscanner = NULL;
+ return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner)
+{
+ register int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner)
+{
+ register int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+}
+#endif
+
+void *rcsalloc (yy_size_t size , yyscan_t yyscanner)
+{
+ return (void *) malloc( size );
+}
+
+void *rcsrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner)
+{
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return (void *) realloc( (char *) ptr, size );
+}
+
+void rcsfree (void * ptr , yyscan_t yyscanner)
+{
+ free( (char *) ptr ); /* see rcsrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+#line 73 "rcstokenizer.l"
+
+
+
diff --git a/usr.bin/csup/lister.c b/usr.bin/csup/lister.c
new file mode 100644
index 0000000..b10dbd3
--- /dev/null
+++ b/usr.bin/csup/lister.c
@@ -0,0 +1,569 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "attrstack.h"
+#include "config.h"
+#include "fattr.h"
+#include "globtree.h"
+#include "lister.h"
+#include "misc.h"
+#include "mux.h"
+#include "proto.h"
+#include "status.h"
+#include "stream.h"
+
+/* Internal error codes. */
+#define LISTER_ERR_WRITE (-1) /* Error writing to server. */
+#define LISTER_ERR_STATUS (-2) /* Status file error in lstr->errmsg. */
+
+struct lister {
+ struct config *config;
+ struct stream *wr;
+ char *errmsg;
+};
+
+static int lister_batch(struct lister *);
+static int lister_coll(struct lister *, struct coll *, struct status *);
+static int lister_dodirdown(struct lister *, struct coll *,
+ struct statusrec *, struct attrstack *as);
+static int lister_dodirup(struct lister *, struct coll *,
+ struct statusrec *, struct attrstack *as);
+static int lister_dofile(struct lister *, struct coll *,
+ struct statusrec *);
+static int lister_dodead(struct lister *, struct coll *,
+ struct statusrec *);
+static int lister_dorcsfile(struct lister *, struct coll *,
+ struct statusrec *);
+static int lister_dorcsdead(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;
+ case SR_FILEDEAD:
+ if (depth < prunedepth) {
+ if (!(coll->co_options & CO_CHECKOUTMODE)) {
+ error = lister_dorcsdead(l, coll, sr);
+ if (error)
+ goto bad;
+ }
+ }
+ break;
+ case SR_FILELIVE:
+ if (depth < prunedepth) {
+ if (!(coll->co_options & CO_CHECKOUTMODE)) {
+ error = lister_dorcsfile(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 rcs file live entry found in the status file. */
+static int
+lister_dorcsfile(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;
+ size_t len;
+ 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 = cvspath(coll->co_prefix, sr->sr_file, 0);
+ 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);
+ } else
+ fa = sr->sr_clientattr;
+ if (fa != NULL && fattr_equal(fa, sr->sr_clientattr)) {
+ /*
+ * If the file is an RCS file, we use "loose" equality, so sizes
+ * may disagress because of differences in whitespace.
+ */
+ if (isrcs(sr->sr_file, &len) &&
+ !(coll->co_options & CO_NORCS) &&
+ !(coll->co_options & CO_STRICTCHECKRCS)) {
+ fattr_maskout(fa, FA_SIZE);
+ }
+ sendattr = fa;
+ } else {
+ /*
+ * If different, the user may have changed it, so we report
+ * bogus attributes to force a full comparison.
+ */
+ sendattr = fattr_bogus;
+ }
+ 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);
+}
+
+/* Handle a rcs file dead entry found in the status file. */
+static int
+lister_dorcsdead(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;
+ size_t len;
+ 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 = cvspath(coll->co_prefix, sr->sr_file, 1);
+ 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);
+ } else
+ fa = sr->sr_clientattr;
+ if (fattr_equal(fa, sr->sr_clientattr)) {
+ /*
+ * If the file is an RCS file, we use "loose" equality, so sizes
+ * may disagress because of differences in whitespace.
+ */
+ if (isrcs(sr->sr_file, &len) &&
+ !(coll->co_options & CO_NORCS) &&
+ !(coll->co_options & CO_STRICTCHECKRCS)) {
+ fattr_maskout(fa, FA_SIZE);
+ }
+ sendattr = fa;
+ } else {
+ /*
+ * If different, the user may have changed it, so we report
+ * bogus attributes to force a full comparison.
+ */
+ sendattr = fattr_bogus;
+ }
+ error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr,
+ config->fasupport, coll->co_attrignore);
+ if (error)
+ return (LISTER_ERR_WRITE);
+ return (0);
+}
diff --git a/usr.bin/csup/lister.h b/usr.bin/csup/lister.h
new file mode 100644
index 0000000..a0a9bbe
--- /dev/null
+++ b/usr.bin/csup/lister.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _LISTER_H_
+#define _LISTER_H_
+
+void *lister(void *);
+
+#endif /* !_LISTER_H_ */
diff --git a/usr.bin/csup/main.c b/usr.bin/csup/main.c
new file mode 100644
index 0000000..e7711b3
--- /dev/null
+++ b/usr.bin/csup/main.c
@@ -0,0 +1,347 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "fattr.h"
+#include "misc.h"
+#include "proto.h"
+#include "stream.h"
+
+#define USAGE_OPTFMT " %-12s %s\n"
+#define USAGE_OPTFMTSUB " %-14s %s\n", ""
+
+int verbose = 1;
+
+static void
+usage(char *argv0)
+{
+
+ lprintf(-1, "Usage: %s [options] supfile\n", basename(argv0));
+ lprintf(-1, " Options:\n");
+ lprintf(-1, USAGE_OPTFMT, "-1", "Don't retry automatically on failure "
+ "(same as \"-r 0\")");
+ lprintf(-1, USAGE_OPTFMT, "-4", "Force usage of IPv4 addresses");
+ lprintf(-1, USAGE_OPTFMT, "-6", "Force usage of IPv6 addresses");
+ lprintf(-1, USAGE_OPTFMT, "-a",
+ "Require server to authenticate itself to us");
+ 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, reqauth;
+ 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;
+ reqauth = 0;
+
+ while ((c = getopt(argc, argv,
+ "146aA: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':
+ /* Require server authentication */
+ reqauth = 1;
+ 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;
+ config->reqauth = reqauth;
+ lprintf(2, "Connecting to %s\n", config->host);
+
+ i = 0;
+ fattr_init(); /* Initialize the fattr API. */
+ timer = bt_new(300, 7200, 2.0, 0.1);
+ for (;;) {
+ status = proto_connect(config, family, port);
+ if (status == STATUS_SUCCESS) {
+ status = proto_run(config);
+ if (status != STATUS_TRANSIENTFAILURE)
+ break;
+ }
+ if (retries >= 0 && i >= retries)
+ break;
+ nexttry = time(0) + bt_get(timer);
+ localtime_r(&nexttry, &tm);
+ lprintf(1, "Will retry at %02d:%02d:%02d\n",
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ bt_pause(timer);
+ lprintf(1, "Retrying\n");
+ i++;
+ }
+ bt_free(timer);
+ fattr_fini();
+ if (lflag) {
+ unlink(lockfile);
+ flock(lockfd, LOCK_UN);
+ close(lockfd);
+ }
+ config_free(config);
+ if (status != STATUS_SUCCESS)
+ return (1);
+ return (0);
+}
diff --git a/usr.bin/csup/main.h b/usr.bin/csup/main.h
new file mode 100644
index 0000000..217c7eb
--- /dev/null
+++ b/usr.bin/csup/main.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id$
+ */
+
+extern int verbose;
diff --git a/usr.bin/csup/misc.c b/usr.bin/csup/misc.c
new file mode 100644
index 0000000..ae16b59
--- /dev/null
+++ b/usr.bin/csup/misc.c
@@ -0,0 +1,645 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <openssl/md5.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <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);
+}
+
+const char *
+pathlast(const char *path)
+{
+ const 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);
+}
+
+/*
+ * Checks if a file is an RCS file.
+ */
+int
+isrcs(const char *file, size_t *len)
+{
+ const char *cp;
+
+ if (file[0] == '/')
+ return (0);
+ cp = file;
+ while ((cp = strstr(cp, "..")) != NULL) {
+ if (cp == file || cp[2] == '\0' ||
+ (cp[-1] == '/' && cp[2] == '/'))
+ return (0);
+ cp += 2;
+ }
+ *len = strlen(file);
+ if (*len < 2 || file[*len - 1] != 'v' || file[*len - 2] != ',') {
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * 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)
+{
+ char *path;
+ size_t len;
+
+ if (!isrcs(file, &len))
+ return (NULL);
+ xasprintf(&path, "%s/%.*s", prefix, (int)len - 2, file);
+ return (path);
+}
+
+/*
+ * Returns a cvs path allocated with malloc() containing absolute pathname to a
+ * file in cvs mode which can reside in the attic. XXX: filename has really no
+ * restrictions.
+ */
+char *
+cvspath(const char *prefix, const char *file, int attic)
+{
+ const char *last;
+ char *path;
+
+ last = pathlast(file);
+ if (attic)
+ xasprintf(&path, "%s/%.*sAttic/%s", prefix, (int)(last - file),
+ file, last);
+ else
+ xasprintf(&path, "%s/%s", prefix, file);
+
+ return (path);
+}
+
+/*
+ * Regular or attic path if regular fails.
+ * XXX: This should perhaps also check if the Attic file exists too, and return
+ * NULL if not.
+ */
+char *
+atticpath(const char *prefix, const char *file)
+{
+ char *path;
+
+ path = cvspath(prefix, file, 0);
+ if (access(path, F_OK) != 0) {
+ free(path);
+ path = cvspath(prefix, file, 1);
+ }
+ 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);
+}
+
+/* Compare two revisions. */
+int
+rcsnum_cmp(char *revision1, char *revision2)
+{
+ char *ptr1, *ptr2, *dot1, *dot2;
+ int num1len, num2len, ret;
+
+ ptr1 = revision1;
+ ptr2 = revision2;
+ while (*ptr1 != '\0' && *ptr2 != '\0') {
+ dot1 = strchr(ptr1, '.');
+ dot2 = strchr(ptr2, '.');
+ if (dot1 == NULL)
+ dot1 = strchr(ptr1, '\0');
+ if (dot2 == NULL)
+ dot2 = strchr(ptr2, '\0');
+
+ num1len = dot1 - ptr1;
+ num2len = dot2 - ptr2;
+ /* Check the distance between each, showing how many digits */
+ if (num1len > num2len)
+ return (1);
+ else if (num1len < num2len)
+ return (-1);
+
+ /* Equal distance means we must check each character. */
+ ret = strncmp(ptr1, ptr2, num1len);
+ if (ret != 0)
+ return (ret);
+ ptr1 = (*dot1 == '.') ? (dot1 + 1) : dot1;
+ ptr2 = (*dot2 == '.') ? (dot2 + 1) : dot2;
+ }
+
+ if (*ptr1 != '\0' && *ptr2 == '\0')
+ return (1);
+ if (*ptr1 == '\0' && *ptr2 != '\0')
+ return (-1);
+ return (0);
+
+}
+
+/* Returns 0 if a rcsrev is not a trunk revision number. */
+int
+rcsrev_istrunk(char *revnum)
+{
+ char *tmp;
+
+ tmp = strchr(revnum, '.');
+ tmp++;
+ if (strchr(tmp, '.') != NULL)
+ return (0);
+ return (1);
+}
+
+/* Return prefix of rcsfile. */
+char *
+rcsrev_prefix(char *revnum)
+{
+ char *modrev, *pos;
+
+ modrev = xstrdup(revnum);
+ pos = strrchr(modrev, '.');
+ if (pos == NULL) {
+ free(modrev);
+ return (NULL);
+ }
+ *pos = '\0';
+ return (modrev);
+}
diff --git a/usr.bin/csup/misc.h b/usr.bin/csup/misc.h
new file mode 100644
index 0000000..a7ca3a6
--- /dev/null
+++ b/usr.bin/csup/misc.h
@@ -0,0 +1,138 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _MISC_H_
+#define _MISC_H_
+
+#include <openssl/md5.h>
+
+#include <sys/types.h>
+
+/* If we're not compiling in a C99 environment, define the C99 types. */
+#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901
+
+#ifdef uint32_t
+#undef uint32_t
+#endif
+#define uint32_t u_int32_t
+
+#ifdef uint16_t
+#undef uint16_t
+#endif
+#define uint16_t u_int16_t
+
+#ifdef uint8_t
+#undef uint8_t
+#endif
+#define uint8_t u_int8_t
+
+#else
+#include <stdint.h>
+#endif
+
+/* This is a GCC-specific keyword but some other compilers (namely icc)
+ understand it, and the code won't work if we can't disable padding
+ anyways. */
+#undef __packed
+#define __packed __attribute__((__packed__))
+
+/* We explicitely don't define this with icc because it defines __GNUC__
+ but doesn't support it. */
+#undef __printflike
+#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && \
+ (__GNUC__ > 2 || __GNUC__ == 2 && __GNUC__MINOR__ >= 7)
+#define __printflike(fmtarg, firstvararg) \
+ __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
+#else
+#define __printflike(fmtarg, firstvararg)
+#endif
+
+/* Exit codes. */
+#define STATUS_SUCCESS 0
+#define STATUS_FAILURE 1
+#define STATUS_TRANSIENTFAILURE 2
+#define STATUS_INTERRUPTED 3
+
+struct config;
+struct stream;
+
+/* Thread parameters. */
+struct thread_args {
+ struct config *config;
+ struct stream *rd;
+ struct stream *wr;
+ int status;
+ char *errmsg;
+};
+
+/* Minimum size for MD5_File() and MD5_End() buffers. */
+#define MD5_DIGEST_SIZE 33
+
+#define min(a, b) ((a) > (b) ? (b) : (a))
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+struct backoff_timer;
+struct pattlist;
+struct tm;
+
+int 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);
+const char *pathlast(const char *);
+int isrcs(const char *, size_t *);
+char *checkoutpath(const char *, const char *);
+char *cvspath(const char *, const char *, int);
+char *atticpath(const char *, const char *);
+char *path_prefix(char *);
+char *path_first(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);
+int rcsnum_cmp(char *, char *);
+int rcsrev_istrunk(char *);
+char *rcsrev_prefix(char *);
+
+struct pattlist *pattlist_new(void);
+void pattlist_add(struct pattlist *, const char *);
+char *pattlist_get(struct pattlist *, size_t);
+size_t pattlist_size(struct pattlist *);
+void pattlist_free(struct pattlist *);
+
+struct backoff_timer *bt_new(time_t, time_t, float, float);
+time_t bt_get(struct backoff_timer *);
+void bt_pause(struct backoff_timer *);
+void bt_free(struct backoff_timer *);
+
+#endif /* !_MISC_H_ */
diff --git a/usr.bin/csup/mux.c b/usr.bin/csup/mux.c
new file mode 100644
index 0000000..b344be1
--- /dev/null
+++ b/usr.bin/csup/mux.c
@@ -0,0 +1,1202 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "misc.h"
+#include "mux.h"
+
+/*
+ * Packet types.
+ */
+#define MUX_STARTUPREQ 0
+#define MUX_STARTUPREP 1
+#define MUX_CONNECT 2
+#define MUX_ACCEPT 3
+#define MUX_RESET 4
+#define MUX_DATA 5
+#define MUX_WINDOW 6
+#define MUX_CLOSE 7
+
+/*
+ * Header sizes.
+ */
+#define MUX_STARTUPHDRSZ 3
+#define MUX_CONNECTHDRSZ 8
+#define MUX_ACCEPTHDRSZ 8
+#define MUX_RESETHDRSZ 2
+#define MUX_DATAHDRSZ 4
+#define MUX_WINDOWHDRSZ 6
+#define MUX_CLOSEHDRSZ 2
+
+#define MUX_PROTOVER 0 /* Protocol version. */
+
+struct mux_header {
+ uint8_t type;
+ union {
+ struct {
+ uint16_t version;
+ } __packed mh_startup;
+ struct {
+ uint8_t id;
+ uint16_t mss;
+ uint32_t window;
+ } __packed mh_connect;
+ struct {
+ uint8_t id;
+ uint16_t mss;
+ uint32_t window;
+ } __packed mh_accept;
+ struct {
+ uint8_t id;
+ } __packed mh_reset;
+ struct {
+ uint8_t id;
+ uint16_t len;
+ } __packed mh_data;
+ struct {
+ uint8_t id;
+ uint32_t window;
+ } __packed mh_window;
+ struct {
+ uint8_t id;
+ } __packed mh_close;
+ } mh_u;
+} __packed;
+
+#define mh_startup mh_u.mh_startup
+#define mh_connect mh_u.mh_connect
+#define mh_accept mh_u.mh_accept
+#define mh_reset mh_u.mh_reset
+#define mh_data mh_u.mh_data
+#define mh_window mh_u.mh_window
+#define mh_close mh_u.mh_close
+
+#define MUX_MAXCHAN 2
+
+/* Channel states. */
+#define CS_UNUSED 0
+#define CS_LISTENING 1
+#define CS_CONNECTING 2
+#define CS_ESTABLISHED 3
+#define CS_RDCLOSED 4
+#define CS_WRCLOSED 5
+#define CS_CLOSED 6
+
+/* Channel flags. */
+#define CF_CONNECT 0x01
+#define CF_ACCEPT 0x02
+#define CF_RESET 0x04
+#define CF_WINDOW 0x08
+#define CF_DATA 0x10
+#define CF_CLOSE 0x20
+
+#define CHAN_SBSIZE (16 * 1024) /* Send buffer size. */
+#define CHAN_RBSIZE (16 * 1024) /* Receive buffer size. */
+#define CHAN_MAXSEGSIZE 1024 /* Maximum segment size. */
+
+/* Circular buffer. */
+struct buf {
+ uint8_t *data;
+ size_t size;
+ size_t in;
+ size_t out;
+};
+
+struct chan {
+ int flags;
+ int state;
+ pthread_mutex_t lock;
+ struct mux *mux;
+
+ /* Receiver state variables. */
+ struct buf *recvbuf;
+ pthread_cond_t rdready;
+ uint32_t recvseq;
+ uint16_t recvmss;
+
+ /* Sender state variables. */
+ struct buf *sendbuf;
+ pthread_cond_t wrready;
+ uint32_t sendseq;
+ uint32_t sendwin;
+ uint16_t sendmss;
+};
+
+struct mux {
+ int closed;
+ int status;
+ int socket;
+ pthread_mutex_t lock;
+ pthread_cond_t done;
+ struct chan *channels[MUX_MAXCHAN];
+ int nchans;
+
+ /* Sender thread data. */
+ pthread_t sender;
+ pthread_cond_t sender_newwork;
+ pthread_cond_t sender_started;
+ int sender_waiting;
+ int sender_ready;
+ int sender_lastid;
+
+ /* Receiver thread data. */
+ pthread_t receiver;
+};
+
+static int sock_writev(int, struct iovec *, int);
+static int sock_write(int, void *, size_t);
+static ssize_t sock_read(int, void *, size_t);
+static int sock_readwait(int, void *, size_t);
+
+static int mux_init(struct mux *);
+static void mux_lock(struct mux *);
+static void mux_unlock(struct mux *);
+
+static struct chan *chan_new(struct mux *);
+static struct chan *chan_get(struct mux *, int);
+static struct chan *chan_connect(struct mux *, int);
+static void chan_lock(struct chan *);
+static void chan_unlock(struct chan *);
+static int chan_insert(struct mux *, struct chan *);
+static void chan_free(struct chan *);
+
+static struct buf *buf_new(size_t);
+static size_t buf_count(struct buf *);
+static size_t buf_avail(struct buf *);
+static void buf_get(struct buf *, void *, size_t);
+static void buf_put(struct buf *, const void *, size_t);
+static void buf_free(struct buf *);
+
+static void sender_wakeup(struct mux *);
+static void *sender_loop(void *);
+static int sender_waitforwork(struct mux *, int *);
+static int sender_scan(struct mux *, int *);
+static void sender_cleanup(void *);
+
+static void *receiver_loop(void *);
+
+static int
+sock_writev(int s, struct iovec *iov, int iovcnt)
+{
+ ssize_t nbytes;
+
+again:
+ nbytes = writev(s, iov, iovcnt);
+ if (nbytes != -1) {
+ while (nbytes > 0 && (size_t)nbytes >= iov->iov_len) {
+ nbytes -= iov->iov_len;
+ iov++;
+ iovcnt--;
+ }
+ if (nbytes == 0)
+ return (0);
+ iov->iov_len -= nbytes;
+ iov->iov_base = (char *)iov->iov_base + nbytes;
+ } else if (errno != EINTR) {
+ return (-1);
+ }
+ goto again;
+}
+
+static int
+sock_write(int s, void *buf, size_t size)
+{
+ struct iovec iov;
+ int ret;
+
+ iov.iov_base = buf;
+ iov.iov_len = size;
+ ret = sock_writev(s, &iov, 1);
+ return (ret);
+}
+
+static ssize_t
+sock_read(int s, void *buf, size_t size)
+{
+ ssize_t nbytes;
+
+again:
+ nbytes = read(s, buf, size);
+ if (nbytes == -1 && errno == EINTR)
+ goto again;
+ return (nbytes);
+}
+
+static int
+sock_readwait(int s, void *buf, size_t size)
+{
+ char *cp;
+ ssize_t nbytes;
+ size_t left;
+
+ cp = buf;
+ left = size;
+ while (left > 0) {
+ nbytes = sock_read(s, cp, left);
+ if (nbytes == 0) {
+ errno = ECONNRESET;
+ return (-1);
+ }
+ if (nbytes < 0)
+ return (-1);
+ left -= nbytes;
+ cp += nbytes;
+ }
+ return (0);
+}
+
+static void
+mux_lock(struct mux *m)
+{
+ int error;
+
+ error = pthread_mutex_lock(&m->lock);
+ assert(!error);
+}
+
+static void
+mux_unlock(struct mux *m)
+{
+ int error;
+
+ error = pthread_mutex_unlock(&m->lock);
+ assert(!error);
+}
+
+/* Create a TCP multiplexer on the given socket. */
+struct mux *
+mux_open(int sock, struct chan **chan)
+{
+ struct mux *m;
+ struct chan *chan0;
+ int error;
+
+ m = xmalloc(sizeof(struct mux));
+ memset(m->channels, 0, sizeof(m->channels));
+ m->nchans = 0;
+ m->closed = 0;
+ m->status = -1;
+ m->socket = sock;
+
+ m->sender_waiting = 0;
+ m->sender_lastid = 0;
+ m->sender_ready = 0;
+ pthread_mutex_init(&m->lock, NULL);
+ pthread_cond_init(&m->done, NULL);
+ pthread_cond_init(&m->sender_newwork, NULL);
+ pthread_cond_init(&m->sender_started, NULL);
+
+ error = mux_init(m);
+ if (error)
+ goto bad;
+ chan0 = chan_connect(m, 0);
+ if (chan0 == NULL)
+ goto bad;
+ *chan = chan0;
+ return (m);
+bad:
+ mux_shutdown(m, NULL, STATUS_FAILURE);
+ (void)mux_close(m);
+ return (NULL);
+}
+
+int
+mux_close(struct mux *m)
+{
+ struct chan *chan;
+ int i, status;
+
+ assert(m->closed);
+ for (i = 0; i < m->nchans; i++) {
+ chan = m->channels[i];
+ if (chan != NULL)
+ chan_free(chan);
+ }
+ pthread_cond_destroy(&m->sender_started);
+ pthread_cond_destroy(&m->sender_newwork);
+ pthread_cond_destroy(&m->done);
+ pthread_mutex_destroy(&m->lock);
+ status = m->status;
+ free(m);
+ return (status);
+}
+
+/* Close a channel. */
+int
+chan_close(struct chan *chan)
+{
+
+ chan_lock(chan);
+ if (chan->state == CS_ESTABLISHED) {
+ chan->state = CS_WRCLOSED;
+ chan->flags |= CF_CLOSE;
+ } else if (chan->state == CS_RDCLOSED) {
+ chan->state = CS_CLOSED;
+ chan->flags |= CF_CLOSE;
+ } else if (chan->state == CS_WRCLOSED || chan->state == CS_CLOSED) {
+ chan_unlock(chan);
+ return (0);
+ } else {
+ chan_unlock(chan);
+ return (-1);
+ }
+ chan_unlock(chan);
+ sender_wakeup(chan->mux);
+ return (0);
+}
+
+void
+chan_wait(struct chan *chan)
+{
+
+ chan_lock(chan);
+ while (chan->state != CS_CLOSED)
+ pthread_cond_wait(&chan->rdready, &chan->lock);
+ chan_unlock(chan);
+}
+
+/* Returns the ID of an available channel in the listening state. */
+int
+chan_listen(struct mux *m)
+{
+ struct chan *chan;
+ int i;
+
+ mux_lock(m);
+ for (i = 0; i < m->nchans; i++) {
+ chan = m->channels[i];
+ chan_lock(chan);
+ if (chan->state == CS_UNUSED) {
+ mux_unlock(m);
+ chan->state = CS_LISTENING;
+ chan_unlock(chan);
+ return (i);
+ }
+ chan_unlock(chan);
+ }
+ mux_unlock(m);
+ chan = chan_new(m);
+ chan->state = CS_LISTENING;
+ i = chan_insert(m, chan);
+ if (i == -1)
+ chan_free(chan);
+ return (i);
+}
+
+struct chan *
+chan_accept(struct mux *m, int id)
+{
+ struct chan *chan;
+
+ chan = chan_get(m, id);
+ while (chan->state == CS_LISTENING)
+ pthread_cond_wait(&chan->rdready, &chan->lock);
+ if (chan->state != CS_ESTABLISHED) {
+ errno = ECONNRESET;
+ chan_unlock(chan);
+ return (NULL);
+ }
+ chan_unlock(chan);
+ return (chan);
+}
+
+/* Read bytes from a channel. */
+ssize_t
+chan_read(struct chan *chan, void *buf, size_t size)
+{
+ char *cp;
+ size_t count, n;
+
+ cp = buf;
+ chan_lock(chan);
+ for (;;) {
+ if (chan->state == CS_RDCLOSED || chan->state == CS_CLOSED) {
+ chan_unlock(chan);
+ return (0);
+ }
+ if (chan->state != CS_ESTABLISHED &&
+ chan->state != CS_WRCLOSED) {
+ chan_unlock(chan);
+ errno = EBADF;
+ return (-1);
+ }
+ count = buf_count(chan->recvbuf);
+ if (count > 0)
+ break;
+ pthread_cond_wait(&chan->rdready, &chan->lock);
+ }
+ n = min(count, size);
+ buf_get(chan->recvbuf, cp, n);
+ chan->recvseq += n;
+ chan->flags |= CF_WINDOW;
+ chan_unlock(chan);
+ /* We need to wake up the sender so that it sends a window update. */
+ sender_wakeup(chan->mux);
+ return (n);
+}
+
+/* Write bytes to a channel. */
+ssize_t
+chan_write(struct chan *chan, const void *buf, size_t size)
+{
+ const char *cp;
+ size_t avail, n, pos;
+
+ pos = 0;
+ cp = buf;
+ chan_lock(chan);
+ while (pos < size) {
+ for (;;) {
+ if (chan->state != CS_ESTABLISHED &&
+ chan->state != CS_RDCLOSED) {
+ chan_unlock(chan);
+ errno = EPIPE;
+ return (-1);
+ }
+ avail = buf_avail(chan->sendbuf);
+ if (avail > 0)
+ break;
+ pthread_cond_wait(&chan->wrready, &chan->lock);
+ }
+ n = min(avail, size - pos);
+ buf_put(chan->sendbuf, cp + pos, n);
+ pos += n;
+ }
+ chan_unlock(chan);
+ sender_wakeup(chan->mux);
+ return (size);
+}
+
+/*
+ * Internal channel API.
+ */
+
+static struct chan *
+chan_connect(struct mux *m, int id)
+{
+ struct chan *chan;
+
+ chan = chan_get(m, id);
+ if (chan->state != CS_UNUSED) {
+ chan_unlock(chan);
+ return (NULL);
+ }
+ chan->state = CS_CONNECTING;
+ chan->flags |= CF_CONNECT;
+ chan_unlock(chan);
+ sender_wakeup(m);
+ chan_lock(chan);
+ while (chan->state == CS_CONNECTING)
+ pthread_cond_wait(&chan->wrready, &chan->lock);
+ if (chan->state != CS_ESTABLISHED) {
+ chan_unlock(chan);
+ return (NULL);
+ }
+ chan_unlock(chan);
+ return (chan);
+}
+
+/*
+ * Get a channel from its ID, creating it if necessary.
+ * The channel is returned locked.
+ */
+static struct chan *
+chan_get(struct mux *m, int id)
+{
+ struct chan *chan;
+
+ assert(id < MUX_MAXCHAN);
+ mux_lock(m);
+ chan = m->channels[id];
+ if (chan == NULL) {
+ chan = chan_new(m);
+ m->channels[id] = chan;
+ m->nchans++;
+ }
+ chan_lock(chan);
+ mux_unlock(m);
+ return (chan);
+}
+
+/* Lock a channel. */
+static void
+chan_lock(struct chan *chan)
+{
+ int error;
+
+ error = pthread_mutex_lock(&chan->lock);
+ assert(!error);
+}
+
+/* Unlock a channel. */
+static void
+chan_unlock(struct chan *chan)
+{
+ int error;
+
+ error = pthread_mutex_unlock(&chan->lock);
+ assert(!error);
+}
+
+/*
+ * Create a new channel.
+ */
+static struct chan *
+chan_new(struct mux *m)
+{
+ struct chan *chan;
+
+ chan = xmalloc(sizeof(struct chan));
+ chan->state = CS_UNUSED;
+ chan->flags = 0;
+ chan->mux = m;
+ chan->sendbuf = buf_new(CHAN_SBSIZE);
+ chan->sendseq = 0;
+ chan->sendwin = 0;
+ chan->sendmss = 0;
+ chan->recvbuf = buf_new(CHAN_RBSIZE);
+ chan->recvseq = 0;
+ chan->recvmss = CHAN_MAXSEGSIZE;
+ pthread_mutex_init(&chan->lock, NULL);
+ pthread_cond_init(&chan->rdready, NULL);
+ pthread_cond_init(&chan->wrready, NULL);
+ return (chan);
+}
+
+/* Free any resources associated with a channel. */
+static void
+chan_free(struct chan *chan)
+{
+
+ pthread_cond_destroy(&chan->rdready);
+ pthread_cond_destroy(&chan->wrready);
+ pthread_mutex_destroy(&chan->lock);
+ buf_free(chan->recvbuf);
+ buf_free(chan->sendbuf);
+ free(chan);
+}
+
+/* Insert the new channel in the channel list. */
+static int
+chan_insert(struct mux *m, struct chan *chan)
+{
+ int i;
+
+ mux_lock(m);
+ for (i = 0; i < MUX_MAXCHAN; i++) {
+ if (m->channels[i] == NULL) {
+ m->channels[i] = chan;
+ m->nchans++;
+ mux_unlock(m);
+ return (i);
+ }
+ }
+ errno = ENOBUFS;
+ return (-1);
+}
+
+/*
+ * Initialize the multiplexer protocol.
+ *
+ * This means negotiating protocol version and starting
+ * the receiver and sender threads.
+ */
+static int
+mux_init(struct mux *m)
+{
+ struct mux_header mh;
+ int error;
+
+ mh.type = MUX_STARTUPREQ;
+ mh.mh_startup.version = htons(MUX_PROTOVER);
+ error = sock_write(m->socket, &mh, MUX_STARTUPHDRSZ);
+ if (error)
+ return (-1);
+ error = sock_readwait(m->socket, &mh, MUX_STARTUPHDRSZ);
+ if (error)
+ return (-1);
+ if (mh.type != MUX_STARTUPREP ||
+ ntohs(mh.mh_startup.version) != MUX_PROTOVER)
+ return (-1);
+ mux_lock(m);
+ error = pthread_create(&m->sender, NULL, sender_loop, m);
+ if (error) {
+ mux_unlock(m);
+ return (-1);
+ }
+ /*
+ * Make sure the sender thread has run and is waiting for new work
+ * before going on. Otherwise, it might lose the race and a
+ * request, which will cause a deadlock.
+ */
+ while (!m->sender_ready)
+ pthread_cond_wait(&m->sender_started, &m->lock);
+
+ mux_unlock(m);
+ error = pthread_create(&m->receiver, NULL, receiver_loop, m);
+ if (error)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Close all the channels, terminate the sender and receiver thread.
+ * This is an important function because it is used everytime we need
+ * to wake up all the worker threads to abort the program.
+ *
+ * This function accepts an error message that will be printed if the
+ * multiplexer wasn't already closed. This is useful because it ensures
+ * that only the first error message will be printed, and that it will
+ * be printed before doing the actual shutdown work. If this is a
+ * normal shutdown, NULL can be passed instead.
+ *
+ * The "status" parameter of the first mux_shutdown() call is retained
+ * and then returned by mux_close(), so that the main thread can know
+ * what type of error happened in the end, if any.
+ */
+void
+mux_shutdown(struct mux *m, const char *errmsg, int status)
+{
+ pthread_t self, sender, receiver;
+ struct chan *chan;
+ const char *name;
+ void *val;
+ int i, ret;
+
+ mux_lock(m);
+ if (m->closed) {
+ mux_unlock(m);
+ return;
+ }
+ m->closed = 1;
+ m->status = status;
+ self = pthread_self();
+ sender = m->sender;
+ receiver = m->receiver;
+ if (errmsg != NULL) {
+ if (pthread_equal(self, receiver))
+ name = "Receiver";
+ else if (pthread_equal(self, sender))
+ name = "Sender";
+ else
+ name = NULL;
+ if (name == NULL)
+ lprintf(-1, "%s\n", errmsg);
+ else
+ lprintf(-1, "%s: %s\n", name, errmsg);
+ }
+
+ for (i = 0; i < MUX_MAXCHAN; i++) {
+ if (m->channels[i] != NULL) {
+ chan = m->channels[i];
+ chan_lock(chan);
+ if (chan->state != CS_UNUSED) {
+ chan->state = CS_CLOSED;
+ chan->flags = 0;
+ pthread_cond_broadcast(&chan->rdready);
+ pthread_cond_broadcast(&chan->wrready);
+ }
+ chan_unlock(chan);
+ }
+ }
+ mux_unlock(m);
+
+ if (!pthread_equal(self, receiver)) {
+ ret = pthread_cancel(receiver);
+ assert(!ret);
+ pthread_join(receiver, &val);
+ assert(val == PTHREAD_CANCELED);
+ }
+ if (!pthread_equal(self, sender)) {
+ ret = pthread_cancel(sender);
+ assert(!ret);
+ pthread_join(sender, &val);
+ assert(val == PTHREAD_CANCELED);
+ }
+}
+
+static void
+sender_wakeup(struct mux *m)
+{
+ int waiting;
+
+ mux_lock(m);
+ waiting = m->sender_waiting;
+ mux_unlock(m);
+ /*
+ * We don't care about the race here: if the sender was
+ * waiting and is not anymore, we'll just send a useless
+ * signal; if he wasn't waiting then he won't go to sleep
+ * before having sent what we want him to.
+ */
+ if (waiting)
+ pthread_cond_signal(&m->sender_newwork);
+}
+
+static void *
+sender_loop(void *arg)
+{
+ struct iovec iov[3];
+ struct mux_header mh;
+ struct mux *m;
+ struct chan *chan;
+ struct buf *buf;
+ uint32_t winsize;
+ uint16_t hdrsize, size, len;
+ int error, id, iovcnt, what = 0;
+
+ m = (struct mux *)arg;
+ what = 0;
+again:
+ id = sender_waitforwork(m, &what);
+ chan = chan_get(m, id);
+ hdrsize = size = 0;
+ switch (what) {
+ case CF_CONNECT:
+ mh.type = MUX_CONNECT;
+ mh.mh_connect.id = id;
+ mh.mh_connect.mss = htons(chan->recvmss);
+ mh.mh_connect.window = htonl(chan->recvseq +
+ chan->recvbuf->size);
+ hdrsize = MUX_CONNECTHDRSZ;
+ break;
+ case CF_ACCEPT:
+ mh.type = MUX_ACCEPT;
+ mh.mh_accept.id = id;
+ mh.mh_accept.mss = htons(chan->recvmss);
+ mh.mh_accept.window = htonl(chan->recvseq +
+ chan->recvbuf->size);
+ hdrsize = MUX_ACCEPTHDRSZ;
+ break;
+ case CF_RESET:
+ mh.type = MUX_RESET;
+ mh.mh_reset.id = id;
+ hdrsize = MUX_RESETHDRSZ;
+ break;
+ case CF_WINDOW:
+ mh.type = MUX_WINDOW;
+ mh.mh_window.id = id;
+ mh.mh_window.window = htonl(chan->recvseq +
+ chan->recvbuf->size);
+ hdrsize = MUX_WINDOWHDRSZ;
+ break;
+ case CF_DATA:
+ mh.type = MUX_DATA;
+ mh.mh_data.id = id;
+ size = min(buf_count(chan->sendbuf), chan->sendmss);
+ winsize = chan->sendwin - chan->sendseq;
+ if (winsize < size)
+ size = winsize;
+ mh.mh_data.len = htons(size);
+ hdrsize = MUX_DATAHDRSZ;
+ break;
+ case CF_CLOSE:
+ mh.type = MUX_CLOSE;
+ mh.mh_close.id = id;
+ hdrsize = MUX_CLOSEHDRSZ;
+ break;
+ }
+ if (size > 0) {
+ assert(mh.type == MUX_DATA);
+ /*
+ * Older FreeBSD versions (and maybe other OSes) have the
+ * iov_base field defined as char *. Cast to char * to
+ * silence a warning in this case.
+ */
+ iov[0].iov_base = (char *)&mh;
+ iov[0].iov_len = hdrsize;
+ iovcnt = 1;
+ /* We access the buffer directly to avoid some copying. */
+ buf = chan->sendbuf;
+ len = min(size, buf->size + 1 - buf->out);
+ iov[iovcnt].iov_base = buf->data + buf->out;
+ iov[iovcnt].iov_len = len;
+ iovcnt++;
+ if (size > len) {
+ /* Wrapping around. */
+ iov[iovcnt].iov_base = buf->data;
+ iov[iovcnt].iov_len = size - len;
+ iovcnt++;
+ }
+ /*
+ * Since we're the only thread sending bytes from the
+ * buffer and modifying buf->out, it's safe to unlock
+ * here during I/O. It avoids keeping the channel lock
+ * too long, since write() might block.
+ */
+ chan_unlock(chan);
+ error = sock_writev(m->socket, iov, iovcnt);
+ if (error)
+ goto bad;
+ chan_lock(chan);
+ chan->sendseq += size;
+ buf->out += size;
+ if (buf->out > buf->size)
+ buf->out -= buf->size + 1;
+ pthread_cond_signal(&chan->wrready);
+ chan_unlock(chan);
+ } else {
+ chan_unlock(chan);
+ error = sock_write(m->socket, &mh, hdrsize);
+ if (error)
+ goto bad;
+ }
+ goto again;
+bad:
+ if (error == EPIPE)
+ mux_shutdown(m, strerror(errno), STATUS_TRANSIENTFAILURE);
+ else
+ mux_shutdown(m, strerror(errno), STATUS_FAILURE);
+ return (NULL);
+}
+
+static void
+sender_cleanup(void *arg)
+{
+ struct mux *m;
+
+ m = (struct mux *)arg;
+ mux_unlock(m);
+}
+
+static int
+sender_waitforwork(struct mux *m, int *what)
+{
+ int id;
+
+ mux_lock(m);
+ pthread_cleanup_push(sender_cleanup, m);
+ if (!m->sender_ready) {
+ pthread_cond_signal(&m->sender_started);
+ m->sender_ready = 1;
+ }
+ while ((id = sender_scan(m, what)) == -1) {
+ m->sender_waiting = 1;
+ pthread_cond_wait(&m->sender_newwork, &m->lock);
+ }
+ m->sender_waiting = 0;
+ pthread_cleanup_pop(1);
+ return (id);
+}
+
+/*
+ * Scan for work to do for the sender. Has to be called with
+ * the multiplexer lock held.
+ */
+static int
+sender_scan(struct mux *m, int *what)
+{
+ struct chan *chan;
+ int id;
+
+ if (m->nchans <= 0)
+ return (-1);
+ id = m->sender_lastid;
+ do {
+ id++;
+ if (id >= m->nchans)
+ id = 0;
+ chan = m->channels[id];
+ chan_lock(chan);
+ if (chan->state != CS_UNUSED) {
+ if (chan->sendseq != chan->sendwin &&
+ buf_count(chan->sendbuf) > 0)
+ chan->flags |= CF_DATA;
+ if (chan->flags) {
+ /* By order of importance. */
+ if (chan->flags & CF_CONNECT)
+ *what = CF_CONNECT;
+ else if (chan->flags & CF_ACCEPT)
+ *what = CF_ACCEPT;
+ else if (chan->flags & CF_RESET)
+ *what = CF_RESET;
+ else if (chan->flags & CF_WINDOW)
+ *what = CF_WINDOW;
+ else if (chan->flags & CF_DATA)
+ *what = CF_DATA;
+ else if (chan->flags & CF_CLOSE)
+ *what = CF_CLOSE;
+ chan->flags &= ~*what;
+ chan_unlock(chan);
+ m->sender_lastid = id;
+ return (id);
+ }
+ }
+ chan_unlock(chan);
+ } while (id != m->sender_lastid);
+ return (-1);
+}
+
+/* Read the rest of a packet header depending on its type. */
+#define SOCK_READREST(s, mh, hsize) \
+ sock_readwait(s, (char *)&mh + sizeof(mh.type), (hsize) - sizeof(mh.type))
+
+void *
+receiver_loop(void *arg)
+{
+ struct mux_header mh;
+ struct mux *m;
+ struct chan *chan;
+ struct buf *buf;
+ uint16_t size, len;
+ int error;
+
+ m = (struct mux *)arg;
+ while ((error = sock_readwait(m->socket, &mh.type,
+ sizeof(mh.type))) == 0) {
+ switch (mh.type) {
+ case MUX_CONNECT:
+ error = SOCK_READREST(m->socket, mh, MUX_CONNECTHDRSZ);
+ if (error)
+ goto bad;
+ chan = chan_get(m, mh.mh_connect.id);
+ if (chan->state == CS_LISTENING) {
+ chan->state = CS_ESTABLISHED;
+ chan->sendmss = ntohs(mh.mh_connect.mss);
+ chan->sendwin = ntohl(mh.mh_connect.window);
+ chan->flags |= CF_ACCEPT;
+ pthread_cond_signal(&chan->rdready);
+ } else
+ chan->flags |= CF_RESET;
+ chan_unlock(chan);
+ sender_wakeup(m);
+ break;
+ case MUX_ACCEPT:
+ error = SOCK_READREST(m->socket, mh, MUX_ACCEPTHDRSZ);
+ if (error)
+ goto bad;
+ chan = chan_get(m, mh.mh_accept.id);
+ if (chan->state == CS_CONNECTING) {
+ chan->sendmss = ntohs(mh.mh_accept.mss);
+ chan->sendwin = ntohl(mh.mh_accept.window);
+ chan->state = CS_ESTABLISHED;
+ pthread_cond_signal(&chan->wrready);
+ chan_unlock(chan);
+ } else {
+ chan->flags |= CF_RESET;
+ chan_unlock(chan);
+ sender_wakeup(m);
+ }
+ break;
+ case MUX_RESET:
+ error = SOCK_READREST(m->socket, mh, MUX_RESETHDRSZ);
+ if (error)
+ goto bad;
+ goto badproto;
+ case MUX_WINDOW:
+ error = SOCK_READREST(m->socket, mh, MUX_WINDOWHDRSZ);
+ if (error)
+ goto bad;
+ chan = chan_get(m, mh.mh_window.id);
+ if (chan->state == CS_ESTABLISHED ||
+ chan->state == CS_RDCLOSED) {
+ chan->sendwin = ntohl(mh.mh_window.window);
+ chan_unlock(chan);
+ sender_wakeup(m);
+ } else {
+ chan_unlock(chan);
+ }
+ break;
+ case MUX_DATA:
+ error = SOCK_READREST(m->socket, mh, MUX_DATAHDRSZ);
+ if (error)
+ goto bad;
+ chan = chan_get(m, mh.mh_data.id);
+ len = ntohs(mh.mh_data.len);
+ buf = chan->recvbuf;
+ if ((chan->state != CS_ESTABLISHED &&
+ chan->state != CS_WRCLOSED) ||
+ (len > buf_avail(buf) ||
+ len > chan->recvmss)) {
+ chan_unlock(chan);
+ goto badproto;
+ return (NULL);
+ }
+ /*
+ * Similarly to the sender code, it's safe to
+ * unlock the channel here.
+ */
+ chan_unlock(chan);
+ size = min(buf->size + 1 - buf->in, len);
+ error = sock_readwait(m->socket,
+ buf->data + buf->in, size);
+ if (error)
+ goto bad;
+ if (len > size) {
+ /* Wrapping around. */
+ error = sock_readwait(m->socket,
+ buf->data, len - size);
+ if (error)
+ goto bad;
+ }
+ chan_lock(chan);
+ buf->in += len;
+ if (buf->in > buf->size)
+ buf->in -= buf->size + 1;
+ pthread_cond_signal(&chan->rdready);
+ chan_unlock(chan);
+ break;
+ case MUX_CLOSE:
+ error = SOCK_READREST(m->socket, mh, MUX_CLOSEHDRSZ);
+ if (error)
+ goto bad;
+ chan = chan_get(m, mh.mh_close.id);
+ if (chan->state == CS_ESTABLISHED)
+ chan->state = CS_RDCLOSED;
+ else if (chan->state == CS_WRCLOSED)
+ chan->state = CS_CLOSED;
+ else
+ goto badproto;
+ pthread_cond_signal(&chan->rdready);
+ chan_unlock(chan);
+ break;
+ default:
+ goto badproto;
+ }
+ }
+bad:
+ if (errno == ECONNRESET || errno == ECONNABORTED)
+ mux_shutdown(m, strerror(errno), STATUS_TRANSIENTFAILURE);
+ else
+ mux_shutdown(m, strerror(errno), STATUS_FAILURE);
+ return (NULL);
+badproto:
+ mux_shutdown(m, "Protocol error", STATUS_FAILURE);
+ return (NULL);
+}
+
+/*
+ * Circular buffers API.
+ */
+
+static struct buf *
+buf_new(size_t size)
+{
+ struct buf *buf;
+
+ buf = xmalloc(sizeof(struct buf));
+ buf->data = xmalloc(size + 1);
+ buf->size = size;
+ buf->in = 0;
+ buf->out = 0;
+ return (buf);
+}
+
+static void
+buf_free(struct buf *buf)
+{
+
+ free(buf->data);
+ free(buf);
+}
+
+/* Number of bytes stored in the buffer. */
+static size_t
+buf_count(struct buf *buf)
+{
+ size_t count;
+
+ if (buf->in >= buf->out)
+ count = buf->in - buf->out;
+ else
+ count = buf->size + 1 + buf->in - buf->out;
+ return (count);
+}
+
+/* Number of bytes available in the buffer. */
+static size_t
+buf_avail(struct buf *buf)
+{
+ size_t avail;
+
+ if (buf->out > buf->in)
+ avail = buf->out - buf->in - 1;
+ else
+ avail = buf->size + buf->out - buf->in;
+ return (avail);
+}
+
+static void
+buf_put(struct buf *buf, const void *data, size_t size)
+{
+ const char *cp;
+ size_t len;
+
+ assert(size > 0);
+ assert(buf_avail(buf) >= size);
+ cp = data;
+ len = buf->size + 1 - buf->in;
+ if (len < size) {
+ /* Wrapping around. */
+ memcpy(buf->data + buf->in, cp, len);
+ memcpy(buf->data, cp + len, size - len);
+ } else {
+ /* Not wrapping around. */
+ memcpy(buf->data + buf->in, cp, size);
+ }
+ buf->in += size;
+ if (buf->in > buf->size)
+ buf->in -= buf->size + 1;
+}
+
+static void
+buf_get(struct buf *buf, void *data, size_t size)
+{
+ char *cp;
+ size_t len;
+
+ assert(size > 0);
+ assert(buf_count(buf) >= size);
+ cp = data;
+ len = buf->size + 1 - buf->out;
+ if (len < size) {
+ /* Wrapping around. */
+ memcpy(cp, buf->data + buf->out, len);
+ memcpy(cp + len, buf->data, size - len);
+ } else {
+ /* Not wrapping around. */
+ memcpy(cp, buf->data + buf->out, size);
+ }
+ buf->out += size;
+ if (buf->out > buf->size)
+ buf->out -= buf->size + 1;
+}
diff --git a/usr.bin/csup/mux.h b/usr.bin/csup/mux.h
new file mode 100644
index 0000000..ff36083
--- /dev/null
+++ b/usr.bin/csup/mux.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _MUX_H_
+#define _MUX_H_
+
+struct mux;
+struct chan;
+
+struct mux *mux_open(int, struct chan **);
+void mux_shutdown(struct mux *, const char *, int);
+int mux_close(struct mux *);
+
+void chan_wait(struct chan *);
+int chan_listen(struct mux *);
+struct chan *chan_accept(struct mux *, int);
+ssize_t chan_read(struct chan *, void *, size_t);
+ssize_t chan_write(struct chan *, const void *, size_t);
+int chan_close(struct chan *);
+
+#endif /* !_MUX_H_ */
diff --git a/usr.bin/csup/parse.y b/usr.bin/csup/parse.y
new file mode 100644
index 0000000..3dcd617
--- /dev/null
+++ b/usr.bin/csup/parse.y
@@ -0,0 +1,91 @@
+%{
+/*-
+ * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+
+#include "config.h"
+#include "token.h"
+
+%}
+
+%union {
+ char *str;
+ int i;
+}
+
+%token DEFAULT
+%token <i> NAME
+%token <i> BOOLEAN
+%token EQUAL
+%token <str> STRING
+
+%%
+
+config_file
+ : config_list
+ |
+ ;
+
+config_list
+ : config
+ | config_list config
+ ;
+
+config
+ : default_line
+ | collection
+ ;
+
+default_line
+ : DEFAULT options
+ { coll_setdef(); }
+ ;
+
+collection
+ : STRING options
+ { coll_add($1); }
+ ;
+
+options
+ :
+ | options option
+ ;
+
+option
+ : BOOLEAN
+ { coll_setopt($1, NULL); }
+ | value
+ ;
+
+value
+ : NAME EQUAL STRING
+ { coll_setopt($1, $3); }
+ ;
+
+%%
diff --git a/usr.bin/csup/pathcomp.c b/usr.bin/csup/pathcomp.c
new file mode 100644
index 0000000..380d042
--- /dev/null
+++ b/usr.bin/csup/pathcomp.c
@@ -0,0 +1,182 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "misc.h"
+#include "pathcomp.h"
+
+struct pathcomp {
+ char *target;
+ size_t targetlen;
+ char *trashed;
+ char *prev;
+ size_t prevlen;
+ size_t goal;
+ size_t curlen;
+};
+
+struct pathcomp *
+pathcomp_new(void)
+{
+ struct pathcomp *pc;
+
+ pc = xmalloc(sizeof(struct pathcomp));
+ pc->curlen = 0;
+ pc->target = NULL;
+ pc->targetlen = 0;
+ pc->trashed = NULL;
+ pc->prev = NULL;
+ pc->prevlen = 0;
+ return (pc);
+}
+
+int
+pathcomp_put(struct pathcomp *pc, int type, char *path)
+{
+ char *cp;
+
+ assert(pc->target == NULL);
+ if (*path == '/')
+ return (-1);
+
+ switch (type) {
+ case PC_DIRDOWN:
+ pc->target = path;
+ pc->targetlen = strlen(path);
+ break;
+ case PC_FILE:
+ case PC_DIRUP:
+ cp = strrchr(path, '/');
+ pc->target = path;
+ if (cp != NULL)
+ pc->targetlen = cp - path;
+ else
+ pc->targetlen = 0;
+ break;
+ }
+ if (pc->prev != NULL)
+ pc->goal = commonpathlength(pc->prev, pc->prevlen, pc->target,
+ pc->targetlen);
+ else
+ pc->goal = 0;
+ if (pc->curlen == pc->goal) /* No need to go up. */
+ pc->goal = pc->targetlen;
+ return (0);
+}
+
+int
+pathcomp_get(struct pathcomp *pc, int *type, char **name)
+{
+ char *cp;
+ size_t slashpos, start;
+
+ if (pc->curlen > pc->goal) { /* Going up. */
+ assert(pc->prev != NULL);
+ pc->prev[pc->curlen] = '\0';
+ cp = pc->prev + pc->curlen - 1;
+ while (cp >= pc->prev) {
+ if (*cp == '/')
+ break;
+ cp--;
+ }
+ if (cp >= pc->prev)
+ slashpos = cp - pc->prev;
+ else
+ slashpos = 0;
+ pc->curlen = slashpos;
+ if (pc->curlen <= pc->goal) { /* Done going up. */
+ assert(pc->curlen == pc->goal);
+ pc->goal = pc->targetlen;
+ }
+ *type = PC_DIRUP;
+ *name = pc->prev;
+ return (1);
+ } else if (pc->curlen < pc->goal) { /* Going down. */
+ /* Restore the previously overwritten '/' character. */
+ if (pc->trashed != NULL) {
+ *pc->trashed = '/';
+ pc->trashed = NULL;
+ }
+ if (pc->curlen == 0)
+ start = pc->curlen;
+ else
+ start = pc->curlen + 1;
+ slashpos = start;
+ while (slashpos < pc->goal) {
+ if (pc->target[slashpos] == '/')
+ break;
+ slashpos++;
+ }
+ if (pc->target[slashpos] != '\0') {
+ assert(pc->target[slashpos] == '/');
+ pc->trashed = pc->target + slashpos;
+ pc->target[slashpos] = '\0';
+ }
+ pc->curlen = slashpos;
+ *type = PC_DIRDOWN;
+ *name = pc->target;
+ return (1);
+ } else { /* Done. */
+ if (pc->target != NULL) {
+ if (pc->trashed != NULL) {
+ *pc->trashed = '/';
+ pc->trashed = NULL;
+ }
+ if (pc->prev != NULL)
+ free(pc->prev);
+ pc->prev = xmalloc(pc->targetlen + 1);
+ memcpy(pc->prev, pc->target, pc->targetlen);
+ pc->prev[pc->targetlen] = '\0';
+ pc->prevlen = pc->targetlen;
+ pc->target = NULL;
+ pc->targetlen = 0;
+ }
+ return (0);
+ }
+}
+
+void
+pathcomp_finish(struct pathcomp *pc)
+{
+
+ pc->target = NULL;
+ pc->targetlen = 0;
+ pc->goal = 0;
+}
+
+void
+pathcomp_free(struct pathcomp *pc)
+{
+
+ if (pc->prev != NULL)
+ free(pc->prev);
+ free(pc);
+}
diff --git a/usr.bin/csup/pathcomp.h b/usr.bin/csup/pathcomp.h
new file mode 100644
index 0000000..3cea410
--- /dev/null
+++ b/usr.bin/csup/pathcomp.h
@@ -0,0 +1,44 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _PATHCOMP_H
+#define _PATHCOMP_H
+
+/* File types */
+#define PC_DIRDOWN 0
+#define PC_FILE 1
+#define PC_DIRUP 2
+
+struct pathcomp;
+
+struct pathcomp *pathcomp_new(void);
+int pathcomp_put(struct pathcomp *, int, char *);
+int pathcomp_get(struct pathcomp *, int *, char **);
+void pathcomp_finish(struct pathcomp *);
+void pathcomp_free(struct pathcomp *);
+
+#endif /* !_PATHCOMP_H */
diff --git a/usr.bin/csup/proto.c b/usr.bin/csup/proto.c
new file mode 100644
index 0000000..166a134
--- /dev/null
+++ b/usr.bin/csup/proto.c
@@ -0,0 +1,997 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <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 "auth.h"
+#include "config.h"
+#include "detailer.h"
+#include "fattr.h"
+#include "fixups.h"
+#include "globtree.h"
+#include "keyword.h"
+#include "lister.h"
+#include "misc.h"
+#include "mux.h"
+#include "proto.h"
+#include "queue.h"
+#include "stream.h"
+#include "threads.h"
+#include "updater.h"
+
+struct killer {
+ pthread_t thread;
+ sigset_t sigset;
+ struct mux *mux;
+ int killedby;
+};
+
+static void killer_start(struct killer *, struct mux *);
+static void *killer_run(void *);
+static void killer_stop(struct killer *);
+
+static int proto_waitconnect(int);
+static int proto_greet(struct config *);
+static int proto_negproto(struct config *);
+static int proto_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);
+}
+
+/*
+ * 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) {
+ if (coll->co_options & CO_SKIP)
+ continue;
+ 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));
+
+ /* Set up a mask of file attributes that we don't want to sync
+ with the server. */
+ if (!(coll->co_options & CO_SETOWNER))
+ coll->co_attrignore |= FA_OWNER | FA_GROUP;
+ if (!(coll->co_options & CO_SETMODE))
+ coll->co_attrignore |= FA_MODE;
+ if (!(coll->co_options & CO_SETFLAGS))
+ coll->co_attrignore |= FA_FLAGS;
+ }
+ return (STATUS_SUCCESS);
+bad:
+ lprintf(-1, "Protocol error during collection exchange\n");
+ return (STATUS_FAILURE);
+}
+
+static struct mux *
+proto_mux(struct config *config)
+{
+ struct mux *m;
+ struct stream *s, *wr;
+ struct chan *chan0, *chan1;
+ int id;
+
+ s = config->server;
+ lprintf(2, "Establishing multiplexed-mode data connection\n");
+ proto_printf(s, "MUX\n");
+ stream_flush(s);
+ m = mux_open(config->socket, &chan0);
+ if (m == NULL) {
+ lprintf(-1, "Cannot open the multiplexer\n");
+ return (NULL);
+ }
+ id = chan_listen(m);
+ if (id == -1) {
+ lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno));
+ mux_close(m);
+ return (NULL);
+ }
+ wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL);
+ proto_printf(wr, "CHAN %d\n", id);
+ stream_close(wr);
+ chan1 = chan_accept(m, id);
+ if (chan1 == NULL) {
+ lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno));
+ mux_close(m);
+ return (NULL);
+ }
+ config->chan0 = chan0;
+ config->chan1 = chan1;
+ return (m);
+}
+
+/*
+ * Initializes the connection to the CVSup server, that is handle
+ * the protocol negotiation, logging in, exchanging file attributes
+ * support and collections information, and finally run the update
+ * session.
+ */
+int
+proto_run(struct config *config)
+{
+ struct thread_args lister_args;
+ struct thread_args detailer_args;
+ struct thread_args updater_args;
+ struct thread_args *args;
+ struct killer killer;
+ struct threads *workers;
+ struct mux *m;
+ int i, status;
+
+ /*
+ * We pass NULL for the close() function because we'll reuse
+ * the socket after the stream is closed.
+ */
+ config->server = stream_open_fd(config->socket, stream_read_fd,
+ stream_write_fd, NULL);
+ status = proto_greet(config);
+ if (status == STATUS_SUCCESS)
+ status = proto_negproto(config);
+ if (status == STATUS_SUCCESS)
+ status = auth_login(config);
+ if (status == STATUS_SUCCESS)
+ status = proto_fileattr(config);
+ if (status == STATUS_SUCCESS)
+ status = proto_xchgcoll(config);
+ if (status != STATUS_SUCCESS)
+ return (status);
+
+ /* Multi-threaded action starts here. */
+ m = proto_mux(config);
+ if (m == NULL)
+ return (STATUS_FAILURE);
+
+ stream_close(config->server);
+ config->server = NULL;
+ config->fixups = fixups_new();
+ killer_start(&killer, m);
+
+ /* Start the worker threads. */
+ workers = threads_new();
+ args = &lister_args;
+ args->config = config;
+ args->status = -1;
+ args->errmsg = NULL;
+ args->rd = NULL;
+ args->wr = stream_open(config->chan0,
+ NULL, (stream_writefn_t *)chan_write, NULL);
+ threads_create(workers, lister, args);
+
+ args = &detailer_args;
+ args->config = config;
+ args->status = -1;
+ args->errmsg = NULL;
+ args->rd = stream_open(config->chan0,
+ (stream_readfn_t *)chan_read, NULL, NULL);
+ args->wr = stream_open(config->chan1,
+ NULL, (stream_writefn_t *)chan_write, NULL);
+ threads_create(workers, detailer, args);
+
+ args = &updater_args;
+ args->config = config;
+ args->status = -1;
+ args->errmsg = NULL;
+ args->rd = stream_open(config->chan1,
+ (stream_readfn_t *)chan_read, NULL, NULL);
+ args->wr = NULL;
+ threads_create(workers, updater, args);
+
+ lprintf(2, "Running\n");
+ /* Wait for all the worker threads to finish. */
+ status = STATUS_SUCCESS;
+ for (i = 0; i < 3; i++) {
+ args = threads_wait(workers);
+ if (args->rd != NULL)
+ stream_close(args->rd);
+ if (args->wr != NULL)
+ stream_close(args->wr);
+ if (args->status != STATUS_SUCCESS) {
+ assert(args->errmsg != NULL);
+ if (status == STATUS_SUCCESS) {
+ status = args->status;
+ /* Shutdown the multiplexer to wake up all
+ the other threads. */
+ mux_shutdown(m, args->errmsg, status);
+ }
+ free(args->errmsg);
+ }
+ }
+ threads_free(workers);
+ if (status == STATUS_SUCCESS) {
+ lprintf(2, "Shutting down connection to server\n");
+ chan_close(config->chan0);
+ chan_close(config->chan1);
+ chan_wait(config->chan0);
+ chan_wait(config->chan1);
+ mux_shutdown(m, NULL, STATUS_SUCCESS);
+ }
+ killer_stop(&killer);
+ fixups_free(config->fixups);
+ status = mux_close(m);
+ if (status == STATUS_SUCCESS) {
+ lprintf(1, "Finished successfully\n");
+ } else if (status == STATUS_INTERRUPTED) {
+ lprintf(-1, "Interrupted\n");
+ if (killer.killedby != -1)
+ kill(getpid(), killer.killedby);
+ }
+ return (status);
+}
+
+/*
+ * Write a string into the stream, escaping characters as needed.
+ * Characters escaped:
+ *
+ * SPACE -> "\_"
+ * TAB -> "\t"
+ * NEWLINE -> "\n"
+ * CR -> "\r"
+ * \ -> "\\"
+ */
+static int
+proto_escape(struct stream *wr, const char *s)
+{
+ size_t len;
+ ssize_t n;
+ char c;
+
+ /* Handle characters that need escaping. */
+ do {
+ len = strcspn(s, " \t\r\n\\");
+ n = stream_write(wr, s, len);
+ if (n == -1)
+ return (-1);
+ c = s[len];
+ switch (c) {
+ case ' ':
+ n = stream_write(wr, "\\_", 2);
+ break;
+ case '\t':
+ n = stream_write(wr, "\\t", 2);
+ break;
+ case '\r':
+ n = stream_write(wr, "\\r", 2);
+ break;
+ case '\n':
+ n = stream_write(wr, "\\n", 2);
+ break;
+ case '\\':
+ n = stream_write(wr, "\\\\", 2);
+ break;
+ }
+ if (n == -1)
+ return (-1);
+ s += len + 1;
+ } while (c != '\0');
+ return (0);
+}
+
+/*
+ * A simple printf() implementation specifically tailored for csup.
+ * List of the supported formats:
+ *
+ * %c Print a char.
+ * %d or %i Print an int as decimal.
+ * %x Print an int as hexadecimal.
+ * %o Print an int as octal.
+ * %t Print a time_t as decimal.
+ * %s Print a char * escaping some characters as needed.
+ * %S Print a char * without escaping.
+ * %f Print an encoded struct fattr *.
+ * %F Print an encoded struct fattr *, specifying the supported
+ * attributes.
+ */
+int
+proto_printf(struct stream *wr, const char *format, ...)
+{
+ fattr_support_t *support;
+ long long longval;
+ struct fattr *fa;
+ const char *fmt;
+ va_list ap;
+ char *cp, *s, *attr;
+ ssize_t n;
+ size_t size;
+ off_t off;
+ 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 'O':
+ off = va_arg(ap, off_t);
+ rv = stream_printf(wr, "%llu", off);
+ 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 'z':
+ size = va_arg(ap, size_t);
+ rv = stream_printf(wr, "%zu", size);
+ 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 size_t token.
+ */
+int
+proto_get_sizet(char **s, size_t *val, int base)
+{
+ unsigned long long tmp;
+ char *cp, *end;
+
+ cp = proto_get_ascii(s);
+ if (cp == NULL)
+ return (-1);
+ errno = 0;
+ tmp = strtoll(cp, &end, base);
+ if (errno || *end != '\0')
+ return (-1);
+ *val = (size_t)tmp;
+ return (0);
+}
+
+/*
+ * Get a time_t token.
+ *
+ * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
+ * is more portable and 64bits should be enough for a timestamp.
+ */
+int
+proto_get_time(char **s, time_t *val)
+{
+ long long tmp;
+ char *cp, *end;
+
+ cp = proto_get_ascii(s);
+ if (cp == NULL)
+ return (-1);
+ errno = 0;
+ tmp = strtoll(cp, &end, 10);
+ if (errno || *end != '\0')
+ return (-1);
+ *val = (time_t)tmp;
+ return (0);
+}
+
+/* Start the killer thread. It is used to protect against some signals
+ during the multi-threaded run so that we can gracefully fail. */
+static void
+killer_start(struct killer *k, struct mux *m)
+{
+ int error;
+
+ k->mux = m;
+ k->killedby = -1;
+ sigemptyset(&k->sigset);
+ sigaddset(&k->sigset, SIGINT);
+ sigaddset(&k->sigset, SIGHUP);
+ sigaddset(&k->sigset, SIGTERM);
+ sigaddset(&k->sigset, SIGPIPE);
+ pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
+ error = pthread_create(&k->thread, NULL, killer_run, k);
+ if (error)
+ err(1, "pthread_create");
+}
+
+/* The main loop of the killer thread. */
+static void *
+killer_run(void *arg)
+{
+ struct killer *k;
+ int error, sig, old;
+
+ k = arg;
+again:
+ error = sigwait(&k->sigset, &sig);
+ assert(!error);
+ if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
+ if (k->killedby == -1) {
+ k->killedby = sig;
+ /* Ensure we don't get canceled during the shutdown. */
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
+ mux_shutdown(k->mux, "Cleaning up ...",
+ STATUS_INTERRUPTED);
+ pthread_setcancelstate(old, NULL);
+ }
+ }
+ goto again;
+}
+
+/* Stop the killer thread. */
+static void
+killer_stop(struct killer *k)
+{
+ void *val;
+ int error;
+
+ error = pthread_cancel(k->thread);
+ assert(!error);
+ pthread_join(k->thread, &val);
+ assert(val == PTHREAD_CANCELED);
+ pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);
+}
diff --git a/usr.bin/csup/proto.h b/usr.bin/csup/proto.h
new file mode 100644
index 0000000..0c4a782
--- /dev/null
+++ b/usr.bin/csup/proto.h
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _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_sizet(char **, size_t *, int);
+int proto_get_time(char **, time_t *);
+
+#endif /* !_PROTO_H_ */
diff --git a/usr.bin/csup/queue.h b/usr.bin/csup/queue.h
new file mode 100644
index 0000000..aa9cac1
--- /dev/null
+++ b/usr.bin/csup/queue.h
@@ -0,0 +1,227 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * $FreeBSD$
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _QUEUE_H_
+#define _QUEUE_H_
+
+#undef __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 --git a/usr.bin/csup/rcsfile.c b/usr.bin/csup/rcsfile.c
new file mode 100644
index 0000000..1f3ede1
--- /dev/null
+++ b/usr.bin/csup/rcsfile.c
@@ -0,0 +1,1412 @@
+/*-
+ * Copyright (c) 2007-2009, Ulf Lilleengen <lulf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "diff.h"
+#include "keyword.h"
+#include "misc.h"
+#include "proto.h"
+#include "queue.h"
+#include "rcsfile.h"
+#include "rcsparse.h"
+#include "stream.h"
+
+#define BUF_SIZE_DEFAULT 128
+
+/*
+ * RCS parser library. This is the part of the library that handles the
+ * importing, editing and exporting of RCS files. It currently supports only the
+ * part of the RCS file specification that is needed for csup (for instance,
+ * newphrases are not supported), and assumes that you can store the whole RCS
+ * file in memory.
+ */
+
+/*
+ * Linked list for string tokens.
+ */
+struct string {
+ char *str;
+ STAILQ_ENTRY(string) string_next;
+};
+
+/*
+ * Linked list of tags and revision numbers, in the RCS file header.
+ */
+struct tag {
+ char *tag;
+ char *revnum;
+ STAILQ_ENTRY(tag) tag_next;
+};
+
+/*
+ * A RCS delta. The delta is identified by a revision number, and contains the
+ * most important RCS attributes that is needed by csup. It also contains
+ * pointers to other nodes in the RCS file delta structure.
+ */
+struct delta {
+ char *revdate;
+ char *revnum;
+ char *author;
+ char *state;
+ struct buf *log;
+ struct buf *text;
+ int placeholder;
+ struct delta *diffbase;
+ struct delta *prev;
+
+ LIST_ENTRY(delta) delta_next;
+ STAILQ_ENTRY(delta) delta_prev;
+ LIST_ENTRY(delta) table_next;
+ STAILQ_ENTRY(delta) stack_next;
+ LIST_HEAD(, branch) branchlist;
+ LIST_ENTRY(delta) branch_next_date;
+};
+
+/*
+ * A branch data structure containing information about deltas in the branch as
+ * well as a base revision number.
+ */
+struct branch {
+ char *revnum;
+ LIST_HEAD(, delta) deltalist; /* Next delta in our branch. */
+ LIST_ENTRY(branch) branch_next;
+};
+
+/*
+ * The rcsfile structure is the "main" structure of the RCS parser library. It
+ * contains administrative data as well as pointers to the deltas within the
+ * file.
+ */
+struct rcsfile {
+ char *name;
+ char *head;
+ char *branch; /* Default branch. */
+ char *cvsroot;
+ char *colltag;
+ STAILQ_HEAD(, string) accesslist;
+ STAILQ_HEAD(, tag) taglist;
+ int strictlock;
+ char *comment;
+ int expand;
+ int ro;
+ struct branch *trunk; /* The tip delta. */
+
+ LIST_HEAD(, delta) deltatable;
+
+ char *desc;
+};
+
+static void rcsfile_freedelta(struct delta *);
+static void rcsfile_insertdelta(struct branch *, struct delta *,
+ int);
+static struct delta *rcsfile_createdelta(char *);
+static int rcsfile_write_deltatext(struct rcsfile *,
+ struct stream *);
+static int rcsfile_puttext(struct rcsfile *, struct stream *,
+ struct delta *, struct delta *);
+static struct branch *rcsfile_getbranch(struct rcsfile *, char *);
+static void rcsfile_insertsorteddelta(struct rcsfile *,
+ struct delta *);
+static struct stream *rcsfile_getdeltatext(struct rcsfile *, struct delta *,
+ struct buf **);
+static int rcsdelta_writestring(char *, size_t, struct stream *);
+static void rcsdelta_insertbranch(struct delta *, struct branch *);
+
+/* Space formatting of RCS file. */
+const char *head_space = "\t";
+const char *branch_space = "\t";
+const char *tag_space = "\t";
+const char *date_space = "\t";
+const char *auth_space = "\t";
+const char *state_space = "\t";
+const char *next_space = "\t";
+const char *branches_space = "\t";
+const char *comment_space ="\t";
+const char *expand_space = "\t";
+
+void print_stream(struct stream *);
+
+/* Print the contents of a stream, for debugging. */
+void
+print_stream(struct stream *s)
+{
+ char *line;
+
+ line = stream_getln(s, NULL);
+ while (line != NULL) {
+ lprintf(-1, "%s\n", line);
+ line = stream_getln(s, NULL);
+ }
+ lprintf(-1, "\n");
+}
+
+/*
+ * Parse rcsfile from path and return a pointer to it.
+ */
+struct rcsfile *
+rcsfile_frompath(char *path, char *name, char *cvsroot, char *colltag, int ro)
+{
+ struct rcsfile *rf;
+ FILE *infp;
+ int error;
+
+ if (path == NULL || name == NULL || cvsroot == NULL || colltag == NULL)
+ return (NULL);
+
+ rf = xmalloc(sizeof(struct rcsfile));
+ rf->name = xstrdup(name);
+ rf->cvsroot = xstrdup(cvsroot);
+ rf->colltag = xstrdup(colltag);
+
+ /* Initialize head branch. */
+ rf->trunk = xmalloc(sizeof(struct branch));
+ rf->trunk->revnum = xstrdup("1");
+ LIST_INIT(&rf->trunk->deltalist);
+ /* Initialize delta list. */
+ LIST_INIT(&rf->deltatable);
+ /* Initialize tag list. */
+ STAILQ_INIT(&rf->taglist);
+ /* Initialize accesslist. */
+ STAILQ_INIT(&rf->accesslist);
+
+ /* Initialize all fields. */
+ rf->head = NULL;
+ rf->branch = NULL;
+ rf->strictlock = 0;
+ rf->comment = NULL;
+ rf->expand = EXPAND_DEFAULT;
+ rf->desc = NULL;
+ rf->ro = ro;
+
+ infp = fopen(path, "r");
+ if (infp == NULL) {
+ lprintf(-1, "Cannot open \"%s\": %s\n", path, strerror(errno));
+ rcsfile_free(rf);
+ return (NULL);
+ }
+ error = rcsparse_run(rf, infp, ro);
+ fclose(infp);
+ if (error) {
+ lprintf(-1, "Error parsing \"%s\"\n", name);
+ rcsfile_free(rf);
+ return (NULL);
+ }
+ return (rf);
+}
+
+/*
+ * Write content of rcsfile to server. Assumes we have a complete RCS file
+ * loaded.
+ */
+int
+rcsfile_send_details(struct rcsfile *rf, struct stream *wr)
+{
+ struct delta *d;
+ struct tag *t;
+ const char *keyword;
+ int error;
+
+ assert(rf != NULL);
+
+ error = proto_printf(wr, "V %s\n", rf->name);
+ if (error)
+ return(error);
+
+ /* Write default branch. */
+ if (rf->branch == NULL)
+ error = proto_printf(wr, "b\n");
+ else
+ error = proto_printf(wr, "B %s\n", rf->branch);
+ if (error)
+ return(error);
+
+ /* Write deltas to server. */
+ error = proto_printf(wr, "D\n");
+ if (error)
+ return(error);
+
+ LIST_FOREACH(d, &rf->deltatable, table_next) {
+ error = proto_printf(wr, "%s %s\n", d->revnum, d->revdate);
+ if (error)
+ return(error);
+ }
+ error = proto_printf(wr, ".\n");
+
+ if (error)
+ return(error);
+ /* Write expand. */
+ if (rf->expand != EXPAND_DEFAULT) {
+ keyword = keyword_encode_expand(rf->expand);
+ if (keyword != NULL) {
+ error = proto_printf(wr, "E %s\n",
+ keyword_encode_expand(rf->expand));
+ if (error)
+ return(error);
+ }
+ }
+
+ /* Write tags to server. */
+ error = proto_printf(wr, "T\n");
+ if (error)
+ return(error);
+ STAILQ_FOREACH(t, &rf->taglist, tag_next) {
+ error = proto_printf(wr, "%s %s\n", t->tag, t->revnum);
+ if (error)
+ return(error);
+ }
+ error = proto_printf(wr, ".\n");
+ if (error)
+ return(error);
+ error = proto_printf(wr, ".\n");
+ return (error);
+}
+
+/*
+ * Write a RCS file to disk represented by the destination stream. Keep track of
+ * deltas with a stack and an inverted stack.
+ */
+int
+rcsfile_write(struct rcsfile *rf, struct stream *dest)
+{
+ STAILQ_HEAD(, delta) deltastack;
+ STAILQ_HEAD(, delta) deltalist_inverted;
+ struct tag *t;
+ struct branch *b;
+ struct delta *d, *d_tmp, *d_next;
+ int error;
+
+ /* First write head. */
+ d = LIST_FIRST(&rf->trunk->deltalist);
+ if (stream_printf(dest, "head%s%s;\n", head_space, d->revnum) < 0)
+ return (-1);
+
+ /* Write branch, if we have. */
+ if (rf->branch != NULL) {
+ if (stream_printf(dest, "branch%s%s;\n", branch_space,
+ rf->branch) < 0)
+ return (-1);
+ }
+
+ /* Write access. */
+ if (stream_printf(dest, "access") < 0)
+ return (-1);
+#if 0
+ if (!STAILQ_EMPTY(&rf->accesslist)) {
+ /*
+ * XXX: Write out access. This doesn't seem to be necessary for
+ * the time being.
+ */
+ }
+#endif
+ if (stream_printf(dest, ";\n") < 0)
+ return (-1);
+
+ /* Write out taglist. */
+ if (stream_printf(dest, "symbols") < 0)
+ return (-1);
+ if (!STAILQ_EMPTY(&rf->taglist)) {
+ STAILQ_FOREACH(t, &rf->taglist, tag_next) {
+ if (stream_printf(dest, "\n%s%s:%s", tag_space, t->tag,
+ t->revnum) < 0)
+ return (-1);
+ }
+ }
+
+ /* Write out locks and strict. */
+ if (stream_printf(dest, ";\nlocks;") < 0)
+ return (-1);
+ if (rf->strictlock) {
+ if (stream_printf(dest, " strict;") < 0)
+ return (-1);
+ }
+ if (stream_printf(dest, "\n") < 0)
+ return (-1);
+
+ /* Write out the comment. */
+ if (rf->comment != NULL) {
+ if (stream_printf(dest, "comment%s%s;\n", comment_space,
+ rf->comment) < 0)
+ return (-1);
+ }
+ if (rf->expand != EXPAND_DEFAULT) {
+ if (stream_printf(dest, "expand%s@%s@;\n", expand_space,
+ keyword_encode_expand(rf->expand)) < 0)
+ return (-1);
+ }
+
+ if (stream_printf(dest, "\n\n") < 0)
+ return (-1);
+
+ /*
+ * Write out deltas. We use a stack where we push the appropriate deltas
+ * that is to be written out during the loop.
+ */
+ STAILQ_INIT(&deltastack);
+ d = LIST_FIRST(&rf->trunk->deltalist);
+ STAILQ_INSERT_HEAD(&deltastack, d, stack_next);
+ while (!STAILQ_EMPTY(&deltastack)) {
+ d = STAILQ_FIRST(&deltastack);
+ STAILQ_REMOVE_HEAD(&deltastack, stack_next);
+ /* Do not write out placeholders just to be safe. */
+ if (d->placeholder)
+ continue;
+ if (stream_printf(dest, "%s\n", d->revnum) < 0)
+ return (-1);
+ if (stream_printf(dest, "date%s%s;%sauthor %s;%sstate",
+ date_space, d->revdate, auth_space, d->author,
+ state_space) < 0)
+ return (-1);
+ if (d->state != NULL) {
+ if (stream_printf(dest, " %s", d->state) < 0)
+ return (-1);
+ }
+ if (stream_printf(dest, ";\nbranches") < 0)
+ return (-1);
+ /*
+ * Write out our branches. Add them to a reversed list for use
+ * later when we write out the text.
+ */
+ STAILQ_INIT(&deltalist_inverted);
+ LIST_FOREACH(b, &d->branchlist, branch_next) {
+ d_tmp = LIST_FIRST(&b->deltalist);
+ STAILQ_INSERT_HEAD(&deltalist_inverted, d_tmp, delta_prev);
+ STAILQ_INSERT_HEAD(&deltastack, d_tmp, stack_next);
+ }
+
+ /* Push branch heads on stack. */
+ STAILQ_FOREACH(d_tmp, &deltalist_inverted, delta_prev) {
+ if (d_tmp == NULL) {
+ lprintf(2, "Empty branch!\n");
+ return (-1);
+ }
+ if (stream_printf(dest, "\n%s%s", branches_space,
+ d_tmp->revnum) < 0)
+ return (-1);
+ }
+
+ if (stream_printf(dest, ";\nnext%s", next_space) < 0)
+ return (-1);
+ /* Push next delta on stack. */
+ d_next = LIST_NEXT(d, delta_next);
+ if (d_next != NULL) {
+ if (stream_printf(dest, "%s", d_next->revnum) < 0)
+ return (-1);
+ STAILQ_INSERT_HEAD(&deltastack, d_next, stack_next);
+ }
+ if (stream_printf(dest, ";\n\n") < 0)
+ return (-1);
+ }
+ /* Write out desc. */
+ if (stream_printf(dest, "\ndesc\n@@") < 0)
+ return (-1);
+ d = LIST_FIRST(&rf->trunk->deltalist);
+
+ /* Write out deltatexts. */
+ error = rcsfile_write_deltatext(rf, dest);
+ if (stream_printf(dest, "\n") < 0)
+ return (-1);
+ return (error);
+}
+
+/*
+ * Write out deltatexts of a delta and it's subbranches recursively.
+ */
+int
+rcsfile_write_deltatext(struct rcsfile *rf, struct stream *dest)
+{
+ STAILQ_HEAD(, delta) deltastack;
+ LIST_HEAD(, delta) branchlist_datesorted;
+ struct delta *d, *d_tmp, *d_next, *d_tmp2, *d_tmp3;
+ struct stream *in;
+ struct branch *b;
+ size_t size;
+ char *line;
+ int error;
+
+ error = 0;
+ STAILQ_INIT(&deltastack);
+ d = LIST_FIRST(&rf->trunk->deltalist);
+ d->prev = NULL;
+ STAILQ_INSERT_HEAD(&deltastack, d, stack_next);
+ while (!STAILQ_EMPTY(&deltastack)) {
+ d = STAILQ_FIRST(&deltastack);
+ STAILQ_REMOVE_HEAD(&deltastack, stack_next);
+ /* Do not write out placeholders just to be safe. */
+ if (d->placeholder)
+ return (0);
+ if (stream_printf(dest, "\n\n\n%s\n", d->revnum) < 0)
+ return (-1);
+ if (stream_printf(dest, "log\n@") < 0)
+ return (-1);
+ in = stream_open_buf(d->log);
+ line = stream_getln(in, &size);
+ while (line != NULL) {
+ if (stream_write(dest, line, size) == -1)
+ return (-1);
+ line = stream_getln(in, &size);
+ }
+ stream_close(in);
+ if (stream_printf(dest, "@\ntext\n@") < 0)
+ return (-1);
+ error = rcsfile_puttext(rf, dest, d, d->prev);
+ if (error)
+ return (error);
+ if (stream_printf(dest, "@") < 0)
+ return (-1);
+
+ LIST_INIT(&branchlist_datesorted);
+ d_next = LIST_NEXT(d, delta_next);
+ if (d_next != NULL) {
+ d_next->prev = d;
+ /*
+ * If it's trunk, treat it like the oldest, if not treat
+ * it like a child.
+ */
+ if (rcsrev_istrunk(d_next->revnum))
+ STAILQ_INSERT_HEAD(&deltastack, d_next,
+ stack_next);
+ else
+ LIST_INSERT_HEAD(&branchlist_datesorted, d_next,
+ branch_next_date);
+ }
+
+ /*
+ * First, we need to sort our branches based on their date to
+ * take into account some self-hacked RCS files.
+ */
+ LIST_FOREACH(b, &d->branchlist, branch_next) {
+ d_tmp = LIST_FIRST(&b->deltalist);
+ if (LIST_EMPTY(&branchlist_datesorted)) {
+ LIST_INSERT_HEAD(&branchlist_datesorted, d_tmp,
+ branch_next_date);
+ continue;
+ }
+
+ d_tmp2 = LIST_FIRST(&branchlist_datesorted);
+ if (rcsnum_cmp(d_tmp->revdate, d_tmp2->revdate) <= 0) {
+ LIST_INSERT_BEFORE(d_tmp2, d_tmp,
+ branch_next_date);
+ continue;
+ }
+ while ((d_tmp3 = LIST_NEXT(d_tmp2, branch_next_date))
+ != NULL) {
+ if (rcsnum_cmp(d_tmp->revdate, d_tmp3->revdate)
+ <= 0)
+ break;
+ d_tmp2 = d_tmp3;
+ }
+ LIST_INSERT_AFTER(d_tmp2, d_tmp, branch_next_date);
+ }
+ /*
+ * Invert the deltalist of a branch, since we're writing them
+ * the opposite way.
+ */
+ LIST_FOREACH(d_tmp, &branchlist_datesorted, branch_next_date) {
+ d_tmp->prev = d;
+ STAILQ_INSERT_HEAD(&deltastack, d_tmp, stack_next);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Generates text given a delta and a diffbase.
+ */
+static int
+rcsfile_puttext(struct rcsfile *rf, struct stream *dest, struct delta *d,
+ struct delta *diffbase)
+{
+ struct stream *in, *rd, *orig;
+ struct keyword *k;
+ struct diffinfo dibuf, *di;
+ struct buf *b;
+ size_t size;
+ char *line;
+ int error;
+
+ di = &dibuf;
+ b = NULL;
+ error = 0;
+
+ /* Write if the diffbase is the previous */
+ if (d->diffbase == diffbase) {
+
+ /* Write out the text. */
+ in = stream_open_buf(d->text);
+ line = stream_getln(in, &size);
+ while (line != NULL) {
+ if (stream_write(dest, line, size) == -1) {
+ error = -1;
+ goto cleanup;
+ }
+ line = stream_getln(in, &size);
+ }
+ stream_close(in);
+ /* We need to apply diff to produce text, this is probably HEAD. */
+ } else if (diffbase == NULL) {
+ /* Apply diff. */
+ orig = rcsfile_getdeltatext(rf, d, &b);
+ if (orig == NULL) {
+ error = -1;
+ goto cleanup;
+ }
+ line = stream_getln(orig, &size);
+ while (line != NULL) {
+ if (stream_write(dest, line, size) == -1) {
+ error = -1;
+ goto cleanup;
+ }
+ line = stream_getln(orig, &size);
+ }
+ stream_close(orig);
+ /*
+ * A new head was probably added, and now the previous HEAD must be
+ * changed to include the diff instead.
+ */
+ } else if (diffbase->diffbase == d) {
+ /* Get reverse diff. */
+ orig = rcsfile_getdeltatext(rf, d, &b);
+ if (orig == NULL) {
+ error = -1;
+ goto cleanup;
+ }
+ di->di_rcsfile = rf->name;
+ di->di_cvsroot = rf->cvsroot;
+ di->di_revnum = d->revnum;
+ di->di_revdate = d->revdate;
+ di->di_author = d->author;
+ di->di_tag = rf->colltag;
+ di->di_state = d->state;
+ di->di_expand = EXPAND_OLD;
+ k = keyword_new();
+
+ rd = stream_open_buf(diffbase->text);
+ error = diff_reverse(rd, orig, dest, k, di);
+ if (error) {
+ lprintf(-1, "Error applying reverse diff: %d\n", error);
+ goto cleanup;
+ }
+ keyword_free(k);
+ stream_close(rd);
+ stream_close(orig);
+ }
+cleanup:
+ if (b != NULL)
+ buf_free(b);
+ return (error);
+}
+
+/*
+ * Return a stream with an applied diff of a delta.
+ * XXX: extra overhead on the last apply. Could write directly to file, but
+ * makes things complicated though.
+ */
+static struct stream *
+rcsfile_getdeltatext(struct rcsfile *rf, struct delta *d, struct buf **buf_dest)
+{
+ struct diffinfo dibuf, *di;
+ struct stream *orig, *dest, *rd;
+ struct buf *buf_orig;
+ struct keyword *k;
+ int error;
+
+ buf_orig = NULL;
+ error = 0;
+
+ /*
+ * If diffbase is NULL or we are head (the old head), we have a normal
+ * complete deltatext.
+ */
+ if (d->diffbase == NULL && !strcmp(rf->head, d->revnum)) {
+ orig = stream_open_buf(d->text);
+ return (orig);
+ }
+
+ di = &dibuf;
+ /* If not, we need to apply our diff to that of our diffbase. */
+ orig = rcsfile_getdeltatext(rf, d->diffbase, &buf_orig);
+ if (orig == NULL)
+ return (NULL);
+
+ /*
+ * Now that we are sure we have a complete deltatext in ret, let's apply
+ * our diff to it.
+ */
+ *buf_dest = buf_new(BUF_SIZE_DEFAULT);
+ dest = stream_open_buf(*buf_dest);
+
+ di->di_rcsfile = rf->name;
+ di->di_cvsroot = rf->cvsroot;
+ di->di_revnum = d->revnum;
+ di->di_revdate = d->revdate;
+ di->di_author = d->author;
+ di->di_tag = rf->colltag;
+ di->di_state = d->state;
+ di->di_expand = EXPAND_OLD;
+ rd = stream_open_buf(d->text);
+ k = keyword_new();
+ error = diff_apply(rd, orig, dest, k, di, 0);
+ stream_flush(dest);
+ stream_close(rd);
+ stream_close(orig);
+ stream_close(dest);
+ keyword_free(k);
+ if (buf_orig != NULL)
+ buf_free(buf_orig);
+ if (error) {
+ lprintf(-1, "Error applying diff: %d\n", error);
+ return (NULL);
+ }
+
+ /* Now reopen the stream for the reading. */
+ dest = stream_open_buf(*buf_dest);
+ return (dest);
+}
+
+/* Print content of rcsfile. Useful for debugging. */
+void
+rcsfile_print(struct rcsfile *rf)
+{
+ struct delta *d;
+ struct tag *t;
+ struct string *s;
+ struct stream *in;
+ char *line;
+
+ lprintf(1, "\n");
+ if (rf->name != NULL)
+ lprintf(1, "name: '%s'\n", rf->name);
+ if (rf->head != NULL)
+ lprintf(1, "head: '%s'\n", rf->head);
+ if (rf->branch != NULL)
+ lprintf(1, "branch: '%s'\n", rf->branch);
+ lprintf(1, "Access: ");
+ STAILQ_FOREACH(s, &rf->accesslist, string_next)
+ lprintf(1, "'%s' ", s->str);
+ lprintf(1, "\n");
+
+ /* Print all tags. */
+ STAILQ_FOREACH(t, &rf->taglist, tag_next) {
+ lprintf(1, "Tag: ");
+ if (t->tag != NULL)
+ lprintf(1, "name: %s ", t->tag);
+ if (t->revnum != NULL)
+ lprintf(1, "rev: %s", t->revnum);
+ lprintf(1, "\n");
+ }
+
+ if (rf->strictlock)
+ lprintf(1, "Strict!\n");
+ if (rf->comment != NULL)
+ lprintf(1, "comment: '%s'\n", rf->comment);
+ if (rf->expand != EXPAND_DEFAULT)
+ lprintf(1, "expand: '%s'\n", keyword_encode_expand(rf->expand));
+
+ /* Print all deltas. */
+ LIST_FOREACH(d, &rf->deltatable, table_next) {
+ lprintf(1, "Delta: ");
+ if (d->revdate != NULL)
+ lprintf(1, "date: %s ", d->revdate);
+ if (d->revnum != NULL)
+ lprintf(1, "rev: %s", d->revnum);
+ if (d->author != NULL)
+ lprintf(1, "author: %s", d->author);
+ if (d->state != NULL)
+ lprintf(1, "state: %s", d->state);
+
+ lprintf(1, "Text:\n");
+ in = stream_open_buf(d->text);
+ line = stream_getln(in, NULL);
+ while (line != NULL) {
+ lprintf(1, "TEXT: %s\n", line);
+ line = stream_getln(in, NULL);
+ }
+ stream_close(in);
+ lprintf(1, "\n");
+ }
+
+ if (rf->desc != NULL)
+ lprintf(1, "desc: '%s'\n", rf->desc);
+}
+
+/* Free all memory associated with a struct rcsfile. */
+void
+rcsfile_free(struct rcsfile *rf)
+{
+ struct delta *d;
+ struct tag *t;
+ struct string *s;
+
+ if (rf->name != NULL)
+ free(rf->name);
+ if (rf->head != NULL)
+ free(rf->head);
+ if (rf->branch != NULL)
+ free(rf->branch);
+ if (rf->cvsroot != NULL)
+ free(rf->cvsroot);
+ if (rf->colltag != NULL)
+ free(rf->colltag);
+
+ /* Free all access ids. */
+ while (!STAILQ_EMPTY(&rf->accesslist)) {
+ s = STAILQ_FIRST(&rf->accesslist);
+ STAILQ_REMOVE_HEAD(&rf->accesslist, string_next);
+ if (s->str != NULL)
+ free(s->str);
+ free(s);
+ }
+
+ /* Free all tags. */
+ while (!STAILQ_EMPTY(&rf->taglist)) {
+ t = STAILQ_FIRST(&rf->taglist);
+ STAILQ_REMOVE_HEAD(&rf->taglist, tag_next);
+ if (t->tag != NULL)
+ free(t->tag);
+ if (t->revnum != NULL)
+ free(t->revnum);
+ free(t);
+ }
+
+ if (rf->comment != NULL)
+ free(rf->comment);
+
+ /* Free all deltas in global list */
+ while (!LIST_EMPTY(&rf->deltatable)) {
+ d = LIST_FIRST(&rf->deltatable);
+ if (!rf->ro)
+ LIST_REMOVE(d, delta_next);
+ LIST_REMOVE(d, table_next);
+ rcsfile_freedelta(d);
+ }
+
+ /* Free global branch. */
+ if (rf->trunk->revnum != NULL)
+ free(rf->trunk->revnum);
+ free(rf->trunk);
+
+ if (rf->desc != NULL)
+ free(rf->desc);
+
+ free(rf);
+}
+
+/*
+ * Free a RCS delta.
+ */
+static void
+rcsfile_freedelta(struct delta *d)
+{
+ struct branch *b;
+
+ if (d->revdate != NULL)
+ free(d->revdate);
+ if (d->revnum != NULL)
+ free(d->revnum);
+ if (d->author != NULL)
+ free(d->author);
+ if (d->state != NULL)
+ free(d->state);
+ if (d->log != NULL)
+ buf_free(d->log);
+ if (d->text != NULL)
+ buf_free(d->text);
+
+ /* Free all subbranches of a delta. */
+ while (!LIST_EMPTY(&d->branchlist)) {
+ b = LIST_FIRST(&d->branchlist);
+ LIST_REMOVE(b, branch_next);
+ free(b->revnum);
+ free(b);
+ }
+ free(d);
+}
+
+/*
+ * Functions for editing RCS deltas.
+ */
+
+/* Add a new entry to the access list. */
+void
+rcsfile_addaccess(struct rcsfile *rf, char *id)
+{
+ struct string *s;
+
+ s = xmalloc(sizeof(struct string));
+ s->str = xstrdup(id);
+ STAILQ_INSERT_TAIL(&rf->accesslist, s, string_next);
+}
+
+/* Add a tag to a RCS file. */
+void
+rcsfile_addtag(struct rcsfile *rf, char *tag, char *revnum)
+{
+ struct tag *t;
+
+ t = xmalloc(sizeof(struct tag));
+ t->tag = xstrdup(tag);
+ t->revnum = xstrdup(revnum);
+
+ STAILQ_INSERT_HEAD(&rf->taglist, t, tag_next);
+}
+
+/* Import a tag to a RCS file. */
+void
+rcsfile_importtag(struct rcsfile *rf, char *tag, char *revnum)
+{
+ struct tag *t;
+
+ t = xmalloc(sizeof(struct tag));
+ t->tag = xstrdup(tag);
+ t->revnum = xstrdup(revnum);
+
+ STAILQ_INSERT_TAIL(&rf->taglist, t, tag_next);
+}
+
+/*
+ * Delete a revision from the global delta list and the branch it is in. Csup
+ * will tell us to delete the tags involved.
+ */
+void
+rcsfile_deleterev(struct rcsfile *rf, char *revname)
+{
+ struct delta *d;
+
+ d = rcsfile_getdelta(rf, revname);
+ if (!rf->ro)
+ LIST_REMOVE(d, delta_next);
+ LIST_REMOVE(d, table_next);
+ rcsfile_freedelta(d);
+}
+
+/* Delete a tag from the tag list. */
+void
+rcsfile_deletetag(struct rcsfile *rf, char *tag, char *revnum)
+{
+ struct tag *t;
+
+ STAILQ_FOREACH(t, &rf->taglist, tag_next) {
+ if ((strcmp(tag, t->tag) == 0) &&
+ (strcmp(revnum, t->revnum) == 0)) {
+ STAILQ_REMOVE(&rf->taglist, t, tag, tag_next);
+ free(t->tag);
+ free(t->revnum);
+ free(t);
+ return;
+ }
+ }
+}
+
+/*
+ * Searches the global deltalist for a delta.
+ */
+struct delta *
+rcsfile_getdelta(struct rcsfile *rf, char *revnum)
+{
+ struct delta *d;
+
+ LIST_FOREACH(d, &rf->deltatable, table_next) {
+ if (strcmp(revnum, d->revnum) == 0)
+ return (d);
+ }
+ return (NULL);
+}
+
+/* Set rcsfile head. */
+void
+rcsfile_setval(struct rcsfile *rf, int field, char *val)
+{
+ size_t len;
+
+ switch (field) {
+ case RCSFILE_HEAD:
+ if (rf->head != NULL)
+ free(rf->head);
+ rf->head = xstrdup(val);
+ break;
+ case RCSFILE_BRANCH:
+ if (rf->branch != NULL)
+ free(rf->branch);
+ rf->branch = (val == NULL) ? NULL : xstrdup(val);
+ break;
+ case RCSFILE_STRICT:
+ if (val != NULL)
+ rf->strictlock = 1;
+ break;
+ case RCSFILE_COMMENT:
+ if (rf->comment != NULL)
+ free(rf->comment);
+ rf->comment = xstrdup(val);
+ break;
+ case RCSFILE_EXPAND:
+ len = strlen(val) - 1;
+ val++;
+ val[len - 1] = '\0';
+ rf->expand = keyword_decode_expand(val);
+ break;
+ case RCSFILE_DESC:
+ if (rf->desc != NULL)
+ free(rf->desc);
+ rf->desc = xstrdup(val);
+ break;
+ default:
+ lprintf(-1, "Setting invalid RCSfile value.\n");
+ break;
+ }
+}
+
+/* Create and initialize a delta. */
+static struct delta *
+rcsfile_createdelta(char *revnum)
+{
+ struct delta *d;
+
+ d = xmalloc(sizeof(struct delta));
+ d->revnum = xstrdup(revnum);
+ d->revdate = NULL;
+ d->state = NULL;
+ d->author = NULL;
+ d->log = buf_new(BUF_SIZE_DEFAULT);
+ d->text = buf_new(BUF_SIZE_DEFAULT);
+ d->diffbase = NULL;
+
+ LIST_INIT(&d->branchlist);
+ return (d);
+}
+
+/* Add a delta to a imported delta tree. Used by the updater. */
+struct delta *
+rcsfile_addelta(struct rcsfile *rf, char *revnum, char *revdate, char *author,
+ char *diffbase)
+{
+ struct branch *b;
+ struct delta *d, *d_bp, *d_next;
+ char *brev, *bprev;
+ int trunk;
+
+ d_next = NULL;
+ d = rcsfile_getdelta(rf, revnum);
+ if (d != NULL) {
+ lprintf(-1, "Delta %s already exists!\n", revnum);
+ return (NULL);
+ }
+ d = rcsfile_createdelta(revnum);
+ d->placeholder = 0;
+ d->revdate = xstrdup(revdate);
+ d->author = xstrdup(author);
+ d->diffbase = rcsfile_getdelta(rf, diffbase);
+
+ /* If it's trunk, insert it in the head branch list. */
+ b = rcsrev_istrunk(d->revnum) ? rf->trunk :
+ rcsfile_getbranch(rf, d->revnum);
+
+ /*
+ * We didn't find a branch, check if we can find a branchpoint and
+ * create a branch there.
+ */
+ if (b == NULL) {
+ brev = rcsrev_prefix(d->revnum);
+ bprev = rcsrev_prefix(brev);
+
+ d_bp = rcsfile_getdelta(rf, bprev);
+ free(bprev);
+ if (d_bp == NULL) {
+ lprintf(-1, "No branch point for adding delta %s\n",
+ d->revnum);
+ return (NULL);
+ }
+
+ /* Create the branch and insert in delta. */
+ b = xmalloc(sizeof(struct branch));
+ b->revnum = brev;
+ LIST_INIT(&b->deltalist);
+ rcsdelta_insertbranch(d_bp, b);
+ }
+
+ /* Insert both into the tree, and into the lookup list. */
+ trunk = rcsrev_istrunk(d->revnum);
+ rcsfile_insertdelta(b, d, trunk);
+ rcsfile_insertsorteddelta(rf, d);
+ return (d);
+}
+
+/* Adds a delta to a rcsfile struct. Used by the parser. */
+void
+rcsfile_importdelta(struct rcsfile *rf, char *revnum, char *revdate, char *author,
+ char *state, char *next)
+{
+ struct branch *b;
+ struct delta *d, *d_bp, *d_next;
+ char *brev, *bprev;
+ int trunk;
+
+ d_next = NULL;
+ d = rcsfile_getdelta(rf, revnum);
+
+ if (d == NULL) {
+ /* If not, we'll just create a new entry. */
+ d = rcsfile_createdelta(revnum);
+ d->placeholder = 0;
+ } else {
+ if (d->placeholder == 0) {
+ lprintf(-1, "Trying to import already existing delta\n");
+ return;
+ }
+ }
+ /*
+ * If already exists, assume that only revnum is filled out, and set the
+ * rest of the fields. This should be an OK assumption given that we can
+ * be sure internally that the structure is sufficiently initialized so
+ * we won't have any unfreed memory.
+ */
+ d->revdate = xstrdup(revdate);
+ d->author = xstrdup(author);
+ if (state != NULL)
+ d->state = xstrdup(state);
+
+ /* If we have a next, create a placeholder for it. */
+ if (next != NULL) {
+ d_next = rcsfile_createdelta(next);
+ d_next->placeholder = 1;
+ /* Diffbase should be the previous. */
+ d_next->diffbase = d;
+ }
+
+ /* If we're opening read-only, do minimal work. */
+ if (rf->ro) {
+ if (!d->placeholder)
+ rcsfile_insertsorteddelta(rf, d);
+ else
+ d->placeholder = 0;
+ if (d_next != NULL)
+ rcsfile_insertsorteddelta(rf, d_next);
+ return;
+ }
+
+ /* If it's trunk, insert it in the head branch list. */
+ b = rcsrev_istrunk(d->revnum) ? rf->trunk : rcsfile_getbranch(rf,
+ d->revnum);
+
+ /*
+ * We didn't find a branch, check if we can find a branchpoint and
+ * create a branch there.
+ */
+ if (b == NULL) {
+ brev = rcsrev_prefix(d->revnum);
+ bprev = rcsrev_prefix(brev);
+
+ d_bp = rcsfile_getdelta(rf, bprev);
+ free(bprev);
+ if (d_bp == NULL) {
+ lprintf(-1, "No branch point for adding delta %s\n",
+ d->revnum);
+ return;
+ }
+
+ /* Create the branch and insert in delta. */
+ b = xmalloc(sizeof(struct branch));
+ b->revnum = brev;
+ LIST_INIT(&b->deltalist);
+ rcsdelta_insertbranch(d_bp, b);
+ }
+
+ /* Insert if not a placeholder. */
+ if (!d->placeholder) {
+ /* Insert both into the tree, and into the lookup list. */
+ if (rcsrev_istrunk(d->revnum))
+ rcsfile_insertdelta(b, d, 1);
+ else {
+ rcsfile_insertdelta(b, d, 0);
+ /*
+ * On import we need to set the diffbase to our
+ * branchpoint for writing out later.
+ */
+ if (LIST_FIRST(&b->deltalist) == d) {
+ brev = rcsrev_prefix(d->revnum);
+ bprev = rcsrev_prefix(brev);
+ d_bp = rcsfile_getdelta(rf, bprev);
+ /* This should really not happen. */
+ assert(d_bp != NULL);
+ d->diffbase = d_bp;
+ free(brev);
+ free(bprev);
+ }
+ }
+ rcsfile_insertsorteddelta(rf, d);
+ } else /* Not a placeholder anymore. */ {
+ d->placeholder = 0;
+ /* Put it into the tree. */
+ trunk = rcsrev_istrunk(d->revnum);
+ rcsfile_insertdelta(b, d, trunk);
+ }
+
+ /* If we have a next, insert the placeholder into the lookup list. */
+ if (d_next != NULL)
+ rcsfile_insertsorteddelta(rf, d_next);
+}
+
+/*
+ * Find the branch of a revision number.
+ */
+static struct branch *
+rcsfile_getbranch(struct rcsfile *rf, char *revnum)
+{
+ struct branch *b;
+ struct delta *d;
+ char *branchrev, *bprev;
+
+ branchrev = rcsrev_prefix(revnum);
+ bprev = rcsrev_prefix(branchrev);
+ d = rcsfile_getdelta(rf, bprev);
+ free(bprev);
+ LIST_FOREACH(b, &d->branchlist, branch_next) {
+ if(rcsnum_cmp(b->revnum, branchrev) == 0) {
+ free(branchrev);
+ return (b);
+ }
+ }
+ free(branchrev);
+ return (NULL);
+}
+
+/* Insert a branch into a delta, sorted by branch revision date. */
+static void
+rcsdelta_insertbranch(struct delta *d, struct branch *b)
+{
+ struct branch *b_iter;
+
+ /* If it's empty, insert into head. */
+ if (LIST_EMPTY(&d->branchlist)) {
+ LIST_INSERT_HEAD(&d->branchlist, b, branch_next);
+ return;
+ }
+
+ /* Just put it in before the revdate that is lower. */
+ LIST_FOREACH(b_iter, &d->branchlist, branch_next) {
+ if (rcsnum_cmp(b->revnum, b_iter->revnum) > 0) {
+ LIST_INSERT_BEFORE(b_iter, b, branch_next);
+ return;
+ }
+ if (LIST_NEXT(b_iter, branch_next) == NULL)
+ break;
+ }
+ /* Insert after last element. */
+ LIST_INSERT_AFTER(b_iter, b, branch_next);
+}
+
+/* Insert a delta into the correct place in the table of the rcsfile. */
+static void
+rcsfile_insertsorteddelta(struct rcsfile *rf, struct delta *d)
+{
+ struct delta *d2;
+
+ /* If it's empty, insert into head. */
+ if (LIST_EMPTY(&rf->deltatable)) {
+ LIST_INSERT_HEAD(&rf->deltatable, d, table_next);
+ return;
+ }
+
+ /* Just put it in before the revdate that is lower. */
+ LIST_FOREACH(d2, &rf->deltatable, table_next) {
+ if (rcsnum_cmp(d->revnum, d2->revnum) <= 0) {
+ LIST_INSERT_BEFORE(d2, d, table_next);
+ return;
+ }
+ if (LIST_NEXT(d2, table_next) == NULL)
+ break;
+ }
+ /* Insert after last element. */
+ LIST_INSERT_AFTER(d2, d, table_next);
+}
+
+/*
+ * Insert a delta into the correct place in branch. A trunk branch will have
+ * different ordering scheme and be sorted by revision number, but a normal
+ * branch will be sorted by date to maintain compability with branches that is
+ * "hand-hacked".
+ */
+static void
+rcsfile_insertdelta(struct branch *b, struct delta *d, int trunk)
+{
+ struct delta *d2;
+
+ /* If it's empty, insert into head. */
+ if (LIST_EMPTY(&b->deltalist)) {
+ LIST_INSERT_HEAD(&b->deltalist, d, delta_next);
+ return;
+ }
+
+ /*
+ * Just put it in before the revnum that is lower. Sort trunk branch by
+ * branchnum but the subbranches after deltadate.
+ */
+ LIST_FOREACH(d2, &b->deltalist, delta_next) {
+ if (trunk) {
+ if (rcsnum_cmp(d->revnum, d2->revnum) >= 0) {
+ LIST_INSERT_BEFORE(d2, d, delta_next);
+ return;
+ }
+ } else {
+ /* XXX: here we depend on the date being set, but it
+ * should be before this is called anyway. */
+ if (rcsnum_cmp(d->revnum, d2->revnum) < 0) {
+ LIST_INSERT_BEFORE(d2, d, delta_next);
+ return;
+ }
+ }
+ if (LIST_NEXT(d2, delta_next) == NULL)
+ break;
+ }
+ /* Insert after last element. */
+ LIST_INSERT_AFTER(d2, d, delta_next);
+}
+
+
+/* Add logtext to a delta. Assume the delta already exists. */
+int
+rcsdelta_addlog(struct delta *d, char *log, int len)
+{
+ struct stream *dest;
+ int nbytes;
+
+ assert(d != NULL);
+ /* Strip away '@' at beginning and end. */
+ log++;
+ len--;
+ log[len - 1] = '\0';
+ dest = stream_open_buf(d->log);
+ nbytes = stream_write(dest, log, len - 1);
+ stream_close(dest);
+ return ((nbytes == -1) ? -1 : 0);
+}
+
+/* Add deltatext to a delta. Assume the delta already exists. */
+int
+rcsdelta_addtext(struct delta *d, char *text, int len)
+{
+ struct stream *dest;
+ int nbytes;
+
+ assert(d != NULL);
+ /* Strip away '@' at beginning and end. */
+ text++;
+ len--;
+ text[len - 1] = '\0';
+
+ dest = stream_open_buf(d->text);
+ nbytes = stream_write(dest, text, len - 1);
+ stream_close(dest);
+ return ((nbytes == -1) ? -1 : 0);
+}
+
+/* Add a deltatext logline to a delta. */
+int
+rcsdelta_appendlog(struct delta *d, char *logline, size_t size)
+{
+ struct stream *dest;
+ int error;
+
+ assert(d != NULL);
+ dest = stream_open_buf(d->log);
+ error = rcsdelta_writestring(logline, size, dest);
+ stream_close(dest);
+ return (error);
+}
+
+/* Add a deltatext textline to a delta. */
+int
+rcsdelta_appendtext(struct delta *d, char *textline, size_t size)
+{
+ struct stream *dest;
+ int error;
+
+ assert(d != NULL);
+ dest = stream_open_buf(d->text);
+ error = rcsdelta_writestring(textline, size, dest);
+ stream_close(dest);
+ return (error);
+}
+
+static int
+rcsdelta_writestring(char *textline, size_t size, struct stream *dest)
+{
+ char buf[3];
+ size_t i;
+ int count;
+
+ for (i = 0; i < size; i++) {
+ buf[0] = textline[i];
+ buf[1] = '\0';
+ count = 1;
+ /* Expand @'s */
+ if (buf[0] == '@') {
+ buf[1] = '@';
+ buf[2] = '\0';
+ count = 2;
+ }
+ if (stream_write(dest, buf, count) == -1)
+ return (-1);
+ }
+ return (0);
+}
+
+/* Set delta state. */
+void
+rcsdelta_setstate(struct delta *d, char *state)
+{
+
+ if (d->state != NULL)
+ free(state);
+ if (state != NULL) {
+ d->state = xstrdup(state);
+ return;
+ }
+ d->state = NULL;
+}
+
+/* Truncate the deltalog with a certain offset. */
+void
+rcsdelta_truncatelog(struct delta *d, off_t offset)
+{
+
+ stream_truncate_buf(d->log, offset);
+}
+
+/* Truncate the deltatext with a certain offset. */
+void
+rcsdelta_truncatetext(struct delta *d, off_t offset)
+{
+
+ stream_truncate_buf(d->text, offset);
+}
diff --git a/usr.bin/csup/rcsfile.h b/usr.bin/csup/rcsfile.h
new file mode 100644
index 0000000..5fa9f31
--- /dev/null
+++ b/usr.bin/csup/rcsfile.h
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 2007-2009, Ulf Lilleengen <lulf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _RCSFILE_H_
+#define _RCSFILE_H_
+
+/* RCSFILE fields. */
+#define RCSFILE_HEAD 0
+#define RCSFILE_BRANCH 1
+#define RCSFILE_STRICT 2
+#define RCSFILE_COMMENT 3
+#define RCSFILE_EXPAND 4
+#define RCSFILE_DESC 5
+
+struct rcsfile;
+struct delta;
+struct stream;
+
+/* Fetching, sending and writing an RCS file. */
+struct rcsfile *rcsfile_frompath(char *, char *, char *, char *, int);
+int rcsfile_send_details(struct rcsfile *, struct stream *);
+int rcsfile_write(struct rcsfile *, struct stream *);
+void rcsfile_print(struct rcsfile *);
+void rcsfile_free(struct rcsfile *);
+
+/* Used for adding and setting rcsfile values. */
+void rcsfile_addaccess(struct rcsfile *, char *);
+void rcsfile_addtag(struct rcsfile *, char *, char *);
+void rcsfile_importtag(struct rcsfile *, char *, char *);
+void rcsfile_deleterev(struct rcsfile *, char *);
+void rcsfile_deletetag(struct rcsfile *, char *, char *);
+struct delta *rcsfile_getdelta(struct rcsfile *, char *);
+void rcsfile_setval(struct rcsfile *, int, char *);
+
+/* Functions used for operating on RCS deltas. */
+struct delta *rcsfile_addelta(struct rcsfile *, char *, char *, char *,
+ char *);
+void rcsfile_importdelta(struct rcsfile *, char *, char *, char *,
+ char *, char *);
+
+int rcsdelta_addlog(struct delta *, char *, int);
+int rcsdelta_addtext(struct delta *, char *, int);
+int rcsdelta_appendlog(struct delta *, char *, size_t);
+int rcsdelta_appendtext(struct delta *, char *, size_t);
+void rcsdelta_setstate(struct delta *, char *);
+void rcsdelta_truncatetext(struct delta *, off_t);
+void rcsdelta_truncatelog(struct delta *, off_t);
+#endif /* !_RCSFILE_H_ */
diff --git a/usr.bin/csup/rcsparse.c b/usr.bin/csup/rcsparse.c
new file mode 100644
index 0000000..5ea690c
--- /dev/null
+++ b/usr.bin/csup/rcsparse.c
@@ -0,0 +1,357 @@
+/*-
+ * Copyright (c) 2008-2009, Ulf Lilleengen <lulf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "misc.h"
+#include "queue.h"
+#include "rcsfile.h"
+#include "rcsparse.h"
+#include "rcstokenizer.h"
+
+/*
+ * This is an RCS-parser using lex for tokenizing and makes sure the RCS syntax
+ * is correct as it constructs an RCS file that is used by csup.
+ */
+
+static void asserttoken(yyscan_t *, int);
+static int parse_admin(struct rcsfile *, yyscan_t *);
+static int parse_deltas(struct rcsfile *, yyscan_t *, int);
+static int parse_deltatexts(struct rcsfile *, yyscan_t *, int);
+static char *duptext(yyscan_t *, int *);
+
+struct string {
+ char *str;
+ STAILQ_ENTRY(string) next;
+};
+
+static void
+asserttoken(yyscan_t *sp, int token)
+{
+ int t;
+
+ t = token;
+ t = rcslex(*sp);
+ assert(t == token);
+}
+
+static char *
+duptext(yyscan_t *sp, int *arglen)
+{
+ char *tmp, *val;
+ int len;
+
+ tmp = rcsget_text(*sp);
+ len = rcsget_leng(*sp);
+ val = xmalloc(len + 1);
+ memcpy(val, tmp, len);
+ val[len] = '\0';
+ if (arglen != NULL)
+ *arglen = len;
+ return (val);
+}
+
+/*
+ * Start up parser, and use the rcsfile hook to add objects.
+ */
+int
+rcsparse_run(struct rcsfile *rf, FILE *infp, int ro)
+{
+ yyscan_t scanner;
+ char *desc;
+ int error, tok;
+
+ error = 0;
+ rcslex_init(&scanner);
+ rcsset_in(infp, scanner);
+ tok = parse_admin(rf, &scanner);
+ tok = parse_deltas(rf, &scanner, tok);
+ assert(tok == KEYWORD);
+ asserttoken(&scanner, STRING);
+ desc = duptext(&scanner, NULL);
+ rcsfile_setval(rf, RCSFILE_DESC, desc);
+ free(desc);
+ tok = rcslex(scanner);
+ /* Parse deltatexts if we need to edit. */
+ if (!ro) {
+ error = parse_deltatexts(rf, &scanner, tok);
+ if (error)
+ return (error);
+ }
+ rcslex_destroy(scanner);
+ return (0);
+}
+
+/*
+ * Parse the admin part of a RCS file.
+ */
+static int
+parse_admin(struct rcsfile *rf, yyscan_t *sp)
+{
+ char *branch, *comment, *expand, *head, *id, *revnum, *tag, *tmp;
+ int strict, token;
+
+ strict = 0;
+ branch = NULL;
+
+ /* head {num}; */
+ asserttoken(sp, KEYWORD);
+ asserttoken(sp, NUM);
+ head = duptext(sp, NULL);
+ rcsfile_setval(rf, RCSFILE_HEAD, head);
+ free(head);
+ asserttoken(sp, SEMIC);
+
+ /* { branch {num}; } */
+ token = rcslex(*sp);
+ if (token == KEYWORD_TWO) {
+ asserttoken(sp, NUM);
+ branch = duptext(sp, NULL);
+ rcsfile_setval(rf, RCSFILE_BRANCH, branch);
+ free(branch);
+ asserttoken(sp, SEMIC);
+ token = rcslex(*sp);
+ }
+
+ /* access {id]*; */
+ assert(token == KEYWORD);
+ token = rcslex(*sp);
+ while (token == ID) {
+ id = duptext(sp, NULL);
+ rcsfile_addaccess(rf, id);
+ free(id);
+ token = rcslex(*sp);
+ }
+ assert(token == SEMIC);
+
+ /* symbols {sym : num}*; */
+ asserttoken(sp, KEYWORD);
+ token = rcslex(*sp);
+ while (token == ID) {
+ tag = duptext(sp, NULL);
+ asserttoken(sp, COLON);
+ asserttoken(sp, NUM);
+ revnum = duptext(sp, NULL);
+ rcsfile_importtag(rf, tag, revnum);
+ free(tag);
+ free(revnum);
+ token = rcslex(*sp);
+ }
+ assert(token == SEMIC);
+
+ /* locks {id : num}*; */
+ asserttoken(sp, KEYWORD);
+ token = rcslex(*sp);
+ while (token == ID) {
+ /* XXX: locks field is skipped */
+ asserttoken(sp, COLON);
+ asserttoken(sp, NUM);
+ token = rcslex(*sp);
+ }
+ assert(token == SEMIC);
+ token = rcslex(*sp);
+ while (token == KEYWORD) {
+ tmp = rcsget_text(*sp);
+
+ /* {strict ;} */
+ if (!strcmp(tmp, "strict")) {
+ rcsfile_setval(rf, RCSFILE_STRICT, tmp);
+ asserttoken(sp, SEMIC);
+ /* { comment {string}; } */
+ } else if (!strcmp(tmp, "comment")) {
+ token = rcslex(*sp);
+ if (token == STRING) {
+ comment = duptext(sp, NULL);
+ rcsfile_setval(rf, RCSFILE_COMMENT, comment);
+ free(comment);
+ }
+ asserttoken(sp, SEMIC);
+ /* { expand {string}; } */
+ } else if (!strcmp(tmp, "expand")) {
+ token = rcslex(*sp);
+ if (token == STRING) {
+ expand = duptext(sp, NULL);
+ rcsfile_setval(rf, RCSFILE_EXPAND, expand);
+ free(expand);
+ }
+ asserttoken(sp, SEMIC);
+ }
+ /* {newphrase }* */
+ token = rcslex(*sp);
+ while (token == ID) {
+ token = rcslex(*sp);
+ /* XXX: newphrases ignored */
+ while (token == ID || token == NUM || token == STRING ||
+ token == COLON) {
+ token = rcslex(*sp);
+ }
+ asserttoken(sp, SEMIC);
+ token = rcslex(*sp);
+ }
+ }
+ return (token);
+}
+
+/*
+ * Parse RCS deltas.
+ */
+static int
+parse_deltas(struct rcsfile *rf, yyscan_t *sp, int token)
+{
+ STAILQ_HEAD(, string) branchlist;
+ char *revnum, *revdate, *author, *state, *next;
+
+ /* In case we don't have deltas. */
+ if (token != NUM)
+ return (token);
+ do {
+ next = NULL;
+ state = NULL;
+
+ /* num */
+ assert(token == NUM);
+ revnum = duptext(sp, NULL);
+ /* date num; */
+ asserttoken(sp, KEYWORD);
+ asserttoken(sp, NUM);
+ revdate = duptext(sp, NULL);
+ asserttoken(sp, SEMIC);
+ /* author id; */
+ asserttoken(sp, KEYWORD);
+ asserttoken(sp, ID);
+ author = duptext(sp, NULL);
+ asserttoken(sp, SEMIC);
+ /* state {id}; */
+ asserttoken(sp, KEYWORD);
+ token = rcslex(*sp);
+ if (token == ID) {
+ state = duptext(sp, NULL);
+ token = rcslex(*sp);
+ }
+ assert(token == SEMIC);
+ /* branches {num}*; */
+ asserttoken(sp, KEYWORD);
+ token = rcslex(*sp);
+ STAILQ_INIT(&branchlist);
+ while (token == NUM)
+ token = rcslex(*sp);
+ assert(token == SEMIC);
+ /* next {num}; */
+ asserttoken(sp, KEYWORD);
+ token = rcslex(*sp);
+ if (token == NUM) {
+ next = duptext(sp, NULL);
+ token = rcslex(*sp);
+ }
+ assert(token == SEMIC);
+ /* {newphrase }* */
+ token = rcslex(*sp);
+ while (token == ID) {
+ token = rcslex(*sp);
+ /* XXX: newphrases ignored. */
+ while (token == ID || token == NUM || token == STRING ||
+ token == COLON) {
+ token = rcslex(*sp);
+ }
+ asserttoken(sp, SEMIC);
+ token = rcslex(*sp);
+ }
+ rcsfile_importdelta(rf, revnum, revdate, author, state, next);
+ free(revnum);
+ free(revdate);
+ free(author);
+ if (state != NULL)
+ free(state);
+ if (next != NULL)
+ free(next);
+ } while (token == NUM);
+
+ return (token);
+}
+
+/*
+ * Parse RCS deltatexts.
+ */
+static int
+parse_deltatexts(struct rcsfile *rf, yyscan_t *sp, int token)
+{
+ struct delta *d;
+ char *log, *revnum, *text;
+ int error, len;
+
+ error = 0;
+ /* In case we don't have deltatexts. */
+ if (token != NUM)
+ return (-1);
+ do {
+ /* num */
+ assert(token == NUM);
+ revnum = duptext(sp, NULL);
+ /* Get delta we're adding text to. */
+ d = rcsfile_getdelta(rf, revnum);
+ free(revnum);
+
+ /* log string */
+ asserttoken(sp, KEYWORD);
+ asserttoken(sp, STRING);
+ log = duptext(sp, &len);
+ error = rcsdelta_addlog(d, log, len);
+ free(log);
+ if (error)
+ return (-1);
+ /* { newphrase }* */
+ token = rcslex(*sp);
+ while (token == ID) {
+ token = rcslex(*sp);
+ /* XXX: newphrases ignored. */
+ while (token == ID || token == NUM || token == STRING ||
+ token == COLON) {
+ token = rcslex(*sp);
+ }
+ asserttoken(sp, SEMIC);
+ token = rcslex(*sp);
+ }
+ /* text string */
+ assert(token == KEYWORD);
+ asserttoken(sp, STRING);
+ text = duptext(sp, &len);
+ error = rcsdelta_addtext(d, text, len);
+ /*
+ * If this happens, something is wrong with the RCS file, and it
+ * should be resent.
+ */
+ free(text);
+ if (error)
+ return (-1);
+ token = rcslex(*sp);
+ } while (token == NUM);
+
+ return (0);
+}
diff --git a/usr.bin/csup/rcsparse.h b/usr.bin/csup/rcsparse.h
new file mode 100644
index 0000000..01b0156
--- /dev/null
+++ b/usr.bin/csup/rcsparse.h
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 2008-2009, Ulf Lilleengen <lulf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _RCSPARSE_H_
+#define _RCSPARSE_H_
+#define ID 0
+#define NUM 1
+#define KEYWORD 2
+#define KEYWORD_TWO 3
+#define STRING 4
+#define SEMIC 5
+#define COLON 6
+
+struct rcsfile;
+int rcsparse_run(struct rcsfile *, FILE *, int);
+#endif /* !_RCSPARSE_H_ */
diff --git a/usr.bin/csup/rcstokenizer.h b/usr.bin/csup/rcstokenizer.h
new file mode 100644
index 0000000..66ea724
--- /dev/null
+++ b/usr.bin/csup/rcstokenizer.h
@@ -0,0 +1,333 @@
+#ifndef rcsHEADER_H
+#define rcsHEADER_H 1
+#define rcsIN_HEADER 1
+
+#line 6 "rcstokenizer.h"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 35
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else /* ! __cplusplus */
+
+/* C99 requires __STDC__ to be defined as 1. */
+#if defined (__STDC__)
+
+#define YY_USE_CONST
+
+#endif /* defined (__STDC__) */
+#endif /* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+ are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ yy_size_t yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+void rcsrestart (FILE *input_file ,yyscan_t yyscanner );
+void rcs_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+YY_BUFFER_STATE rcs_create_buffer (FILE *file,int size ,yyscan_t yyscanner );
+void rcs_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void rcs_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void rcspush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+void rcspop_buffer_state (yyscan_t yyscanner );
+
+YY_BUFFER_STATE rcs_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner );
+YY_BUFFER_STATE rcs_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
+YY_BUFFER_STATE rcs_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner );
+
+void *rcsalloc (yy_size_t ,yyscan_t yyscanner );
+void *rcsrealloc (void *,yy_size_t ,yyscan_t yyscanner );
+void rcsfree (void * ,yyscan_t yyscanner );
+
+/* Begin user sect3 */
+
+#define rcswrap(n) 1
+#define YY_SKIP_YYWRAP
+
+#define yytext_ptr yytext_r
+
+#ifdef YY_HEADER_EXPORT_START_CONDITIONS
+#define INITIAL 0
+
+#endif
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+int rcslex_init (yyscan_t* scanner);
+
+int rcslex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
+
+/* Accessor methods to globals.
+ These are made visible to non-reentrant scanners for convenience. */
+
+int rcslex_destroy (yyscan_t yyscanner );
+
+int rcsget_debug (yyscan_t yyscanner );
+
+void rcsset_debug (int debug_flag ,yyscan_t yyscanner );
+
+YY_EXTRA_TYPE rcsget_extra (yyscan_t yyscanner );
+
+void rcsset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner );
+
+FILE *rcsget_in (yyscan_t yyscanner );
+
+void rcsset_in (FILE * in_str ,yyscan_t yyscanner );
+
+FILE *rcsget_out (yyscan_t yyscanner );
+
+void rcsset_out (FILE * out_str ,yyscan_t yyscanner );
+
+int rcsget_leng (yyscan_t yyscanner );
+
+char *rcsget_text (yyscan_t yyscanner );
+
+int rcsget_lineno (yyscan_t yyscanner );
+
+void rcsset_lineno (int line_number ,yyscan_t yyscanner );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int rcswrap (yyscan_t yyscanner );
+#else
+extern int rcswrap (yyscan_t yyscanner );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int rcslex (yyscan_t yyscanner);
+
+#define YY_DECL int rcslex (yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+#undef YY_NEW_FILE
+#undef YY_FLUSH_BUFFER
+#undef yy_set_bol
+#undef yy_new_buffer
+#undef yy_set_interactive
+#undef YY_DO_BEFORE_ACTION
+
+#ifdef YY_DECL_IS_OURS
+#undef YY_DECL_IS_OURS
+#undef YY_DECL
+#endif
+
+#line 73 "rcstokenizer.l"
+
+
+#line 332 "rcstokenizer.h"
+#undef rcsIN_HEADER
+#endif /* rcsHEADER_H */
diff --git a/usr.bin/csup/rcstokenizer.l b/usr.bin/csup/rcstokenizer.l
new file mode 100644
index 0000000..56f0f41
--- /dev/null
+++ b/usr.bin/csup/rcstokenizer.l
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 2007-2009, Ulf Lilleengen <lulf@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$
+ */
+
+/*
+ * This tokenizer must be generated by a lexxer with support for reentrancy.
+ */
+%{
+#include <string.h>
+
+#include "misc.h"
+#include "rcsparse.h"
+
+%}
+%option reentrant noyywrap
+%option header-file="rcstokenizer.h"
+
+everything (.|\n)*
+num [0-9\.]+
+whitespace [\t\n ]
+digit [0-9]
+idchar [^$,.:;\t\n ]
+string @([^@]|\n|"@@")*@
+keyword head|access|symbols|locks|comment|expand|strict|date|author|state|branches|next|desc|log|text
+keyword2 branch
+newline \n
+%%
+
+{keyword2} {
+ return (KEYWORD_TWO);
+}
+{keyword} {
+ return (KEYWORD);
+}
+{string} {
+ return (STRING);
+}
+{num} {
+ return (NUM);
+}
+{num}?{idchar}({idchar}|{num})* {
+/* This will use ID as both ID and SYM. Do extra checking elsewhere.*/
+ return (ID);
+}
+; { return (SEMIC); }
+: { return (COLON); }
+\n ;
+[ \t]+ ;
+%%
diff --git a/usr.bin/csup/rsyncfile.c b/usr.bin/csup/rsyncfile.c
new file mode 100644
index 0000000..7680bcc
--- /dev/null
+++ b/usr.bin/csup/rsyncfile.c
@@ -0,0 +1,223 @@
+/*-
+ * Copyright (c) 2008-2009, Ulf Lilleengen <lulf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "misc.h"
+#include "rsyncfile.h"
+
+#define MINBLOCKSIZE 1024
+#define MAXBLOCKSIZE (16 * 1024)
+#define RECEIVEBUFFERSIZE (15 * 1024)
+#define BLOCKINFOSIZE 26
+#define SEARCHREGION 10
+#define MAXBLOCKS (RECEIVEBUFFERSIZE / BLOCKINFOSIZE)
+
+#define CHAR_OFFSET 3
+#define RSUM_SIZE 9
+
+struct rsyncfile {
+ char *start;
+ char *buf;
+ char *end;
+ size_t blocksize;
+ size_t fsize;
+ int fd;
+
+ char *blockptr;
+ int blocknum;
+ char blockmd5[MD5_DIGEST_SIZE];
+ char rsumstr[RSUM_SIZE];
+ uint32_t rsum;
+};
+
+static size_t rsync_chooseblocksize(size_t);
+static uint32_t rsync_rollsum(char *, size_t);
+
+/* Open a file and initialize variable for rsync operation. */
+struct rsyncfile *
+rsync_open(char *path, size_t blocksize, int rdonly)
+{
+ struct rsyncfile *rf;
+ struct stat st;
+ int error;
+
+ rf = xmalloc(sizeof(*rf));
+ error = stat(path, &st);
+ if (error) {
+ free(rf);
+ return (NULL);
+ }
+ rf->fsize = st.st_size;
+
+ rf->fd = open(path, rdonly ? O_RDONLY : O_RDWR);
+ if (rf->fd < 0) {
+ free(rf);
+ return (NULL);
+ }
+ rf->buf = mmap(0, rf->fsize, PROT_READ, MAP_SHARED, rf->fd, 0);
+ if (rf->buf == MAP_FAILED) {
+ free(rf);
+ return (NULL);
+ }
+ rf->start = rf->buf;
+ rf->end = rf->buf + rf->fsize;
+ rf->blocksize = (blocksize == 0 ? rsync_chooseblocksize(rf->fsize) :
+ blocksize);
+ rf->blockptr = rf->buf;
+ rf->blocknum = 0;
+ return (rf);
+}
+
+/* Close and free all resources related to an rsync file transfer. */
+int
+rsync_close(struct rsyncfile *rf)
+{
+ int error;
+
+ error = munmap(rf->buf, rf->fsize);
+ if (error)
+ return (error);
+ close(rf->fd);
+ free(rf);
+ return (0);
+}
+
+/*
+ * Choose the most appropriate block size for an rsync transfer. Modeled
+ * algorithm after cvsup.
+ */
+static size_t
+rsync_chooseblocksize(size_t fsize)
+{
+ size_t bestrem, blocksize, bs, hisearch, losearch, rem;
+
+ blocksize = fsize / MAXBLOCKS;
+ losearch = blocksize - SEARCHREGION;
+ hisearch = blocksize + SEARCHREGION;
+
+ if (losearch < MINBLOCKSIZE) {
+ losearch = MINBLOCKSIZE;
+ hisearch = losearch + (2 * SEARCHREGION);
+ } else if (hisearch > MAXBLOCKSIZE) {
+ hisearch = MAXBLOCKSIZE;
+ losearch = hisearch - (2 * SEARCHREGION);
+ }
+
+ bestrem = MAXBLOCKSIZE;
+ for (bs = losearch; bs <= hisearch; bs++) {
+ rem = fsize % bs;
+ if (rem < bestrem) {
+ bestrem = rem;
+ blocksize = bs;
+ }
+ }
+ return (bestrem);
+}
+
+/* Get the next rsync block of a file. */
+int
+rsync_nextblock(struct rsyncfile *rf)
+{
+ MD5_CTX ctx;
+ size_t blocksize;
+
+ if (rf->blockptr >= rf->end)
+ return (0);
+ blocksize = min((size_t)(rf->end - rf->blockptr), rf->blocksize);
+ /* Calculate MD5 of the block. */
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, rf->blockptr, blocksize);
+ MD5_End(rf->blockmd5, &ctx);
+
+ rf->rsum = rsync_rollsum(rf->blockptr, blocksize);
+ snprintf(rf->rsumstr, RSUM_SIZE, "%x", rf->rsum);
+ rf->blocknum++;
+ rf->blockptr += blocksize;
+ return (1);
+}
+
+/* Get the rolling checksum of a file. */
+static uint32_t
+rsync_rollsum(char *buf, size_t len)
+{
+ uint32_t a, b;
+ char *ptr, *limit;
+
+ a = b = 0;
+ ptr = buf;
+ limit = buf + len;
+
+ while (ptr < limit) {
+ a += *ptr + CHAR_OFFSET;
+ b += a;
+ ptr++;
+ }
+ return ((b << 16) | a);
+}
+
+/* Get running sum so far. */
+char *
+rsync_rsum(struct rsyncfile *rf)
+{
+
+ return (rf->rsumstr);
+}
+
+/* Get MD5 of current block. */
+char *
+rsync_blockmd5(struct rsyncfile *rf)
+{
+
+ return (rf->blockmd5);
+}
+
+/* Accessor for blocksize. */
+size_t
+rsync_blocksize(struct rsyncfile *rf)
+{
+
+ return (rf->blocksize);
+}
+
+/* Accessor for filesize. */
+size_t
+rsync_filesize(struct rsyncfile *rf)
+{
+
+ return (rf->fsize);
+}
diff --git a/usr.bin/csup/rsyncfile.h b/usr.bin/csup/rsyncfile.h
new file mode 100644
index 0000000..2c41a28
--- /dev/null
+++ b/usr.bin/csup/rsyncfile.h
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 2008-2009, Ulf Lilleengen <lulf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _RSYNCFILE_H_
+#define _RSYNCFILE_H_
+
+struct rsyncfile;
+struct rsyncfile *rsync_open(char *, size_t, int);
+int rsync_nextblock(struct rsyncfile *);
+char *rsync_rsum(struct rsyncfile *);
+char *rsync_blockmd5(struct rsyncfile *);
+int rsync_close(struct rsyncfile *);
+size_t rsync_blocksize(struct rsyncfile *);
+size_t rsync_filesize(struct rsyncfile *);
+
+#endif /* !_RSYNCFILE_H_ */
diff --git a/usr.bin/csup/status.c b/usr.bin/csup/status.c
new file mode 100644
index 0000000..3482e8e
--- /dev/null
+++ b/usr.bin/csup/status.c
@@ -0,0 +1,874 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "fattr.h"
+#include "misc.h"
+#include "pathcomp.h"
+#include "proto.h"
+#include "queue.h"
+#include "status.h"
+#include "stream.h"
+
+#define STATUS_VERSION 5
+
+/* Internal error codes. */
+#define STATUS_ERR_READ (-1)
+#define STATUS_ERR_WRITE (-2)
+#define STATUS_ERR_PARSE (-3)
+#define STATUS_ERR_UNSORTED (-4)
+#define STATUS_ERR_TRUNC (-5)
+#define STATUS_ERR_BOGUS_DIRUP (-6)
+#define STATUS_ERR_BAD_TYPE (-7)
+#define STATUS_ERR_RENAME (-8)
+
+static struct status *status_new(char *, time_t, struct stream *);
+static struct statusrec *status_rd(struct status *);
+static struct statusrec *status_rdraw(struct status *, char **);
+static int status_wr(struct status *, struct statusrec *);
+static int status_wrraw(struct status *, struct statusrec *,
+ char *);
+static struct status *status_fromrd(char *, struct stream *);
+static struct status *status_fromnull(char *);
+static void status_free(struct status *);
+
+static void statusrec_init(struct statusrec *);
+static void statusrec_fini(struct statusrec *);
+static int statusrec_cook(struct statusrec *, char *);
+static int statusrec_cmp(struct statusrec *, struct statusrec *);
+
+struct status {
+ char *path;
+ char *tempfile;
+ int error;
+ int suberror;
+ struct pathcomp *pc;
+ struct statusrec buf;
+ struct statusrec *previous;
+ struct statusrec *current;
+ struct stream *rd;
+ struct stream *wr;
+ time_t scantime;
+ int eof;
+ int linenum;
+ int depth;
+ int dirty;
+};
+
+static void
+statusrec_init(struct statusrec *sr)
+{
+
+ memset(sr, 0, sizeof(*sr));
+}
+
+static int
+statusrec_cook(struct statusrec *sr, char *line)
+{
+ char *clientattr, *serverattr;
+
+ switch (sr->sr_type) {
+ case SR_FILEDEAD:
+ case SR_FILELIVE:
+ 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;
+ 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 'A':
+ sr.sr_type = SR_FILELIVE;
+ break;
+ 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;
+ case 'V':
+ sr.sr_type = SR_FILELIVE;
+ break;
+ case 'v':
+ sr.sr_type = SR_FILEDEAD;
+ 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;
+ case SR_FILELIVE:
+ error = proto_printf(st->wr, "V %s %f\n", sr->sr_file,
+ sr->sr_clientattr);
+ break;
+ case SR_FILEDEAD:
+ error = proto_printf(st->wr, "v %s %f\n", sr->sr_file,
+ sr->sr_clientattr);
+ 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;
+ case SR_FILELIVE:
+ cmd = 'V';
+ break;
+ case SR_FILEDEAD:
+ cmd = 'v';
+ break;
+ default:
+ assert(0);
+ return (-1);
+ }
+ if (sr->sr_type == SR_DIRDOWN)
+ error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file);
+ else
+ error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file,
+ line);
+ if (error) {
+ st->error = STATUS_ERR_WRITE;
+ st->suberror = errno;
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+statusrec_fini(struct statusrec *sr)
+{
+
+ fattr_free(sr->sr_serverattr);
+ fattr_free(sr->sr_clientattr);
+ free(sr->sr_file);
+}
+
+static int
+statusrec_cmp(struct statusrec *a, struct statusrec *b)
+{
+ size_t lena, lenb;
+
+ if (a->sr_type == SR_DIRUP || b->sr_type == SR_DIRUP) {
+ lena = strlen(a->sr_file);
+ lenb = strlen(b->sr_file);
+ if (a->sr_type == SR_DIRUP &&
+ ((lena < lenb && b->sr_file[lena] == '/') || lena == lenb)
+ && strncmp(a->sr_file, b->sr_file, lena) == 0)
+ return (1);
+ if (b->sr_type == SR_DIRUP &&
+ ((lenb < lena && a->sr_file[lenb] == '/') || lenb == lena)
+ && strncmp(a->sr_file, b->sr_file, lenb) == 0)
+ return (-1);
+ }
+ return (pathcmp(a->sr_file, b->sr_file));
+}
+
+static struct status *
+status_new(char *path, time_t scantime, struct stream *file)
+{
+ struct status *st;
+
+ st = xmalloc(sizeof(struct status));
+ st->path = path;
+ st->error = 0;
+ st->suberror = 0;
+ st->tempfile = NULL;
+ st->scantime = scantime;
+ st->rd = file;
+ st->wr = NULL;
+ st->previous = NULL;
+ st->current = NULL;
+ st->dirty = 0;
+ st->eof = 0;
+ st->linenum = 0;
+ st->depth = 0;
+ st->pc = pathcomp_new();
+ statusrec_init(&st->buf);
+ return (st);
+}
+
+static void
+status_free(struct status *st)
+{
+
+ if (st->previous != NULL)
+ statusrec_fini(st->previous);
+ if (st->rd != NULL)
+ stream_close(st->rd);
+ if (st->wr != NULL)
+ stream_close(st->wr);
+ if (st->tempfile != NULL)
+ free(st->tempfile);
+ free(st->path);
+ pathcomp_free(st->pc);
+ free(st);
+}
+
+static struct status *
+status_fromrd(char *path, struct stream *file)
+{
+ struct status *st;
+ char *id, *line;
+ time_t scantime;
+ int error, ver;
+
+ /* Get the first line of the file and validate it. */
+ line = stream_getln(file, NULL);
+ if (line == NULL) {
+ stream_close(file);
+ return (NULL);
+ }
+ id = proto_get_ascii(&line);
+ error = proto_get_int(&line, &ver, 10);
+ if (error) {
+ stream_close(file);
+ return (NULL);
+ }
+ error = proto_get_time(&line, &scantime);
+ if (error || line != NULL) {
+ stream_close(file);
+ return (NULL);
+ }
+
+ if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) {
+ stream_close(file);
+ return (NULL);
+ }
+
+ st = status_new(path, scantime, file);
+ st->linenum = 1;
+ return (st);
+}
+
+static struct status *
+status_fromnull(char *path)
+{
+ struct status *st;
+
+ st = status_new(path, -1, NULL);
+ st->eof = 1;
+ return (st);
+}
+
+/*
+ * Open the status file. If scantime is not -1, the file is opened
+ * for updating, otherwise, it is opened read-only. If the status file
+ * couldn't be opened, NULL is returned and errmsg is set to the error
+ * message.
+ */
+struct status *
+status_open(struct coll *coll, time_t scantime, char **errmsg)
+{
+ struct status *st;
+ struct stream *file;
+ struct fattr *fa;
+ char *destpath, *path;
+ int error, rv;
+
+ path = coll_statuspath(coll);
+ file = stream_open_file(path, O_RDONLY);
+ if (file == NULL) {
+ if (errno != ENOENT) {
+ xasprintf(errmsg, "Could not open \"%s\": %s\n",
+ path, strerror(errno));
+ free(path);
+ return (NULL);
+ }
+ st = status_fromnull(path);
+ } else {
+ st = status_fromrd(path, file);
+ if (st == NULL) {
+ xasprintf(errmsg, "Error in \"%s\": Bad header line",
+ path);
+ free(path);
+ return (NULL);
+ }
+ }
+
+ if (scantime != -1) {
+ /* Open for writing too. */
+ xasprintf(&destpath, "%s/%s/%s/", coll->co_base,
+ coll->co_colldir, coll->co_name);
+ st->tempfile = tempname(destpath);
+ if (mkdirhier(destpath, coll->co_umask) != 0) {
+ xasprintf(errmsg, "Cannot create directories leading "
+ "to \"%s\": %s", destpath, strerror(errno));
+ free(destpath);
+ status_free(st);
+ return (NULL);
+ }
+ free(destpath);
+ st->wr = stream_open_file(st->tempfile,
+ O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (st->wr == NULL) {
+ xasprintf(errmsg, "Cannot create \"%s\": %s",
+ st->tempfile, strerror(errno));
+ status_free(st);
+ return (NULL);
+ }
+ fa = fattr_new(FT_FILE, -1);
+ fattr_mergedefault(fa);
+ fattr_umask(fa, coll->co_umask);
+ rv = fattr_install(fa, st->tempfile, NULL);
+ fattr_free(fa);
+ if (rv == -1) {
+ xasprintf(errmsg,
+ "Cannot set attributes for \"%s\": %s",
+ st->tempfile, strerror(errno));
+ status_free(st);
+ return (NULL);
+ }
+ if (scantime != st->scantime)
+ st->dirty = 1;
+ error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION,
+ scantime);
+ if (error) {
+ st->error = STATUS_ERR_WRITE;
+ st->suberror = errno;
+ *errmsg = status_errmsg(st);
+ status_free(st);
+ return (NULL);
+ }
+ }
+ return (st);
+}
+
+/*
+ * Get an entry from the status file. If name is NULL, the next entry
+ * is returned. If name is not NULL, the entry matching this name is
+ * returned, or NULL if it couldn't be found. If deleteto is set to 1,
+ * all the entries read from the status file while looking for the
+ * given name are deleted.
+ */
+int
+status_get(struct status *st, char *name, int isdirup, int deleteto,
+ struct statusrec **psr)
+{
+ struct statusrec key;
+ struct statusrec *sr;
+ char *line;
+ int c, error;
+
+ if (st->eof)
+ return (0);
+
+ if (st->error)
+ return (-1);
+
+ if (name == NULL) {
+ sr = status_rd(st);
+ if (sr == NULL) {
+ if (st->error)
+ return (-1);
+ return (0);
+ }
+ *psr = sr;
+ return (1);
+ }
+
+ if (st->current != NULL) {
+ sr = st->current;
+ st->current = NULL;
+ } else {
+ sr = status_rd(st);
+ if (sr == NULL) {
+ if (st->error)
+ return (-1);
+ return (0);
+ }
+ }
+
+ key.sr_file = name;
+ if (isdirup)
+ key.sr_type = SR_DIRUP;
+ else
+ key.sr_type = SR_CHECKOUTLIVE;
+
+ c = statusrec_cmp(sr, &key);
+ if (c < 0) {
+ if (st->wr != NULL && !deleteto) {
+ error = status_wr(st, sr);
+ if (error)
+ return (-1);
+ }
+ /* Loop until we find the good entry. */
+ for (;;) {
+ sr = status_rdraw(st, &line);
+ if (sr == NULL) {
+ if (st->error)
+ return (-1);
+ return (0);
+ }
+ c = statusrec_cmp(sr, &key);
+ if (c >= 0)
+ break;
+ if (st->wr != NULL && !deleteto) {
+ error = status_wrraw(st, sr, line);
+ if (error)
+ return (-1);
+ }
+ }
+ error = statusrec_cook(sr, line);
+ if (error) {
+ st->error = STATUS_ERR_PARSE;
+ return (-1);
+ }
+ }
+ st->current = sr;
+ if (c != 0)
+ return (0);
+ *psr = sr;
+ return (1);
+}
+
+/*
+ * Put this entry into the status file. If an entry with the same name
+ * existed in the status file, it is replaced by this one, otherwise,
+ * the entry is added to the status file.
+ */
+int
+status_put(struct status *st, struct statusrec *sr)
+{
+ struct statusrec *old;
+ int error, ret;
+
+ ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old);
+ if (ret == -1)
+ return (-1);
+ if (ret) {
+ if (old->sr_type == SR_DIRDOWN) {
+ /* DirUp should never match DirDown */
+ assert(old->sr_type != SR_DIRUP);
+ if (sr->sr_type == SR_CHECKOUTLIVE ||
+ sr->sr_type == SR_CHECKOUTDEAD) {
+ /* We are replacing a directory with a file.
+ Delete all entries inside the directory we
+ are replacing. */
+ ret = status_get(st, sr->sr_file, 1, 1, &old);
+ if (ret == -1)
+ return (-1);
+ assert(ret);
+ }
+ } else
+ st->current = NULL;
+ }
+ st->dirty = 1;
+ error = status_wr(st, sr);
+ if (error)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Delete the specified entry from the status file.
+ */
+int
+status_delete(struct status *st, char *name, int isdirup)
+{
+ struct statusrec *sr;
+ int ret;
+
+ ret = status_get(st, name, isdirup, 0, &sr);
+ if (ret == -1)
+ return (-1);
+ if (ret) {
+ st->current = NULL;
+ st->dirty = 1;
+ }
+ return (0);
+}
+
+/*
+ * Check whether we hit the end of file.
+ */
+int
+status_eof(struct status *st)
+{
+
+ return (st->eof);
+}
+
+/*
+ * Returns the error message if there was an error, otherwise returns
+ * NULL. The error message is allocated dynamically and needs to be
+ * freed by the caller after use.
+ */
+char *
+status_errmsg(struct status *st)
+{
+ char *errmsg;
+
+ if (!st->error)
+ return (NULL);
+ switch (st->error) {
+ case STATUS_ERR_READ:
+ xasprintf(&errmsg, "Read failure on \"%s\": %s",
+ st->path, strerror(st->suberror));
+ break;
+ case STATUS_ERR_WRITE:
+ xasprintf(&errmsg, "Write failure on \"%s\": %s",
+ st->tempfile, strerror(st->suberror));
+ break;
+ case STATUS_ERR_PARSE:
+ xasprintf(&errmsg, "Error in \"%s\": %d: "
+ "Could not parse status record", st->path, st->linenum);
+ break;
+ case STATUS_ERR_UNSORTED:
+ xasprintf(&errmsg, "Error in \"%s\": %d: "
+ "File is not sorted properly", st->path, st->linenum);
+ break;
+ case STATUS_ERR_TRUNC:
+ xasprintf(&errmsg, "Error in \"%s\": "
+ "File is truncated", st->path);
+ break;
+ case STATUS_ERR_BOGUS_DIRUP:
+ xasprintf(&errmsg, "Error in \"%s\": %d: "
+ "\"U\" entry has no matching \"D\"", st->path, st->linenum);
+ break;
+ case STATUS_ERR_BAD_TYPE:
+ xasprintf(&errmsg, "Error in \"%s\": %d: "
+ "Invalid file type \"%c\"", st->path, st->linenum,
+ st->suberror);
+ break;
+ case STATUS_ERR_RENAME:
+ xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s",
+ st->tempfile, st->path, strerror(st->suberror));
+ break;
+ default:
+ assert(0);
+ return (NULL);
+ }
+ return (errmsg);
+}
+
+/*
+ * Close the status file and free any resource associated with it.
+ * It is OK to pass NULL for errmsg only if the status file was
+ * opened read-only. If it wasn't opened read-only, status_close()
+ * can produce an error and errmsg is not allowed to be NULL. If
+ * there was no errors, errmsg is set to NULL.
+ */
+void
+status_close(struct status *st, char **errmsg)
+{
+ struct statusrec *sr;
+ char *line, *name;
+ int error, type;
+
+ if (st->wr != NULL) {
+ if (st->dirty) {
+ if (st->current != NULL) {
+ error = status_wr(st, st->current);
+ if (error) {
+ *errmsg = status_errmsg(st);
+ goto bad;
+ }
+ st->current = NULL;
+ }
+ /* Copy the rest of the file. */
+ while ((sr = status_rdraw(st, &line)) != NULL) {
+ error = status_wrraw(st, sr, line);
+ if (error) {
+ *errmsg = status_errmsg(st);
+ goto bad;
+ }
+ }
+ if (st->error) {
+ *errmsg = status_errmsg(st);
+ goto bad;
+ }
+
+ /* Close off all the open directories. */
+ pathcomp_finish(st->pc);
+ while (pathcomp_get(st->pc, &type, &name)) {
+ assert(type == PC_DIRUP);
+ error = proto_printf(st->wr, "U %s %f\n",
+ name, fattr_bogus);
+ if (error) {
+ st->error = STATUS_ERR_WRITE;
+ st->suberror = errno;
+ *errmsg = status_errmsg(st);
+ goto bad;
+ }
+ }
+
+ /* Rename tempfile. */
+ error = rename(st->tempfile, st->path);
+ if (error) {
+ st->error = STATUS_ERR_RENAME;
+ st->suberror = errno;
+ *errmsg = status_errmsg(st);
+ goto bad;
+ }
+ } else {
+ /* Just discard the tempfile. */
+ unlink(st->tempfile);
+ }
+ *errmsg = NULL;
+ }
+ status_free(st);
+ return;
+bad:
+ status_free(st);
+}
diff --git a/usr.bin/csup/status.h b/usr.bin/csup/status.h
new file mode 100644
index 0000000..86efdda
--- /dev/null
+++ b/usr.bin/csup/status.h
@@ -0,0 +1,72 @@
+/*-
+ * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _STATUS_H_
+#define _STATUS_H_
+
+#include <time.h>
+
+struct coll;
+struct fattr;
+struct status;
+
+#define SR_DIRDOWN 0
+#define SR_CHECKOUTLIVE 1
+#define SR_CHECKOUTDEAD 2
+#define SR_FILELIVE 3
+#define SR_FILEDEAD 4
+#define SR_DIRUP 5
+
+struct statusrec {
+ int sr_type;
+ char *sr_file;
+ char *sr_tag;
+ char *sr_date;
+ char *sr_revnum;
+ char *sr_revdate;
+
+ /*
+ * "clientrttr" contains the attributes of the client's file if there
+ * is one. "serverattr" contains the attributes of the corresponding
+ * file on the server. In CVS mode, these are identical. But in
+ * checkout mode, "clientattr" represents the checked-out file while
+ * "serverattr" represents the corresponding RCS file on the server.
+ */
+ struct fattr *sr_serverattr;
+ struct fattr *sr_clientattr;
+};
+
+struct status *status_open(struct coll *, time_t, char **);
+int status_get(struct status *, char *, int, int,
+ struct statusrec **);
+int status_put(struct status *, struct statusrec *);
+int status_eof(struct status *);
+char *status_errmsg(struct status *);
+int status_delete(struct status *, char *, int);
+void status_close(struct status *, char **);
+
+#endif /* !_STATUS_H_ */
diff --git a/usr.bin/csup/stream.c b/usr.bin/csup/stream.c
new file mode 100644
index 0000000..aa229b4
--- /dev/null
+++ b/usr.bin/csup/stream.c
@@ -0,0 +1,1303 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <zlib.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "misc.h"
+#include "stream.h"
+
+/*
+ * Simple stream API to make my life easier. If the fgetln() and
+ * funopen() functions were standard and if funopen() wasn't using
+ * wrong types for the function pointers, I could have just used
+ * stdio, but life sucks.
+ *
+ * For now, streams are always block-buffered.
+ */
+
+/*
+ * Try to quiet warnings as much as possible with GCC while staying
+ * compatible with other compilers.
+ */
+#ifndef __unused
+#if defined(__GNUC__) && (__GNUC__ > 2 || __GNUC__ == 2 && __GNUC_MINOR__ >= 7)
+#define __unused __attribute__((__unused__))
+#else
+#define __unused
+#endif
+#endif
+
+/*
+ * Flags passed to the flush methods.
+ *
+ * STREAM_FLUSH_CLOSING is passed during the last flush call before
+ * closing a stream. This allows the zlib filter to emit the EOF
+ * marker as appropriate. In all other cases, STREAM_FLUSH_NORMAL
+ * should be passed.
+ *
+ * These flags are completely unused in the default flush method,
+ * but they are very important for the flush method of the zlib
+ * filter.
+ */
+typedef enum {
+ STREAM_FLUSH_NORMAL,
+ STREAM_FLUSH_CLOSING
+} stream_flush_t;
+
+/*
+ * This is because buf_new() will always allocate size + 1 bytes,
+ * so our buffer sizes will still be power of 2 values.
+ */
+#define STREAM_BUFSIZ 1023
+
+struct buf {
+ char *buf;
+ size_t size;
+ size_t in;
+ size_t off;
+};
+
+struct stream {
+ void *cookie;
+ int fd;
+ int buf;
+ 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 void buf_more(struct buf *, size_t);
+static void buf_less(struct buf *, size_t);
+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;
+ char lastc;
+#define PRINT 1
+#define WS 2
+#define STRING 3
+#define SEEN 4
+ int state;
+};
+
+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);
+static int md5rcsfilter_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
+ },
+ {
+ STREAM_FILTER_MD5RCS,
+ md5filter_init,
+ md5filter_fini,
+ md5filter_fill,
+ md5rcsfilter_flush
+ }
+
+};
+
+
+/* Create a new buffer. */
+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);
+ memset(buf->buf, 0, 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. */
+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->buf = 0;
+ 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);
+}
+
+/* Associate a buf with a stream. */
+struct stream *
+stream_open_buf(struct buf *b)
+{
+ struct stream *stream;
+
+ stream = stream_new(stream_read_buf, stream_append_buf, stream_close_buf);
+ stream->cookie = b;
+ stream->buf = 1;
+ b->in = 0;
+ return (stream);
+}
+
+/*
+ * Truncate a buffer, just decrease offset pointer.
+ * XXX: this can be dangerous if not used correctly.
+ */
+void
+stream_truncate_buf(struct buf *b, off_t off)
+{
+ b->off += off;
+}
+
+/* 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 character buffers. */
+ssize_t
+stream_read_buf(void *cookie, void *buf, size_t size)
+{
+ struct buf *b;
+ size_t avail;
+
+ /* Use in to be read offset. */
+ b = (struct buf *)cookie;
+ /* Just return what we have if the request is to large. */
+ avail = b->off - b->in;
+ if (avail < size) {
+ memcpy(buf, (b->buf + b->in), avail);
+ b->in += avail;
+ return (avail);
+ }
+ memcpy(buf, (b->buf + b->in), size);
+ b->in += size;
+ return (size);
+}
+
+/* Convenience write function for appending character buffers. */
+ssize_t
+stream_append_buf(void *cookie, const void *buf, size_t size)
+{
+ struct buf *b;
+ size_t avail;
+
+ /* Use off to be write offset. */
+ b = (struct buf *)cookie;
+
+ avail = b->size - b->off;
+ if (size > avail)
+ buf_grow(b, b->size + size);
+ memcpy((b->buf + b->off), buf, size);
+ b->off += size;
+ b->buf[b->off] = '\0';
+ return (size);
+}
+
+/* Convenience close function for freeing character buffers. */
+int
+stream_close_buf(void *cookie)
+{
+ void *data;
+
+ data = cookie;
+ /* Basically a NOP. */
+ return (0);
+}
+
+/* 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);
+}
+
+/* A blocking stream_read call. */
+ssize_t
+stream_read_blocking(struct stream *stream, void *buf, size_t size)
+{
+ struct buf *rdbuf;
+ ssize_t ret;
+ size_t n;
+
+ rdbuf = stream->rdbuf;
+ while (buf_count(rdbuf) <= size) {
+ ret = stream_fill(stream);
+ if (ret <= 0)
+ return (-1);
+ }
+ /* XXX: Should be at least size bytes in the buffer, right? */
+ /* Just do this to make sure. */
+ 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->buf) {
+ stream_truncate_buf(stream->cookie, off);
+ return (0);
+ }
+ 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;
+ mf->lastc = ';';
+ mf->state = PRINT;
+ 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);
+}
+
+/* MD5 flush for RCS, where whitespaces are omitted. */
+static int
+md5rcsfilter_flush(struct stream *stream, struct buf *buf, stream_flush_t how)
+{
+ struct md5filter *mf;
+ char *ptr, *end;
+ char *start;
+ char space[2];
+ int error;
+
+ mf = stream->fdata;
+ space[0] = ' ';
+ space[1] = '\0';
+ ptr = buf->buf + buf->off;
+ end = buf->buf + buf->off + buf->in;
+
+#define IS_WS(var) ((var) == ' ' || (var) == '\n' || (var) == '\t' || \
+ (var) == '\010' || (var) == '\013' || (var) == '\f' || \
+ (var) == '\r')
+
+#define IS_SPECIAL(var) ((var) == '$' || (var) == ',' || (var) == ':' || \
+ (var) == ';' || (var) == '@')
+
+#define IS_PRINT(var) (!IS_WS(var) && (var) != '@')
+
+ /* XXX: We can do better than this state machine. */
+ while (ptr < end) {
+ switch (mf->state) {
+ /* Outside RCS statements. */
+ case PRINT:
+ start = ptr;
+ while (ptr < end && IS_PRINT(*ptr)) {
+ mf->lastc = *ptr;
+ ptr++;
+ }
+ MD5_Update(&mf->ctx, start, (ptr - start));
+ if (ptr < end) {
+ if (*ptr == '@') {
+ MD5_Update(&mf->ctx, ptr, 1);
+ ptr++;
+ mf->state = STRING;
+ } else {
+ mf->state = WS;
+ }
+ }
+ break;
+ case WS:
+ while (ptr < end && IS_WS(*ptr)) {
+ ptr++;
+ }
+ if (ptr < end) {
+ if (*ptr == '@') {
+ if (mf->lastc == '@') {
+ MD5_Update(&mf->ctx,
+ space, 1);
+ }
+ MD5_Update(&mf->ctx, ptr, 1);
+ ptr++;
+ mf->state = STRING;
+ } else {
+ if (!IS_SPECIAL(*ptr) &&
+ !IS_SPECIAL(mf->lastc)) {
+ MD5_Update(&mf->ctx,
+ space, 1);
+ }
+ mf->state = PRINT;
+ }
+ }
+ break;
+ case STRING:
+ start = ptr;
+ while (ptr < end && *ptr != '@') {
+ ptr++;
+ }
+ MD5_Update(&mf->ctx, start, (ptr - start));
+ if (ptr < end) {
+ MD5_Update(&mf->ctx, ptr, 1);
+ ptr++;
+ mf->state = SEEN;
+ }
+ break;
+ case SEEN:
+ if (*ptr == '@') {
+ MD5_Update(&mf->ctx, ptr, 1);
+ ptr++;
+ mf->state = STRING;
+ } else if(IS_WS(*ptr)) {
+ mf->lastc = '@';
+ mf->state = WS;
+ } else {
+ mf->state = PRINT;
+ }
+ break;
+ default:
+ err(1, "Invalid state");
+ break;
+ }
+ }
+
+ error = stream_flush_default(stream, buf, how);
+ return (error);
+}
+
diff --git a/usr.bin/csup/stream.h b/usr.bin/csup/stream.h
new file mode 100644
index 0000000..2128635
--- /dev/null
+++ b/usr.bin/csup/stream.h
@@ -0,0 +1,84 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _STREAM_H_
+#define _STREAM_H_
+
+#include "misc.h"
+
+/* Stream filters. */
+typedef enum {
+ STREAM_FILTER_NULL,
+ STREAM_FILTER_ZLIB,
+ STREAM_FILTER_MD5,
+ STREAM_FILTER_MD5RCS
+} stream_filter_t;
+
+struct stream;
+struct buf;
+
+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;
+
+/* Convenience functions for handling character buffers. */
+stream_readfn_t stream_read_buf;
+stream_writefn_t stream_append_buf;
+stream_closefn_t stream_close_buf;
+
+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_buf(struct buf *);
+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_read_blocking(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);
+void stream_truncate_buf(struct buf *, 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 *);
+
+struct buf *buf_new(size_t);
+void buf_free(struct buf *);
+#endif /* !_STREAM_H_ */
diff --git a/usr.bin/csup/threads.c b/usr.bin/csup/threads.c
new file mode 100644
index 0000000..46a9860
--- /dev/null
+++ b/usr.bin/csup/threads.c
@@ -0,0 +1,176 @@
+/*-
+ * Copyright (c) 2004-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include "misc.h"
+#include "queue.h"
+#include "threads.h"
+
+/*
+ * This API is a wrapper around the pthread(3) API, which mainly
+ * allows me to wait for multiple threads to exit. We use a
+ * condition variable to signal a thread's death. All threads
+ * created with this API have a common entry/exit point, so we
+ * don't need to add any code in the threads themselves.
+ */
+
+/* Structure describing a thread. */
+struct thread {
+ pthread_t thread;
+ void *(*start)(void *);
+ void *data;
+ struct threads *threads;
+ LIST_ENTRY(thread) runlist;
+ STAILQ_ENTRY(thread) deadlist;
+};
+
+/* A set of threads. */
+struct threads {
+ pthread_mutex_t threads_mtx;
+ pthread_cond_t thread_exited;
+ LIST_HEAD(, thread) threads_running;
+ STAILQ_HEAD(, thread) threads_dead;
+};
+
+static void *thread_start(void *); /* Common entry point for threads. */
+
+static void threads_lock(struct threads *);
+static void threads_unlock(struct threads *);
+
+static void
+threads_lock(struct threads *tds)
+{
+ int error;
+
+ error = pthread_mutex_lock(&tds->threads_mtx);
+ assert(!error);
+}
+
+static void
+threads_unlock(struct threads *tds)
+{
+ int error;
+
+ error = pthread_mutex_unlock(&tds->threads_mtx);
+ assert(!error);
+}
+
+/* Create a new set of threads. */
+struct threads *
+threads_new(void)
+{
+ struct threads *tds;
+
+ tds = xmalloc(sizeof(struct threads));
+ pthread_mutex_init(&tds->threads_mtx, NULL);
+ pthread_cond_init(&tds->thread_exited, NULL);
+ LIST_INIT(&tds->threads_running);
+ STAILQ_INIT(&tds->threads_dead);
+ return (tds);
+}
+
+/* Create a new thread in this set. */
+void
+threads_create(struct threads *tds, void *(*start)(void *), void *data)
+{
+ pthread_attr_t attr;
+ struct thread *td;
+ int error;
+
+ td = xmalloc(sizeof(struct thread));
+ td->threads = tds;
+ td->start = start;
+ td->data = data;
+ /* We don't use pthread_join() to wait for the threads to finish. */
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ threads_lock(tds);
+ error = pthread_create(&td->thread, &attr, thread_start, td);
+ if (error)
+ err(1, "pthread_create");
+ LIST_INSERT_HEAD(&tds->threads_running, td, runlist);
+ threads_unlock(tds);
+}
+
+/* Wait for a thread in the set to exit, and return its data pointer. */
+void *
+threads_wait(struct threads *tds)
+{
+ struct thread *td;
+ void *data;
+
+ threads_lock(tds);
+ while (STAILQ_EMPTY(&tds->threads_dead)) {
+ assert(!LIST_EMPTY(&tds->threads_running));
+ pthread_cond_wait(&tds->thread_exited, &tds->threads_mtx);
+ }
+ td = STAILQ_FIRST(&tds->threads_dead);
+ STAILQ_REMOVE_HEAD(&tds->threads_dead, deadlist);
+ threads_unlock(tds);
+ data = td->data;
+ free(td);
+ return (data);
+}
+
+/* Free a threads set. */
+void
+threads_free(struct threads *tds)
+{
+
+ assert(LIST_EMPTY(&tds->threads_running));
+ assert(STAILQ_EMPTY(&tds->threads_dead));
+ pthread_cond_destroy(&tds->thread_exited);
+ pthread_mutex_destroy(&tds->threads_mtx);
+ free(tds);
+}
+
+/*
+ * Common entry point for threads. This just calls the real start
+ * routine, and then signals the thread's death, after having
+ * removed the thread from the list.
+ */
+static void *
+thread_start(void *data)
+{
+ struct threads *tds;
+ struct thread *td;
+
+ td = data;
+ tds = td->threads;
+ td->start(td->data);
+ threads_lock(tds);
+ LIST_REMOVE(td, runlist);
+ STAILQ_INSERT_TAIL(&tds->threads_dead, td, deadlist);
+ pthread_cond_signal(&tds->thread_exited);
+ threads_unlock(tds);
+ return (NULL);
+}
diff --git a/usr.bin/csup/threads.h b/usr.bin/csup/threads.h
new file mode 100644
index 0000000..66153ce
--- /dev/null
+++ b/usr.bin/csup/threads.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _THREADS_H_
+#define _THREADS_H_
+
+struct threads;
+
+struct threads *threads_new(void);
+void threads_create(struct threads *, void *(*)(void *), void *);
+void *threads_wait(struct threads *);
+void threads_free(struct threads *);
+
+#endif /* !_THREADS_H_ */
diff --git a/usr.bin/csup/token.h b/usr.bin/csup/token.h
new file mode 100644
index 0000000..0dc3ec0
--- /dev/null
+++ b/usr.bin/csup/token.h
@@ -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$
+ */
+#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 --git a/usr.bin/csup/token.l b/usr.bin/csup/token.l
new file mode 100644
index 0000000..267e61f
--- /dev/null
+++ b/usr.bin/csup/token.l
@@ -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$
+ */
+
+#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 --git a/usr.bin/csup/updater.c b/usr.bin/csup/updater.c
new file mode 100644
index 0000000..d9f4210
--- /dev/null
+++ b/usr.bin/csup/updater.c
@@ -0,0 +1,2044 @@
+/*-
+ * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.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 "rcsfile.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. */
+
+#define BUFSIZE 4096
+
+/* Everything needed to update a file. */
+struct file_update {
+ struct statusrec srbuf;
+ char *destpath;
+ char *temppath;
+ char *origpath;
+ 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 attic;
+ 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 *, int);
+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_addfile(struct updater *, struct file_update *,
+ char *, int);
+int updater_addelta(struct rcsfile *, struct stream *, char *);
+static int updater_setattrs(struct updater *, struct file_update *,
+ char *, char *, char *, char *, char *, struct fattr *);
+static int updater_setdirattrs(struct updater *, struct coll *,
+ struct file_update *, char *, char *);
+static int updater_updatefile(struct updater *, struct file_update *fup,
+ const char *, int);
+static int updater_updatenode(struct updater *, struct coll *,
+ struct file_update *, char *, char *);
+static int updater_diff(struct updater *, struct file_update *);
+static int updater_diff_batch(struct updater *, struct file_update *);
+static int updater_diff_apply(struct updater *, struct file_update *,
+ char *);
+static int updater_rcsedit(struct updater *, struct file_update *, char *,
+ char *);
+int updater_append_file(struct updater *, struct file_update *,
+ off_t);
+static int updater_rsync(struct updater *, struct file_update *, size_t);
+static int updater_read_checkout(struct stream *, struct stream *);
+
+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, int attic)
+{
+ struct coll *coll;
+
+ coll = fup->coll;
+ fup->attic = 0;
+ fup->origpath = NULL;
+
+ if (coll->co_options & CO_CHECKOUTMODE)
+ fup->destpath = checkoutpath(coll->co_prefix, name);
+ else {
+ fup->destpath = cvspath(coll->co_prefix, name, attic);
+ fup->origpath = atticpath(coll->co_prefix, name);
+ /* If they're equal, we don't need special care. */
+ if (fup->origpath != NULL &&
+ strcmp(fup->origpath, fup->destpath) == 0) {
+ free(fup->origpath);
+ fup->origpath = NULL;
+ }
+ fup->attic = attic;
+ }
+ 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;
+ }
+ if (fup->origpath != NULL) {
+ free(fup->origpath);
+ fup->origpath = 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 *attr, *cmd, *blocksize, *line, *msg;
+ char *name, *tag, *date, *revdate;
+ char *expand, *wantmd5, *revnum;
+ char *optstr, *rcsopt, *pos;
+ time_t t;
+ off_t position;
+ int attic, 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, 0);
+ 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, 0);
+ 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, 0);
+ 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, 0);
+ 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, 0);
+ 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, 0);
+ 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 'A':
+ case 'a':
+ case 'R':
+ name = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ if (name == NULL || attr == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ attic = (cmd[0] == 'a');
+ error = fup_prepare(fup, name, attic);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+
+ fup->temppath = tempname(fup->destpath);
+ sr = &fup->srbuf;
+ sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ if (sr->sr_serverattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ if (attic)
+ lprintf(1, " Create %s -> Attic\n", name);
+ else
+ lprintf(1, " Create %s\n", name);
+ error = updater_addfile(up, fup, attr, 0);
+ if (error)
+ return (error);
+ break;
+ case 'r':
+ name = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ blocksize = proto_get_ascii(&line);
+ wantmd5 = proto_get_ascii(&line);
+ if (name == NULL || attr == NULL || blocksize == NULL ||
+ wantmd5 == NULL) {
+ return (UPDATER_ERR_PROTO);
+ }
+ error = fup_prepare(fup, name, 0);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ fup->wantmd5 = xstrdup(wantmd5);
+ fup->temppath = tempname(fup->destpath);
+ sr = &fup->srbuf;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ sr->sr_type = SR_FILELIVE;
+ if (sr->sr_serverattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ error = updater_rsync(up, fup, strtol(blocksize, NULL,
+ 10));
+ if (error)
+ return (error);
+ break;
+ case 'I':
+ /*
+ * Create directory and add DirDown entry in status
+ * file.
+ */
+ name = proto_get_ascii(&line);
+ if (name == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ error = fup_prepare(fup, name, 0);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ sr = &fup->srbuf;
+ sr->sr_type = SR_DIRDOWN;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = NULL;
+ sr->sr_clientattr = fattr_new(FT_DIRECTORY, -1);
+ fattr_mergedefault(sr->sr_clientattr);
+
+ error = mkdirhier(fup->destpath, coll->co_umask);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ if (access(fup->destpath, F_OK) != 0) {
+ lprintf(1, " Mkdir %s\n", name);
+ error = fattr_makenode(sr->sr_clientattr,
+ fup->destpath);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ }
+ error = status_put(fup->st, sr);
+ if (error) {
+ up->errmsg = status_errmsg(fup->st);
+ return (UPDATER_ERR_MSG);
+ }
+ break;
+ case 'i':
+ /* Remove DirDown entry in status file. */
+ name = proto_get_ascii(&line);
+ if (name == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ error = fup_prepare(fup, name, 0);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ error = status_delete(fup->st, name, 0);
+ if (error) {
+ up->errmsg = status_errmsg(fup->st);
+ return (UPDATER_ERR_MSG);
+ }
+ break;
+ case 'J':
+ /*
+ * Set attributes of directory and update DirUp entry in
+ * status file.
+ */
+ name = proto_get_ascii(&line);
+ if (name == NULL)
+ return (UPDATER_ERR_PROTO);
+ attr = proto_get_ascii(&line);
+ if (attr == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ error = fup_prepare(fup, name, 0);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ error = updater_setdirattrs(up, coll, fup, name, attr);
+ if (error)
+ return (error);
+ break;
+ case 'j':
+ /*
+ * Remove directory and delete its DirUp entry in status
+ * file.
+ */
+ name = proto_get_ascii(&line);
+ if (name == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ error = fup_prepare(fup, name, 0);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ lprintf(1, " Rmdir %s\n", name);
+ updater_deletefile(fup->destpath);
+ error = status_delete(fup->st, name, 0);
+ if (error) {
+ up->errmsg = status_errmsg(fup->st);
+ return (UPDATER_ERR_MSG);
+ }
+ break;
+ case 'L':
+ case 'l':
+ name = proto_get_ascii(&line);
+ if (name == NULL)
+ return (UPDATER_ERR_PROTO);
+ attr = proto_get_ascii(&line);
+ if (attr == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ attic = (cmd[0] == 'l');
+ sr = &fup->srbuf;
+ sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ sr->sr_clientattr = fattr_decode(attr);
+ if (sr->sr_serverattr == NULL ||
+ sr->sr_clientattr == NULL)
+ return (UPDATER_ERR_PROTO);
+
+ /* Save space. Described in detail in updatefile. */
+ if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT)
+ || fattr_getlinkcount(sr->sr_clientattr) <= 1)
+ fattr_maskout(sr->sr_clientattr,
+ FA_DEV | FA_INODE);
+ fattr_maskout(sr->sr_clientattr, FA_FLAGS);
+ error = status_put(fup->st, sr);
+ if (error) {
+ up->errmsg = status_errmsg(fup->st);
+ return (UPDATER_ERR_MSG);
+ }
+ break;
+ case 'N':
+ case 'n':
+ name = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ if (name == NULL || attr == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ attic = (cmd[0] == 'n');
+ error = fup_prepare(fup, name, attic);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ sr = &fup->srbuf;
+ sr->sr_type = (attic ? SR_FILEDEAD : SR_FILELIVE);
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ sr->sr_clientattr = fattr_new(FT_SYMLINK, -1);
+ fattr_mergedefault(sr->sr_clientattr);
+ fattr_maskout(sr->sr_clientattr, FA_FLAGS);
+ error = updater_updatenode(up, coll, fup, name, attr);
+ if (error)
+ return (error);
+ break;
+ case 'V':
+ case 'v':
+ name = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ optstr = proto_get_ascii(&line);
+ wantmd5 = proto_get_ascii(&line);
+ rcsopt = NULL; /* XXX: Not supported. */
+ if (attr == NULL || line != NULL || wantmd5 == NULL)
+ return (UPDATER_ERR_PROTO);
+ attic = (cmd[0] == 'v');
+ error = fup_prepare(fup, name, attic);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ fup->temppath = tempname(fup->destpath);
+ fup->wantmd5 = xstrdup(wantmd5);
+ sr = &fup->srbuf;
+ sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ if (sr->sr_serverattr == NULL)
+ return (UPDATER_ERR_PROTO);
+
+ error = 0;
+ error = updater_rcsedit(up, fup, name, rcsopt);
+ if (error)
+ return (error);
+ break;
+ case 'X':
+ case 'x':
+ name = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ if (name == NULL || attr == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ attic = (cmd[0] == 'x');
+ error = fup_prepare(fup, name, attic);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+
+ fup->temppath = tempname(fup->destpath);
+ sr = &fup->srbuf;
+ sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ if (sr->sr_serverattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ lprintf(1, " Fixup %s\n", name);
+ error = updater_addfile(up, fup, attr, 1);
+ if (error)
+ return (error);
+ break;
+ case 'Z':
+ name = proto_get_ascii(&line);
+ attr = proto_get_ascii(&line);
+ pos = proto_get_ascii(&line);
+ if (name == NULL || attr == NULL || pos == NULL ||
+ line != NULL)
+ return (UPDATER_ERR_PROTO);
+ error = fup_prepare(fup, name, 0);
+ fup->temppath = tempname(fup->destpath);
+ sr = &fup->srbuf;
+ sr->sr_type = SR_FILELIVE;
+ sr->sr_file = xstrdup(name);
+ sr->sr_serverattr = fattr_decode(attr);
+ if (sr->sr_serverattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ position = strtol(pos, NULL, 10);
+ lprintf(1, " Append to %s\n", name);
+ error = updater_append_file(up, fup, position);
+ if (error)
+ return (error);
+ 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);
+}
+
+/*
+ * Update attributes of a directory.
+ */
+static int
+updater_setdirattrs(struct updater *up, struct coll *coll,
+ struct file_update *fup, char *name, char *attr)
+{
+ struct statusrec *sr;
+ struct fattr *fa;
+ int error, rv;
+
+ sr = &fup->srbuf;
+ sr->sr_type = SR_DIRUP;
+ sr->sr_file = xstrdup(name);
+ sr->sr_clientattr = fattr_decode(attr);
+ sr->sr_serverattr = fattr_decode(attr);
+ if (sr->sr_clientattr == NULL || sr->sr_serverattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ fattr_mergedefault(sr->sr_clientattr);
+ fattr_umask(sr->sr_clientattr, coll->co_umask);
+ rv = fattr_install(sr->sr_clientattr, fup->destpath, NULL);
+ lprintf(1, " SetAttrs %s\n", name);
+ if (rv == -1) {
+ xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s",
+ fup->temppath, fup->destpath, strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ /*
+ * Now, make sure they were set and record what was set in the status
+ * file.
+ */
+ fa = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
+ if (fa == NULL) {
+ xasprintf(&up->errmsg, "Cannot open \%s\": %s", fup->destpath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ fattr_free(sr->sr_clientattr);
+ fattr_maskout(fa, FA_FLAGS);
+ sr->sr_clientattr = fa;
+ error = status_put(fup->st, sr);
+ if (error) {
+ up->errmsg = status_errmsg(fup->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);
+}
+
+/*
+ * Edit a file and add delta.
+ */
+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, 1);
+ if (error) {
+ /* XXX Bad error message */
+ xasprintf(&up->errmsg, "Bad diff from server");
+ return (UPDATER_ERR_MSG);
+ }
+ return (0);
+}
+
+/* Update or create a node. */
+static int
+updater_updatenode(struct updater *up, struct coll *coll,
+ struct file_update *fup, char *name, char *attr)
+{
+ struct fattr *fa, *fileattr;
+ struct status *st;
+ struct statusrec *sr;
+ int error, rv;
+
+ sr = &fup->srbuf;
+ st = fup->st;
+ fa = fattr_decode(attr);
+
+ if (fattr_type(fa) == FT_SYMLINK) {
+ lprintf(1, " Symlink %s -> %s\n", name,
+ fattr_getlinktarget(fa));
+ } else {
+ lprintf(1, " Mknod %s\n", name);
+ }
+
+ /* Create directory. */
+ error = mkdirhier(fup->destpath, coll->co_umask);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+
+ /* If it does not exist, create it. */
+ if (access(fup->destpath, F_OK) != 0)
+ fattr_makenode(fa, fup->destpath);
+
+ /*
+ * Coming from attic? I don't think this is a problem since we have
+ * determined attic before we call this function (Look at UpdateNode in
+ * cvsup).
+ */
+ fattr_umask(fa, coll->co_umask);
+ rv = fattr_install(fa, fup->destpath, fup->temppath);
+ if (rv == -1) {
+ xasprintf(&up->errmsg, "Cannot update attributes on "
+ "\"%s\": %s", fup->destpath, strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ /*
+ * XXX: Executes not implemented. Have not encountered much use for it
+ * yet.
+ */
+ /*
+ * 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 it is a symlink, write only out it's path. */
+ if (fattr_type(fa) == FT_SYMLINK) {
+ fattr_maskout(sr->sr_clientattr, ~(FA_FILETYPE |
+ FA_LINKTARGET));
+ }
+ fattr_maskout(sr->sr_clientattr, FA_FLAGS);
+ error = status_put(st, sr);
+ if (error) {
+ up->errmsg = status_errmsg(st);
+ return (UPDATER_ERR_MSG);
+ }
+ fattr_free(fa);
+
+ return (0);
+}
+
+/*
+ * Fetches a new file in CVS mode.
+ */
+static int
+updater_addfile(struct updater *up, struct file_update *fup, char *attr,
+ int isfixup)
+{
+ struct coll *coll;
+ struct stream *to;
+ struct statusrec *sr;
+ struct fattr *fa;
+ char buf[BUFSIZE];
+ char md5[MD5_DIGEST_SIZE];
+ ssize_t nread;
+ off_t fsize, remains;
+ char *cmd, *line, *path;
+ int error;
+
+ coll = fup->coll;
+ path = fup->destpath;
+ sr = &fup->srbuf;
+ fa = fattr_decode(attr);
+ fsize = fattr_filesize(fa);
+
+ error = mkdirhier(path, coll->co_umask);
+ if (error)
+ return (UPDATER_ERR_PROTO);
+ to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ 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);
+ remains = fsize;
+ do {
+ nread = stream_read(up->rd, buf, (BUFSIZE > remains ?
+ remains : BUFSIZE));
+ if (nread == -1)
+ return (UPDATER_ERR_PROTO);
+ remains -= nread;
+ if (stream_write(to, buf, nread) == -1)
+ goto bad;
+ } while (remains > 0);
+ stream_close(to);
+ line = stream_getln(up->rd, NULL);
+ if (line == NULL)
+ return (UPDATER_ERR_PROTO);
+ /* Check for EOF. */
+ if (!(*line == '.' || (strncmp(line, ".<", 2) != 0)))
+ return (UPDATER_ERR_PROTO);
+ line = stream_getln(up->rd, NULL);
+ if (line == NULL)
+ return (UPDATER_ERR_PROTO);
+
+ 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);
+
+ sr->sr_clientattr = fattr_frompath(fup->temppath, FATTR_NOFOLLOW);
+ if (sr->sr_clientattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ fattr_override(sr->sr_clientattr, sr->sr_serverattr,
+ FA_MODTIME | FA_MASK);
+ error = updater_updatefile(up, fup, md5, isfixup);
+ fup->wantmd5 = NULL; /* So that it doesn't get freed. */
+ return (error);
+bad:
+ xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+}
+
+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;
+ ssize_t nbytes;
+ size_t size;
+ char *cmd, *path, *line;
+ 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;
+ }
+ nbytes = stream_write(to, line, size);
+ if (nbytes == -1)
+ goto bad;
+ 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;
+ }
+}
+
+/*
+ * Edit an RCS file.
+ */
+static int
+updater_rcsedit(struct updater *up, struct file_update *fup, char *name,
+ char *rcsopt)
+{
+ struct coll *coll;
+ struct stream *dest;
+ struct statusrec *sr;
+ struct status *st;
+ struct rcsfile *rf;
+ struct fattr *oldfattr;
+ char md5[MD5_DIGEST_SIZE];
+ char *branch, *cmd, *expand, *line, *path, *revnum, *tag, *temppath;
+ int error;
+
+ coll = fup->coll;
+ sr = &fup->srbuf;
+ st = fup->st;
+ temppath = fup->temppath;
+ path = fup->origpath != NULL ? fup->origpath : fup->destpath;
+ error = 0;
+
+ /* If the path is new, we must create the Attic dir if needed. */
+ if (fup->origpath != NULL) {
+ error = mkdirhier(fup->destpath, coll->co_umask);
+ if (error) {
+ xasprintf(&up->errmsg, "Unable to create Attic dir for "
+ "%s\n", fup->origpath);
+ return (UPDATER_ERR_MSG);
+ }
+ }
+ /*
+ * XXX: we could avoid parsing overhead if we're reading ahead before we
+ * parse the file.
+ */
+ oldfattr = fattr_frompath(path, FATTR_NOFOLLOW);
+ if (oldfattr == NULL) {
+ xasprintf(&up->errmsg, "%s: Cannot get attributes: %s", path,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ fattr_merge(sr->sr_serverattr, oldfattr);
+ rf = NULL;
+
+ /* Macro for making touching an RCS file faster. */
+#define UPDATER_OPENRCS(rf, up, path, name, cvsroot, tag) do { \
+ if ((rf) == NULL) { \
+ lprintf(1, " Edit %s", fup->coname); \
+ if (fup->attic) \
+ lprintf(1, " -> Attic"); \
+ lprintf(1, "\n"); \
+ (rf) = rcsfile_frompath((path), (name), (cvsroot), \
+ (tag), 0); \
+ if ((rf) == NULL) { \
+ xasprintf(&(up)->errmsg, \
+ "Error reading rcsfile %s\n", (name)); \
+ return (UPDATER_ERR_MSG); \
+ } \
+ } \
+} while (0)
+
+ while ((line = stream_getln(up->rd, NULL)) != NULL) {
+ if (strcmp(line, ".") == 0)
+ break;
+ cmd = proto_get_ascii(&line);
+ if (cmd == NULL) {
+ lprintf(-1, "Error editing %s\n", name);
+ return (UPDATER_ERR_PROTO);
+ }
+ switch(cmd[0]) {
+ case 'B':
+ branch = proto_get_ascii(&line);
+ if (branch == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ break;
+ case 'b':
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ rcsfile_setval(rf, RCSFILE_BRANCH, NULL);
+ break;
+ case 'D':
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ error = updater_addelta(rf, up->rd, line);
+ if (error)
+ return (error);
+ break;
+ case 'd':
+ revnum = proto_get_ascii(&line);
+ if (revnum == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ rcsfile_deleterev(rf, revnum);
+ break;
+ case 'E':
+ expand = proto_get_ascii(&line);
+ if (expand == NULL || line != NULL)
+ return (UPDATER_ERR_PROTO);
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ rcsfile_setval(rf, RCSFILE_EXPAND, expand);
+ break;
+ case 'T':
+ tag = proto_get_ascii(&line);
+ revnum = proto_get_ascii(&line);
+ if (tag == NULL || revnum == NULL ||
+ line != NULL)
+ return (UPDATER_ERR_PROTO);
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ rcsfile_addtag(rf, tag, revnum);
+ break;
+ case 't':
+ tag = proto_get_ascii(&line);
+ revnum = proto_get_ascii(&line);
+ if (tag == NULL || revnum == NULL ||
+ line != NULL)
+ return (UPDATER_ERR_PROTO);
+ UPDATER_OPENRCS(rf, up, path, name,
+ coll->co_cvsroot, coll->co_tag);
+ rcsfile_deletetag(rf, tag, revnum);
+ break;
+ default:
+ return (UPDATER_ERR_PROTO);
+ }
+ }
+
+ if (rf == NULL) {
+ fattr_maskout(oldfattr, ~FA_MODTIME);
+ if (fattr_equal(oldfattr, sr->sr_serverattr))
+ lprintf(1, " SetAttrs %s", fup->coname);
+ else
+ lprintf(1, " Touch %s", fup->coname);
+ /* Install new attributes. */
+ fattr_umask(sr->sr_serverattr, coll->co_umask);
+ fattr_install(sr->sr_serverattr, fup->destpath, NULL);
+ if (fup->attic)
+ lprintf(1, " -> Attic");
+ lprintf(1, "\n");
+ fattr_free(oldfattr);
+ goto finish;
+ }
+
+ /* Write and rename temp file. */
+ dest = stream_open_file(fup->temppath,
+ O_RDWR | O_CREAT | O_TRUNC, 0600);
+ if (dest == NULL) {
+ xasprintf(&up->errmsg, "Error opening file %s for writing: %s\n",
+ fup->temppath, strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ stream_filter_start(dest, STREAM_FILTER_MD5RCS, md5);
+ error = rcsfile_write(rf, dest);
+ stream_close(dest);
+ rcsfile_free(rf);
+ if (error) {
+ xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+
+finish:
+ sr->sr_clientattr = fattr_frompath(path, FATTR_NOFOLLOW);
+ if (sr->sr_clientattr == NULL) {
+ xasprintf(&up->errmsg, "%s: Cannot get attributes: %s",
+ fup->destpath, strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ fattr_override(sr->sr_clientattr, sr->sr_serverattr,
+ FA_MODTIME | FA_MASK);
+ if (rf != NULL) {
+ error = updater_updatefile(up, fup, md5, 0);
+ fup->wantmd5 = NULL; /* So that it doesn't get freed. */
+ if (error)
+ return (error);
+ } else {
+ /* Record its attributes since we touched it. */
+ if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) ||
+ fattr_getlinkcount(sr->sr_clientattr) <= 1)
+ fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE);
+ error = status_put(st, sr);
+ if (error) {
+ up->errmsg = status_errmsg(st);
+ return (UPDATER_ERR_MSG);
+ }
+ }
+
+ /* In this case, we need to remove the old file afterwards. */
+ /* XXX: Can we be sure that a file not edited is moved? I don't think
+ * this is a problem, since if a file is moved, it should be edited to
+ * show if it's dead or not.
+ */
+ if (fup->origpath != NULL)
+ updater_deletefile(fup->origpath);
+ return (0);
+}
+
+/*
+ * Add a delta to a RCS file.
+ */
+int
+updater_addelta(struct rcsfile *rf, struct stream *rd, char *cmdline)
+{
+ struct delta *d;
+ size_t size;
+ char *author, *cmd, *diffbase, *line, *logline;
+ char *revdate, *revnum, *state, *textline;
+
+ revnum = proto_get_ascii(&cmdline);
+ diffbase = proto_get_ascii(&cmdline);
+ revdate = proto_get_ascii(&cmdline);
+ author = proto_get_ascii(&cmdline);
+ size = 0;
+
+ if (revnum == NULL || revdate == NULL || author == NULL)
+ return (UPDATER_ERR_PROTO);
+
+ /* First add the delta so we have it. */
+ d = rcsfile_addelta(rf, revnum, revdate, author, diffbase);
+ if (d == NULL) {
+ lprintf(-1, "Error adding delta %s\n", revnum);
+ return (UPDATER_ERR_READ);
+ }
+ while ((line = stream_getln(rd, NULL)) != NULL) {
+ if (strcmp(line, ".") == 0)
+ break;
+ cmd = proto_get_ascii(&line);
+ switch (cmd[0]) {
+ case 'L':
+ /* Do the same as in 'C' command. */
+ logline = stream_getln(rd, &size);
+ while (logline != NULL) {
+ if (size == 2 && *logline == '.')
+ break;
+ if (size == 3 &&
+ memcmp(logline, ".+", 2) == 0) {
+ rcsdelta_truncatelog(d, -1);
+ break;
+ }
+ if (size >= 3 &&
+ memcmp(logline, "..", 2) == 0) {
+ size--;
+ logline++;
+ }
+ if (rcsdelta_appendlog(d, logline, size)
+ < 0)
+ return (-1);
+ logline = stream_getln(rd, &size);
+ }
+ break;
+ case 'N':
+ case 'n':
+ /* XXX: Not supported. */
+ break;
+ case 'S':
+ state = proto_get_ascii(&line);
+ if (state == NULL)
+ return (UPDATER_ERR_PROTO);
+ rcsdelta_setstate(d, state);
+ break;
+ case 'T':
+ /* Do the same as in 'C' command. */
+ textline = stream_getln(rd, &size);
+ while (textline != NULL) {
+ if (size == 2 && *textline == '.')
+ break;
+ if (size == 3 &&
+ memcmp(textline, ".+", 2) == 0) {
+ /* Truncate newline. */
+ rcsdelta_truncatetext(d, -1);
+ break;
+ }
+ if (size >= 3 &&
+ memcmp(textline, "..", 2) == 0) {
+ size--;
+ textline++;
+ }
+ if (rcsdelta_appendtext(d, textline,
+ size) < 0)
+ return (-1);
+ textline = stream_getln(rd, &size);
+ }
+ break;
+ }
+ }
+
+ return (0);
+}
+
+int
+updater_append_file(struct updater *up, struct file_update *fup, off_t pos)
+{
+ struct fattr *fa;
+ struct stream *to;
+ struct statusrec *sr;
+ ssize_t nread;
+ off_t bytes;
+ char buf[BUFSIZE], md5[MD5_DIGEST_SIZE];
+ char *line, *cmd;
+ int error, fd;
+
+ sr = &fup->srbuf;
+ fa = sr->sr_serverattr;
+ to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC,
+ 0755);
+ if (to == NULL) {
+ xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->temppath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ fd = open(fup->destpath, O_RDONLY);
+ if (fd < 0) {
+ xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->destpath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+
+ stream_filter_start(to, STREAM_FILTER_MD5, md5);
+ /* First write the existing content. */
+ while ((nread = read(fd, buf, BUFSIZE)) > 0) {
+ if (stream_write(to, buf, nread) == -1)
+ goto bad;
+ }
+ if (nread == -1) {
+ xasprintf(&up->errmsg, "%s: Error reading: %s", fup->destpath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ close(fd);
+
+ bytes = fattr_filesize(fa) - pos;
+ /* Append the new data. */
+ do {
+ nread = stream_read(up->rd, buf,
+ (BUFSIZE > bytes) ? bytes : BUFSIZE);
+ if (nread == -1)
+ return (UPDATER_ERR_PROTO);
+ bytes -= nread;
+ if (stream_write(to, buf, nread) == -1)
+ goto bad;
+ } while (bytes > 0);
+ stream_close(to);
+
+ line = stream_getln(up->rd, NULL);
+ if (line == NULL)
+ return (UPDATER_ERR_PROTO);
+ /* Check for EOF. */
+ if (!(*line == '.' || (strncmp(line, ".<", 2) != 0)))
+ return (UPDATER_ERR_PROTO);
+ line = stream_getln(up->rd, NULL);
+ if (line == NULL)
+ return (UPDATER_ERR_PROTO);
+
+ 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);
+
+ sr->sr_clientattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
+ if (sr->sr_clientattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ fattr_override(sr->sr_clientattr, sr->sr_serverattr,
+ FA_MODTIME | FA_MASK);
+ error = updater_updatefile(up, fup, md5, 0);
+ fup->wantmd5 = NULL; /* So that it doesn't get freed. */
+ return (error);
+bad:
+ xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
+ strerror(errno));
+ return (UPDATER_ERR_MSG);
+}
+
+/*
+ * Read file data from stream of checkout commands, and write it to the
+ * destination.
+ */
+static int
+updater_read_checkout(struct stream *src, struct stream *dest)
+{
+ ssize_t nbytes;
+ size_t size;
+ char *line;
+ int first;
+
+ first = 1;
+ line = stream_getln(src, &size);
+ while (line != NULL) {
+ if (line[size - 1] == '\n')
+ size--;
+ if ((size == 1 && *line == '.') ||
+ (size == 2 && strncmp(line, ".+", 2) == 0))
+ break;
+ if (size >= 2 && strncmp(line, "..", 2) == 0) {
+ size--;
+ line++;
+ }
+ if (!first) {
+ nbytes = stream_write(dest, "\n", 1);
+ if (nbytes == -1)
+ return (UPDATER_ERR_MSG);
+ }
+ nbytes = stream_write(dest, line, size);
+ if (nbytes == -1)
+ return (UPDATER_ERR_MSG);
+ line = stream_getln(src, &size);
+ first = 0;
+ }
+ if (line == NULL)
+ return (UPDATER_ERR_READ);
+ if (size == 1 && *line == '.') {
+ nbytes = stream_write(dest, "\n", 1);
+ if (nbytes == -1)
+ return (UPDATER_ERR_MSG);
+ }
+ return (0);
+}
+
+/* Update file using the rsync protocol. */
+static int
+updater_rsync(struct updater *up, struct file_update *fup, size_t blocksize)
+{
+ struct statusrec *sr;
+ struct stream *to;
+ char md5[MD5_DIGEST_SIZE];
+ ssize_t nbytes;
+ size_t blocknum, blockstart, blockcount;
+ char *buf, *line;
+ int error, orig;
+
+ sr = &fup->srbuf;
+
+ lprintf(1, " Rsync %s\n", fup->coname);
+ /* First open all files that we are going to work on. */
+ 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);
+ }
+ orig = open(fup->destpath, O_RDONLY);
+ if (orig < 0) {
+ xasprintf(&up->errmsg, "%s: Cannot open: %s",
+ fup->destpath, strerror(errno));
+ return (UPDATER_ERR_MSG);
+ }
+ stream_filter_start(to, STREAM_FILTER_MD5, md5);
+
+ error = updater_read_checkout(up->rd, to);
+ if (error) {
+ xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath,
+ strerror(errno));
+ return (error);
+ }
+
+ /* Buffer must contain blocksize bytes. */
+ buf = xmalloc(blocksize);
+ /* Done with the initial text, read and write chunks. */
+ line = stream_getln(up->rd, NULL);
+ while (line != NULL) {
+ if (strcmp(line, ".") == 0)
+ break;
+ error = UPDATER_ERR_PROTO;
+ if (proto_get_sizet(&line, &blockstart, 10) != 0)
+ goto bad;
+ if (proto_get_sizet(&line, &blockcount, 10) != 0)
+ goto bad;
+ /* Read blocks from original file. */
+ lseek(orig, (blocksize * blockstart), SEEK_SET);
+ error = UPDATER_ERR_MSG;
+ for (blocknum = 0; blocknum < blockcount; blocknum++) {
+ nbytes = read(orig, buf, blocksize);
+ if (nbytes < 0) {
+ xasprintf(&up->errmsg, "%s: Cannot read: %s",
+ fup->destpath, strerror(errno));
+ goto bad;
+ }
+ nbytes = stream_write(to, buf, nbytes);
+ if (nbytes == -1) {
+ xasprintf(&up->errmsg, "%s: Cannot write: %s",
+ fup->temppath, strerror(errno));
+ goto bad;
+ }
+ }
+ /* Get the remaining text from the server. */
+ error = updater_read_checkout(up->rd, to);
+ if (error) {
+ xasprintf(&up->errmsg, "%s: Cannot write: %s",
+ fup->temppath, strerror(errno));
+ goto bad;
+ }
+ line = stream_getln(up->rd, NULL);
+ }
+ stream_close(to);
+ close(orig);
+
+ sr->sr_clientattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW);
+ if (sr->sr_clientattr == NULL)
+ return (UPDATER_ERR_PROTO);
+ fattr_override(sr->sr_clientattr, sr->sr_serverattr,
+ FA_MODTIME | FA_MASK);
+
+ error = updater_updatefile(up, fup, md5, 0);
+ fup->wantmd5 = NULL; /* So that it doesn't get freed. */
+bad:
+ free(buf);
+ return (error);
+}
diff --git a/usr.bin/csup/updater.h b/usr.bin/csup/updater.h
new file mode 100644
index 0000000..9ec9ed7
--- /dev/null
+++ b/usr.bin/csup/updater.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 2003-2004, Maxime Henrion <mux@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+#ifndef _UPDATER_H_
+#define _UPDATER_H
+
+void *updater(void *);
+
+#endif /* !_UPDATER_H_ */
OpenPOWER on IntegriCloud