/*- * Copyright (c) 2003-2006, Maxime Henrion * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "diff.h" #include "fattr.h" #include "fixups.h" #include "keyword.h" #include "updater.h" #include "misc.h" #include "mux.h" #include "proto.h" #include "rcsfile.h" #include "status.h" #include "stream.h" /* Internal error codes. */ #define UPDATER_ERR_PROTO (-1) /* Protocol error. */ #define UPDATER_ERR_MSG (-2) /* Error is in updater->errmsg. */ #define UPDATER_ERR_READ (-3) /* Error reading from server. */ #define UPDATER_ERR_DELETELIM (-4) /* File deletion limit exceeded. */ #define BUFSIZE 4096 /* Everything needed to update a file. */ struct file_update { struct statusrec srbuf; char *destpath; char *temppath; char *origpath; char *coname; /* Points somewhere in destpath. */ char *wantmd5; struct coll *coll; struct status *st; /* Those are only used for diff updating. */ char *author; struct stream *orig; struct stream *to; int attic; int expand; }; struct updater { struct config *config; struct stream *rd; char *errmsg; int deletecount; }; static struct file_update *fup_new(struct coll *, struct status *); static int fup_prepare(struct file_update *, char *, int); static void fup_cleanup(struct file_update *); static void fup_free(struct file_update *); static void updater_prunedirs(char *, char *); static int updater_batch(struct updater *, int); static int updater_docoll(struct updater *, struct file_update *, int); static int updater_delete(struct updater *, struct file_update *); static void updater_deletefile(const char *); static int updater_checkout(struct updater *, struct file_update *, int); static int updater_addfile(struct updater *, struct file_update *, char *, int); int updater_addelta(struct rcsfile *, struct stream *, char *); static int updater_setattrs(struct updater *, struct file_update *, char *, char *, char *, char *, char *, struct fattr *); static int updater_setdirattrs(struct updater *, struct coll *, struct file_update *, char *, char *); static int updater_updatefile(struct updater *, struct file_update *fup, const char *, int); static int updater_updatenode(struct updater *, struct coll *, struct file_update *, char *, char *); static int updater_diff(struct updater *, struct file_update *); static int updater_diff_batch(struct updater *, struct file_update *); static int updater_diff_apply(struct updater *, struct file_update *, char *); static int updater_rcsedit(struct updater *, struct file_update *, char *, char *); int updater_append_file(struct updater *, struct file_update *, off_t); static int updater_rsync(struct updater *, struct file_update *, size_t); static int updater_read_checkout(struct stream *, struct stream *); static struct file_update * fup_new(struct coll *coll, struct status *st) { struct file_update *fup; fup = xmalloc(sizeof(struct file_update)); memset(fup, 0, sizeof(*fup)); fup->coll = coll; fup->st = st; return (fup); } static int fup_prepare(struct file_update *fup, char *name, int attic) { struct coll *coll; coll = fup->coll; fup->attic = 0; fup->origpath = NULL; if (coll->co_options & CO_CHECKOUTMODE) fup->destpath = checkoutpath(coll->co_prefix, name); else { fup->destpath = cvspath(coll->co_prefix, name, attic); fup->origpath = atticpath(coll->co_prefix, name); /* If they're equal, we don't need special care. */ if (fup->origpath != NULL && strcmp(fup->origpath, fup->destpath) == 0) { free(fup->origpath); fup->origpath = NULL; } fup->attic = attic; } if (fup->destpath == NULL) return (-1); fup->coname = fup->destpath + coll->co_prefixlen + 1; return (0); } /* Called after each file update to reinit the structure. */ static void fup_cleanup(struct file_update *fup) { struct statusrec *sr; sr = &fup->srbuf; if (fup->destpath != NULL) { free(fup->destpath); fup->destpath = NULL; } if (fup->temppath != NULL) { free(fup->temppath); fup->temppath = NULL; } if (fup->origpath != NULL) { free(fup->origpath); fup->origpath = NULL; } fup->coname = NULL; if (fup->author != NULL) { free(fup->author); fup->author = NULL; } fup->expand = 0; if (fup->wantmd5 != NULL) { free(fup->wantmd5); fup->wantmd5 = NULL; } if (fup->orig != NULL) { stream_close(fup->orig); fup->orig = NULL; } if (fup->to != NULL) { stream_close(fup->to); fup->to = NULL; } if (sr->sr_file != NULL) free(sr->sr_file); if (sr->sr_tag != NULL) free(sr->sr_tag); if (sr->sr_date != NULL) free(sr->sr_date); if (sr->sr_revnum != NULL) free(sr->sr_revnum); if (sr->sr_revdate != NULL) free(sr->sr_revdate); fattr_free(sr->sr_clientattr); fattr_free(sr->sr_serverattr); memset(sr, 0, sizeof(*sr)); } static void fup_free(struct file_update *fup) { fup_cleanup(fup); free(fup); } void * updater(void *arg) { struct thread_args *args; struct updater upbuf, *up; int error; args = arg; up = &upbuf; up->config = args->config; up->rd = args->rd; up->errmsg = NULL; up->deletecount = 0; error = updater_batch(up, 0); /* * Make sure to close the fixups even in case of an error, * so that the lister thread doesn't block indefinitely. */ fixups_close(up->config->fixups); if (!error) error = updater_batch(up, 1); switch (error) { case UPDATER_ERR_PROTO: xasprintf(&args->errmsg, "Updater failed: Protocol error"); args->status = STATUS_FAILURE; break; case UPDATER_ERR_MSG: xasprintf(&args->errmsg, "Updater failed: %s", up->errmsg); free(up->errmsg); args->status = STATUS_FAILURE; break; case UPDATER_ERR_READ: if (stream_eof(up->rd)) { xasprintf(&args->errmsg, "Updater failed: " "Premature EOF from server"); } else { xasprintf(&args->errmsg, "Updater failed: " "Network read failure: %s", strerror(errno)); } args->status = STATUS_TRANSIENTFAILURE; break; case UPDATER_ERR_DELETELIM: xasprintf(&args->errmsg, "Updater failed: " "File deletion limit exceeded"); args->status = STATUS_FAILURE; break; default: assert(error == 0); args->status = STATUS_SUCCESS; }; return (NULL); } static int updater_batch(struct updater *up, int isfixups) { struct stream *rd; struct coll *coll; struct status *st; struct file_update *fup; char *line, *cmd, *errmsg, *collname, *release; int error; rd = up->rd; STAILQ_FOREACH(coll, &up->config->colls, co_next) { if (coll->co_options & CO_SKIP) continue; umask(coll->co_umask); line = stream_getln(rd, NULL); if (line == NULL) return (UPDATER_ERR_READ); cmd = proto_get_ascii(&line); collname = proto_get_ascii(&line); release = proto_get_ascii(&line); if (release == NULL || line != NULL) return (UPDATER_ERR_PROTO); if (strcmp(cmd, "COLL") != 0 || strcmp(collname, coll->co_name) != 0 || strcmp(release, coll->co_release) != 0) return (UPDATER_ERR_PROTO); if (!isfixups) lprintf(1, "Updating collection %s/%s\n", coll->co_name, coll->co_release); if (coll->co_options & CO_COMPRESS) stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); st = status_open(coll, coll->co_scantime, &errmsg); if (st == NULL) { up->errmsg = errmsg; return (UPDATER_ERR_MSG); } fup = fup_new(coll, st); error = updater_docoll(up, fup, isfixups); status_close(st, &errmsg); fup_free(fup); if (errmsg != NULL) { /* Discard previous error. */ if (up->errmsg != NULL) free(up->errmsg); up->errmsg = errmsg; return (UPDATER_ERR_MSG); } if (error) return (error); if (coll->co_options & CO_COMPRESS) stream_filter_stop(rd); } line = stream_getln(rd, NULL); if (line == NULL) return (UPDATER_ERR_READ); if (strcmp(line, ".") != 0) return (UPDATER_ERR_PROTO); return (0); } static int updater_docoll(struct updater *up, struct file_update *fup, int isfixups) { struct stream *rd; struct coll *coll; struct statusrec srbuf, *sr; struct fattr *rcsattr, *tmp; char *attr, *cmd, *blocksize, *line, *msg; char *name, *tag, *date, *revdate; char *expand, *wantmd5, *revnum; char *optstr, *rcsopt, *pos; time_t t; off_t position; int attic, error, needfixupmsg; error = 0; rd = up->rd; coll = fup->coll; needfixupmsg = isfixups; while ((line = stream_getln(rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; memset(&srbuf, 0, sizeof(srbuf)); if (needfixupmsg) { lprintf(1, "Applying fixups for collection %s/%s\n", coll->co_name, coll->co_release); needfixupmsg = 0; } cmd = proto_get_ascii(&line); if (cmd == NULL || strlen(cmd) != 1) return (UPDATER_ERR_PROTO); switch (cmd[0]) { case 'T': /* Update recorded information for checked-out file. */ name = proto_get_ascii(&line); tag = proto_get_ascii(&line); date = proto_get_ascii(&line); revnum = proto_get_ascii(&line); revdate = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); rcsattr = fattr_decode(attr); if (rcsattr == NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); error = updater_setattrs(up, fup, name, tag, date, revnum, revdate, rcsattr); fattr_free(rcsattr); if (error) return (error); break; case 'c': /* Checkout dead file. */ name = proto_get_ascii(&line); tag = proto_get_ascii(&line); date = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); /* Theoritically, the file does not exist on the client. Just to make sure, we'll delete it here, if it exists. */ if (access(fup->destpath, F_OK) == 0) { error = updater_delete(up, fup); if (error) return (error); } sr = &srbuf; sr->sr_type = SR_CHECKOUTDEAD; sr->sr_file = name; sr->sr_tag = tag; sr->sr_date = date; sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); error = status_put(fup->st, sr); fattr_free(sr->sr_serverattr); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'U': /* Update live checked-out file. */ name = proto_get_ascii(&line); tag = proto_get_ascii(&line); date = proto_get_ascii(&line); proto_get_ascii(&line); /* XXX - oldRevNum */ proto_get_ascii(&line); /* XXX - fromAttic */ proto_get_ascii(&line); /* XXX - logLines */ expand = proto_get_ascii(&line); attr = proto_get_ascii(&line); wantmd5 = proto_get_ascii(&line); if (wantmd5 == NULL || line != NULL) return (UPDATER_ERR_PROTO); sr = &fup->srbuf; sr->sr_type = SR_CHECKOUTLIVE; sr->sr_file = xstrdup(name); sr->sr_date = xstrdup(date); sr->sr_tag = xstrdup(tag); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); fup->expand = keyword_decode_expand(expand); if (fup->expand == -1) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); fup->wantmd5 = xstrdup(wantmd5); fup->temppath = tempname(fup->destpath); error = updater_diff(up, fup); if (error) return (error); break; case 'u': /* Update dead checked-out file. */ name = proto_get_ascii(&line); tag = proto_get_ascii(&line); date = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); error = updater_delete(up, fup); if (error) return (error); sr = &srbuf; sr->sr_type = SR_CHECKOUTDEAD; sr->sr_file = name; sr->sr_tag = tag; sr->sr_date = date; sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); error = status_put(fup->st, sr); fattr_free(sr->sr_serverattr); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'C': case 'Y': /* Checkout file. */ name = proto_get_ascii(&line); tag = proto_get_ascii(&line); date = proto_get_ascii(&line); revnum = proto_get_ascii(&line); revdate = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); sr = &fup->srbuf; sr->sr_type = SR_CHECKOUTLIVE; sr->sr_file = xstrdup(name); sr->sr_tag = xstrdup(tag); sr->sr_date = xstrdup(date); sr->sr_revnum = xstrdup(revnum); sr->sr_revdate = xstrdup(revdate); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); t = rcsdatetotime(revdate); if (t == -1) return (UPDATER_ERR_PROTO); sr->sr_clientattr = fattr_new(FT_FILE, t); tmp = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); fattr_override(sr->sr_clientattr, tmp, FA_MASK); fattr_free(tmp); fattr_mergedefault(sr->sr_clientattr); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); fup->temppath = tempname(fup->destpath); if (*cmd == 'Y') error = updater_checkout(up, fup, 1); else error = updater_checkout(up, fup, 0); if (error) return (error); break; case 'D': /* Delete file. */ name = proto_get_ascii(&line); if (name == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); error = updater_delete(up, fup); if (error) return (error); error = status_delete(fup->st, name, 0); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'A': case 'a': case 'R': name = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (name == NULL || attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); attic = (cmd[0] == 'a'); error = fup_prepare(fup, name, attic); if (error) return (UPDATER_ERR_PROTO); fup->temppath = tempname(fup->destpath); sr = &fup->srbuf; sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE; sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); if (attic) lprintf(1, " Create %s -> Attic\n", name); else lprintf(1, " Create %s\n", name); error = updater_addfile(up, fup, attr, 0); if (error) return (error); break; case 'r': name = proto_get_ascii(&line); attr = proto_get_ascii(&line); blocksize = proto_get_ascii(&line); wantmd5 = proto_get_ascii(&line); if (name == NULL || attr == NULL || blocksize == NULL || wantmd5 == NULL) { return (UPDATER_ERR_PROTO); } error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); fup->wantmd5 = xstrdup(wantmd5); fup->temppath = tempname(fup->destpath); sr = &fup->srbuf; sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); sr->sr_type = SR_FILELIVE; if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); error = updater_rsync(up, fup, strtol(blocksize, NULL, 10)); if (error) return (error); break; case 'I': /* * Create directory and add DirDown entry in status * file. */ name = proto_get_ascii(&line); if (name == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); sr = &fup->srbuf; sr->sr_type = SR_DIRDOWN; sr->sr_file = xstrdup(name); sr->sr_serverattr = NULL; sr->sr_clientattr = fattr_new(FT_DIRECTORY, -1); fattr_mergedefault(sr->sr_clientattr); error = mkdirhier(fup->destpath, coll->co_umask); if (error) return (UPDATER_ERR_PROTO); if (access(fup->destpath, F_OK) != 0) { lprintf(1, " Mkdir %s\n", name); error = fattr_makenode(sr->sr_clientattr, fup->destpath); if (error) return (UPDATER_ERR_PROTO); } error = status_put(fup->st, sr); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'i': /* Remove DirDown entry in status file. */ name = proto_get_ascii(&line); if (name == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); error = status_delete(fup->st, name, 0); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'J': /* * Set attributes of directory and update DirUp entry in * status file. */ name = proto_get_ascii(&line); if (name == NULL) return (UPDATER_ERR_PROTO); attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); error = updater_setdirattrs(up, coll, fup, name, attr); if (error) return (error); break; case 'j': /* * Remove directory and delete its DirUp entry in status * file. */ name = proto_get_ascii(&line); if (name == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); if (error) return (UPDATER_ERR_PROTO); lprintf(1, " Rmdir %s\n", name); updater_deletefile(fup->destpath); error = status_delete(fup->st, name, 0); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'L': case 'l': name = proto_get_ascii(&line); if (name == NULL) return (UPDATER_ERR_PROTO); attr = proto_get_ascii(&line); if (attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); attic = (cmd[0] == 'l'); sr = &fup->srbuf; sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE; sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); sr->sr_clientattr = fattr_decode(attr); if (sr->sr_serverattr == NULL || sr->sr_clientattr == NULL) return (UPDATER_ERR_PROTO); /* Save space. Described in detail in updatefile. */ if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) || fattr_getlinkcount(sr->sr_clientattr) <= 1) fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE); fattr_maskout(sr->sr_clientattr, FA_FLAGS); error = status_put(fup->st, sr); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } break; case 'N': case 'n': name = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (name == NULL || attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); attic = (cmd[0] == 'n'); error = fup_prepare(fup, name, attic); if (error) return (UPDATER_ERR_PROTO); sr = &fup->srbuf; sr->sr_type = (attic ? SR_FILEDEAD : SR_FILELIVE); sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); sr->sr_clientattr = fattr_new(FT_SYMLINK, -1); fattr_mergedefault(sr->sr_clientattr); fattr_maskout(sr->sr_clientattr, FA_FLAGS); error = updater_updatenode(up, coll, fup, name, attr); if (error) return (error); break; case 'V': case 'v': name = proto_get_ascii(&line); attr = proto_get_ascii(&line); optstr = proto_get_ascii(&line); wantmd5 = proto_get_ascii(&line); rcsopt = NULL; /* XXX: Not supported. */ if (attr == NULL || line != NULL || wantmd5 == NULL) return (UPDATER_ERR_PROTO); attic = (cmd[0] == 'v'); error = fup_prepare(fup, name, attic); if (error) return (UPDATER_ERR_PROTO); fup->temppath = tempname(fup->destpath); fup->wantmd5 = xstrdup(wantmd5); sr = &fup->srbuf; sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE; sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); error = 0; error = updater_rcsedit(up, fup, name, rcsopt); if (error) return (error); break; case 'X': case 'x': name = proto_get_ascii(&line); attr = proto_get_ascii(&line); if (name == NULL || attr == NULL || line != NULL) return (UPDATER_ERR_PROTO); attic = (cmd[0] == 'x'); error = fup_prepare(fup, name, attic); if (error) return (UPDATER_ERR_PROTO); fup->temppath = tempname(fup->destpath); sr = &fup->srbuf; sr->sr_type = attic ? SR_FILEDEAD : SR_FILELIVE; sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); lprintf(1, " Fixup %s\n", name); error = updater_addfile(up, fup, attr, 1); if (error) return (error); break; case 'Z': name = proto_get_ascii(&line); attr = proto_get_ascii(&line); pos = proto_get_ascii(&line); if (name == NULL || attr == NULL || pos == NULL || line != NULL) return (UPDATER_ERR_PROTO); error = fup_prepare(fup, name, 0); fup->temppath = tempname(fup->destpath); sr = &fup->srbuf; sr->sr_type = SR_FILELIVE; sr->sr_file = xstrdup(name); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); position = strtol(pos, NULL, 10); lprintf(1, " Append to %s\n", name); error = updater_append_file(up, fup, position); if (error) return (error); break; case '!': /* Warning from server. */ msg = proto_get_rest(&line); if (msg == NULL) return (UPDATER_ERR_PROTO); lprintf(-1, "Server warning: %s\n", msg); break; default: return (UPDATER_ERR_PROTO); } fup_cleanup(fup); } if (line == NULL) return (UPDATER_ERR_READ); return (0); } /* Delete file. */ static int updater_delete(struct updater *up, struct file_update *fup) { struct config *config; struct coll *coll; config = up->config; coll = fup->coll; if (coll->co_options & CO_DELETE) { lprintf(1, " Delete %s\n", fup->coname); if (config->deletelim >= 0 && up->deletecount >= config->deletelim) return (UPDATER_ERR_DELETELIM); up->deletecount++; updater_deletefile(fup->destpath); if (coll->co_options & CO_CHECKOUTMODE) updater_prunedirs(coll->co_prefix, fup->destpath); } else { lprintf(1," NoDelete %s\n", fup->coname); } return (0); } static void updater_deletefile(const char *path) { int error; error = fattr_delete(path); if (error && errno != ENOENT) { lprintf(-1, "Cannot delete \"%s\": %s\n", path, strerror(errno)); } } static int updater_setattrs(struct updater *up, struct file_update *fup, char *name, char *tag, char *date, char *revnum, char *revdate, struct fattr *rcsattr) { struct statusrec sr; struct status *st; struct coll *coll; struct fattr *fileattr, *fa; char *path; int error, rv; coll = fup->coll; st = fup->st; path = fup->destpath; fileattr = fattr_frompath(path, FATTR_NOFOLLOW); if (fileattr == NULL) { /* The file has vanished. */ error = status_delete(st, name, 0); if (error) { up->errmsg = status_errmsg(st); return (UPDATER_ERR_MSG); } return (0); } fa = fattr_forcheckout(rcsattr, coll->co_umask); fattr_override(fileattr, fa, FA_MASK); fattr_free(fa); rv = fattr_install(fileattr, path, NULL); if (rv == -1) { lprintf(1, " SetAttrs %s\n", fup->coname); fattr_free(fileattr); xasprintf(&up->errmsg, "Cannot set attributes for \"%s\": %s", path, strerror(errno)); return (UPDATER_ERR_MSG); } if (rv == 1) { lprintf(1, " SetAttrs %s\n", fup->coname); fattr_free(fileattr); fileattr = fattr_frompath(path, FATTR_NOFOLLOW); if (fileattr == NULL) { /* We're being very unlucky. */ error = status_delete(st, name, 0); if (error) { up->errmsg = status_errmsg(st); return (UPDATER_ERR_MSG); } return (0); } } fattr_maskout(fileattr, FA_COIGNORE); sr.sr_type = SR_CHECKOUTLIVE; sr.sr_file = name; sr.sr_tag = tag; sr.sr_date = date; sr.sr_revnum = revnum; sr.sr_revdate = revdate; sr.sr_clientattr = fileattr; sr.sr_serverattr = rcsattr; error = status_put(st, &sr); fattr_free(fileattr); if (error) { up->errmsg = status_errmsg(st); return (UPDATER_ERR_MSG); } return (0); } static int updater_updatefile(struct updater *up, struct file_update *fup, const char *md5, int isfixup) { struct coll *coll; struct status *st; struct statusrec *sr; struct fattr *fileattr; int error, rv; coll = fup->coll; sr = &fup->srbuf; st = fup->st; if (strcmp(fup->wantmd5, md5) != 0) { if (isfixup) { lprintf(-1, "%s: Checksum mismatch -- " "file not updated\n", fup->destpath); } else { lprintf(-1, "%s: Checksum mismatch -- " "will transfer entire file\n", fup->destpath); fixups_put(up->config->fixups, fup->coll, sr->sr_file); } if (coll->co_options & CO_KEEPBADFILES) lprintf(-1, "Bad version saved in %s\n", fup->temppath); else updater_deletefile(fup->temppath); return (0); } fattr_umask(sr->sr_clientattr, coll->co_umask); rv = fattr_install(sr->sr_clientattr, fup->destpath, fup->temppath); if (rv == -1) { xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s", fup->temppath, fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } /* XXX Executes */ /* * We weren't necessarily able to set all the file attributes to the * desired values, and any executes may have altered the attributes. * To make sure we record the actual attribute values, we fetch * them from the file. * * However, we preserve the link count as received from the * server. This is important for preserving hard links in mirror * mode. */ fileattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW); if (fileattr == NULL) { xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } fattr_override(fileattr, sr->sr_clientattr, FA_LINKCOUNT); fattr_free(sr->sr_clientattr); sr->sr_clientattr = fileattr; /* * To save space, don't write out the device and inode unless * the link count is greater than 1. These attributes are used * only for detecting hard links. If the link count is 1 then we * know there aren't any hard links. */ if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) || fattr_getlinkcount(sr->sr_clientattr) <= 1) fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE); if (coll->co_options & CO_CHECKOUTMODE) fattr_maskout(sr->sr_clientattr, FA_COIGNORE); error = status_put(st, sr); if (error) { up->errmsg = status_errmsg(st); return (UPDATER_ERR_MSG); } return (0); } /* * Update attributes of a directory. */ static int updater_setdirattrs(struct updater *up, struct coll *coll, struct file_update *fup, char *name, char *attr) { struct statusrec *sr; struct fattr *fa; int error, rv; sr = &fup->srbuf; sr->sr_type = SR_DIRUP; sr->sr_file = xstrdup(name); sr->sr_clientattr = fattr_decode(attr); sr->sr_serverattr = fattr_decode(attr); if (sr->sr_clientattr == NULL || sr->sr_serverattr == NULL) return (UPDATER_ERR_PROTO); fattr_mergedefault(sr->sr_clientattr); fattr_umask(sr->sr_clientattr, coll->co_umask); rv = fattr_install(sr->sr_clientattr, fup->destpath, NULL); lprintf(1, " SetAttrs %s\n", name); if (rv == -1) { xasprintf(&up->errmsg, "Cannot install \"%s\" to \"%s\": %s", fup->temppath, fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } /* * Now, make sure they were set and record what was set in the status * file. */ fa = fattr_frompath(fup->destpath, FATTR_NOFOLLOW); if (fa == NULL) { xasprintf(&up->errmsg, "Cannot open \%s\": %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } fattr_free(sr->sr_clientattr); fattr_maskout(fa, FA_FLAGS); sr->sr_clientattr = fa; error = status_put(fup->st, sr); if (error) { up->errmsg = status_errmsg(fup->st); return (UPDATER_ERR_MSG); } return (0); } static int updater_diff(struct updater *up, struct file_update *fup) { char md5[MD5_DIGEST_SIZE]; struct coll *coll; struct statusrec *sr; struct fattr *fa, *tmp; char *author, *path, *revnum, *revdate; char *line, *cmd; int error; coll = fup->coll; sr = &fup->srbuf; path = fup->destpath; lprintf(1, " Edit %s\n", fup->coname); while ((line = stream_getln(up->rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; cmd = proto_get_ascii(&line); if (cmd == NULL || strcmp(cmd, "D") != 0) return (UPDATER_ERR_PROTO); revnum = proto_get_ascii(&line); proto_get_ascii(&line); /* XXX - diffbase */ revdate = proto_get_ascii(&line); author = proto_get_ascii(&line); if (author == NULL || line != NULL) return (UPDATER_ERR_PROTO); if (sr->sr_revnum != NULL) free(sr->sr_revnum); if (sr->sr_revdate != NULL) free(sr->sr_revdate); if (fup->author != NULL) free(fup->author); sr->sr_revnum = xstrdup(revnum); sr->sr_revdate = xstrdup(revdate); fup->author = xstrdup(author); if (fup->orig == NULL) { /* First patch, the "origin" file is the one we have. */ fup->orig = stream_open_file(path, O_RDONLY); if (fup->orig == NULL) { xasprintf(&up->errmsg, "%s: Cannot open: %s", path, strerror(errno)); return (UPDATER_ERR_MSG); } } else { /* Subsequent patches. */ stream_close(fup->orig); fup->orig = fup->to; stream_rewind(fup->orig); unlink(fup->temppath); free(fup->temppath); fup->temppath = tempname(path); } fup->to = stream_open_file(fup->temppath, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fup->to == NULL) { xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } lprintf(2, " Add delta %s %s %s\n", sr->sr_revnum, sr->sr_revdate, fup->author); error = updater_diff_batch(up, fup); if (error) return (error); } if (line == NULL) return (UPDATER_ERR_READ); fa = fattr_frompath(path, FATTR_FOLLOW); tmp = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); fattr_override(fa, tmp, FA_MASK); fattr_free(tmp); fattr_maskout(fa, FA_MODTIME); sr->sr_clientattr = fa; if (MD5_File(fup->temppath, md5) == -1) { xasprintf(&up->errmsg, "Cannot calculate checksum for \"%s\": %s", path, strerror(errno)); return (UPDATER_ERR_MSG); } error = updater_updatefile(up, fup, md5, 0); return (error); } /* * Edit a file and add delta. */ static int updater_diff_batch(struct updater *up, struct file_update *fup) { struct stream *rd; char *cmd, *line, *state, *tok; int error; state = NULL; rd = up->rd; while ((line = stream_getln(rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; cmd = proto_get_ascii(&line); if (cmd == NULL || strlen(cmd) != 1) { error = UPDATER_ERR_PROTO; goto bad; } switch (cmd[0]) { case 'L': line = stream_getln(rd, NULL); /* XXX - We're just eating the log for now. */ while (line != NULL && strcmp(line, ".") != 0 && strcmp(line, ".+") != 0) line = stream_getln(rd, NULL); if (line == NULL) { error = UPDATER_ERR_READ; goto bad; } break; case 'S': tok = proto_get_ascii(&line); if (tok == NULL || line != NULL) { error = UPDATER_ERR_PROTO; goto bad; } if (state != NULL) free(state); state = xstrdup(tok); break; case 'T': error = updater_diff_apply(up, fup, state); if (error) goto bad; break; default: error = UPDATER_ERR_PROTO; goto bad; } } if (line == NULL) { error = UPDATER_ERR_READ; goto bad; } if (state != NULL) free(state); return (0); bad: if (state != NULL) free(state); return (error); } int updater_diff_apply(struct updater *up, struct file_update *fup, char *state) { struct diffinfo dibuf, *di; struct coll *coll; struct statusrec *sr; int error; coll = fup->coll; sr = &fup->srbuf; di = &dibuf; di->di_rcsfile = sr->sr_file; di->di_cvsroot = coll->co_cvsroot; di->di_revnum = sr->sr_revnum; di->di_revdate = sr->sr_revdate; di->di_author = fup->author; di->di_tag = sr->sr_tag; di->di_state = state; di->di_expand = fup->expand; error = diff_apply(up->rd, fup->orig, fup->to, coll->co_keyword, di, 1); if (error) { /* XXX Bad error message */ xasprintf(&up->errmsg, "Bad diff from server"); return (UPDATER_ERR_MSG); } return (0); } /* Update or create a node. */ static int updater_updatenode(struct updater *up, struct coll *coll, struct file_update *fup, char *name, char *attr) { struct fattr *fa, *fileattr; struct status *st; struct statusrec *sr; int error, rv; sr = &fup->srbuf; st = fup->st; fa = fattr_decode(attr); if (fattr_type(fa) == FT_SYMLINK) { lprintf(1, " Symlink %s -> %s\n", name, fattr_getlinktarget(fa)); } else { lprintf(1, " Mknod %s\n", name); } /* Create directory. */ error = mkdirhier(fup->destpath, coll->co_umask); if (error) return (UPDATER_ERR_PROTO); /* If it does not exist, create it. */ if (access(fup->destpath, F_OK) != 0) fattr_makenode(fa, fup->destpath); /* * Coming from attic? I don't think this is a problem since we have * determined attic before we call this function (Look at UpdateNode in * cvsup). */ fattr_umask(fa, coll->co_umask); rv = fattr_install(fa, fup->destpath, fup->temppath); if (rv == -1) { xasprintf(&up->errmsg, "Cannot update attributes on " "\"%s\": %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } /* * XXX: Executes not implemented. Have not encountered much use for it * yet. */ /* * We weren't necessarily able to set all the file attributes to the * desired values, and any executes may have altered the attributes. * To make sure we record the actual attribute values, we fetch * them from the file. * * However, we preserve the link count as received from the * server. This is important for preserving hard links in mirror * mode. */ fileattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW); if (fileattr == NULL) { xasprintf(&up->errmsg, "Cannot stat \"%s\": %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } fattr_override(fileattr, sr->sr_clientattr, FA_LINKCOUNT); fattr_free(sr->sr_clientattr); sr->sr_clientattr = fileattr; /* * To save space, don't write out the device and inode unless * the link count is greater than 1. These attributes are used * only for detecting hard links. If the link count is 1 then we * know there aren't any hard links. */ if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) || fattr_getlinkcount(sr->sr_clientattr) <= 1) fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE); /* If it is a symlink, write only out it's path. */ if (fattr_type(fa) == FT_SYMLINK) { fattr_maskout(sr->sr_clientattr, ~(FA_FILETYPE | FA_LINKTARGET)); } fattr_maskout(sr->sr_clientattr, FA_FLAGS); error = status_put(st, sr); if (error) { up->errmsg = status_errmsg(st); return (UPDATER_ERR_MSG); } fattr_free(fa); return (0); } /* * Fetches a new file in CVS mode. */ static int updater_addfile(struct updater *up, struct file_update *fup, char *attr, int isfixup) { struct coll *coll; struct stream *to; struct statusrec *sr; struct fattr *fa; char buf[BUFSIZE]; char md5[MD5_DIGEST_SIZE]; ssize_t nread; off_t fsize, remains; char *cmd, *line, *path; int error; coll = fup->coll; path = fup->destpath; sr = &fup->srbuf; fa = fattr_decode(attr); fsize = fattr_filesize(fa); error = mkdirhier(path, coll->co_umask); if (error) return (UPDATER_ERR_PROTO); to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC, 0755); if (to == NULL) { xasprintf(&up->errmsg, "%s: Cannot create: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } stream_filter_start(to, STREAM_FILTER_MD5, md5); remains = fsize; do { nread = stream_read(up->rd, buf, (BUFSIZE > remains ? remains : BUFSIZE)); if (nread == -1) return (UPDATER_ERR_PROTO); remains -= nread; if (stream_write(to, buf, nread) == -1) goto bad; } while (remains > 0); stream_close(to); line = stream_getln(up->rd, NULL); if (line == NULL) return (UPDATER_ERR_PROTO); /* Check for EOF. */ if (!(*line == '.' || (strncmp(line, ".<", 2) != 0))) return (UPDATER_ERR_PROTO); line = stream_getln(up->rd, NULL); if (line == NULL) return (UPDATER_ERR_PROTO); cmd = proto_get_ascii(&line); fup->wantmd5 = proto_get_ascii(&line); if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0) return (UPDATER_ERR_PROTO); sr->sr_clientattr = fattr_frompath(fup->temppath, FATTR_NOFOLLOW); if (sr->sr_clientattr == NULL) return (UPDATER_ERR_PROTO); fattr_override(sr->sr_clientattr, sr->sr_serverattr, FA_MODTIME | FA_MASK); error = updater_updatefile(up, fup, md5, isfixup); fup->wantmd5 = NULL; /* So that it doesn't get freed. */ return (error); bad: xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } static int updater_checkout(struct updater *up, struct file_update *fup, int isfixup) { char md5[MD5_DIGEST_SIZE]; struct statusrec *sr; struct coll *coll; struct stream *to; ssize_t nbytes; size_t size; char *cmd, *path, *line; int error, first; coll = fup->coll; sr = &fup->srbuf; path = fup->destpath; if (isfixup) lprintf(1, " Fixup %s\n", fup->coname); else lprintf(1, " Checkout %s\n", fup->coname); error = mkdirhier(path, coll->co_umask); if (error) { xasprintf(&up->errmsg, "Cannot create directories leading to \"%s\": %s", path, strerror(errno)); return (UPDATER_ERR_MSG); } to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (to == NULL) { xasprintf(&up->errmsg, "%s: Cannot create: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } stream_filter_start(to, STREAM_FILTER_MD5, md5); line = stream_getln(up->rd, &size); first = 1; while (line != NULL) { if (line[size - 1] == '\n') size--; if ((size == 1 && *line == '.') || (size == 2 && memcmp(line, ".+", 2) == 0)) break; if (size >= 2 && memcmp(line, "..", 2) == 0) { size--; line++; } if (!first) { nbytes = stream_write(to, "\n", 1); if (nbytes == -1) goto bad; } nbytes = stream_write(to, line, size); if (nbytes == -1) goto bad; line = stream_getln(up->rd, &size); first = 0; } if (line == NULL) { stream_close(to); return (UPDATER_ERR_READ); } if (size == 1 && *line == '.') { nbytes = stream_write(to, "\n", 1); if (nbytes == -1) goto bad; } stream_close(to); /* Get the checksum line. */ line = stream_getln(up->rd, NULL); if (line == NULL) return (UPDATER_ERR_READ); cmd = proto_get_ascii(&line); fup->wantmd5 = proto_get_ascii(&line); if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0) return (UPDATER_ERR_PROTO); error = updater_updatefile(up, fup, md5, isfixup); fup->wantmd5 = NULL; /* So that it doesn't get freed. */ if (error) return (error); return (0); bad: xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } /* * Remove all empty directories below file. * This function will trash the path passed to it. */ static void updater_prunedirs(char *base, char *file) { char *cp; int error; while ((cp = strrchr(file, '/')) != NULL) { *cp = '\0'; if (strcmp(base, file) == 0) return; error = rmdir(file); if (error) return; } } /* * Edit an RCS file. */ static int updater_rcsedit(struct updater *up, struct file_update *fup, char *name, char *rcsopt) { struct coll *coll; struct stream *dest; struct statusrec *sr; struct status *st; struct rcsfile *rf; struct fattr *oldfattr; char md5[MD5_DIGEST_SIZE]; char *branch, *cmd, *expand, *line, *path, *revnum, *tag, *temppath; int error; coll = fup->coll; sr = &fup->srbuf; st = fup->st; temppath = fup->temppath; path = fup->origpath != NULL ? fup->origpath : fup->destpath; error = 0; /* If the path is new, we must create the Attic dir if needed. */ if (fup->origpath != NULL) { error = mkdirhier(fup->destpath, coll->co_umask); if (error) { xasprintf(&up->errmsg, "Unable to create Attic dir for " "%s\n", fup->origpath); return (UPDATER_ERR_MSG); } } /* * XXX: we could avoid parsing overhead if we're reading ahead before we * parse the file. */ oldfattr = fattr_frompath(path, FATTR_NOFOLLOW); if (oldfattr == NULL) { xasprintf(&up->errmsg, "%s: Cannot get attributes: %s", path, strerror(errno)); return (UPDATER_ERR_MSG); } fattr_merge(sr->sr_serverattr, oldfattr); rf = NULL; /* Macro for making touching an RCS file faster. */ #define UPDATER_OPENRCS(rf, up, path, name, cvsroot, tag) do { \ if ((rf) == NULL) { \ lprintf(1, " Edit %s", fup->coname); \ if (fup->attic) \ lprintf(1, " -> Attic"); \ lprintf(1, "\n"); \ (rf) = rcsfile_frompath((path), (name), (cvsroot), \ (tag), 0); \ if ((rf) == NULL) { \ xasprintf(&(up)->errmsg, \ "Error reading rcsfile %s\n", (name)); \ return (UPDATER_ERR_MSG); \ } \ } \ } while (0) while ((line = stream_getln(up->rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; cmd = proto_get_ascii(&line); if (cmd == NULL) { lprintf(-1, "Error editing %s\n", name); return (UPDATER_ERR_PROTO); } switch(cmd[0]) { case 'B': branch = proto_get_ascii(&line); if (branch == NULL || line != NULL) return (UPDATER_ERR_PROTO); UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); break; case 'b': UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); rcsfile_setval(rf, RCSFILE_BRANCH, NULL); break; case 'D': UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); error = updater_addelta(rf, up->rd, line); if (error) return (error); break; case 'd': revnum = proto_get_ascii(&line); if (revnum == NULL || line != NULL) return (UPDATER_ERR_PROTO); UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); rcsfile_deleterev(rf, revnum); break; case 'E': expand = proto_get_ascii(&line); if (expand == NULL || line != NULL) return (UPDATER_ERR_PROTO); UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); rcsfile_setval(rf, RCSFILE_EXPAND, expand); break; case 'T': tag = proto_get_ascii(&line); revnum = proto_get_ascii(&line); if (tag == NULL || revnum == NULL || line != NULL) return (UPDATER_ERR_PROTO); UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); rcsfile_addtag(rf, tag, revnum); break; case 't': tag = proto_get_ascii(&line); revnum = proto_get_ascii(&line); if (tag == NULL || revnum == NULL || line != NULL) return (UPDATER_ERR_PROTO); UPDATER_OPENRCS(rf, up, path, name, coll->co_cvsroot, coll->co_tag); rcsfile_deletetag(rf, tag, revnum); break; default: return (UPDATER_ERR_PROTO); } } if (rf == NULL) { fattr_maskout(oldfattr, ~FA_MODTIME); if (fattr_equal(oldfattr, sr->sr_serverattr)) lprintf(1, " SetAttrs %s", fup->coname); else lprintf(1, " Touch %s", fup->coname); /* Install new attributes. */ fattr_umask(sr->sr_serverattr, coll->co_umask); fattr_install(sr->sr_serverattr, fup->destpath, NULL); if (fup->attic) lprintf(1, " -> Attic"); lprintf(1, "\n"); fattr_free(oldfattr); goto finish; } /* Write and rename temp file. */ dest = stream_open_file(fup->temppath, O_RDWR | O_CREAT | O_TRUNC, 0600); if (dest == NULL) { xasprintf(&up->errmsg, "Error opening file %s for writing: %s\n", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } stream_filter_start(dest, STREAM_FILTER_MD5RCS, md5); error = rcsfile_write(rf, dest); stream_close(dest); rcsfile_free(rf); if (error) { xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } finish: sr->sr_clientattr = fattr_frompath(path, FATTR_NOFOLLOW); if (sr->sr_clientattr == NULL) { xasprintf(&up->errmsg, "%s: Cannot get attributes: %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } fattr_override(sr->sr_clientattr, sr->sr_serverattr, FA_MODTIME | FA_MASK); if (rf != NULL) { error = updater_updatefile(up, fup, md5, 0); fup->wantmd5 = NULL; /* So that it doesn't get freed. */ if (error) return (error); } else { /* Record its attributes since we touched it. */ if (!(fattr_getmask(sr->sr_clientattr) & FA_LINKCOUNT) || fattr_getlinkcount(sr->sr_clientattr) <= 1) fattr_maskout(sr->sr_clientattr, FA_DEV | FA_INODE); error = status_put(st, sr); if (error) { up->errmsg = status_errmsg(st); return (UPDATER_ERR_MSG); } } /* In this case, we need to remove the old file afterwards. */ /* XXX: Can we be sure that a file not edited is moved? I don't think * this is a problem, since if a file is moved, it should be edited to * show if it's dead or not. */ if (fup->origpath != NULL) updater_deletefile(fup->origpath); return (0); } /* * Add a delta to a RCS file. */ int updater_addelta(struct rcsfile *rf, struct stream *rd, char *cmdline) { struct delta *d; size_t size; char *author, *cmd, *diffbase, *line, *logline; char *revdate, *revnum, *state, *textline; revnum = proto_get_ascii(&cmdline); diffbase = proto_get_ascii(&cmdline); revdate = proto_get_ascii(&cmdline); author = proto_get_ascii(&cmdline); size = 0; if (revnum == NULL || revdate == NULL || author == NULL) return (UPDATER_ERR_PROTO); /* First add the delta so we have it. */ d = rcsfile_addelta(rf, revnum, revdate, author, diffbase); if (d == NULL) { lprintf(-1, "Error adding delta %s\n", revnum); return (UPDATER_ERR_READ); } while ((line = stream_getln(rd, NULL)) != NULL) { if (strcmp(line, ".") == 0) break; cmd = proto_get_ascii(&line); switch (cmd[0]) { case 'L': /* Do the same as in 'C' command. */ logline = stream_getln(rd, &size); while (logline != NULL) { if (size == 2 && *logline == '.') break; if (size == 3 && memcmp(logline, ".+", 2) == 0) { rcsdelta_truncatelog(d, -1); break; } if (size >= 3 && memcmp(logline, "..", 2) == 0) { size--; logline++; } if (rcsdelta_appendlog(d, logline, size) < 0) return (-1); logline = stream_getln(rd, &size); } break; case 'N': case 'n': /* XXX: Not supported. */ break; case 'S': state = proto_get_ascii(&line); if (state == NULL) return (UPDATER_ERR_PROTO); rcsdelta_setstate(d, state); break; case 'T': /* Do the same as in 'C' command. */ textline = stream_getln(rd, &size); while (textline != NULL) { if (size == 2 && *textline == '.') break; if (size == 3 && memcmp(textline, ".+", 2) == 0) { /* Truncate newline. */ rcsdelta_truncatetext(d, -1); break; } if (size >= 3 && memcmp(textline, "..", 2) == 0) { size--; textline++; } if (rcsdelta_appendtext(d, textline, size) < 0) return (-1); textline = stream_getln(rd, &size); } break; } } return (0); } int updater_append_file(struct updater *up, struct file_update *fup, off_t pos) { struct fattr *fa; struct stream *to; struct statusrec *sr; ssize_t nread; off_t bytes; char buf[BUFSIZE], md5[MD5_DIGEST_SIZE]; char *line, *cmd; int error, fd; sr = &fup->srbuf; fa = sr->sr_serverattr; to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC, 0755); if (to == NULL) { xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } fd = open(fup->destpath, O_RDONLY); if (fd < 0) { xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } stream_filter_start(to, STREAM_FILTER_MD5, md5); /* First write the existing content. */ while ((nread = read(fd, buf, BUFSIZE)) > 0) { if (stream_write(to, buf, nread) == -1) goto bad; } if (nread == -1) { xasprintf(&up->errmsg, "%s: Error reading: %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } close(fd); bytes = fattr_filesize(fa) - pos; /* Append the new data. */ do { nread = stream_read(up->rd, buf, (BUFSIZE > bytes) ? bytes : BUFSIZE); if (nread == -1) return (UPDATER_ERR_PROTO); bytes -= nread; if (stream_write(to, buf, nread) == -1) goto bad; } while (bytes > 0); stream_close(to); line = stream_getln(up->rd, NULL); if (line == NULL) return (UPDATER_ERR_PROTO); /* Check for EOF. */ if (!(*line == '.' || (strncmp(line, ".<", 2) != 0))) return (UPDATER_ERR_PROTO); line = stream_getln(up->rd, NULL); if (line == NULL) return (UPDATER_ERR_PROTO); cmd = proto_get_ascii(&line); fup->wantmd5 = proto_get_ascii(&line); if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0) return (UPDATER_ERR_PROTO); sr->sr_clientattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW); if (sr->sr_clientattr == NULL) return (UPDATER_ERR_PROTO); fattr_override(sr->sr_clientattr, sr->sr_serverattr, FA_MODTIME | FA_MASK); error = updater_updatefile(up, fup, md5, 0); fup->wantmd5 = NULL; /* So that it doesn't get freed. */ return (error); bad: xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } /* * Read file data from stream of checkout commands, and write it to the * destination. */ static int updater_read_checkout(struct stream *src, struct stream *dest) { ssize_t nbytes; size_t size; char *line; int first; first = 1; line = stream_getln(src, &size); while (line != NULL) { if (line[size - 1] == '\n') size--; if ((size == 1 && *line == '.') || (size == 2 && strncmp(line, ".+", 2) == 0)) break; if (size >= 2 && strncmp(line, "..", 2) == 0) { size--; line++; } if (!first) { nbytes = stream_write(dest, "\n", 1); if (nbytes == -1) return (UPDATER_ERR_MSG); } nbytes = stream_write(dest, line, size); if (nbytes == -1) return (UPDATER_ERR_MSG); line = stream_getln(src, &size); first = 0; } if (line == NULL) return (UPDATER_ERR_READ); if (size == 1 && *line == '.') { nbytes = stream_write(dest, "\n", 1); if (nbytes == -1) return (UPDATER_ERR_MSG); } return (0); } /* Update file using the rsync protocol. */ static int updater_rsync(struct updater *up, struct file_update *fup, size_t blocksize) { struct statusrec *sr; struct stream *to; char md5[MD5_DIGEST_SIZE]; ssize_t nbytes; size_t blocknum, blockstart, blockcount; char *buf, *line; int error, orig; sr = &fup->srbuf; lprintf(1, " Rsync %s\n", fup->coname); /* First open all files that we are going to work on. */ to = stream_open_file(fup->temppath, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (to == NULL) { xasprintf(&up->errmsg, "%s: Cannot create: %s", fup->temppath, strerror(errno)); return (UPDATER_ERR_MSG); } orig = open(fup->destpath, O_RDONLY); if (orig < 0) { xasprintf(&up->errmsg, "%s: Cannot open: %s", fup->destpath, strerror(errno)); return (UPDATER_ERR_MSG); } stream_filter_start(to, STREAM_FILTER_MD5, md5); error = updater_read_checkout(up->rd, to); if (error) { xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); return (error); } /* Buffer must contain blocksize bytes. */ buf = xmalloc(blocksize); /* Done with the initial text, read and write chunks. */ line = stream_getln(up->rd, NULL); while (line != NULL) { if (strcmp(line, ".") == 0) break; error = UPDATER_ERR_PROTO; if (proto_get_sizet(&line, &blockstart, 10) != 0) goto bad; if (proto_get_sizet(&line, &blockcount, 10) != 0) goto bad; /* Read blocks from original file. */ lseek(orig, (blocksize * blockstart), SEEK_SET); error = UPDATER_ERR_MSG; for (blocknum = 0; blocknum < blockcount; blocknum++) { nbytes = read(orig, buf, blocksize); if (nbytes < 0) { xasprintf(&up->errmsg, "%s: Cannot read: %s", fup->destpath, strerror(errno)); goto bad; } nbytes = stream_write(to, buf, nbytes); if (nbytes == -1) { xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); goto bad; } } /* Get the remaining text from the server. */ error = updater_read_checkout(up->rd, to); if (error) { xasprintf(&up->errmsg, "%s: Cannot write: %s", fup->temppath, strerror(errno)); goto bad; } line = stream_getln(up->rd, NULL); } stream_close(to); close(orig); sr->sr_clientattr = fattr_frompath(fup->destpath, FATTR_NOFOLLOW); if (sr->sr_clientattr == NULL) return (UPDATER_ERR_PROTO); fattr_override(sr->sr_clientattr, sr->sr_serverattr, FA_MODTIME | FA_MASK); error = updater_updatefile(up, fup, md5, 0); fup->wantmd5 = NULL; /* So that it doesn't get freed. */ bad: free(buf); return (error); }