c6aa3ac446
contributed, and main development is happening in the FreeBSD repo. Suggested by: joel
875 lines
20 KiB
C
875 lines
20 KiB
C
/*-
|
|
* 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);
|
|
}
|