Index: Makefile =================================================================== RCS file: /cvs/csup/csup/Makefile,v retrieving revision 1.31 diff -u -p -r1.31 Makefile --- Makefile 22 May 2005 22:43:07 -0000 1.31 +++ Makefile 29 May 2005 15:04:11 -0000 @@ -5,7 +5,8 @@ BINDIR?= /usr/local/bin UNAME!= /usr/bin/uname -s PROG= csup -SRCS= config.c config.h \ +SRCS= attrstack.c attrstack.h \ + config.c config.h \ detailer.c detailer.h \ diff.c diff.h \ fattr.c fattr.h fattr_os.h \ @@ -16,6 +17,7 @@ SRCS= config.c config.h \ mux.c mux.h \ parse.h parse.y \ proto.c proto.h \ + status.c status.h \ stream.c stream.h \ threads.c threads.h \ token.h token.l \ Index: attrstack.c =================================================================== RCS file: attrstack.c diff -N attrstack.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ attrstack.c 21 May 2005 20:22:30 -0000 @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2003-2005, Maxime Henrion + * 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 + +#include +#include +#include + +#include "attrstack.h" +#include "fattr.h" + +#define ATTRSTACK_DEFSIZE 16 /* Initial size of the stack. */ + +struct attrstack { + struct fattr **stack; + size_t cur; + size_t size; +}; + +struct attrstack * +attrstack_new(void) +{ + struct attrstack *as; + + as = malloc(sizeof(struct attrstack)); + if (as == NULL) + err(1, "malloc"); + as->stack = malloc(sizeof(struct fattr *) * ATTRSTACK_DEFSIZE); + as->size = ATTRSTACK_DEFSIZE; + as->cur = 0; + return (as); +} + +struct fattr * +attrstack_pop(struct attrstack *as) +{ + + assert(as->cur > 0); + return (as->stack[--as->cur]); +} + +void +attrstack_push(struct attrstack *as, struct fattr *fa) +{ + struct fattr **new; + + if (as->cur >= as->size) { + as->size *= 2; + new = realloc(as->stack, sizeof(struct fattr *) * as->size); + if (new == NULL) + err(1, "realloc"); + as->stack = new; + } + as->stack[as->cur++] = fa; +} + +size_t +attrstack_size(struct attrstack *as) +{ + + return (as->cur); +} + +void +attrstack_free(struct attrstack *as) +{ + + assert(as->cur == 0); + free(as->stack); + free(as); +} Index: attrstack.h =================================================================== RCS file: attrstack.h diff -N attrstack.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ attrstack.h 22 May 2005 17:38:43 -0000 @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 2003-2005, Maxime Henrion + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ +#ifndef _ATTRSTACK_H_ +#define _ATTRSTACK_H_ + +struct fattr; +struct attrstack; + +struct attrstack *attrstack_new(void); +void attrstack_push(struct attrstack *, struct fattr *); +struct fattr *attrstack_pop(struct attrstack *); +size_t attrstack_size(struct attrstack *); +void attrstack_free(struct attrstack *); + +#endif /* !_ATTRSTACK_H_ */ Index: config.c =================================================================== RCS file: /cvs/csup/csup/config.c,v retrieving revision 1.31 diff -u -p -r1.31 config.c --- config.c 9 Jul 2005 18:54:17 -0000 1.31 +++ config.c 3 Dec 2005 03:50:49 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ #include #include +#include "fattr.h" #include "config.h" #include "keyword.h" #include "misc.h" @@ -58,7 +59,7 @@ static const char *cfgfile; */ struct config * config_init(const char *file, char *host, char *base, char *colldir, - in_port_t port, int compress) + in_port_t port, int compress, int truststatus) { struct coll *cur; mode_t mask; @@ -69,10 +70,6 @@ config_init(const char *file, char *host err(1, "malloc"); config->host = NULL; STAILQ_INIT(&config->colls); - if (colldir != NULL) - config->colldir = colldir; - else - config->colldir = "sup"; /* Set default collection options. */ defaults = coll_alloc(); @@ -130,6 +127,12 @@ config_init(const char *file, char *host cur->co_options |= CO_COMPRESS; else if (compress < 0) cur->co_options &= ~CO_COMPRESS; + if (truststatus) + cur->co_options |= CO_TRUSTSTATUSFILE; + if (colldir) + cur->co_colldir = colldir; + else + cur->co_colldir = "sup"; } /* Override host if necessary. */ @@ -204,6 +207,24 @@ coll_new(void) return (new); } +char * +coll_statuspath(struct coll *coll) +{ + char *path; + + if (coll->co_options & CO_USERELSUFFIX) { + asprintf(&path, "%s/%s/%s/checkouts.%s:%s", coll->co_base, + coll->co_colldir, coll->co_name, coll->co_release, + coll->co_tag); + } else { + asprintf(&path, "%s/%s/%s/checkouts", coll->co_base, + coll->co_colldir, coll->co_name); + } + if (path == NULL) + err(1, "asprintf"); + return (path); +} + void coll_add(char *name) { @@ -217,13 +238,22 @@ void coll_free(struct coll *coll) { - free(coll->co_base); - free(coll->co_date); - free(coll->co_prefix); - free(coll->co_release); - free(coll->co_tag); - free(coll->co_cvsroot); - free(coll->co_name); + if (coll == NULL) + return; + if (coll->co_base != NULL) + free(coll->co_base); + if (coll->co_base != NULL) + free(coll->co_date); + if (coll->co_base != NULL) + free(coll->co_prefix); + if (coll->co_base != NULL) + free(coll->co_release); + if (coll->co_base != NULL) + free(coll->co_tag); + if (coll->co_base != NULL) + free(coll->co_cvsroot); + if (coll->co_base != NULL) + free(coll->co_name); keyword_free(coll->co_keyword); free(coll); } @@ -236,23 +266,28 @@ coll_setopt(int opt, char *value) coll = cur_coll; switch (opt) { case PT_BASE: - free(coll->co_base); + if (coll->co_base != NULL) + free(coll->co_base); coll->co_base = value; break; case PT_DATE: - free(coll->co_date); + if (coll->co_base != NULL) + free(coll->co_date); coll->co_date = value; break; case PT_PREFIX: - free(coll->co_prefix); + if (coll->co_base != NULL) + free(coll->co_prefix); coll->co_prefix = value; break; case PT_RELEASE: - free(coll->co_release); + if (coll->co_base != NULL) + free(coll->co_release); coll->co_release = value; break; case PT_TAG: - free(coll->co_tag); + if (coll->co_base != NULL) + free(coll->co_tag); coll->co_tag = value; break; case PT_UMASK: Index: config.h =================================================================== RCS file: /cvs/csup/csup/config.h,v retrieving revision 1.22 diff -u -p -r1.22 config.h --- config.h 22 May 2005 17:38:00 -0000 1.22 +++ config.h 12 Dec 2005 21:17:24 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,9 @@ #include "fattr.h" #include "queue.h" +#include +#include + /* * Collection options. */ @@ -71,6 +74,8 @@ struct coll { char *co_release; char *co_tag; char *co_cvsroot; + const char *co_colldir; + time_t co_scantime; /* Set by the detailer thread. */ int co_options; mode_t co_umask; struct keyword *co_keyword; @@ -79,23 +84,23 @@ struct coll { struct config { STAILQ_HEAD(, coll) colls; - const char *colldir; char *host; - in_port_t port; + uint16_t port; int socket; int id0, id1; struct stream *server; fattr_support_t fasupport; }; -struct config *config_init(const char *, char *, char *, char *, in_port_t, - int); +struct config *config_init(const char *, char *, char *, char *, uint16_t, + int, int); int config_sethost(char *); struct coll *coll_new(void); -void coll_add(char *); -void coll_free(struct coll *); -void coll_setdef(void); -void coll_setopt(int, char *); +char *coll_statuspath(struct coll *); +void coll_add(char *); +void coll_free(struct coll *); +void coll_setdef(void); +void coll_setopt(int, char *); #endif /* !_CONFIG_H_ */ Index: detailer.c =================================================================== RCS file: /cvs/csup/csup/detailer.c,v retrieving revision 1.30 diff -u -p -r1.30 detailer.c --- detailer.c 9 Jul 2005 18:37:14 -0000 1.30 +++ detailer.c 12 Dec 2005 21:19:43 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,9 +26,6 @@ * $Id: detailer.c,v 1.30 2005/07/09 18:37:14 mux Exp $ */ -#include -#include - #include #include @@ -37,17 +34,22 @@ #include "misc.h" #include "mux.h" #include "proto.h" +#include "status.h" #include "stream.h" +static int detailer_coll(struct coll *, struct status *); +static int detailer_dofile(struct coll *, struct status *, char *); + +static struct stream *rd, *wr; + void * detailer(void *arg) { - char md5[MD5_DIGEST_SIZE]; - struct stat sb; struct config *config; struct coll *coll; - struct stream *rd, *wr; - char *cmd, *collname, *file, *line, *path, *release; + struct status *st; + char *errmsg; + char *cmd, *collname, *line, *release; int error; config = arg; @@ -57,10 +59,11 @@ detailer(void *arg) if (coll->co_options & CO_SKIP) continue; line = stream_getln(rd, NULL); - cmd = proto_getstr(&line); - collname = proto_getstr(&line); - release = proto_getstr(&line); - if (release == NULL || strcmp(cmd, "COLL") != 0 || + cmd = proto_get_ascii(&line); + collname = proto_get_ascii(&line); + release = proto_get_ascii(&line); + error = proto_get_time(&line, &coll->co_scantime); + if (error || line != NULL || strcmp(cmd, "COLL") != 0 || strcmp(collname, coll->co_name) != 0 || strcmp(release, coll->co_release) != 0) goto bad; @@ -70,31 +73,17 @@ detailer(void *arg) stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); } - line = stream_getln(rd, NULL); - if (line == NULL) - goto bad; - while (strcmp(line, ".") != 0) { - cmd = proto_getstr(&line); - file = proto_getstr(&line); - if (file == NULL || strcmp(cmd, "U") != 0) - goto bad; - path = checkoutpath(coll->co_prefix, file); - if (path == NULL) - goto bad; - error = stat(path, &sb); - if (!error && MD5file(path, md5) == 0) - proto_printf(wr, "S %s %s %s %s\n", file, - coll->co_tag, coll->co_date, md5); - else - proto_printf(wr, "C %s %s %s\n", file, - coll->co_tag, coll->co_date); - free(path); - stream_flush(wr); - line = stream_getln(rd, NULL); - if (line == NULL) - goto bad; + st = status_open(coll, -1, &errmsg); + if (st == NULL) { + lprintf(-1, "Detailer: %s\n", errmsg); + stream_close(rd); + stream_close(wr); + return (NULL); } - proto_printf(wr, ".\n"); + error = detailer_coll(coll, st); + status_close(st, NULL); + if (error) + goto bad; if (coll->co_options & CO_COMPRESS) { stream_filter_stop(rd); stream_filter_stop(wr); @@ -130,3 +119,88 @@ bad: stream_close(rd); return (NULL); } + +static int +detailer_coll(struct coll *coll, struct status *st) +{ + char *cmd, *file, *line; + int error; + + line = stream_getln(rd, NULL); + if (line == NULL) + return (-1); + while (strcmp(line, ".") != 0) { + cmd = proto_get_ascii(&line); + file = proto_get_ascii(&line); + if (file == NULL || line != NULL) + return (-1); + if (strcmp(cmd, "D") == 0) { + /* XXX: status file? */ + stream_printf(wr, "D %s\n", file); + } else { + if (strcmp(cmd, "U") != 0) + return (-1); + error = detailer_dofile(coll, st, file); + if (error) + return (-1); + } + stream_flush(wr); + line = stream_getln(rd, NULL); + if (line == NULL) + return (-1); + } + stream_printf(wr, ".\n"); + return (0); +} + +static int +detailer_dofile(struct coll *coll, struct status *st, char *file) +{ + char md5[MD5_DIGEST_SIZE]; + struct fattr *fa; + struct statusrec *sr; + char *path; + int error; + + path = checkoutpath(coll->co_prefix, file); + if (path == NULL) + return (-1); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + /* We don't have the file, so the only option at this + point is to tell the server to send it. The server + may figure out that the file is dead, in which case + it will tell us. */ + stream_printf(wr, "C %s %s %s\n", + file, coll->co_tag, coll->co_date); + free(path); + return (0); + } + sr = status_get(st, file, 0, 0); + + /* If our recorded information doesn't match the file that the + client has, then ignore the recorded information. */ + if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE || + !fattr_equal(sr->sr_clientattr, fa))) + sr = NULL; + fattr_free(fa); + if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) { + stream_printf(wr, "U %s %s %s %s %s\n", file, coll->co_tag, + coll->co_date, sr->sr_revnum, sr->sr_revdate); + free(path); + return (0); + } + + error = MD5file(path, md5); + if (error) + return (-1); + free(path); + if (sr == NULL) { + stream_printf(wr, "S %s %s %s %s\n", file, coll->co_tag, + coll->co_date, md5); + } else { + stream_printf(wr, "s %s %s %s %s %s\n", file, coll->co_tag, + coll->co_date, sr->sr_revnum, md5); + } + return (0); +} Index: fattr.c =================================================================== RCS file: /cvs/csup/csup/fattr.c,v retrieving revision 1.22 diff -u -p -r1.22 fattr.c --- fattr.c 9 Jul 2005 18:54:17 -0000 1.22 +++ fattr.c 12 Nov 2005 00:05:26 -0000 @@ -127,6 +127,20 @@ fattr_new(int type) return (new); } +struct fattr * +fattr_default(int type) +{ + struct fattr *fa; + + fa = fattr_new(type); + if (fa->type == FT_DIRECTORY) + fa->mode = 0777 | FA_PERMMASK; + else + fa->mode = 0666 | FA_PERMMASK; + fa->mask |= FA_MODE; + return (fa); +} + /* Returns a new file attribute structure based on a stat structure. */ struct fattr * fattr_fromstat(struct stat *sb) @@ -284,7 +298,10 @@ fattr_encode(const struct fattr *fa, fat pw = NULL; gr = NULL; - mask = fa->mask & support[fa->type]; + if (support == NULL) + mask = fa->mask; + else + mask = fa->mask & support[fa->type]; /* XXX - Use getpwuid_r() and getgrgid_r(). */ if (fa->mask & FA_OWNER) { pw = getpwuid(fa->uid); @@ -439,6 +456,14 @@ fattr_free(struct fattr *fa) } void +fattr_umask(struct fattr *fa, mode_t newumask) +{ + + if (fa->mask & FA_MODE) + fa->mode = fa->mode & ~newumask; +} + +void fattr_maskout(struct fattr *fa, int mask) { @@ -649,7 +674,7 @@ fattr_override(struct fattr *fa, const s * it has been applied successfully. */ int -fattr_install(struct fattr *fa, const char *frompath, const char *topath) +fattr_install(struct fattr *fa, const char *topath, const char *frompath) { struct timeval tv[2]; struct fattr *old; @@ -685,7 +710,7 @@ fattr_install(struct fattr *fa, const ch * since as far as I know that's the way things are. */ if ((old->mask & FA_FLAGS) && old->flags > 0) { - chflags(topath, 0); + (void)chflags(topath, 0); old->flags = 0; } @@ -693,9 +718,11 @@ fattr_install(struct fattr *fa, const ch if (!inplace && (fa->type == FT_DIRECTORY) != (old->type == FT_DIRECTORY)) { if (old->type == FT_DIRECTORY) - rmdir(topath); + error = rmdir(topath); else - unlink(topath); + error = unlink(topath); + if (error) + goto bad; } /* Change those attributes that we can before moving the file Index: fattr.h =================================================================== RCS file: /cvs/csup/csup/fattr.h,v retrieving revision 1.17 diff -u -p -r1.17 fattr.h --- fattr.h 16 Jun 2005 22:21:47 -0000 1.17 +++ fattr.h 11 Nov 2005 00:46:11 -0000 @@ -28,6 +28,8 @@ #ifndef _FATTR_H_ #define _FATTR_H_ +#include + /* * File types. */ @@ -81,6 +83,7 @@ typedef int fattr_support_t[FT_NUMBER]; extern const struct fattr *fattr_bogus; struct fattr *fattr_new(int); +struct fattr *fattr_default(int); struct fattr *fattr_fromstat(struct stat *); struct fattr *fattr_frompath(const char *, int); struct fattr *fattr_fromfd(int); @@ -90,6 +93,7 @@ struct fattr *fattr_dup(const struct fat char *fattr_encode(const struct fattr *, fattr_support_t); int fattr_type(const struct fattr *); void fattr_maskout(struct fattr *, int); +void fattr_umask(struct fattr *, mode_t); void fattr_merge(struct fattr *, const struct fattr *); void fattr_override(struct fattr *, const struct fattr *, int); int fattr_install(struct fattr *, const char *, const char *); Index: lister.c =================================================================== RCS file: /cvs/csup/csup/lister.c,v retrieving revision 1.15 diff -u -p -r1.15 lister.c --- lister.c 22 May 2005 17:33:26 -0000 1.15 +++ lister.c 17 Dec 2005 02:38:49 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,35 +28,307 @@ #include -#include +#include +#include +#include +#include +#include +#include +#include "attrstack.h" #include "config.h" +#include "fattr.h" #include "lister.h" +#include "misc.h" #include "mux.h" +#include "status.h" #include "stream.h" +static int lister_coll(struct config *, struct stream *, struct coll *, + struct status *); +static void lister_sendbogus(struct config *, struct stream *, + struct statusrec *); +static int lister_dodirdown(struct config *, struct stream *, + struct coll *, struct statusrec *, struct attrstack *as); +static int lister_dodirup(struct config *, struct stream *, struct coll *, + struct statusrec *, struct attrstack *as); +static int lister_dofile(struct config *, struct stream *, struct coll *, + struct statusrec *); +static int lister_dodead(struct config *, struct stream *, struct coll *, + struct statusrec *); + void * lister(void *arg) { struct config *config; struct coll *coll; struct stream *wr; + struct status *st; + char *errmsg; + int error; config = arg; wr = stream_fdopen(config->id0, NULL, chan_write, NULL); STAILQ_FOREACH(coll, &config->colls, co_next) { if (coll->co_options & CO_SKIP) continue; + st = status_open(coll, -1, &errmsg); + if (st == NULL) { + lprintf(-1, "Lister: %s\n", errmsg); + stream_close(wr); + return (NULL); + } stream_printf(wr, "COLL %s %s\n", coll->co_name, coll->co_release); if (coll->co_options & CO_COMPRESS) stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); - stream_printf(wr, ".\n"); + error = lister_coll(config, wr, coll, st); + if (error) { + status_close(st, NULL); + stream_close(wr); + return (NULL); + } if (coll->co_options & CO_COMPRESS) stream_filter_stop(wr); stream_flush(wr); + status_close(st, NULL); } stream_printf(wr, ".\n"); stream_close(wr); return (NULL); } + +/* List a single collection based on the status file. */ +static int +lister_coll(struct config *config, struct stream *wr, struct coll *coll, + struct status *st) +{ + struct attrstack *as; + struct statusrec *sr; + struct fattr *fa; + int depth, error, prunedepth; + + depth = 0; + prunedepth = INT_MAX; + as = attrstack_new(); + while ((sr = status_get(st, NULL, 0, 0)) != NULL) { + switch (sr->sr_type) { + case SR_DIRDOWN: + depth++; + if (depth < prunedepth) { + error = lister_dodirdown(config, wr, coll, sr, + as); + if (error) { + prunedepth = depth; + break; + } + } + break; + case SR_DIRUP: + if (depth < prunedepth) { + error = lister_dodirup(config, wr, coll, sr, + as); + } else if (depth == prunedepth) { + /* Finished pruning. */ + prunedepth = INT_MAX; + } + depth--; + continue; + case SR_CHECKOUTLIVE: + if (depth < prunedepth) + error = lister_dofile(config, wr, coll, sr); + break; + case SR_CHECKOUTDEAD: + if (depth < prunedepth) + error = lister_dodead(config, wr, coll, sr); + break; + default: + goto bad; + } + } + if (!status_eof(st) || depth != 0) + goto bad; + stream_printf(wr, ".\n"); + attrstack_free(as); + return (0); +bad: + lprintf(-1, "Lister: Bad status file for collection \"%s\". " + "Delete it and try again.\n", coll->co_name); + while (depth-- > 0) { + fa = attrstack_pop(as); + fattr_free(fa); + } + attrstack_free(as); + return (-1); +} + +/* Handle a directory up entry found in the status file. */ +static int +lister_dodirdown(struct config *config, struct stream *wr, struct coll *coll, + struct statusrec *sr, struct attrstack *as) +{ + struct fattr *fa, *fa2; + char *path; + + if (coll->co_options & CO_TRUSTSTATUSFILE) { + fa = fattr_new(FT_DIRECTORY); + } else { + asprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file); + if (path == NULL) + err(1, "asprintf"); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + /* The directory doesn't exist, prune + * everything below it. */ + free(path); + return (-1); + } + if (fattr_type(fa) == FT_SYMLINK) { + fa2 = fattr_frompath(path, FATTR_FOLLOW); + if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) { + /* XXX - When not in checkout mode, CVSup warns + * here about the file being a symlink to a + * directory instead of a directory. */ + fattr_free(fa); + fa = fa2; + } else { + fattr_free(fa2); + } + } + free(path); + } + + if (fattr_type(fa) != FT_DIRECTORY) { + fattr_free(fa); + /* Report it as something bogus so + * that it will be replaced. */ + lister_sendbogus(config, wr, sr); + return (-1); + } + + /* It really is a directory. */ + attrstack_push(as, fa); + stream_printf(wr, "D %s\n", pathlast(sr->sr_file)); + return (0); +} + +/* Handle a directory up entry found in the status file. */ +static int +lister_dodirup(struct config *config, struct stream *wr, struct coll *coll, + struct statusrec *sr, struct attrstack *as) +{ + struct fattr *fa, *fa2; + char *attrs; + + fa = attrstack_pop(as); + if (coll->co_options & CO_TRUSTSTATUSFILE) { + fattr_free(fa); + fa = sr->sr_clientattr; + } + + fa2 = sr->sr_clientattr; + if (fattr_equal(fa, fa2)) + attrs = fattr_encode(fa, config->fasupport); + else + attrs = fattr_encode(fattr_bogus, config->fasupport); + stream_printf(wr, "U %s\n", attrs); + free(attrs); + if (!(coll->co_options & CO_TRUSTSTATUSFILE)) + fattr_free(fa); + stream_flush(wr); /* XXX - CVSup flushes here.*/ + return (0); +} + +/* Handle a checkout live entry found in the status file. */ +static int +lister_dofile(struct config *config, struct stream *wr, struct coll *coll, + struct statusrec *sr) +{ + struct fattr *fa, *fa2, *cfa, *sfa, *rfa; + char *attrs, *path; + int error; + + fa = NULL; + rfa = NULL; + error = 0; + if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { + path = checkoutpath(coll->co_prefix, sr->sr_file); + if (path == NULL) + goto bad; + rfa = fattr_frompath(path, FATTR_NOFOLLOW); + free(path); + if (rfa == NULL) + goto bad; + fa = rfa; + } + cfa = sr->sr_clientattr; + if (fa == NULL) + fa = cfa; + sfa = sr->sr_serverattr; + fa2 = fattr_forcheckout(sfa, coll->co_umask); + if (!fattr_equal(fa, cfa) || !fattr_equal(fa, fa2) || + strcmp(coll->co_tag, sr->sr_tag) != 0 || + strcmp(coll->co_date, sr->sr_date) != 0) { + fattr_free(fa2); + fattr_free(rfa); + goto bad; + } + attrs = fattr_encode(sfa, config->fasupport); + stream_printf(wr, "F %s %s\n", pathlast(sr->sr_file), attrs); + free(attrs); + fattr_free(fa2); + fattr_free(rfa); + return (0); +bad: + lister_sendbogus(config, wr, sr); + return (-1); +} + +/* Handle a checkout dead entry found in the status file. */ +static int +lister_dodead(struct config *config, struct stream *wr, struct coll *coll, + struct statusrec *sr) +{ + struct fattr *fa; + char *attrs, *path; + + if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { + path = checkoutpath(coll->co_prefix, sr->sr_file); + if (path == NULL) + goto bad; + fa = fattr_frompath(path, FATTR_NOFOLLOW); + free(path); + if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) { + /* + * We shouldn't have this file but we do. Report + * it to the server, which will either send a + * deletion request, of (if the file has come alive) + * sent the correct version. + */ + fattr_free(fa); + goto bad; + } + fattr_free(fa); + } + if (strcmp(coll->co_tag, sr->sr_tag) != 0 || + strcmp(coll->co_date, sr->sr_date) != 0) + attrs = fattr_encode(fattr_bogus, config->fasupport); + else + attrs = fattr_encode(sr->sr_serverattr, config->fasupport); + stream_printf(wr, "f %s %s\n", pathlast(sr->sr_file), attrs); + free(attrs); + return (0); +bad: + lister_sendbogus(config, wr, sr); + return (-1); +} + +static void +lister_sendbogus(struct config *config, struct stream *wr, struct statusrec *sr) +{ + char *attrs; + + attrs = fattr_encode(fattr_bogus, config->fasupport); + stream_printf(wr, "F %s %s\n", pathlast(sr->sr_file), attrs); + free(attrs); +} Index: main.c =================================================================== RCS file: /cvs/csup/csup/main.c,v retrieving revision 1.20 diff -u -p -r1.20 main.c --- main.c 9 Jul 2005 18:54:17 -0000 1.20 +++ main.c 3 Dec 2005 03:57:04 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,6 +61,8 @@ usage(char *argv0) "Verbosity level (0..2, default 1)"); lprintf(-1, USAGE_OPTFMT, "-p port", "Alternate server port (default 5999)"); + lprintf(-1, USAGE_OPTFMT, "-s", + "Don't stat client files; trust the checkouts file"); lprintf(-1, USAGE_OPTFMT, "-v", "Print version and exit"); lprintf(-1, USAGE_OPTFMT, "-z", "Enable compression for all " "collections"); @@ -74,15 +76,16 @@ main(int argc, char *argv[]) struct config *config; char *argv0, *base, *colldir, *host, *file, *lockfile; in_port_t port; - int c, compress, error, lockfd, lflag; + int c, compress, error, lockfd, lflag, truststatus; port = 0; compress = 0; + truststatus = 0; lflag = 0; lockfd = 0; argv0 = argv[0]; base = colldir = host = lockfile = NULL; - while ((c = getopt(argc, argv, "b:c:gh:l:L:p:P:vzZ")) != -1) { + while ((c = getopt(argc, argv, "b:c:gh:l:L:p:P:svzZ")) != -1) { switch (c) { case 'b': base = optarg; @@ -139,6 +142,9 @@ main(int argc, char *argv[]) return (1); } break; + case 's': + truststatus = 1; + break; case 'v': lprintf(-1, "Csup version 0.1\n"); return (0); @@ -168,7 +174,8 @@ main(int argc, char *argv[]) file = argv[0]; lprintf(2, "Parsing supfile \"%s\"\n", file); - config = config_init(file, host, base, colldir, port, compress); + config = config_init(file, host, base, colldir, port, compress, + truststatus); lprintf(2, "Connecting to %s\n", config->host); error = proto_connect(config); if (error) Index: misc.c =================================================================== RCS file: /cvs/csup/csup/misc.c,v retrieving revision 1.14 diff -u -p -r1.14 misc.c --- misc.c 9 Jul 2005 18:54:17 -0000 1.14 +++ misc.c 17 Dec 2005 01:45:24 -0000 @@ -26,10 +26,12 @@ * $Id: misc.c,v 1.14 2005/07/09 18:54:17 mux Exp $ */ +#include #include #include #include +#include #include #include #include @@ -111,23 +113,15 @@ md5tostr(unsigned char *md5, char *s) int pathcmp(const char *s1, const char *s2) { - int c1, c2; + char c1, c2; do { - c1 = *s1++ & 0xff; - if (c1 == '/') { - if (*s1 != '\0') - c1 = 0x100; - else - c1 = 0; - } - c2 = *s2++ & 0xff; - if (c2 == '/') { - if (*s2 != '\0') - c2 = 0x100; - else - c2 = 0; - } + c1 = *s1++; + if (c1 == '/') + c1 = 1; + c2 = *s2++; + if (c2 == '/') + c2 = 1; } while (c1 == c2 && c1 != '\0'); return (c1 - c2); @@ -148,7 +142,7 @@ pathlast(char *path) /* * Returns a buffer allocated with malloc() containing the absolute - * pathname to the checkout file made from the prefix, and the path + * pathname to the checkout file made from the prefix and the path * of the corresponding RCS file relatively to the prefix. If the * filename is not an RCS filename, NULL will be returned. */ @@ -176,3 +170,38 @@ checkoutpath(const char *prefix, const c err(1, "asprintf"); return (path); } + +/* + * Compute temporary pathnames. + * This can look a bit like overkill but we mimic CVSup's behaviour. + */ +#define TEMPNAME_PREFIX "#cvs.csup" + +static pthread_mutex_t tempname_mtx = PTHREAD_MUTEX_INITIALIZER; +static pid_t tempname_pid = -1; +static int tempname_count; + +char * +tempname(const char *path) +{ + char *cp, *temp; + int count; + + pthread_mutex_lock(&tempname_mtx); + if (tempname_pid == -1) { + tempname_pid = getpid(); + tempname_count = 0; + } + count = tempname_count++; + pthread_mutex_unlock(&tempname_mtx); + cp = strrchr(path, '/'); + if (cp == NULL) + asprintf(&temp, "%s-%ld.%d", TEMPNAME_PREFIX, + (long)tempname_pid, count); + else + asprintf(&temp, "%.*s%s-%ld.%d", (int)(cp - path + 1), path, + TEMPNAME_PREFIX, (long)tempname_pid, count); + if (temp == NULL) + err(1, "asprintf"); + return (temp); +} Index: misc.h =================================================================== RCS file: /cvs/csup/csup/misc.h,v retrieving revision 1.14 diff -u -p -r1.14 misc.h --- misc.h 5 Jun 2005 02:36:10 -0000 1.14 +++ misc.h 8 Nov 2005 19:08:46 -0000 @@ -55,5 +55,6 @@ void md5tostr(unsigned char *, char *); int pathcmp(const char *, const char *); char *pathlast(char *); char *checkoutpath(const char *, const char *); +char *tempname(const char *); #endif /* !_MISC_H_ */ Index: mux.c =================================================================== RCS file: /cvs/csup/csup/mux.c,v retrieving revision 1.52 diff -u -p -r1.52 mux.c --- mux.c 9 Jul 2005 18:54:17 -0000 1.52 +++ mux.c 9 Jul 2005 18:54:24 -0000 @@ -869,10 +869,13 @@ receiver_loop(void *arg) goto bad; chan = chan_get(mh.mh_window.id); if (chan->state == CS_ESTABLISHED || - chan->state == CS_RDCLOSED) + chan->state == CS_RDCLOSED) { chan->sendwin = ntohl(mh.mh_window.window); - chan_unlock(chan); - sender_wakeup(); + chan_unlock(chan); + sender_wakeup(); + } else { + chan_unlock(chan); + } break; case MUX_DATA: error = sock_readwait(s, (char *)&mh + sizeof(mh.type), Index: proto.c =================================================================== RCS file: /cvs/csup/csup/proto.c,v retrieving revision 1.48 diff -u -p -r1.48 proto.c --- proto.c 9 Jul 2005 18:54:17 -0000 1.48 +++ proto.c 17 Dec 2005 02:36:31 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -140,13 +140,13 @@ proto_greet(struct config *config) s = config->server; line = stream_getln(s, NULL); - tok = proto_getstr(&line); + tok = proto_get_ascii(&line); if (tok == NULL) goto bad; if (strcmp(tok, "OK") == 0) { - proto_getstr(&line); /* XXX major number */ - proto_getstr(&line); /* XXX minor number */ - tok = proto_getstr(&line); + proto_get_ascii(&line); /* XXX major number */ + proto_get_ascii(&line); /* XXX minor number */ + tok = proto_get_ascii(&line); } else if (strcmp(tok, "!") == 0) { lprintf(-1, "Rejected by server: %s\n", line); return (-1); @@ -164,33 +164,26 @@ static int proto_negproto(struct config *config) { struct stream *s; - char *cmd, *line, *maj, *min; - int pmaj, pmin; + char *cmd, *line; + int error, maj, min; s = config->server; stream_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER); stream_flush(s); line = stream_getln(s, NULL); - cmd = proto_getstr(&line); + cmd = proto_get_ascii(&line); if (strcmp(cmd, "!") == 0) { lprintf(-1, "Protocol negotiation failed: %s\n", line); return (1); } else if (strcmp(cmd, "PROTO") != 0) goto bad; - maj = proto_getstr(&line); - min = proto_getstr(&line); - if (min == NULL || line != NULL) - goto bad; - errno = 0; - pmaj = strtol(maj, NULL, 10); - if (errno == EINVAL) - goto bad; - errno = 0; - pmin = strtol(min, NULL, 10); - if (errno == EINVAL) + error = proto_get_int(&line, &maj); + if (!error) + error = proto_get_int(&line, &min); + if (error) goto bad; - if (pmaj != PROTO_MAJ || pmin != PROTO_MIN) { - lprintf(-1, "Server protocol version %s.%s not supported " + if (maj != PROTO_MAJ || min != PROTO_MIN) { + lprintf(-1, "Server protocol version %d.%d not supported " "by client\n", maj, min); return (1); } @@ -213,9 +206,9 @@ proto_login(struct config *config) stream_printf(s, "USER %s %s\n", getlogin(), host); stream_flush(s); line = stream_getln(s, NULL); - cmd = proto_getstr(&line); - realm = proto_getstr(&line); - challenge = proto_getstr(&line); + cmd = proto_get_ascii(&line); + realm = proto_get_ascii(&line); + challenge = proto_get_ascii(&line); if (challenge == NULL || line != NULL) goto bad; if (strcmp(realm, ".") != 0 || strcmp(challenge, ".") != 0) { @@ -226,7 +219,7 @@ proto_login(struct config *config) stream_printf(s, "AUTHMD5 . . .\n"); stream_flush(s); line = stream_getln(s, NULL); - cmd = proto_getstr(&line); + cmd = proto_get_ascii(&line); if (strcmp(cmd, "OK") == 0) return (0); if (strcmp(cmd, "!") == 0 && line != NULL) { @@ -257,7 +250,7 @@ proto_fileattr(struct config *config) stream_printf(s, ".\n"); stream_flush(s); line = stream_getln(s, NULL); - cmd = proto_getstr(&line); + cmd = proto_get_ascii(&line); if (line == NULL || strcmp(cmd, "ATTR") != 0) goto bad; errno = 0; @@ -311,10 +304,10 @@ proto_xchgcoll(struct config *config) line = stream_getln(s, NULL); if (line == NULL) goto bad; - cmd = proto_getstr(&line); - coll = proto_getstr(&line); - release = proto_getstr(&line); - options = proto_getstr(&line); + cmd = proto_get_ascii(&line); + coll = proto_get_ascii(&line); + release = proto_get_ascii(&line); + options = proto_get_ascii(&line); if (options == NULL || line != NULL) goto bad; if (strcmp(cmd, "COLL") != 0) @@ -333,11 +326,11 @@ proto_xchgcoll(struct config *config) while ((line = stream_getln(s, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; - cmd = proto_getstr(&line); + cmd = proto_get_ascii(&line); if (cmd == NULL) goto bad; if (strcmp(cmd, "!") == 0) { - msg = proto_getstr(&line); + msg = proto_get_ascii(&line); if (err == NULL) goto bad; lprintf(-1, "Server message: %s\n", msg); @@ -346,8 +339,8 @@ proto_xchgcoll(struct config *config) if (cur->co_cvsroot == NULL) err(1, "strdup"); } else if (strcmp(cmd, "KEYALIAS") == 0) { - ident = proto_getstr(&line); - rcskey = proto_getstr(&line); + ident = proto_get_ascii(&line); + rcskey = proto_get_ascii(&line); if (rcskey == NULL || line != NULL) goto bad; error = keyword_alias(cur->co_keyword, ident, @@ -355,14 +348,14 @@ proto_xchgcoll(struct config *config) if (error) goto bad; } else if (strcmp(cmd, "KEYON") == 0) { - ident = proto_getstr(&line); + ident = proto_get_ascii(&line); if (ident == NULL || line != NULL) goto bad; error = keyword_enable(cur->co_keyword, ident); if (error) goto bad; } else if (strcmp(cmd, "KEYOFF") == 0) { - ident = proto_getstr(&line); + ident = proto_get_ascii(&line); if (ident == NULL || line != NULL) goto bad; error = keyword_disable(cur->co_keyword, ident); @@ -472,9 +465,10 @@ proto_init(struct config *config) } /* - * Very simple printf() implementation that only understands %d - * and %s formats. For the %s format, some characters in the - * string need to be encoded. + * Very simple printf() implementation that only understands standard + * format specifiers %d and %s, plus an additional %t format for printing + * time_t integers (unlike the C99 %t length modifier for ptrdiff_t). + * For the %s format, some characters in the string may get escaped. */ int proto_printf(struct stream *wr, const char *format, ...) @@ -484,14 +478,18 @@ proto_printf(struct stream *wr, const ch va_list ap; char *cp, *s; size_t len; + ssize_t n; int val, ret; char c; + n = 0; fmt = format; va_start(ap, format); while ((cp = strchr(fmt, '%')) != NULL) { if (cp > fmt) - stream_write(wr, fmt, cp - fmt); + n = stream_write(wr, fmt, cp - fmt); + if (n == -1) + return (-1); if (*++cp == '\0') goto done; switch (*cp) { @@ -503,7 +501,7 @@ proto_printf(struct stream *wr, const ch with 128-bit ints some day... */ if ((unsigned)ret > sizeof(buf) + 1) errx(1, "%s: increase buffer size", __func__); - stream_write(wr, buf, ret); + n = stream_write(wr, buf, ret); break; case 's': s = va_arg(ap, char *); @@ -513,42 +511,59 @@ proto_printf(struct stream *wr, const ch do { len = strcspn(s, " \t\n\\"); c = s[len]; - stream_write(wr, s, len); + n = stream_write(wr, s, len); + if (n == -1) + return (-1); s += len + 1; switch (c) { case ' ': - stream_write(wr, "\\_", 2); + n = stream_write(wr, "\\_", 2); break; case '\t': - stream_write(wr, "\\t", 2); + n = stream_write(wr, "\\t", 2); break; case '\n': - stream_write(wr, "\\n", 2); + n = stream_write(wr, "\\n", 2); break; case '\\': - stream_write(wr, "\\\\", 2); + n = stream_write(wr, "\\\\", 2); break; } + if (n == -1) + return (-1); } while (c != '\0'); break; + case 't': + val = va_arg(ap, time_t); + ret = snprintf(buf, sizeof(buf), "%lld", + (long long)val); + if ((unsigned)ret > sizeof(buf) + 1) + errx(1, "%s: increase buffer size", __func__); + n = stream_write(wr, buf, ret); + break; case '%': - stream_write(wr, "%", 1); + n = stream_write(wr, "%", 1); break; } fmt = cp + 1; } - if (*fmt != '\0') - stream_write(wr, fmt, strlen(fmt)); + if (n == -1) + return (-1); + if (*fmt != '\0') { + n = stream_write(wr, fmt, strlen(fmt)); + if (n == -1) + return (-1); + } done: va_end(ap); return (0); } /* - * Eat a token in the string. + * Get an ascii token in the string. */ char * -proto_getstr(char **s) +proto_get_ascii(char **s) { char *cp, *cp2, *ret; @@ -582,3 +597,44 @@ proto_getstr(char **s) } return (ret); } + +/* + * Get an int token. + */ +int +proto_get_int(char **s, int *val) +{ + char *cp, *end; + + cp = proto_get_ascii(s); + if (cp == NULL) + return (-1); + errno = 0; + *val = strtol(cp, &end, 10); + if (errno || *end != '\0') + return (-1); + return (0); +} + +/* + * Get a time_t token. + * + * Ideally, we would use an intmax_t and strtoimax() here, but strtoll() + * is more portable and 64bits should be enough for a timestamp. + */ +int +proto_get_time(char **s, time_t *val) +{ + long long tmp; + char *cp, *end; + + cp = proto_get_ascii(s); + if (cp == NULL) + return (-1); + errno = 0; + tmp = strtoll(cp, &end, 10); + if (errno || *end != '\0') + return (-1); + *val = (time_t)tmp; + return (0); +} Index: proto.h =================================================================== RCS file: /cvs/csup/csup/proto.h,v retrieving revision 1.7 diff -u -p -r1.7 proto.h --- proto.h 9 Jul 2005 18:37:14 -0000 1.7 +++ proto.h 17 Dec 2005 16:09:55 -0000 @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2003-2004, Maxime Henrion + * Copyright (c) 2003-2005, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,11 +28,15 @@ #ifndef _PROTO_H_ #define _PROTO_H_ +#include + struct stream; int proto_connect(struct config *); int proto_init(struct config *); int proto_printf(struct stream *, const char *, ...); -char *proto_getstr(char **); +char *proto_get_ascii(char **); +int proto_get_int(char **, int *); +int proto_get_time(char **, time_t *); #endif /* !_PROTO_H_ */ Index: status.c =================================================================== RCS file: status.c diff -N status.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ status.c 17 Dec 2005 16:09:50 -0000 @@ -0,0 +1,681 @@ +/*- + * Copyright (c) 2003-2005, Maxime Henrion + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "fattr.h" +#include "misc.h" +#include "proto.h" +#include "status.h" +#include "stream.h" + +#define STATUS_VERSION 5 + +static struct status *status_new(char *, time_t, struct stream *); +static struct statusrec *status_rd(struct status *); +static struct statusrec *status_rdraw(struct status *, char **); +static int status_wr(struct status *, struct statusrec *); +static int status_wrraw(struct status *, struct statusrec *, + char *); +static struct status *status_fromrd(char *, struct stream *); +static struct status *status_fromnull(char *); +static void status_free(struct status *); + +static void statusrec_init(struct statusrec *); +static void statusrec_fini(struct statusrec *); +static int statusrec_cook(struct statusrec *, char *); +static int statusrec_cmp(struct statusrec *, struct statusrec *); + +struct status { + char *path; + char *tempfile; + struct statusrec buf; + struct statusrec *previous; + struct statusrec *current; + struct stream *rd; + struct stream *wr; + time_t scantime; + int eof; + int linenum; + int depth; + int dirty; +}; + +static void +statusrec_init(struct statusrec *sr) +{ + + memset(sr, 0, sizeof(*sr)); +} + +static int +statusrec_cook(struct statusrec *sr, char *line) +{ + char *clientattr, *serverattr; + + switch (sr->sr_type) { + case SR_DIRDOWN: + /* Nothing to do. */ + break; + case SR_CHECKOUTLIVE: + sr->sr_tag = proto_get_ascii(&line); + sr->sr_date = proto_get_ascii(&line); + serverattr = proto_get_ascii(&line); + sr->sr_revnum = proto_get_ascii(&line); + sr->sr_revdate = proto_get_ascii(&line); + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_serverattr = fattr_decode(serverattr); + if (sr->sr_serverattr == NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) { + fattr_free(sr->sr_serverattr); + return (-1); + } + break; + case SR_CHECKOUTDEAD: + sr->sr_type = SR_CHECKOUTDEAD; + sr->sr_tag = proto_get_ascii(&line); + sr->sr_date = proto_get_ascii(&line); + serverattr = proto_get_ascii(&line); + if (serverattr == NULL || line != NULL) + return (-1); + sr->sr_serverattr = fattr_decode(serverattr); + if (sr->sr_serverattr == NULL) + return (-1); + break; + case SR_DIRUP: + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) + return (-1); + break; + default: + return (-1); + } + return (0); +} + +static struct statusrec * +status_rd(struct status *st) +{ + struct statusrec *sr; + char *line; + int error; + + sr = status_rdraw(st, &line); + if (sr == NULL) + return (NULL); + error = statusrec_cook(sr, line); + if (error) + return (NULL); + return (sr); +} + +static struct statusrec * +status_rdraw(struct status *st, char **linep) +{ + struct statusrec sr; + char *cmd, *line, *file; + + line = stream_getln(st->rd, NULL); + if (line == NULL) { + if (stream_eof(st->rd)) { + st->eof = 1; + return (NULL); + } + lprintf(-1, "Error reading status file\n"); + return (NULL); + } + st->linenum++; + cmd = proto_get_ascii(&line); + file = proto_get_ascii(&line); + if (file == NULL) + return (NULL); + + if (strcmp(cmd, "D") == 0) { + sr.sr_type = SR_DIRDOWN; + st->depth++; + } else if (strcmp(cmd, "C") == 0) { + sr.sr_type = SR_CHECKOUTLIVE; + } else if (strcmp(cmd, "c") == 0) { + sr.sr_type = SR_CHECKOUTDEAD; + } else if (strcmp(cmd, "U") == 0) { + sr.sr_type = SR_DIRUP; + if (st->depth <= 0) { + lprintf(-1, "\"U\" entry has no matching \"D\"\n"); + return (NULL); + } + st->depth--; + } else { + lprintf(-1, "Invalid file type \"%s\"\n", cmd); + return (NULL); + } + + sr.sr_file = strdup(file); + if (sr.sr_file == NULL) + err(1, "strdup"); + if (st->previous != NULL && + statusrec_cmp(st->previous, &sr) >= 0) { + lprintf(-1, "File is not sorted properly\n"); + lprintf(-1, "\"%s\" \"%s\" (%d)\n", st->previous->sr_file, + sr.sr_file, statusrec_cmp(st->previous, &sr)); + free(sr.sr_file); + return (NULL); + } + + if (st->previous == NULL) { + st->previous = &st->buf; + } else { + statusrec_fini(st->previous); + statusrec_init(st->previous); + } + st->previous->sr_type = sr.sr_type; + st->previous->sr_file = sr.sr_file; + *linep = line; + return (st->previous); +} + +static int +status_wr(struct status *st, struct statusrec *sr) +{ + char *clientattr, *serverattr, *attr; + int error; + + switch (sr->sr_type) { + case SR_DIRDOWN: + error = proto_printf(st->wr, "D %s\n", sr->sr_file); + break; + case SR_DIRUP: + attr = fattr_encode(fattr_bogus, NULL); + error = proto_printf(st->wr, "U %s %s\n", sr->sr_file, attr); + free(attr); + break; + case SR_CHECKOUTLIVE: + clientattr = fattr_encode(sr->sr_clientattr, NULL); + serverattr = fattr_encode(sr->sr_serverattr, NULL); + error = proto_printf(st->wr, "C %s %s %s %s %s %s %s\n", + sr->sr_file, sr->sr_tag, sr->sr_date, serverattr, + sr->sr_revnum, sr->sr_revdate, clientattr); + free(clientattr); + free(serverattr); + break; + case SR_CHECKOUTDEAD: + serverattr = fattr_encode(sr->sr_serverattr, NULL); + error = proto_printf(st->wr, "c %s %s %s %s\n", sr->sr_file, + sr->sr_tag, sr->sr_date, serverattr); + free(serverattr); + break; + default: + return (-1); + } + if (error) + return (-1); + return (0); +} + +static int +status_wrraw(struct status *st, struct statusrec *sr, char *line) +{ + char type; + int ret; + + if (st->wr == NULL) + return (0); + switch (sr->sr_type) { + case SR_DIRDOWN: + type = 'D'; + break; + case SR_DIRUP: + type = 'U'; + break; + case SR_CHECKOUTLIVE: + type = 'C'; + break; + case SR_CHECKOUTDEAD: + type = 'c'; + break; + default: + return (-1); + } + /* XXX - We should probably use proto_printf() here. */ + if (sr->sr_type == SR_DIRDOWN) + ret = stream_printf(st->wr, "%c %s\n", type, sr->sr_file); + else + ret = stream_printf(st->wr, "%c %s %s\n", type, sr->sr_file, + line); + if (ret == -1) + return (-1); + return (0); +} + +static void +statusrec_fini(struct statusrec *sr) +{ + + fattr_free(sr->sr_serverattr); + fattr_free(sr->sr_clientattr); + free(sr->sr_file); +} + +static int +statusrec_cmp(struct statusrec *a, struct statusrec *b) +{ + size_t lena, lenb; + + if (a->sr_type == SR_DIRUP || b->sr_type == SR_DIRUP) { + lena = strlen(a->sr_file); + lenb = strlen(b->sr_file); + if (a->sr_type == SR_DIRUP && + ((lena < lenb && b->sr_file[lena] == '/') || lena == lenb) + && strncmp(a->sr_file, b->sr_file, lena) == 0) + return (1); + if (b->sr_type == SR_DIRUP && + ((lenb < lena && a->sr_file[lenb] == '/') || lenb == lena) + && strncmp(a->sr_file, b->sr_file, lenb) == 0) + return (-1); + } + return (pathcmp(a->sr_file, b->sr_file)); +} + +static struct status * +status_new(char *path, time_t scantime, struct stream *file) +{ + struct status *st; + + st = malloc(sizeof(struct status)); + if (st == NULL) + err(1, "malloc"); + st->path = path; + st->tempfile = NULL; + st->scantime = scantime; + st->rd = file; + st->wr = NULL; + st->previous = NULL; + st->current = NULL; + st->dirty = 0; + st->eof = 0; + st->linenum = 0; + st->depth = 0; + statusrec_init(&st->buf); + return (st); +} + +static void +status_free(struct status *st) +{ + + if (st->previous != NULL) + statusrec_fini(st->previous); + if (st->rd != NULL) + stream_close(st->rd); + if (st->wr != NULL) + stream_close(st->wr); + if (st->tempfile != NULL) + free(st->tempfile); + free(st->path); + free(st); +} + +static struct status * +status_fromrd(char *path, struct stream *file) +{ + struct status *st; + char *id, *line; + time_t scantime; + int error, ver; + + /* Get the first line of the file and validate it. */ + line = stream_getln(file, NULL); + if (line == NULL) { + stream_close(file); + return (NULL); + } + id = proto_get_ascii(&line); + error = proto_get_int(&line, &ver); + if (error) { + stream_close(file); + return (NULL); + } + error = proto_get_time(&line, &scantime); + if (error || line != NULL) { + stream_close(file); + return (NULL); + } + + if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) { + stream_close(file); + return (NULL); + } + + st = status_new(path, scantime, file); + st->linenum = 1; + return (st); +} + +static struct status * +status_fromnull(char *path) +{ + struct status *st; + + st = status_new(path, -1, NULL); + return (st); +} + +/* + * Open the status file. If scantime is not -1, the file is opened + * for updating, otherwise, it is opened read-only. If the status file + * couldn't be opened, NULL is returned and errmsg is set to the error + * message. + */ +struct status * +status_open(struct coll *coll, time_t scantime, char **errmsg) +{ + struct status *st; + struct stream *file; + struct fattr *fa; + char *destpath, *path; + int error, rv; + + path = coll_statuspath(coll); + file = stream_open_file(path, O_RDONLY); + if (file == NULL) { + if (errno != ENOENT) { + /* XXX */ + } + lprintf(-1, "Couldn't open \"%s\"\n", path); + st = status_fromnull(path); + } else + st = status_fromrd(path, file); + if (st == NULL) { + asprintf(errmsg, "Error in status file \"%s\". " + "Delete it and try again.\n", path); + if (*errmsg == NULL) + err(1, "asprintf"); + free(path); + return (NULL); + } + + if (scantime != -1) { + /* Open for writing too. */ + asprintf(&destpath, "%s/%s/%s/", coll->co_base, + coll->co_colldir, coll->co_name); + if (destpath == NULL) + err(1, "asprintf"); + st->tempfile = tempname(destpath); + free(destpath); + st->wr = stream_open_file(st->tempfile, + O_CREAT | O_TRUNC | O_WRONLY, 0644); + /* XXX Create directories leading to tempfile. */ + if (st->wr == NULL) { + asprintf(errmsg, "Cannot create \"%s\": %s", + st->tempfile, strerror(errno)); + if (*errmsg == NULL) + err(1, "asprintf"); + status_free(st); + return (NULL); + } + fa = fattr_default(FT_FILE); + fattr_umask(fa, coll->co_umask); + rv = fattr_install(fa, st->tempfile, NULL); + fattr_free(fa); + if (rv == -1) { + asprintf(errmsg, "Cannot set attributes for \"%s\": %s", + st->tempfile, strerror(errno)); + if (*errmsg == NULL) + err(1, "asprintf"); + status_free(st); + return (NULL); + } + if (scantime != st->scantime) + st->dirty = 1; + error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION, + scantime); + if (error) { + /* XXX */ + } + } + return (st); +} + +/* + * Get an entry from the status file. If name is NULL, the next entry + * is returned. If name is not NULL, the entry matching this name is + * returned, or NULL if it couldn't be found. If deleteto is set to 1, + * all the entries read from the status file while looking for the + * given name are deleted. + */ +struct statusrec * +status_get(struct status *st, char *name, int isdirup, int deleteto) +{ + struct statusrec key; + struct statusrec *sr; + char *line; + int c, error; + + if (st->rd == NULL || st->eof) + return (NULL); + + if (name == NULL) { + sr = status_rd(st); + return (sr); + } + + if (st->current != NULL) { + sr = st->current; + st->current = NULL; + } else { + sr = status_rd(st); + if (sr == NULL) + return (NULL); + } + + key.sr_file = name; + if (isdirup) + key.sr_type = SR_DIRUP; + else + key.sr_type = SR_CHECKOUTLIVE; + + c = statusrec_cmp(sr, &key); + if (c != 0) { + if (st->wr != NULL && !deleteto) { + error = status_wr(st, sr); + if (error) + /* XXX */ + return (NULL); + } + /* Loop until we find the good entry. */ + for (;;) { + sr = status_rdraw(st, &line); + if (sr == NULL) + return (NULL); + c = statusrec_cmp(sr, &key); + if (c >= 0) + break; + if (st->wr != NULL && !deleteto) { + error = status_wrraw(st, sr, line); + if (error) + return (NULL); + } + } + error = statusrec_cook(sr, line); + if (error) { + lprintf(-1, "Error in status file\n"); + return (NULL); + } + } + if (c != 0) { + st->current = sr; + return (NULL); + } + st->current = sr; + return (sr); +} + +/* + * Put this entry into the status file. If an entry with the same name + * existed in the status file, it is replaced by this one, otherwise, + * the entry is added to the status file. + */ +int +status_put(struct status *st, struct statusrec *sr) +{ + struct statusrec *old; + int error; + + old = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0); + if (old != NULL) { + if (old->sr_type == SR_DIRDOWN) { + /* DirUp should never match DirDown */ + assert(old->sr_type != SR_DIRUP); + if (sr->sr_type == SR_CHECKOUTLIVE || + sr->sr_type == SR_CHECKOUTDEAD) { + /* We are replacing a directory with a file. + Delete all entries inside the directory we + are replacing. */ + old = status_get(st, sr->sr_file, 1, 1); + assert(old != NULL); + } + } else + st->current = NULL; + } else { + error = status_wr(st, old); + if (error) + /* XXX */ + return (-1); + } + st->dirty = 1; + error = status_wr(st, sr); + if (error) + /* XXX */ + return (-1); + return (0); +} + +/* + * Delete the specified entry from the status file. + */ +int +status_delete(struct status *st, char *name, int isdirup) +{ + struct statusrec *sr; + + sr = status_get(st, name, isdirup, 0); + if (sr != NULL) { + st->current = NULL; + st->dirty = 1; + } + return (0); +} + +/* + * Check whether we hit the end of file. + */ +int +status_eof(struct status *st) +{ + + return (st->eof); +} + +/* + * Close the status file and free any resource associated with it. + * It is OK to pass NULL for errmsg only if the status file was + * opened read-only. If it wasn't opened read-only, status_close() + * can produce an error and errmsg is not allowed to be NULL. If + * there was no errors, errmsg is set to NULL. + */ +void +status_close(struct status *st, char **errmsg) +{ + struct statusrec *sr; + char *line; + int error; + + if (st->wr != NULL) { + if (st->dirty) { + if (st->current != NULL) { + error = status_wr(st, st->current); + if (error) { + asprintf(errmsg, "Write failure on " + "\"%s\": %s", st->tempfile, + strerror(errno)); + if (*errmsg == NULL) + err(1, "asprintf"); + goto bad; + } + st->current = NULL; + } + /* Copy the rest of the file. */ + while ((sr = status_rdraw(st, &line)) != NULL) { + error = status_wrraw(st, sr, line); + if (error) { + asprintf(errmsg, "Write failure on " + "\"%s\": %s", st->tempfile, + strerror(errno)); + if (*errmsg == NULL) + err(1, "asprintf"); + goto bad; + } + } + /* Rename tempfile. */ + error = rename(st->tempfile, st->path); + if (error) { + asprintf(errmsg, "Cannot rename \"%s\" to " + "\"%s\": %s", st->tempfile, st->path, + strerror(errno)); + if (*errmsg == NULL) + err(1, "asprintf"); + goto bad; + } + } else { + /* Just discard the tempfile. */ + unlink(st->tempfile); + } + *errmsg = NULL; + } + status_free(st); + return; +bad: + status_free(st); +} Index: status.h =================================================================== RCS file: status.h diff -N status.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ status.h 7 Dec 2005 01:00:35 -0000 @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 2003-2005, Maxime Henrion + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ +#ifndef _STATUS_H_ +#define _STATUS_H_ + +#include + +struct coll; +struct fattr; +struct status; + +#define SR_DIRDOWN 0 +#define SR_CHECKOUTLIVE 1 +#define SR_CHECKOUTDEAD 2 +#define SR_FILELIVE 3 +#define SR_FILEDEAD 4 +#define SR_DIRUP 5 + +struct statusrec { + int sr_type; + char *sr_file; + char *sr_tag; + char *sr_date; + char *sr_revnum; + char *sr_revdate; + + /* + * "clientrttr" contains the attributes of the client's file if there + * is one. "serverattr" contains the attributes of the corresponding + * file on the server. In CVS mode, these are identical. But in + * checkout mode, "clientattr" represents the checked-out file while + * "serverattr" represents the corresponding RCS file on the server. + */ + struct fattr *sr_serverattr; + struct fattr *sr_clientattr; +}; + +struct status *status_open(struct coll *, time_t, char **); +struct statusrec *status_get(struct status *, char *, int, int); +int status_put(struct status *, struct statusrec *); +int status_eof(struct status *); +int status_delete(struct status *, char *, int); +void status_close(struct status *, char **); + +#endif /* !_STATUS_H_ */ Index: stream.c =================================================================== RCS file: /cvs/csup/csup/stream.c,v retrieving revision 1.42 diff -u -p -r1.42 stream.c --- stream.c 9 Jul 2005 18:54:17 -0000 1.42 +++ stream.c 17 Dec 2005 13:54:48 -0000 @@ -103,6 +103,7 @@ struct stream { stream_readfn_t readfn; stream_writefn_t writefn; stream_closefn_t closefn; + int eof; struct stream_filter *filter; void *fdata; }; @@ -318,6 +319,8 @@ stream_fdopen(int id, stream_readfn_t re stream->writefn = writefn; stream->closefn = closefn; stream->filter = stream_filter_lookup(STREAM_FILTER_NULL); + stream->fdata = NULL; + stream->eof = 0; return (stream); } @@ -580,6 +583,14 @@ stream_rewind(struct stream *stream) return (error); } +/* Return EOF status. */ +int +stream_eof(struct stream *stream) +{ + + return (stream->eof); +} + /* Close a stream and free any resources held by it. */ int stream_close(struct stream *stream) @@ -614,11 +625,17 @@ stream_fill_default(struct stream *strea { ssize_t n; + if (stream->eof) + return (0); assert(buf_avail(buf) > 0); n = (*stream->readfn)(stream->id, buf->buf + buf->off + buf->in, buf_avail(buf)); if (n < 0) return (-1); + if (n == 0) { + stream->eof = 1; + return (0); + } buf_more(buf, n); return (n); } @@ -689,6 +706,9 @@ stream_filter_fini(struct stream *stream struct stream_filter *filter; filter = stream->filter; + if (filter == NULL) { + fprintf(stderr, "ARG!\n"); + } if (filter->finifn != NULL) (*filter->finifn)(stream); } Index: stream.h =================================================================== RCS file: /cvs/csup/csup/stream.h,v retrieving revision 1.16 diff -u -p -r1.16 stream.h --- stream.h 5 Jun 2005 02:27:13 -0000 1.16 +++ stream.h 7 Nov 2005 00:00:27 -0000 @@ -54,6 +54,7 @@ int stream_sync(struct stream *); int stream_truncate(struct stream *, off_t); int stream_truncate_rel(struct stream *, off_t); int stream_rewind(struct stream *); +int stream_eof(struct stream *); int stream_close(struct stream *); int stream_filter_start(struct stream *, stream_filter_t, void *); void stream_filter_stop(struct stream *); Index: updater.c =================================================================== RCS file: /cvs/csup/csup/updater.c,v retrieving revision 1.56 diff -u -p -r1.56 updater.c --- updater.c 9 Jul 2005 18:54:17 -0000 1.56 +++ updater.c 17 Dec 2005 16:18:24 -0000 @@ -47,44 +47,52 @@ #include "misc.h" #include "mux.h" #include "proto.h" +#include "status.h" #include "stream.h" -/* Everything we need to update a file. */ -struct update { +/* Everything we need to context a file. */ +struct context { struct coll *coll; + struct stream *rd; + struct status *st; + + struct statusrec sr; char *author; - char *rcsfile; - char *revnum; - char *revdate; char *state; char *tag; + char *cksum; struct stream *from; struct stream *to; - struct stream *stream; - struct fattr *rcsattr; int expand; }; +static void context_fini(struct context *); + static int updater_makedirs(char *); static void updater_prunedirs(char *, char *); static char *updater_maketmp(const char *, const char *); -static int updater_checkout(struct coll *, struct stream *, char *); -static int updater_delete(struct coll *, char *); -static int updater_diff(struct coll *, struct stream *, char *); -static int updater_diff_batch(struct update *); -static int updater_diff_apply(struct update *); +static int updater_docoll(struct context *); +static int updater_checkout(struct context *, char *); +static int updater_delete(struct context *, char *); +static int updater_diff(struct context *, char *); +static int updater_diff_parseln(char *, struct context *); +static int updater_diff_batch(struct context *); +static int updater_diff_apply(struct context *); static int updater_install(struct coll *, struct fattr *, const char *, const char *); void * updater(void *arg) { + struct context ctx; struct config *config; struct coll *coll; + struct status *st; struct stream *rd; - char *line, *cmd, *collname, *release; + char *line, *cmd, *errmsg, *collname, *release; int error; + memset(&ctx, 0, sizeof(ctx)); config = arg; rd = stream_fdopen(config->id1, chan_read, NULL, NULL); error = 0; @@ -93,48 +101,43 @@ updater(void *arg) continue; umask(coll->co_umask); line = stream_getln(rd, NULL); - cmd = proto_getstr(&line); - collname = proto_getstr(&line); - release = proto_getstr(&line); + cmd = proto_get_ascii(&line); + collname = proto_get_ascii(&line); + release = proto_get_ascii(&line); if (release == NULL || line != NULL) goto bad; if (strcmp(cmd, "COLL") != 0 || strcmp(collname, coll->co_name) != 0 || strcmp(release, coll->co_release) != 0) goto bad; + + st = status_open(coll, coll->co_scantime, &errmsg); + if (st == NULL) { + lprintf(-1, "Updater: %s\n", errmsg); + stream_close(rd); + return (NULL); + } + lprintf(1, "Updating collection %s/%s\n", coll->co_name, coll->co_release); if (coll->co_options & CO_COMPRESS) stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); - while ((line = stream_getln(rd, NULL)) != NULL) { - if (strcmp(line, ".") == 0) - break; - cmd = proto_getstr(&line); - if (cmd == NULL) - goto bad; - if (strcmp(cmd, "T") == 0) - /* XXX */; - else if (strcmp(cmd, "c") == 0) - /* XXX */; - else if (strcmp(cmd, "U") == 0) - error = updater_diff(coll, rd, line); - else if (strcmp(cmd, "u") == 0) - error = updater_delete(coll, line); - else if (strcmp(cmd, "C") == 0) - error = updater_checkout(coll, rd, line); - else if (strcmp(cmd, "D") == 0) - error = updater_delete(coll, line); - else { - lprintf(-1, "Updater: Unknown command: " - "\"%s\"\n", cmd); - goto bad; - } - if (error) - goto bad; - } - if (line == NULL) + + ctx.coll = coll; + ctx.rd = rd; + ctx.st = st; + error = updater_docoll(&ctx); + if (error) goto bad; + + status_close(st, &errmsg); + if (errmsg != NULL) { + lprintf(-1, "Updater: %s\n", errmsg); + stream_close(rd); + return (NULL); + } + if (coll->co_options & CO_COMPRESS) stream_filter_stop(rd); } @@ -150,192 +153,284 @@ bad: } static int -updater_delete(struct coll *coll, char *line) +updater_docoll(struct context *ctx) { - char *file, *rcsfile; + struct coll *coll; + char *cmd, *line; int error; - rcsfile = proto_getstr(&line); - file = checkoutpath(coll->co_prefix, rcsfile); - if (file == NULL) { - lprintf(-1, "Updater: Bad filename \"%s\"\n", rcsfile); + error = 0; + coll = ctx->coll; + while ((line = stream_getln(ctx->rd, NULL)) != NULL) { + if (strcmp(line, ".") == 0) + break; + cmd = proto_get_ascii(&line); + if (cmd == NULL) + return (-1); + if (strcmp(cmd, "T") == 0) + ; + //lprintf(-1, "T %s\n", line); + else if (strcmp(cmd, "c") == 0) + ; + //lprintf(-1, "c %s\n", line); + else if (strcmp(cmd, "U") == 0) + error = updater_diff(ctx, line); + else if (strcmp(cmd, "u") == 0) + error = updater_delete(ctx, line); + else if (strcmp(cmd, "C") == 0) + error = updater_checkout(ctx, line); + else if (strcmp(cmd, "D") == 0) + error = updater_delete(ctx, line); + else { + lprintf(-1, "Updater: Unknown command: " + "\"%s\"\n", cmd); + return (-1); + } + if (error) + return (-1); + } + if (line == NULL) return (-1); + return (0); +} + +static int +updater_delete(struct context *ctx, char *line) +{ + struct coll *coll; + char *file, *name; + int error; + + coll = ctx->coll; + name = proto_get_ascii(&line); + /* XXX - destDir handling */ + if (coll->co_options & CO_DELETE) { + file = checkoutpath(coll->co_prefix, name); + if (file == NULL) { + lprintf(-1, "Updater: Bad filename \"%s\"\n", name); + return (-1); + } + lprintf(1, " Delete %s\n", file + strlen(coll->co_prefix) + 1); + error = unlink(file); + if (error) { + free(file); + return (error); + } + updater_prunedirs(coll->co_prefix, file); + free(file); } - lprintf(1, " Delete %s\n", file + strlen(coll->co_prefix) + 1); - error = unlink(file); + error = status_delete(ctx->st, name, 0); if (error) { - free(file); - return (error); + /* XXX */ + return (-1); } - updater_prunedirs(coll->co_prefix, file); - free(file); return (0); } static int -updater_diff(struct coll *coll, struct stream *rd, char *line) +updater_diff_parseln(char *line, struct context *ctx) +{ + struct statusrec *sr; + char *cp, *name, *tag, *date; + char *expand, *attr, *cksum; + + cp = line; + name = proto_get_ascii(&cp); + tag = proto_get_ascii(&cp); + date = proto_get_ascii(&cp); + proto_get_ascii(&cp); /* XXX - oldRevNum */ + proto_get_ascii(&cp); /* XXX - fromAttic */ + proto_get_ascii(&cp); /* XXX - logLines */ + expand = proto_get_ascii(&cp); + attr = proto_get_ascii(&cp); + cksum = proto_get_ascii(&cp); + if (cksum == NULL || cp != NULL) + return (-1); + + sr = &ctx->sr; + sr->sr_type = SR_CHECKOUTLIVE; + sr->sr_file = strdup(name); + sr->sr_date = strdup(date); + sr->sr_tag = strdup(tag); + if (sr->sr_file == NULL || sr->sr_date == NULL || sr->sr_tag == NULL) + err(1, "strdup"); + sr->sr_serverattr = fattr_decode(attr); + + if (strcmp(expand, ".") == 0) + ctx->expand = EXPAND_DEFAULT; + else if (strcmp(expand, "kv") == 0) + ctx->expand = EXPAND_KEYVALUE; + else if (strcmp(expand, "kvl") == 0) + ctx->expand = EXPAND_KEYVALUELOCKER; + else if (strcmp(expand, "k") == 0) + ctx->expand = EXPAND_KEY; + else if (strcmp(expand, "o") == 0) + ctx->expand = EXPAND_OLD; + else if (strcmp(expand, "b") == 0) + ctx->expand = EXPAND_BINARY; + else if (strcmp(expand, "v") == 0) + ctx->expand = EXPAND_VALUE; + else + return (-1); + + ctx->tag = strdup(tag); + ctx->cksum = strdup(cksum); + if (ctx->cksum == NULL || ctx->tag == NULL) + err(1, "strdup"); + return (0); +} + +static void +context_fini(struct context *ctx) +{ + struct statusrec *sr; + + sr = &ctx->sr; + free(ctx->author); + ctx->author = NULL; + free(ctx->cksum); + ctx->cksum = NULL; + free(ctx->tag); + ctx->tag = NULL; + stream_close(ctx->from); + ctx->from = NULL; + stream_close(ctx->to); + ctx->to = NULL; + free(sr->sr_file); + free(sr->sr_tag); + free(sr->sr_date); + free(sr->sr_revnum); + free(sr->sr_revdate); + fattr_free(sr->sr_clientattr); + fattr_free(sr->sr_serverattr); + memset(sr, 0, sizeof(*sr)); +} + +static int +updater_diff(struct context *ctx, char *line) { char md5[MD5_DIGEST_SIZE]; - struct update up; - struct fattr *rcsattr, *fa; - char *author, *expand, *tag, *rcsfile, *revnum, *revdate, *path; - char *attr, *cp, *cksum, *line2, *line3, *tok, *topath; + struct coll *coll; + struct statusrec *sr; + struct fattr *fa; + char *author, *path, *revnum, *revdate; + char *tok, *topath; int error; - line3 = NULL; - rcsattr = NULL; path = NULL; topath = NULL; - memset(&up, 0, sizeof(struct update)); - up.coll = coll; + sr = &ctx->sr; + coll = ctx->coll; - line2 = strdup(line); - if (line2 == NULL) - err(1, "strdup"); - cp = line2; - rcsfile = proto_getstr(&cp); - tag = proto_getstr(&cp); - proto_getstr(&cp); /* XXX - date */ - proto_getstr(&cp); /* XXX - orig revnum */ - proto_getstr(&cp); /* XXX - from attic */ - proto_getstr(&cp); /* XXX - loglines */ - expand = proto_getstr(&cp); - attr = proto_getstr(&cp); - cksum = proto_getstr(&cp); - if (cksum == NULL || cp != NULL) + error = updater_diff_parseln(line, ctx); + if (error) { + lprintf(-1, "Updater: Parse error\n"); goto bad; - path = checkoutpath(coll->co_prefix, rcsfile); + } + + path = checkoutpath(coll->co_prefix, sr->sr_file); if (path == NULL) { - lprintf(-1, "Updater: bad filename %s\n", rcsfile); + lprintf(-1, "Updater: Bad filename \"%s\"\n", sr->sr_file); goto bad; } - if (strcmp(expand, ".") == 0) - up.expand = EXPAND_DEFAULT; - else if (strcmp(expand, "kv") == 0) - up.expand = EXPAND_KEYVALUE; - else if (strcmp(expand, "kvl") == 0) - up.expand = EXPAND_KEYVALUELOCKER; - else if (strcmp(expand, "k") == 0) - up.expand = EXPAND_KEY; - else if (strcmp(expand, "o") == 0) - up.expand = EXPAND_OLD; - else if (strcmp(expand, "b") == 0) - up.expand = EXPAND_BINARY; - else if (strcmp(expand, "v") == 0) - up.expand = EXPAND_VALUE; - else { - lprintf(-1, "Updater: invalid expansion mode \"%s\"\n", - expand); - goto bad; - } - rcsattr = fattr_decode(attr); - if (rcsattr == NULL) { - lprintf(-1, "Updater: invalid file attributes \"%s\"\n", - attr); - goto bad; - } - up.rcsattr = rcsattr; - up.rcsfile = rcsfile; - if (strcmp(tag, ".") != 0) - up.tag = tag; lprintf(1, " Edit %s\n", path + strlen(coll->co_prefix) + 1); - while ((line = stream_getln(rd, NULL)) != NULL) { + while ((line = stream_getln(ctx->rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; - tok = proto_getstr(&line); + tok = proto_get_ascii(&line); if (strcmp(tok, "D") != 0) goto bad; - free(line3); - line3 = strdup(line); - if (line3 == NULL) - err(1, "strdup"); - cp = line3; - revnum = proto_getstr(&cp); - proto_getstr(&cp); /* XXX - diffbase */ - revdate = proto_getstr(&cp); - author = proto_getstr(&cp); - if (author == NULL || cp != NULL) + revnum = proto_get_ascii(&line); + proto_get_ascii(&line); /* XXX - diffbase */ + revdate = proto_get_ascii(&line); + author = proto_get_ascii(&line); + if (author == NULL || line != NULL) goto bad; - up.revnum = revnum; - up.revdate = revdate; - up.author = author; - up.stream = rd; - if (up.from == NULL) { + if (sr->sr_revnum != NULL) + free(sr->sr_revnum); + if (sr->sr_revdate != NULL) + free(sr->sr_revdate); + if (ctx->author != NULL) + free(ctx->author); + ctx->author = strdup(author); + sr->sr_revnum = strdup(revnum); + sr->sr_revdate = strdup(revdate); + if (ctx->author == NULL || sr->sr_revnum == NULL || + sr->sr_revdate == NULL) + err(1, "strdup"); + if (ctx->from == NULL) { /* First patch, the "origin" file is the one we have. */ - up.from = stream_open_file(path, O_RDONLY); - if (up.from == NULL) + ctx->from = stream_open_file(path, O_RDONLY); + if (ctx->from == NULL) goto bad; } else { /* Subsequent patches. */ - stream_close(up.from); - up.from = up.to; - stream_rewind(up.from); + stream_close(ctx->from); + ctx->from = ctx->to; + stream_rewind(ctx->from); /* XXX */ if (unlink(topath) == -1) warn("unlink"); free(topath); } - topath = updater_maketmp(coll->co_prefix, up.rcsfile); + topath = updater_maketmp(coll->co_prefix, sr->sr_file); if (topath == NULL) { perror("Cannot create temporary file"); goto bad; } - up.to = stream_open_file(topath, O_RDWR | O_CREAT | O_EXCL, + ctx->to = stream_open_file(topath,O_RDWR | O_CREAT | O_EXCL, 0600); - if (up.to == NULL) + if (ctx->to == NULL) goto bad; lprintf(2, " Add delta %s %s %s\n", revnum, revdate, author); - error = updater_diff_batch(&up); + error = updater_diff_batch(ctx); if (error) goto bad; } if (line == NULL) goto bad; - fa = fattr_dup(rcsattr); + fa = fattr_dup(sr->sr_serverattr); fattr_maskout(fa, FA_MODTIME); updater_install(coll, fa, topath, path); - fattr_free(fa); - /* XXX - Compute MD5 while writing the file. */ + sr->sr_clientattr = fa; + error = status_put(ctx->st, sr); + if (error) { + lprintf(-1, "Updater: status_put() failed!\n"); + goto bad; + } + if (MD5file(path, md5) == -1) { lprintf(-1, "%s: MD5file() failed\n", __func__); goto bad; } - if (strcmp(cksum, md5) != 0) { + if (strcmp(ctx->cksum, md5) != 0) { lprintf(-1, "Updater: Bad MD5 checksum for \"%s\"\n", path); goto bad; } - stream_close(up.from); - stream_close(up.to); + context_fini(ctx); free(topath); - fattr_free(rcsattr); free(path); - free(line3); - free(line2); return (0); bad: - stream_close(up.from); - stream_close(up.to); + context_fini(ctx); free(topath); - fattr_free(rcsattr); free(path); - free(line3); - free(line2); return (-1); } static int -updater_diff_batch(struct update *up) +updater_diff_batch(struct context *ctx) { struct stream *rd; char *tok, *line; int error; - rd = up->stream; + rd = ctx->rd; while ((line = stream_getln(rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; - tok = proto_getstr(&line); + tok = proto_get_ascii(&line); if (tok == NULL) goto bad; if (strcmp(tok, "L") == 0) { @@ -347,53 +442,57 @@ updater_diff_batch(struct update *up) if (line == NULL) goto bad; } else if (strcmp(tok, "S") == 0) { - tok = proto_getstr(&line); + tok = proto_get_ascii(&line); if (tok == NULL || line != NULL) goto bad; - free(up->state); - up->state = strdup(tok); - if (up->state == NULL) + free(ctx->state); + ctx->state = strdup(tok); + if (ctx->state == NULL) err(1, "strdup"); } else if (strcmp(tok, "T") == 0) { - error = updater_diff_apply(up); + error = updater_diff_apply(ctx); if (error) { - free(up->state); - up->state = NULL; + free(ctx->state); + ctx->state = NULL; return (error); } } } if (line == NULL) goto bad; - free(up->state); - up->state = NULL; + free(ctx->state); + ctx->state = NULL; return (0); bad: lprintf(-1, "Updater: Protocol error\n"); - free(up->state); - up->state = NULL; + free(ctx->state); + ctx->state = NULL; return (-1); } int -updater_diff_apply(struct update *up) +updater_diff_apply(struct context *ctx) { struct diff diff; + struct coll *coll; + struct statusrec *sr; int error; - /* XXX - This is stupid, both structs should be merged. */ - diff.d_orig = up->from; - diff.d_to = up->to; - diff.d_diff = up->stream; - diff.d_author = up->author; - diff.d_cvsroot = up->coll->co_cvsroot; - diff.d_rcsfile = up->rcsfile; - diff.d_revnum = up->revnum; - diff.d_revdate = up->revdate; - diff.d_state = up->state; - diff.d_tag = up->tag; - diff.d_expand = up->expand; - error = diff_apply(&diff, up->coll->co_keyword); + sr = &ctx->sr; + coll = ctx->coll; + + diff.d_orig = ctx->from; + diff.d_to = ctx->to; + diff.d_diff = ctx->rd; + diff.d_author = ctx->author; + diff.d_cvsroot = coll->co_cvsroot; + diff.d_rcsfile = sr->sr_file; + diff.d_revnum = sr->sr_revnum; + diff.d_revdate = sr->sr_revdate; + diff.d_state = ctx->state; + diff.d_tag = ctx->tag; + diff.d_expand = ctx->expand; + error = diff_apply(&diff, coll->co_keyword); if (error) { lprintf(-1, "Updater: Bad diff from server\n"); return (-1); @@ -402,22 +501,26 @@ updater_diff_apply(struct update *up) } static int -updater_checkout(struct coll *coll, struct stream *rd, char *line) +updater_checkout(struct context *ctx, char *line) { char md5[MD5_DIGEST_SIZE]; + struct stream *rd; + struct coll *coll; struct fattr *fa; struct stream *to; char *attr, *cksum, *cmd, *file, *rcsfile; + char *tag, *date, *revnum, *revdate; size_t size; int error, first; - rcsfile = proto_getstr(&line); - /* I'll need those when the status file is supported. */ - proto_getstr(&line); /* tag */ - proto_getstr(&line); /* date */ - proto_getstr(&line); /* revnum */ - proto_getstr(&line); /* revdate */ - attr = proto_getstr(&line); + rd = ctx->rd; + coll = ctx->coll; + rcsfile = proto_get_ascii(&line); + tag = proto_get_ascii(&line); + date = proto_get_ascii(&line); + revnum = proto_get_ascii(&line); + revdate = proto_get_ascii(&line); + attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (-1); @@ -436,7 +539,7 @@ updater_checkout(struct coll *coll, stru lprintf(1, " Checkout %s\n", file + strlen(coll->co_prefix) + 1); error = updater_makedirs(file); if (error) { - lprintf(-1, "Updater: Can't create directory hierarchy: %s\n", + lprintf(-1, "Updater: Cannot create directory hierarchy: %s\n", strerror(errno)); goto bad; } @@ -474,8 +577,8 @@ updater_checkout(struct coll *coll, stru stream_close(to); /* Get the checksum line. */ line = stream_getln(rd, NULL); - cmd = proto_getstr(&line); - cksum = proto_getstr(&line); + cmd = proto_get_ascii(&line); + cksum = proto_get_ascii(&line); if (cksum == NULL || line != NULL || strcmp(cmd, "5") != 0) { goto bad; @@ -566,6 +669,6 @@ updater_install(struct coll *coll, struc tmp = fattr_forcheckout(fa, coll->co_umask); fattr_override(fa, tmp, FA_MASK); fattr_free(tmp); - fattr_install(fa, from, to); + fattr_install(fa, to, from); return (0); }