diff options
Diffstat (limited to 'contrib/csup/updater.c')
-rw-r--r-- | contrib/csup/updater.c | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/contrib/csup/updater.c b/contrib/csup/updater.c new file mode 100644 index 0000000..235bfca --- /dev/null +++ b/contrib/csup/updater.c @@ -0,0 +1,1012 @@ +/*- + * 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 <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 "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. */ + +/* Everything needed to update a file. */ +struct file_update { + struct statusrec srbuf; + char *destpath; + 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 expand; +}; + +struct updater { + struct config *config; + struct stream *rd; + char *errmsg; +}; + +static struct file_update *fup_new(struct coll *, struct status *); +static int fup_prepare(struct file_update *, char *); +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 void updater_delete(struct file_update *); +static int updater_checkout(struct updater *, struct file_update *, int); +static int updater_setattrs(struct updater *, struct file_update *, + char *, char *, char *, char *, char *, struct fattr *); +static void updater_checkmd5(struct updater *, struct file_update *, + const char *, int); +static int updater_updatefile(struct updater *, struct file_update *fup, + const char *, const 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 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) +{ + struct coll *coll; + + coll = fup->coll; + fup->destpath = checkoutpath(coll->co_prefix, name); + 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; + } + 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; + + error = updater_batch(up, 0); + + /* + * Make sure to close the fixups even in case of an error, + * so that the lister thread doesn't block indefinitely. + */ + fixups_close(up->config->fixups); + if (!error) + error = updater_batch(up, 1); + switch (error) { + case UPDATER_ERR_PROTO: + xasprintf(&args->errmsg, "Updater failed: Protocol error"); + args->status = STATUS_FAILURE; + break; + case UPDATER_ERR_MSG: + xasprintf(&args->errmsg, "Updater failed: %s", up->errmsg); + free(up->errmsg); + args->status = STATUS_FAILURE; + break; + case UPDATER_ERR_READ: + if (stream_eof(up->rd)) { + xasprintf(&args->errmsg, "Updater failed: " + "Premature EOF from server"); + } else { + xasprintf(&args->errmsg, "Updater failed: " + "Network read failure: %s", strerror(errno)); + } + args->status = STATUS_TRANSIENTFAILURE; + break; + 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); + fup = fup_new(coll, st); + if (st == NULL) { + fup_free(fup); + up->errmsg = errmsg; + return (UPDATER_ERR_MSG); + } + 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 *cmd, *line, *msg, *attr; + char *name, *tag, *date, *revdate; + char *expand, *wantmd5, *revnum; + time_t t; + int 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); + 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); + 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) + updater_delete(fup); + + 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); + if (error) + return (UPDATER_ERR_PROTO); + + fup->wantmd5 = xstrdup(wantmd5); + 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); + if (error) + return (UPDATER_ERR_PROTO); + updater_delete(fup); + 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); + if (error) + return (UPDATER_ERR_PROTO); + 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); + if (error) + return (UPDATER_ERR_PROTO); + updater_delete(fup); + error = status_delete(fup->st, name, 0); + if (error) { + up->errmsg = status_errmsg(fup->st); + return (UPDATER_ERR_MSG); + } + 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 void +updater_delete(struct file_update *fup) +{ + struct coll *coll; + int error; + + /* XXX - delete limit handling */ + coll = fup->coll; + if (coll->co_options & CO_DELETE) { + lprintf(1, " Delete %s\n", fup->coname); + error = fattr_delete(fup->destpath); + if (error) { + lprintf(-1, "Cannot delete \"%s\": %s\n", + fup->destpath, strerror(errno)); + return; + } + if (coll->co_options & CO_CHECKOUTMODE) + updater_prunedirs(coll->co_prefix, fup->destpath); + } else { + lprintf(1," NoDelete %s\n", fup->coname); + } +} + +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); +} + +/* + * Check that the file we created/updated has a correct MD5 checksum. + * If it doesn't and that this is not a fixup update, add a fixup + * request to checkout the whole file. If it's already a fixup update, + * we just fail. + */ +static void +updater_checkmd5(struct updater *up, struct file_update *fup, const char *md5, + int isfixup) +{ + struct statusrec *sr; + + sr = &fup->srbuf; + if (strcmp(fup->wantmd5, md5) == 0) + return; + if (isfixup) { + lprintf(-1, "%s: Checksum mismatch -- file not updated\n", + fup->destpath); + return; + } + lprintf(-1, "%s: Checksum mismatch -- will transfer entire file\n", + fup->destpath); + fixups_put(up->config->fixups, fup->coll, sr->sr_file); +} + +static int +updater_updatefile(struct updater *up, struct file_update *fup, const char *to, + const char *from) +{ + struct coll *coll; + struct status *st; + struct statusrec *sr; + struct fattr *fileattr; + int error, rv; + + coll = fup->coll; + sr = &fup->srbuf; + st = fup->st; + + fattr_umask(sr->sr_clientattr, coll->co_umask); + rv = fattr_install(sr->sr_clientattr, to, from); + if (rv == -1) { + if (from == NULL) + xasprintf(&up->errmsg, "Cannot install \"%s\": %s", + to, strerror(errno)); + else + xasprintf(&up->errmsg, + "Cannot install \"%s\" to \"%s\": %s", + from, to, 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(to, FATTR_NOFOLLOW); + if (fileattr == NULL) { + xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", to, + 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); +} + +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, *temppath; + int error; + + temppath = NULL; + 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) { + error = UPDATER_ERR_PROTO; + goto bad; + } + 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) { + error = UPDATER_ERR_PROTO; + goto bad; + } + 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)); + error = UPDATER_ERR_MSG; + goto bad; + } + } else { + /* Subsequent patches. */ + stream_close(fup->orig); + fup->orig = fup->to; + stream_rewind(fup->orig); + unlink(temppath); + free(temppath); + } + temppath = tempname(path); + fup->to = stream_open_file(temppath, + O_RDWR | O_CREAT | O_EXCL, 0600); + if (fup->to == NULL) { + xasprintf(&up->errmsg, "%s: Cannot open: %s", + temppath, strerror(errno)); + error = UPDATER_ERR_MSG; + goto bad; + } + lprintf(2, " Add delta %s %s %s\n", sr->sr_revnum, + sr->sr_revdate, fup->author); + error = updater_diff_batch(up, fup); + if (error) + goto bad; + } + if (line == NULL) { + error = UPDATER_ERR_READ; + goto bad; + } + + 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; + + error = updater_updatefile(up, fup, path, temppath); + if (error) + goto bad; + + if (MD5_File(path, md5) == -1) { + xasprintf(&up->errmsg, + "Cannot calculate checksum for \"%s\": %s", + path, strerror(errno)); + error = UPDATER_ERR_MSG; + goto bad; + } + updater_checkmd5(up, fup, md5, 0); + free(temppath); + return (0); +bad: + assert(error); + if (temppath != NULL) + free(temppath); + return (error); +} + +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); + if (error) { + /* XXX Bad error message */ + xasprintf(&up->errmsg, "Bad diff from server"); + return (UPDATER_ERR_MSG); + } + return (0); +} + +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; + char *cmd, *path, *line; + size_t size; + ssize_t nbytes; + 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(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (to == NULL) { + xasprintf(&up->errmsg, "%s: Cannot create: %s", + path, 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; + } + stream_write(to, line, size); + 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); + updater_checkmd5(up, fup, md5, isfixup); + fup->wantmd5 = NULL; /* So that it doesn't get freed. */ + error = updater_updatefile(up, fup, path, NULL); + if (error) + return (error); + return (0); +bad: + xasprintf(&up->errmsg, "%s: Cannot write: %s", path, 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; + } +} |