freebsd-skq/contrib/csup/updater.c
lulf a6bcdcc2b6 - Add proper error checking and printing to the CVSMode code when reading and
writing from/to streams, as leaving them out stops csup from cleaning up on
  SIGINT and friends properly.

MFC after:      1 week
2009-03-25 20:15:48 +00:00

2045 lines
54 KiB
C

/*-
* Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "diff.h"
#include "fattr.h"
#include "fixups.h"
#include "keyword.h"
#include "updater.h"
#include "misc.h"
#include "mux.h"
#include "proto.h"
#include "rcsfile.h"
#include "status.h"
#include "stream.h"
/* Internal error codes. */
#define UPDATER_ERR_PROTO (-1) /* Protocol error. */
#define UPDATER_ERR_MSG (-2) /* Error is in updater->errmsg. */
#define UPDATER_ERR_READ (-3) /* Error reading from server. */
#define UPDATER_ERR_DELETELIM (-4) /* File deletion limit exceeded. */
#define BUFSIZE 4096
/* Everything needed to update a file. */
struct file_update {
struct statusrec srbuf;
char *destpath;
char *temppath;
char *origpath;
char *coname; /* Points somewhere in destpath. */
char *wantmd5;
struct coll *coll;
struct status *st;
/* Those are only used for diff updating. */
char *author;
struct stream *orig;
struct stream *to;
int attic;
int expand;
};
struct updater {
struct config *config;
struct stream *rd;
char *errmsg;
int deletecount;
};
static struct file_update *fup_new(struct coll *, struct status *);
static int fup_prepare(struct file_update *, char *, int);
static void fup_cleanup(struct file_update *);
static void fup_free(struct file_update *);
static void updater_prunedirs(char *, char *);
static int updater_batch(struct updater *, int);
static int updater_docoll(struct updater *, struct file_update *, int);
static int updater_delete(struct updater *, struct file_update *);
static void updater_deletefile(const char *);
static int updater_checkout(struct updater *, struct file_update *, int);
static int updater_addfile(struct updater *, struct file_update *,
char *, int);
int updater_addelta(struct rcsfile *, struct stream *, char *);
static int updater_setattrs(struct updater *, struct file_update *,
char *, char *, char *, char *, char *, struct fattr *);
static int updater_setdirattrs(struct updater *, struct coll *,
struct file_update *, char *, char *);
static int updater_updatefile(struct updater *, struct file_update *fup,
const char *, int);
static int updater_updatenode(struct updater *, struct coll *,
struct file_update *, char *, char *);
static int updater_diff(struct updater *, struct file_update *);
static int updater_diff_batch(struct updater *, struct file_update *);
static int updater_diff_apply(struct updater *, struct file_update *,
char *);
static int updater_rcsedit(struct updater *, struct file_update *, char *,
char *);
int updater_append_file(struct updater *, struct file_update *,
off_t);
static int updater_rsync(struct updater *, struct file_update *, size_t);
static int updater_read_checkout(struct stream *, struct stream *);
static struct file_update *
fup_new(struct coll *coll, struct status *st)
{
struct file_update *fup;
fup = xmalloc(sizeof(struct file_update));
memset(fup, 0, sizeof(*fup));
fup->coll = coll;
fup->st = st;
return (fup);
}
static int
fup_prepare(struct file_update *fup, char *name, int attic)
{
struct coll *coll;
coll = fup->coll;
fup->attic = 0;
fup->origpath = NULL;
if (coll->co_options & CO_CHECKOUTMODE)
fup->destpath = checkoutpath(coll->co_prefix, name);
else {
fup->destpath = cvspath(coll->co_prefix, name, attic);
fup->origpath = atticpath(coll->co_prefix, name);
/* If they're equal, we don't need special care. */
if (fup->origpath != NULL &&
strcmp(fup->origpath, fup->destpath) == 0) {
free(fup->origpath);
fup->origpath = NULL;
}
fup->attic = attic;
}
if (fup->destpath == NULL)
return (-1);
fup->coname = fup->destpath + coll->co_prefixlen + 1;
return (0);
}
/* Called after each file update to reinit the structure. */
static void
fup_cleanup(struct file_update *fup)
{
struct statusrec *sr;
sr = &fup->srbuf;
if (fup->destpath != NULL) {
free(fup->destpath);
fup->destpath = NULL;
}
if (fup->temppath != NULL) {
free(fup->temppath);
fup->temppath = NULL;
}
if (fup->origpath != NULL) {
free(fup->origpath);
fup->origpath = NULL;
}
fup->coname = NULL;
if (fup->author != NULL) {
free(fup->author);
fup->author = NULL;
}
fup->expand = 0;
if (fup->wantmd5 != NULL) {
free(fup->wantmd5);
fup->wantmd5 = NULL;
}
if (fup->orig != NULL) {
stream_close(fup->orig);
fup->orig = NULL;
}
if (fup->to != NULL) {
stream_close(fup->to);
fup->to = NULL;
}
if (sr->sr_file != NULL)
free(sr->sr_file);
if (sr->sr_tag != NULL)
free(sr->sr_tag);
if (sr->sr_date != NULL)
free(sr->sr_date);
if (sr->sr_revnum != NULL)
free(sr->sr_revnum);
if (sr->sr_revdate != NULL)
free(sr->sr_revdate);
fattr_free(sr->sr_clientattr);
fattr_free(sr->sr_serverattr);
memset(sr, 0, sizeof(*sr));
}
static void
fup_free(struct file_update *fup)
{
fup_cleanup(fup);
free(fup);
}
void *
updater(void *arg)
{
struct thread_args *args;
struct updater upbuf, *up;
int error;
args = arg;
up = &upbuf;
up->config = args->config;
up->rd = args->rd;
up->errmsg = NULL;
up->deletecount = 0;
error = updater_batch(up, 0);
/*
* Make sure to close the fixups even in case of an error,
* so that the 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",
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);
}