diff options
Diffstat (limited to 'usr.bin/csup')
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, >, &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_ */ |