summaryrefslogtreecommitdiffstats
path: root/usr.bin/csup/status.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/csup/status.c')
-rw-r--r--usr.bin/csup/status.c874
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);
+}
OpenPOWER on IntegriCloud