freebsd-dev/contrib/csup/updater.c
Ulf Lilleengen 4136ca9478 - Implement rsync support in csup, which is chosen as a protocol for regular
files if the client supports it. The support is implemented with an API to
  operate on files, calculating the rolling checksum and md5 checksum for the
  blocks etc.
- Remove unneeded stream_filter_stop and stream_flush before stream_close.
2008-10-25 10:54:28 +00:00

2120 lines
56 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 1024
/* 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 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 *cmd, *blocksize, *line, *msg, *attr;
char *name, *tag, *date, *revdate;
char *expand, *wantmd5, *revnum;
char *optstr, *rcsopt, *pos;
time_t t;
off_t position;
int error, needfixupmsg;
error = 0;
rd = up->rd;
coll = fup->coll;
needfixupmsg = isfixups;
while ((line = stream_getln(rd, NULL)) != NULL) {
if (strcmp(line, ".") == 0)
break;
memset(&srbuf, 0, sizeof(srbuf));
if (needfixupmsg) {
lprintf(1, "Applying fixups for collection %s/%s\n",
coll->co_name, coll->co_release);
needfixupmsg = 0;
}
cmd = proto_get_ascii(&line);
if (cmd == NULL || strlen(cmd) != 1)
return (UPDATER_ERR_PROTO);
switch (cmd[0]) {
case 'T':
/* Update recorded information for checked-out file. */
name = proto_get_ascii(&line);
tag = proto_get_ascii(&line);
date = proto_get_ascii(&line);
revnum = proto_get_ascii(&line);
revdate = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
if (attr == NULL || line != NULL)
return (UPDATER_ERR_PROTO);
rcsattr = fattr_decode(attr);
if (rcsattr == NULL)
return (UPDATER_ERR_PROTO);
error = fup_prepare(fup, name, 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':
name = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
if (name == NULL || attr == NULL || line != NULL)
return (UPDATER_ERR_PROTO);
error = fup_prepare(fup, name, 1);
if (error)
return (UPDATER_ERR_PROTO);
fup->temppath = tempname(fup->destpath);
sr = &fup->srbuf;
sr->sr_type = SR_FILEDEAD;
sr->sr_file = xstrdup(name);
sr->sr_serverattr = fattr_decode(attr);
if (sr->sr_serverattr == NULL)
return (UPDATER_ERR_PROTO);
lprintf(1, " Create %s -> Attic\n", name);
error = updater_addfile(up, fup, attr);
if (error)
return (error);
break;
case 'A':
case 'R': // XXX
name = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
if (name == NULL || attr == NULL || line != NULL)
return (UPDATER_ERR_PROTO);
error = fup_prepare(fup, name, 0);
if (error)
return (UPDATER_ERR_PROTO);
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);
lprintf(1, " Create %s\n", name);
error = updater_addfile(up, fup, attr);
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);
sr->sr_type = SR_FILELIVE;
fup->wantmd5 = xstrdup(wantmd5);
fup->temppath = tempname(fup->destpath);
sr = &fup->srbuf;
sr->sr_file = xstrdup(name);
sr->sr_serverattr = fattr_decode(attr);
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;
#if 0
case 'h':
/* XXX: SR_LINKFILEDEAD. */
case 'H':
lprintf(1, "Got 'H'\n");
break;
#endif
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);
sr = &fup->srbuf;
sr->sr_type = SR_FILEDEAD;
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 '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);
sr = &fup->srbuf;
sr->sr_type = 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':
name = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
if (name == NULL || attr == NULL || line != NULL)
return (UPDATER_ERR_PROTO);
error = fup_prepare(fup, name, 1);
if (error)
return (UPDATER_ERR_PROTO);
sr = &fup->srbuf;
sr->sr_type = SR_FILEDEAD;
sr->sr_file = xstrdup(name);
sr->sr_serverattr = fattr_decode(attr);
sr->sr_clientattr = fattr_new(FT_SYMLINK, -1);
fattr_mergedefault(sr->sr_clientattr);
error = updater_updatenode(up, coll, fup, name, attr);
if (error)
return (error);
break;
case 'N':
name = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
if (name == NULL || attr == 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_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':
name = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
optstr = proto_get_ascii(&line);
wantmd5 = proto_get_ascii(&line);
rcsopt = NULL; /*rcs_decode(optstr);*/
if (attr == NULL || line != NULL || wantmd5 == NULL)
return (UPDATER_ERR_PROTO);
error = fup_prepare(fup, name, 1);
if (error)
return (UPDATER_ERR_PROTO);
fup->temppath = tempname(fup->destpath);
fup->wantmd5 = xstrdup(wantmd5);
sr = &fup->srbuf;
sr->sr_type = SR_FILEDEAD;
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 'V':
name = proto_get_ascii(&line);
attr = proto_get_ascii(&line);
optstr = proto_get_ascii(&line);
wantmd5 = proto_get_ascii(&line);
rcsopt = NULL; /*rcs_decode(optstr);*/
if (attr == NULL || line != NULL || wantmd5 == NULL)
return (UPDATER_ERR_PROTO);
error = fup_prepare(fup, name, 0);
if (error)
return (UPDATER_ERR_PROTO);
fup->temppath = tempname(fup->destpath);
fup->wantmd5 = xstrdup(wantmd5);
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);
error = updater_rcsedit(up, fup, name, rcsopt);
if (error)
return (error);
break;
/*
X <file> <attr>
Receive the live RCS file <file> in its entirety, as a fixup.
Set its attributes to <attr>. The data follows, in the same
format as for the "A" command. CVS mode only.
x <file> <attr>
Like "X", but put the file into the Attic. CVS mode only.
*/
#if 0
case 'X':
case 'x':
lprintf(1, "Got X\n");
break;
#endif
/*
Z <file> <attr> <pos>
Append some new data to the end of the existing file <file>,
and set its attributes to <attr>. The data should be written
to the file starting at file offset <pos>, which should be
exactly at the end of the file. Exactly n bytes of data follow,
where n is the size attribute minus <pos>. After the data
comes a terminating line, as in the "A" command. The number
of bytes n can be 0, in which case the file's attributes are
simply updated.
*/
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);
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:
lprintf(-1, "Unknown command: %s\n", cmd);
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);
/* XXX: Mask out chflags for now. Cvsup doesn't record them. */
/*fattr_maskout(sr->sr_clientattr, FA_FLAGS);*/
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, issymlink, 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));
issymlink = 1;
} else {
lprintf(1, " Mknod %s\n", name);
issymlink = 0;
}
/* Create directory. */
error = mkdirhier(fup->destpath, coll->co_umask);
if (error)
return (UPDATER_ERR_PROTO);
/* If it exists, update attributes. */
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 */
/*
* 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)
{
char md5[MD5_DIGEST_SIZE];
struct coll *coll;
struct stream *to;
struct statusrec *sr;
struct fattr *fa;
char *path, *line, *cmd;
int error;
off_t fsize;
char buf[BUFSIZE];
ssize_t nread;
off_t remains;
coll = fup->coll;
path = fup->destpath;
nread = 0;
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));
remains -= nread;
stream_write(to, buf, nread);
} 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);
/* UPDATE FILE. */
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, 0);
fup->wantmd5 = NULL; /* So that it doesn't get freed. */
/* UPDATE IT. */
if (error)
return (error);
return (0);
}
static int
updater_checkout(struct updater *up, struct file_update *fup, int isfixup)
{
char md5[MD5_DIGEST_SIZE];
struct statusrec *sr;
struct coll *coll;
struct stream *to;
char *cmd, *path, *line;
size_t size;
ssize_t nbytes;
int error, first;
coll = fup->coll;
sr = &fup->srbuf;
path = fup->destpath;
if (isfixup)
lprintf(1, " Fixup %s\n", fup->coname);
else
lprintf(1, " Checkout %s\n", fup->coname);
error = mkdirhier(path, coll->co_umask);
if (error) {
xasprintf(&up->errmsg,
"Cannot create directories leading to \"%s\": %s",
path, strerror(errno));
return (UPDATER_ERR_MSG);
}
to = stream_open_file(fup->temppath,
O_WRONLY | O_CREAT | O_TRUNC, 0600); /*XXX: Change to correct perm*/
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;
}
stream_write(to, line, size);
line = stream_getln(up->rd, &size);
first = 0;
}
if (line == NULL) {
stream_close(to);
return (UPDATER_ERR_READ);
}
if (size == 1 && *line == '.') {
nbytes = stream_write(to, "\n", 1);
if (nbytes == -1)
goto bad;
}
stream_close(to);
/* Get the checksum line. */
line = stream_getln(up->rd, NULL);
if (line == NULL)
return (UPDATER_ERR_READ);
cmd = proto_get_ascii(&line);
fup->wantmd5 = proto_get_ascii(&line);
if (fup->wantmd5 == NULL || line != NULL || strcmp(cmd, "5") != 0)
return (UPDATER_ERR_PROTO);
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, changed;
rcsopt = NULL; /* XXX: just for now. */
coll = fup->coll;
sr = &fup->srbuf;
st = fup->st;
temppath = fup->temppath;
path = fup->origpath != NULL ? fup->origpath : fup->destpath;
changed = 0;
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.
*/
rf = rcsfile_frompath(path, name, coll->co_cvsroot, coll->co_tag);
if (rf == NULL) {
xasprintf(&up->errmsg, "Error reading rcsfile %s\n", name);
return (UPDATER_ERR_MSG);
}
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);
while ((line = stream_getln(up->rd, NULL)) != NULL) {
if (strcmp(line, ".") == 0)
break;
cmd = proto_get_ascii(&line);
if (cmd == NULL) {
fprintf(stderr, "Error when adding delta\n");
return (UPDATER_ERR_PROTO);
}
switch(cmd[0]) {
case 'B':
branch = proto_get_ascii(&line);
if (branch == NULL || line != NULL) {
fprintf(stderr, "problems with branch\n");
return (UPDATER_ERR_PROTO);
}
rcsfile_setval(rf, RCSFILE_BRANCH, branch);
changed = 1;
break;
case 'b':
rcsfile_setval(rf, RCSFILE_BRANCH, NULL);
changed = 1;
break;
case 'D':
error = updater_addelta(rf, up->rd, line);
changed = 1;
if (error)
return (error);
break;
case 'd':
revnum = proto_get_ascii(&line);
if (revnum == NULL || line != NULL) {
fprintf(stderr, "Problems with delta\n");
return (UPDATER_ERR_PROTO);
}
rcsfile_deleterev(rf, revnum);
changed = 1;
break;
case 'E':
expand = proto_get_ascii(&line);
if (expand == NULL || line != NULL) {
fprintf(stderr, "Expand\n");
return (UPDATER_ERR_PROTO);
}
rcsfile_setval(rf, RCSFILE_EXPAND, expand);
changed = 1;
break;
case 'T':
tag = proto_get_ascii(&line);
revnum = proto_get_ascii(&line);
if (tag == NULL || revnum == NULL ||
line != NULL) {
fprintf(stderr, "Add tag\n");
return (UPDATER_ERR_PROTO);
}
rcsfile_addtag(rf, tag, revnum);
changed = 1;
break;
case 't':
tag = proto_get_ascii(&line);
revnum = proto_get_ascii(&line);
if (tag == NULL || revnum == NULL ||
line != NULL) {
fprintf(stderr, "Delete tag\n");
return (UPDATER_ERR_PROTO);
}
rcsfile_deletetag(rf, tag, revnum);
changed = 1;
break;
default:
fprintf(stderr, "Unknown %s\n", line);
return (UPDATER_ERR_PROTO);
break;
}
}
if (!changed) {
fattr_maskout(oldfattr, ~FA_MODTIME);
if (fattr_equal(oldfattr, sr->sr_serverattr) == 0)
lprintf(1, " SetAttrs %s", fup->coname);
else
lprintf(1, " Touch %s", fup->coname);
if (fup->attic)
lprintf(1, " -> Attic");
lprintf(1, "\n");
fattr_free(oldfattr);
goto finish;
}
lprintf(1, " Edit %s", fup->coname);
if (fup->attic)
lprintf(1, " -> Attic");
lprintf(1, "\n");
/* 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)
return (UPDATER_ERR_PROTO);
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 (changed) {
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) {
/*
* XXX: Should we track delete count or is this a "silent"
* delete?
*/
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;
char *author, *cmd, *diffbase, *line, *logline, *revdate, *revnum, *state,
*textline;
size_t size;
int stop;
revnum = proto_get_ascii(&cmdline);
diffbase = proto_get_ascii(&cmdline); /* XXX: diffBase. */
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)
err(1, "Error adding delta %s\n", revnum);
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++;
}
rcsdelta_appendlog(d, logline, size);
logline = stream_getln(rd, &size);
}
break;
case 'N':
case 'n':
/* XXX: Not implemented. */
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. */
stop = 0;
textline = stream_getln(rd, &size);
while (textline != NULL) {
if (size == 2 && *textline == '.')
stop = 1;
if (size == 3 &&
memcmp(textline, ".+", 2) == 0) {
/* Truncate newline. */
stop = 1;
}
if (size >= 3 &&
memcmp(textline, "..", 2) == 0) {
size--;
textline++;
}
rcsdelta_appendtext(d, textline, size);
if (stop)
break;
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;
char buf[BUFSIZE], *line, *cmd;
char md5[MD5_DIGEST_SIZE];
int error, fd;
off_t bytes;
ssize_t nread;
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)
stream_write(to, buf, nread);
close(fd);
bytes = fattr_filesize(fa) - pos;
/* Append the new data. */
do {
nread = stream_read(up->rd, buf, (BUFSIZE > bytes) ? bytes : BUFSIZE);
bytes -= nread;
stream_write(to, buf, nread);
} 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);
/* UPDATE FILE. */
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. */
/* UPDATE IT. */
if (error)
return (error);
return (0);
}
/*
* 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)
{
char *line;
size_t size;
ssize_t nbytes;
int error, 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 coll *coll;
struct stream *to;
char md5[MD5_DIGEST_SIZE];
char *buf, *line;
int error, orig;
size_t size, blocknum, blockstart, blockcount;
ssize_t nbytes;
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, SEEK_SET, (blocksize * blockstart));
blocknum = 0;
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);
}