summaryrefslogtreecommitdiffstats
path: root/contrib/csup/updater.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/csup/updater.c')
-rw-r--r--contrib/csup/updater.c1012
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;
+ }
+}
OpenPOWER on IntegriCloud