diff options
author | lulf <lulf@FreeBSD.org> | 2010-03-02 07:26:07 +0000 |
---|---|---|
committer | lulf <lulf@FreeBSD.org> | 2010-03-02 07:26:07 +0000 |
commit | c6aa3ac44645e36e9cc1e29aa598a706779c2c29 (patch) | |
tree | 34e14c6edd4db3c9e2addc2e9b8af07a970b71ed /usr.bin/csup/detailer.c | |
parent | b86208843f144382d49d980b9908eb8618cfc5cd (diff) | |
download | FreeBSD-src-c6aa3ac44645e36e9cc1e29aa598a706779c2c29.zip FreeBSD-src-c6aa3ac44645e36e9cc1e29aa598a706779c2c29.tar.gz |
- Move csup away from contrib/ and into usr.bin/. Software is no longer
contributed, and main development is happening in the FreeBSD repo.
Suggested by: joel
Diffstat (limited to 'usr.bin/csup/detailer.c')
-rw-r--r-- | usr.bin/csup/detailer.c | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/usr.bin/csup/detailer.c b/usr.bin/csup/detailer.c new file mode 100644 index 0000000..5592655 --- /dev/null +++ b/usr.bin/csup/detailer.c @@ -0,0 +1,603 @@ +/*- + * 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 <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "config.h" +#include "detailer.h" +#include "fixups.h" +#include "globtree.h" +#include "misc.h" +#include "mux.h" +#include "proto.h" +#include "rcsfile.h" +#include "rsyncfile.h" +#include "status.h" +#include "stream.h" + +/* Internal error codes. */ +#define DETAILER_ERR_PROTO (-1) /* Protocol error. */ +#define DETAILER_ERR_MSG (-2) /* Error is in detailer->errmsg. */ +#define DETAILER_ERR_READ (-3) /* Error reading from server. */ +#define DETAILER_ERR_WRITE (-4) /* Error writing to server. */ + +struct detailer { + struct config *config; + struct stream *rd; + struct stream *wr; + char *errmsg; +}; + +static int detailer_batch(struct detailer *); +static int detailer_coll(struct detailer *, struct coll *, + struct status *); +static int detailer_dofile_co(struct detailer *, struct coll *, + struct status *, char *); +static int detailer_dofile_rcs(struct detailer *, struct coll *, + char *, char *); +static int detailer_dofile_regular(struct detailer *, char *, char *); +static int detailer_dofile_rsync(struct detailer *, char *, char *); +static int detailer_checkrcsattr(struct detailer *, struct coll *, char *, + struct fattr *, int); +int detailer_send_details(struct detailer *, struct coll *, char *, + char *, struct fattr *); + +void * +detailer(void *arg) +{ + struct thread_args *args; + struct detailer dbuf, *d; + int error; + + args = arg; + + d = &dbuf; + d->config = args->config; + d->rd = args->rd; + d->wr = args->wr; + d->errmsg = NULL; + + error = detailer_batch(d); + switch (error) { + case DETAILER_ERR_PROTO: + xasprintf(&args->errmsg, "Detailer failed: Protocol error"); + args->status = STATUS_FAILURE; + break; + case DETAILER_ERR_MSG: + xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg); + free(d->errmsg); + args->status = STATUS_FAILURE; + break; + case DETAILER_ERR_READ: + if (stream_eof(d->rd)) { + xasprintf(&args->errmsg, "Detailer failed: " + "Premature EOF from server"); + } else { + xasprintf(&args->errmsg, "Detailer failed: " + "Network read failure: %s", strerror(errno)); + } + args->status = STATUS_TRANSIENTFAILURE; + break; + case DETAILER_ERR_WRITE: + xasprintf(&args->errmsg, "Detailer failed: " + "Network write failure: %s", strerror(errno)); + args->status = STATUS_TRANSIENTFAILURE; + break; + default: + assert(error == 0); + args->status = STATUS_SUCCESS; + } + return (NULL); +} + +static int +detailer_batch(struct detailer *d) +{ + struct config *config; + struct stream *rd, *wr; + struct coll *coll; + struct status *st; + struct fixup *fixup; + char *cmd, *collname, *line, *release; + int error, fixupseof; + + config = d->config; + rd = d->rd; + wr = d->wr; + STAILQ_FOREACH(coll, &config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + line = stream_getln(rd, NULL); + cmd = proto_get_ascii(&line); + collname = proto_get_ascii(&line); + release = proto_get_ascii(&line); + error = proto_get_time(&line, &coll->co_scantime); + if (error || line != NULL || strcmp(cmd, "COLL") != 0 || + strcmp(collname, coll->co_name) != 0 || + strcmp(release, coll->co_release) != 0) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "COLL %s %s\n", coll->co_name, + coll->co_release); + if (error) + return (DETAILER_ERR_WRITE); + stream_flush(wr); + if (coll->co_options & CO_COMPRESS) { + stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); + stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); + } + st = status_open(coll, -1, &d->errmsg); + if (st == NULL) + return (DETAILER_ERR_MSG); + error = detailer_coll(d, coll, st); + status_close(st, NULL); + if (error) + return (error); + if (coll->co_options & CO_COMPRESS) { + stream_filter_stop(rd); + stream_filter_stop(wr); + } + stream_flush(wr); + } + line = stream_getln(rd, NULL); + if (line == NULL) + return (DETAILER_ERR_READ); + if (strcmp(line, ".") != 0) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + stream_flush(wr); + + /* Now send fixups if needed. */ + fixup = NULL; + fixupseof = 0; + STAILQ_FOREACH(coll, &config->colls, co_next) { + if (coll->co_options & CO_SKIP) + continue; + error = proto_printf(wr, "COLL %s %s\n", coll->co_name, + coll->co_release); + if (error) + return (DETAILER_ERR_WRITE); + if (coll->co_options & CO_COMPRESS) + stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); + while (!fixupseof) { + if (fixup == NULL) + fixup = fixups_get(config->fixups); + if (fixup == NULL) { + fixupseof = 1; + break; + } + if (fixup->f_coll != coll) + break; + if (coll->co_options & CO_CHECKOUTMODE) + error = proto_printf(wr, "Y %s %s %s\n", + fixup->f_name, coll->co_tag, coll->co_date); + else { + error = proto_printf(wr, "A %s\n", + fixup->f_name); + } + if (error) + return (DETAILER_ERR_WRITE); + fixup = NULL; + } + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + if (coll->co_options & CO_COMPRESS) + stream_filter_stop(wr); + stream_flush(wr); + } + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} + +static int +detailer_coll(struct detailer *d, struct coll *coll, struct status *st) +{ + struct fattr *rcsattr; + struct stream *rd, *wr; + char *attr, *cmd, *file, *line, *msg, *path, *target; + int error, attic; + + rd = d->rd; + wr = d->wr; + attic = 0; + line = stream_getln(rd, NULL); + if (line == NULL) + return (DETAILER_ERR_READ); + while (strcmp(line, ".") != 0) { + cmd = proto_get_ascii(&line); + if (cmd == NULL || strlen(cmd) != 1) + return (DETAILER_ERR_PROTO); + switch (cmd[0]) { + case 'D': + /* Delete file. */ + file = proto_get_ascii(&line); + if (file == NULL || line != NULL) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "D %s\n", file); + if (error) + return (DETAILER_ERR_WRITE); + break; + case 'I': + case 'i': + case 'j': + /* Directory operations. */ + file = proto_get_ascii(&line); + if (file == NULL || line != NULL) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "%s %s\n", cmd, file); + if (error) + return (DETAILER_ERR_WRITE); + break; + case 'J': + /* Set directory attributes. */ + file = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (file == NULL || line != NULL || attr == NULL) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "%s %s %s\n", cmd, file, attr); + if (error) + return (DETAILER_ERR_WRITE); + break; + case 'H': + case 'h': + /* Create a hard link. */ + file = proto_get_ascii(&line); + target = proto_get_ascii(&line); + if (file == NULL || target == NULL) + return (DETAILER_ERR_PROTO); + error = proto_printf(wr, "%s %s %s\n", cmd, file, + target); + break; + case 't': + file = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (file == NULL || attr == NULL || line != NULL) { + return (DETAILER_ERR_PROTO); + } + rcsattr = fattr_decode(attr); + if (rcsattr == NULL) { + return (DETAILER_ERR_PROTO); + } + error = detailer_checkrcsattr(d, coll, file, rcsattr, + 1); + break; + + case 'T': + file = proto_get_ascii(&line); + attr = proto_get_ascii(&line); + if (file == NULL || attr == NULL || line != NULL) + return (DETAILER_ERR_PROTO); + rcsattr = fattr_decode(attr); + if (rcsattr == NULL) + return (DETAILER_ERR_PROTO); + error = detailer_checkrcsattr(d, coll, file, rcsattr, + 0); + break; + + case 'U': + /* Add or update file. */ + file = proto_get_ascii(&line); + if (file == NULL || line != NULL) + return (DETAILER_ERR_PROTO); + if (coll->co_options & CO_CHECKOUTMODE) { + error = detailer_dofile_co(d, coll, st, file); + } else { + path = cvspath(coll->co_prefix, file, 0); + rcsattr = fattr_frompath(path, FATTR_NOFOLLOW); + error = detailer_send_details(d, coll, file, + path, rcsattr); + if (rcsattr != NULL) + fattr_free(rcsattr); + free(path); + } + if (error) + return (error); + break; + case '!': + /* Warning from server. */ + msg = proto_get_rest(&line); + if (msg == NULL) + return (DETAILER_ERR_PROTO); + lprintf(-1, "Server warning: %s\n", msg); + break; + default: + return (DETAILER_ERR_PROTO); + } + stream_flush(wr); + line = stream_getln(rd, NULL); + if (line == NULL) + return (DETAILER_ERR_READ); + } + error = proto_printf(wr, ".\n"); + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} + +/* + * Tell the server to update a regular file. + */ +static int +detailer_dofile_regular(struct detailer *d, char *name, char *path) +{ + struct stream *wr; + struct stat st; + char md5[MD5_DIGEST_SIZE]; + int error; + + wr = d->wr; + error = stat(path, &st); + /* If we don't have it or it's unaccessible, we want it again. */ + if (error) { + proto_printf(wr, "A %s\n", name); + return (0); + } + + /* If not, we want the file to be updated. */ + error = MD5_File(path, md5); + if (error) { + lprintf(-1, "Error reading \"%s\"\n", name); + return (error); + } + error = proto_printf(wr, "R %s %O %s\n", name, st.st_size, md5); + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} + +/* + * Tell the server to update a file with the rsync algorithm. + */ +static int +detailer_dofile_rsync(struct detailer *d, char *name, char *path) +{ + struct stream *wr; + struct rsyncfile *rf; + + wr = d->wr; + rf = rsync_open(path, 0, 1); + if (rf == NULL) { + /* Fallback if we fail in opening it. */ + proto_printf(wr, "A %s\n", name); + return (0); + } + proto_printf(wr, "r %s %z %z\n", name, rsync_filesize(rf), + rsync_blocksize(rf)); + /* Detail the blocks. */ + while (rsync_nextblock(rf) != 0) + proto_printf(wr, "%s %s\n", rsync_rsum(rf), rsync_blockmd5(rf)); + proto_printf(wr, ".\n"); + rsync_close(rf); + return (0); +} + +/* + * Tell the server to update an RCS file that we have, or send it if we don't. + */ +static int +detailer_dofile_rcs(struct detailer *d, struct coll *coll, char *name, + char *path) +{ + struct stream *wr; + struct fattr *fa; + struct rcsfile *rf; + int error; + + wr = d->wr; + path = atticpath(coll->co_prefix, name); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + /* We don't have it, so send request to get it. */ + error = proto_printf(wr, "A %s\n", name); + if (error) + return (DETAILER_ERR_WRITE); + free(path); + return (0); + } + + rf = rcsfile_frompath(path, name, coll->co_cvsroot, coll->co_tag, 1); + free(path); + if (rf == NULL) { + error = proto_printf(wr, "A %s\n", name); + if (error) + return (DETAILER_ERR_WRITE); + return (0); + } + /* Tell to update the RCS file. The client version details follow. */ + rcsfile_send_details(rf, wr); + rcsfile_free(rf); + fattr_free(fa); + return (0); +} + +static int +detailer_dofile_co(struct detailer *d, struct coll *coll, struct status *st, + char *file) +{ + struct stream *wr; + struct fattr *fa; + struct statusrec *sr; + char md5[MD5_DIGEST_SIZE]; + char *path; + int error, ret; + + wr = d->wr; + path = checkoutpath(coll->co_prefix, file); + if (path == NULL) + return (DETAILER_ERR_PROTO); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + if (fa == NULL) { + /* We don't have the file, so the only option at this + point is to tell the server to send it. The server + may figure out that the file is dead, in which case + it will tell us. */ + error = proto_printf(wr, "C %s %s %s\n", + file, coll->co_tag, coll->co_date); + free(path); + if (error) + return (DETAILER_ERR_WRITE); + return (0); + } + ret = status_get(st, file, 0, 0, &sr); + if (ret == -1) { + d->errmsg = status_errmsg(st); + free(path); + return (DETAILER_ERR_MSG); + } + if (ret == 0) + sr = NULL; + + /* If our recorded information doesn't match the file that the + client has, then ignore the recorded information. */ + if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE || + !fattr_equal(sr->sr_clientattr, fa))) + sr = NULL; + fattr_free(fa); + if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) { + error = proto_printf(wr, "U %s %s %s %s %s\n", file, + coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate); + free(path); + if (error) + return (DETAILER_ERR_WRITE); + return (0); + } + + /* + * We don't have complete and/or accurate recorded information + * about what version of the file we have. Compute the file's + * checksum as an aid toward identifying which version it is. + */ + error = MD5_File(path, md5); + if (error) { + xasprintf(&d->errmsg, + "Cannot calculate checksum for \"%s\": %s", path, + strerror(errno)); + return (DETAILER_ERR_MSG); + } + free(path); + if (sr == NULL) { + error = proto_printf(wr, "S %s %s %s %s\n", file, + coll->co_tag, coll->co_date, md5); + } else { + error = proto_printf(wr, "s %s %s %s %s %s\n", file, + coll->co_tag, coll->co_date, sr->sr_revnum, md5); + } + if (error) + return (DETAILER_ERR_WRITE); + return (0); +} + +int +detailer_checkrcsattr(struct detailer *d, struct coll *coll, char *name, + struct fattr *server_attr, int attic) +{ + struct fattr *client_attr; + char *attr, *path; + int error; + + /* + * I don't think we can use the status file, since it only records file + * attributes in cvsmode. + */ + client_attr = NULL; + path = cvspath(coll->co_prefix, name, attic); + if (path == NULL) { + return (DETAILER_ERR_PROTO); + } + + if (access(path, F_OK) == 0 && + ((client_attr = fattr_frompath(path, FATTR_NOFOLLOW)) != NULL) && + fattr_equal(client_attr, server_attr)) { + attr = fattr_encode(client_attr, NULL, 0); + if (attic) { + error = proto_printf(d->wr, "l %s %s\n", name, attr); + } else { + error = proto_printf(d->wr, "L %s %s\n", name, attr); + } + free(attr); + free(path); + fattr_free(client_attr); + if (error) + return (DETAILER_ERR_WRITE); + return (0); + } + /* We don't have it, so tell the server to send it. */ + error = detailer_send_details(d, coll, name, path, client_attr); + fattr_free(client_attr); + free(path); + return (error); +} + +int +detailer_send_details(struct detailer *d, struct coll *coll, char *name, + char *path, struct fattr *fa) +{ + int error; + size_t len; + + /* + * Try to check if the file exists either live or dead to see if we can + * edit it and put it live or dead, rather than receiving the entire + * file. + */ + if (fa == NULL) { + path = atticpath(coll->co_prefix, name); + fa = fattr_frompath(path, FATTR_NOFOLLOW); + } + if (fa == NULL) { + error = proto_printf(d->wr, "A %s\n", name); + if (error) + return (DETAILER_ERR_WRITE); + } else if (fattr_type(fa) == FT_FILE) { + if (isrcs(name, &len) && !(coll->co_options & CO_NORCS)) { + detailer_dofile_rcs(d, coll, name, path); + } else if (!(coll->co_options & CO_NORSYNC) && + !globtree_test(coll->co_norsync, name)) { + detailer_dofile_rsync(d, name, path); + } else { + detailer_dofile_regular(d, name, path); + } + } else { + error = proto_printf(d->wr, "N %s\n", name); + if (error) + return (DETAILER_ERR_WRITE); + } + return (0); +} |