diff options
Diffstat (limited to 'usr.bin/csup/status.c')
-rw-r--r-- | usr.bin/csup/status.c | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/usr.bin/csup/status.c b/usr.bin/csup/status.c new file mode 100644 index 0000000..3482e8e --- /dev/null +++ b/usr.bin/csup/status.c @@ -0,0 +1,874 @@ +/*- + * Copyright (c) 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 <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "fattr.h" +#include "misc.h" +#include "pathcomp.h" +#include "proto.h" +#include "queue.h" +#include "status.h" +#include "stream.h" + +#define STATUS_VERSION 5 + +/* Internal error codes. */ +#define STATUS_ERR_READ (-1) +#define STATUS_ERR_WRITE (-2) +#define STATUS_ERR_PARSE (-3) +#define STATUS_ERR_UNSORTED (-4) +#define STATUS_ERR_TRUNC (-5) +#define STATUS_ERR_BOGUS_DIRUP (-6) +#define STATUS_ERR_BAD_TYPE (-7) +#define STATUS_ERR_RENAME (-8) + +static struct status *status_new(char *, time_t, struct stream *); +static struct statusrec *status_rd(struct status *); +static struct statusrec *status_rdraw(struct status *, char **); +static int status_wr(struct status *, struct statusrec *); +static int status_wrraw(struct status *, struct statusrec *, + char *); +static struct status *status_fromrd(char *, struct stream *); +static struct status *status_fromnull(char *); +static void status_free(struct status *); + +static void statusrec_init(struct statusrec *); +static void statusrec_fini(struct statusrec *); +static int statusrec_cook(struct statusrec *, char *); +static int statusrec_cmp(struct statusrec *, struct statusrec *); + +struct status { + char *path; + char *tempfile; + int error; + int suberror; + struct pathcomp *pc; + struct statusrec buf; + struct statusrec *previous; + struct statusrec *current; + struct stream *rd; + struct stream *wr; + time_t scantime; + int eof; + int linenum; + int depth; + int dirty; +}; + +static void +statusrec_init(struct statusrec *sr) +{ + + memset(sr, 0, sizeof(*sr)); +} + +static int +statusrec_cook(struct statusrec *sr, char *line) +{ + char *clientattr, *serverattr; + + switch (sr->sr_type) { + case SR_FILEDEAD: + case SR_FILELIVE: + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) + return (-1); + break; + case SR_DIRDOWN: + /* Nothing to do. */ + if (line != NULL) + return (-1); + break; + case SR_CHECKOUTLIVE: + sr->sr_tag = proto_get_ascii(&line); + sr->sr_date = proto_get_ascii(&line); + serverattr = proto_get_ascii(&line); + sr->sr_revnum = proto_get_ascii(&line); + sr->sr_revdate = proto_get_ascii(&line); + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_serverattr = fattr_decode(serverattr); + if (sr->sr_serverattr == NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) { + fattr_free(sr->sr_serverattr); + return (-1); + } + break; + case SR_CHECKOUTDEAD: + sr->sr_tag = proto_get_ascii(&line); + sr->sr_date = proto_get_ascii(&line); + serverattr = proto_get_ascii(&line); + if (serverattr == NULL || line != NULL) + return (-1); + sr->sr_serverattr = fattr_decode(serverattr); + if (sr->sr_serverattr == NULL) + return (-1); + break; + case SR_DIRUP: + clientattr = proto_get_ascii(&line); + if (clientattr == NULL || line != NULL) + return (-1); + sr->sr_clientattr = fattr_decode(clientattr); + if (sr->sr_clientattr == NULL) + return (-1); + break; + default: + return (-1); + } + return (0); +} + +static struct statusrec * +status_rd(struct status *st) +{ + struct statusrec *sr; + char *line; + int error; + + sr = status_rdraw(st, &line); + if (sr == NULL) + return (NULL); + error = statusrec_cook(sr, line); + if (error) { + st->error = STATUS_ERR_PARSE; + return (NULL); + } + return (sr); +} + +static struct statusrec * +status_rdraw(struct status *st, char **linep) +{ + struct statusrec sr; + char *cmd, *line, *file; + + if (st->rd == NULL || st->eof) + return (NULL); + line = stream_getln(st->rd, NULL); + if (line == NULL) { + if (stream_eof(st->rd)) { + if (st->depth != 0) { + st->error = STATUS_ERR_TRUNC; + return (NULL); + } + st->eof = 1; + return (NULL); + } + st->error = STATUS_ERR_READ; + st->suberror = errno; + return (NULL); + } + st->linenum++; + cmd = proto_get_ascii(&line); + file = proto_get_ascii(&line); + if (file == NULL || strlen(cmd) != 1) { + st->error = STATUS_ERR_PARSE; + return (NULL); + } + + switch (cmd[0]) { + case 'A': + sr.sr_type = SR_FILELIVE; + break; + case 'D': + sr.sr_type = SR_DIRDOWN; + st->depth++; + break; + case 'C': + sr.sr_type = SR_CHECKOUTLIVE; + break; + case 'c': + sr.sr_type = SR_CHECKOUTDEAD; + break; + case 'U': + sr.sr_type = SR_DIRUP; + if (st->depth <= 0) { + st->error = STATUS_ERR_BOGUS_DIRUP; + return (NULL); + } + st->depth--; + break; + case 'V': + sr.sr_type = SR_FILELIVE; + break; + case 'v': + sr.sr_type = SR_FILEDEAD; + break; + default: + st->error = STATUS_ERR_BAD_TYPE; + st->suberror = cmd[0]; + return (NULL); + } + + sr.sr_file = xstrdup(file); + if (st->previous != NULL && + statusrec_cmp(st->previous, &sr) >= 0) { + st->error = STATUS_ERR_UNSORTED; + free(sr.sr_file); + return (NULL); + } + + if (st->previous == NULL) { + st->previous = &st->buf; + } else { + statusrec_fini(st->previous); + statusrec_init(st->previous); + } + st->previous->sr_type = sr.sr_type; + st->previous->sr_file = sr.sr_file; + *linep = line; + return (st->previous); +} + +static int +status_wr(struct status *st, struct statusrec *sr) +{ + struct pathcomp *pc; + const struct fattr *fa; + char *name; + int error, type, usedirupattr; + + pc = st->pc; + error = 0; + usedirupattr = 0; + if (sr->sr_type == SR_DIRDOWN) { + pathcomp_put(pc, PC_DIRDOWN, sr->sr_file); + } else if (sr->sr_type == SR_DIRUP) { + pathcomp_put(pc, PC_DIRUP, sr->sr_file); + usedirupattr = 1; + } else { + pathcomp_put(pc, PC_FILE, sr->sr_file); + } + + while (pathcomp_get(pc, &type, &name)) { + if (type == PC_DIRDOWN) { + error = proto_printf(st->wr, "D %s\n", name); + } else if (type == PC_DIRUP) { + if (usedirupattr) + fa = sr->sr_clientattr; + else + fa = fattr_bogus; + usedirupattr = 0; + error = proto_printf(st->wr, "U %s %f\n", name, fa); + } + if (error) + goto bad; + } + + switch (sr->sr_type) { + case SR_DIRDOWN: + case SR_DIRUP: + /* Already emitted above. */ + break; + case SR_CHECKOUTLIVE: + error = proto_printf(st->wr, "C %s %s %s %f %s %s %f\n", + sr->sr_file, sr->sr_tag, sr->sr_date, sr->sr_serverattr, + sr->sr_revnum, sr->sr_revdate, sr->sr_clientattr); + break; + case SR_CHECKOUTDEAD: + error = proto_printf(st->wr, "c %s %s %s %f\n", sr->sr_file, + sr->sr_tag, sr->sr_date, sr->sr_serverattr); + break; + case SR_FILELIVE: + error = proto_printf(st->wr, "V %s %f\n", sr->sr_file, + sr->sr_clientattr); + break; + case SR_FILEDEAD: + error = proto_printf(st->wr, "v %s %f\n", sr->sr_file, + sr->sr_clientattr); + break; + } + if (error) + goto bad; + return (0); +bad: + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + return (-1); +} + +static int +status_wrraw(struct status *st, struct statusrec *sr, char *line) +{ + char *name; + char cmd; + int error, ret, type; + + if (st->wr == NULL) + return (0); + + /* + * Keep the compressor in sync. At this point, the necessary + * DirDowns and DirUps should have already been emitted, so the + * compressor should return exactly one value in the PC_DIRDOWN + * and PC_DIRUP case and none in the PC_FILE case. + */ + if (sr->sr_type == SR_DIRDOWN) + pathcomp_put(st->pc, PC_DIRDOWN, sr->sr_file); + else if (sr->sr_type == SR_DIRUP) + pathcomp_put(st->pc, PC_DIRUP, sr->sr_file); + else + pathcomp_put(st->pc, PC_FILE, sr->sr_file); + if (sr->sr_type == SR_DIRDOWN || sr->sr_type == SR_DIRUP) { + ret = pathcomp_get(st->pc, &type, &name); + assert(ret); + if (sr->sr_type == SR_DIRDOWN) + assert(type == PC_DIRDOWN); + else + assert(type == PC_DIRUP); + } + ret = pathcomp_get(st->pc, &type, &name); + assert(!ret); + + switch (sr->sr_type) { + case SR_DIRDOWN: + cmd = 'D'; + break; + case SR_DIRUP: + cmd = 'U'; + break; + case SR_CHECKOUTLIVE: + cmd = 'C'; + break; + case SR_CHECKOUTDEAD: + cmd = 'c'; + break; + case SR_FILELIVE: + cmd = 'V'; + break; + case SR_FILEDEAD: + cmd = 'v'; + break; + default: + assert(0); + return (-1); + } + if (sr->sr_type == SR_DIRDOWN) + error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file); + else + error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file, + line); + if (error) { + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + return (-1); + } + return (0); +} + +static void +statusrec_fini(struct statusrec *sr) +{ + + fattr_free(sr->sr_serverattr); + fattr_free(sr->sr_clientattr); + free(sr->sr_file); +} + +static int +statusrec_cmp(struct statusrec *a, struct statusrec *b) +{ + size_t lena, lenb; + + if (a->sr_type == SR_DIRUP || b->sr_type == SR_DIRUP) { + lena = strlen(a->sr_file); + lenb = strlen(b->sr_file); + if (a->sr_type == SR_DIRUP && + ((lena < lenb && b->sr_file[lena] == '/') || lena == lenb) + && strncmp(a->sr_file, b->sr_file, lena) == 0) + return (1); + if (b->sr_type == SR_DIRUP && + ((lenb < lena && a->sr_file[lenb] == '/') || lenb == lena) + && strncmp(a->sr_file, b->sr_file, lenb) == 0) + return (-1); + } + return (pathcmp(a->sr_file, b->sr_file)); +} + +static struct status * +status_new(char *path, time_t scantime, struct stream *file) +{ + struct status *st; + + st = xmalloc(sizeof(struct status)); + st->path = path; + st->error = 0; + st->suberror = 0; + st->tempfile = NULL; + st->scantime = scantime; + st->rd = file; + st->wr = NULL; + st->previous = NULL; + st->current = NULL; + st->dirty = 0; + st->eof = 0; + st->linenum = 0; + st->depth = 0; + st->pc = pathcomp_new(); + statusrec_init(&st->buf); + return (st); +} + +static void +status_free(struct status *st) +{ + + if (st->previous != NULL) + statusrec_fini(st->previous); + if (st->rd != NULL) + stream_close(st->rd); + if (st->wr != NULL) + stream_close(st->wr); + if (st->tempfile != NULL) + free(st->tempfile); + free(st->path); + pathcomp_free(st->pc); + free(st); +} + +static struct status * +status_fromrd(char *path, struct stream *file) +{ + struct status *st; + char *id, *line; + time_t scantime; + int error, ver; + + /* Get the first line of the file and validate it. */ + line = stream_getln(file, NULL); + if (line == NULL) { + stream_close(file); + return (NULL); + } + id = proto_get_ascii(&line); + error = proto_get_int(&line, &ver, 10); + if (error) { + stream_close(file); + return (NULL); + } + error = proto_get_time(&line, &scantime); + if (error || line != NULL) { + stream_close(file); + return (NULL); + } + + if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) { + stream_close(file); + return (NULL); + } + + st = status_new(path, scantime, file); + st->linenum = 1; + return (st); +} + +static struct status * +status_fromnull(char *path) +{ + struct status *st; + + st = status_new(path, -1, NULL); + st->eof = 1; + return (st); +} + +/* + * Open the status file. If scantime is not -1, the file is opened + * for updating, otherwise, it is opened read-only. If the status file + * couldn't be opened, NULL is returned and errmsg is set to the error + * message. + */ +struct status * +status_open(struct coll *coll, time_t scantime, char **errmsg) +{ + struct status *st; + struct stream *file; + struct fattr *fa; + char *destpath, *path; + int error, rv; + + path = coll_statuspath(coll); + file = stream_open_file(path, O_RDONLY); + if (file == NULL) { + if (errno != ENOENT) { + xasprintf(errmsg, "Could not open \"%s\": %s\n", + path, strerror(errno)); + free(path); + return (NULL); + } + st = status_fromnull(path); + } else { + st = status_fromrd(path, file); + if (st == NULL) { + xasprintf(errmsg, "Error in \"%s\": Bad header line", + path); + free(path); + return (NULL); + } + } + + if (scantime != -1) { + /* Open for writing too. */ + xasprintf(&destpath, "%s/%s/%s/", coll->co_base, + coll->co_colldir, coll->co_name); + st->tempfile = tempname(destpath); + if (mkdirhier(destpath, coll->co_umask) != 0) { + xasprintf(errmsg, "Cannot create directories leading " + "to \"%s\": %s", destpath, strerror(errno)); + free(destpath); + status_free(st); + return (NULL); + } + free(destpath); + st->wr = stream_open_file(st->tempfile, + O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (st->wr == NULL) { + xasprintf(errmsg, "Cannot create \"%s\": %s", + st->tempfile, strerror(errno)); + status_free(st); + return (NULL); + } + fa = fattr_new(FT_FILE, -1); + fattr_mergedefault(fa); + fattr_umask(fa, coll->co_umask); + rv = fattr_install(fa, st->tempfile, NULL); + fattr_free(fa); + if (rv == -1) { + xasprintf(errmsg, + "Cannot set attributes for \"%s\": %s", + st->tempfile, strerror(errno)); + status_free(st); + return (NULL); + } + if (scantime != st->scantime) + st->dirty = 1; + error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION, + scantime); + if (error) { + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + *errmsg = status_errmsg(st); + status_free(st); + return (NULL); + } + } + return (st); +} + +/* + * Get an entry from the status file. If name is NULL, the next entry + * is returned. If name is not NULL, the entry matching this name is + * returned, or NULL if it couldn't be found. If deleteto is set to 1, + * all the entries read from the status file while looking for the + * given name are deleted. + */ +int +status_get(struct status *st, char *name, int isdirup, int deleteto, + struct statusrec **psr) +{ + struct statusrec key; + struct statusrec *sr; + char *line; + int c, error; + + if (st->eof) + return (0); + + if (st->error) + return (-1); + + if (name == NULL) { + sr = status_rd(st); + if (sr == NULL) { + if (st->error) + return (-1); + return (0); + } + *psr = sr; + return (1); + } + + if (st->current != NULL) { + sr = st->current; + st->current = NULL; + } else { + sr = status_rd(st); + if (sr == NULL) { + if (st->error) + return (-1); + return (0); + } + } + + key.sr_file = name; + if (isdirup) + key.sr_type = SR_DIRUP; + else + key.sr_type = SR_CHECKOUTLIVE; + + c = statusrec_cmp(sr, &key); + if (c < 0) { + if (st->wr != NULL && !deleteto) { + error = status_wr(st, sr); + if (error) + return (-1); + } + /* Loop until we find the good entry. */ + for (;;) { + sr = status_rdraw(st, &line); + if (sr == NULL) { + if (st->error) + return (-1); + return (0); + } + c = statusrec_cmp(sr, &key); + if (c >= 0) + break; + if (st->wr != NULL && !deleteto) { + error = status_wrraw(st, sr, line); + if (error) + return (-1); + } + } + error = statusrec_cook(sr, line); + if (error) { + st->error = STATUS_ERR_PARSE; + return (-1); + } + } + st->current = sr; + if (c != 0) + return (0); + *psr = sr; + return (1); +} + +/* + * Put this entry into the status file. If an entry with the same name + * existed in the status file, it is replaced by this one, otherwise, + * the entry is added to the status file. + */ +int +status_put(struct status *st, struct statusrec *sr) +{ + struct statusrec *old; + int error, ret; + + ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old); + if (ret == -1) + return (-1); + if (ret) { + if (old->sr_type == SR_DIRDOWN) { + /* DirUp should never match DirDown */ + assert(old->sr_type != SR_DIRUP); + if (sr->sr_type == SR_CHECKOUTLIVE || + sr->sr_type == SR_CHECKOUTDEAD) { + /* We are replacing a directory with a file. + Delete all entries inside the directory we + are replacing. */ + ret = status_get(st, sr->sr_file, 1, 1, &old); + if (ret == -1) + return (-1); + assert(ret); + } + } else + st->current = NULL; + } + st->dirty = 1; + error = status_wr(st, sr); + if (error) + return (-1); + return (0); +} + +/* + * Delete the specified entry from the status file. + */ +int +status_delete(struct status *st, char *name, int isdirup) +{ + struct statusrec *sr; + int ret; + + ret = status_get(st, name, isdirup, 0, &sr); + if (ret == -1) + return (-1); + if (ret) { + st->current = NULL; + st->dirty = 1; + } + return (0); +} + +/* + * Check whether we hit the end of file. + */ +int +status_eof(struct status *st) +{ + + return (st->eof); +} + +/* + * Returns the error message if there was an error, otherwise returns + * NULL. The error message is allocated dynamically and needs to be + * freed by the caller after use. + */ +char * +status_errmsg(struct status *st) +{ + char *errmsg; + + if (!st->error) + return (NULL); + switch (st->error) { + case STATUS_ERR_READ: + xasprintf(&errmsg, "Read failure on \"%s\": %s", + st->path, strerror(st->suberror)); + break; + case STATUS_ERR_WRITE: + xasprintf(&errmsg, "Write failure on \"%s\": %s", + st->tempfile, strerror(st->suberror)); + break; + case STATUS_ERR_PARSE: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "Could not parse status record", st->path, st->linenum); + break; + case STATUS_ERR_UNSORTED: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "File is not sorted properly", st->path, st->linenum); + break; + case STATUS_ERR_TRUNC: + xasprintf(&errmsg, "Error in \"%s\": " + "File is truncated", st->path); + break; + case STATUS_ERR_BOGUS_DIRUP: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "\"U\" entry has no matching \"D\"", st->path, st->linenum); + break; + case STATUS_ERR_BAD_TYPE: + xasprintf(&errmsg, "Error in \"%s\": %d: " + "Invalid file type \"%c\"", st->path, st->linenum, + st->suberror); + break; + case STATUS_ERR_RENAME: + xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s", + st->tempfile, st->path, strerror(st->suberror)); + break; + default: + assert(0); + return (NULL); + } + return (errmsg); +} + +/* + * Close the status file and free any resource associated with it. + * It is OK to pass NULL for errmsg only if the status file was + * opened read-only. If it wasn't opened read-only, status_close() + * can produce an error and errmsg is not allowed to be NULL. If + * there was no errors, errmsg is set to NULL. + */ +void +status_close(struct status *st, char **errmsg) +{ + struct statusrec *sr; + char *line, *name; + int error, type; + + if (st->wr != NULL) { + if (st->dirty) { + if (st->current != NULL) { + error = status_wr(st, st->current); + if (error) { + *errmsg = status_errmsg(st); + goto bad; + } + st->current = NULL; + } + /* Copy the rest of the file. */ + while ((sr = status_rdraw(st, &line)) != NULL) { + error = status_wrraw(st, sr, line); + if (error) { + *errmsg = status_errmsg(st); + goto bad; + } + } + if (st->error) { + *errmsg = status_errmsg(st); + goto bad; + } + + /* Close off all the open directories. */ + pathcomp_finish(st->pc); + while (pathcomp_get(st->pc, &type, &name)) { + assert(type == PC_DIRUP); + error = proto_printf(st->wr, "U %s %f\n", + name, fattr_bogus); + if (error) { + st->error = STATUS_ERR_WRITE; + st->suberror = errno; + *errmsg = status_errmsg(st); + goto bad; + } + } + + /* Rename tempfile. */ + error = rename(st->tempfile, st->path); + if (error) { + st->error = STATUS_ERR_RENAME; + st->suberror = errno; + *errmsg = status_errmsg(st); + goto bad; + } + } else { + /* Just discard the tempfile. */ + unlink(st->tempfile); + } + *errmsg = NULL; + } + status_free(st); + return; +bad: + status_free(st); +} |