diff options
Diffstat (limited to 'usr.bin/csup/updater.c')
-rw-r--r-- | usr.bin/csup/updater.c | 2043 |
1 files changed, 2043 insertions, 0 deletions
diff --git a/usr.bin/csup/updater.c b/usr.bin/csup/updater.c new file mode 100644 index 0000000..e95ec10 --- /dev/null +++ b/usr.bin/csup/updater.c @@ -0,0 +1,2043 @@ +/*- + * 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 detailer 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 = 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); +} |