1156230Smux/*-
2156230Smux * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
3156230Smux * All rights reserved.
4156230Smux *
5156230Smux * Redistribution and use in source and binary forms, with or without
6156230Smux * modification, are permitted provided that the following conditions
7156230Smux * are met:
8156230Smux * 1. Redistributions of source code must retain the above copyright
9156230Smux *    notice, this list of conditions and the following disclaimer.
10156230Smux * 2. Redistributions in binary form must reproduce the above copyright
11156230Smux *    notice, this list of conditions and the following disclaimer in the
12156230Smux *    documentation and/or other materials provided with the distribution.
13156230Smux *
14156230Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15156230Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16156230Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17156230Smux * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18156230Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19156230Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20156230Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21156230Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22156230Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23156230Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24156230Smux * SUCH DAMAGE.
25156230Smux *
26156230Smux * $FreeBSD$
27156230Smux */
28156230Smux
29156230Smux#include <assert.h>
30156230Smux#include <errno.h>
31156230Smux#include <fcntl.h>
32156230Smux#include <stdio.h>
33156230Smux#include <stdlib.h>
34156230Smux#include <string.h>
35156230Smux#include <unistd.h>
36156230Smux
37156230Smux#include "config.h"
38156230Smux#include "fattr.h"
39156230Smux#include "misc.h"
40156230Smux#include "pathcomp.h"
41156230Smux#include "proto.h"
42156230Smux#include "queue.h"
43156230Smux#include "status.h"
44156230Smux#include "stream.h"
45156230Smux
46156230Smux#define	STATUS_VERSION	5
47156230Smux
48156230Smux/* Internal error codes. */
49156230Smux#define	STATUS_ERR_READ		(-1)
50156230Smux#define	STATUS_ERR_WRITE	(-2)
51156230Smux#define	STATUS_ERR_PARSE	(-3)
52156230Smux#define	STATUS_ERR_UNSORTED	(-4)
53156230Smux#define	STATUS_ERR_TRUNC	(-5)
54156230Smux#define	STATUS_ERR_BOGUS_DIRUP	(-6)
55156230Smux#define	STATUS_ERR_BAD_TYPE	(-7)
56156230Smux#define	STATUS_ERR_RENAME	(-8)
57156230Smux
58156230Smuxstatic struct status	*status_new(char *, time_t, struct stream *);
59156230Smuxstatic struct statusrec	*status_rd(struct status *);
60156230Smuxstatic struct statusrec	*status_rdraw(struct status *, char **);
61156230Smuxstatic int		 status_wr(struct status *, struct statusrec *);
62156230Smuxstatic int		 status_wrraw(struct status *, struct statusrec *,
63156230Smux			     char *);
64156230Smuxstatic struct status	*status_fromrd(char *, struct stream *);
65156230Smuxstatic struct status	*status_fromnull(char *);
66156230Smuxstatic void		 status_free(struct status *);
67156230Smux
68156230Smuxstatic void		 statusrec_init(struct statusrec *);
69156230Smuxstatic void		 statusrec_fini(struct statusrec *);
70156230Smuxstatic int		 statusrec_cook(struct statusrec *, char *);
71156230Smuxstatic int		 statusrec_cmp(struct statusrec *, struct statusrec *);
72156230Smux
73156230Smuxstruct status {
74156230Smux	char *path;
75156230Smux	char *tempfile;
76156230Smux	int error;
77156230Smux	int suberror;
78156230Smux	struct pathcomp *pc;
79156230Smux	struct statusrec buf;
80156230Smux	struct statusrec *previous;
81156230Smux	struct statusrec *current;
82156230Smux	struct stream *rd;
83156230Smux	struct stream *wr;
84156230Smux	time_t scantime;
85156230Smux	int eof;
86156230Smux	int linenum;
87156230Smux	int depth;
88156230Smux	int dirty;
89156230Smux};
90156230Smux
91156230Smuxstatic void
92156230Smuxstatusrec_init(struct statusrec *sr)
93156230Smux{
94156230Smux
95156230Smux	memset(sr, 0, sizeof(*sr));
96156230Smux}
97156230Smux
98156230Smuxstatic int
99156230Smuxstatusrec_cook(struct statusrec *sr, char *line)
100156230Smux{
101156230Smux	char *clientattr, *serverattr;
102156230Smux
103156230Smux	switch (sr->sr_type) {
104186781Slulf	case SR_FILEDEAD:
105186781Slulf	case SR_FILELIVE:
106186781Slulf		clientattr = proto_get_ascii(&line);
107186781Slulf		if (clientattr == NULL || line != NULL)
108186781Slulf			return (-1);
109186781Slulf		sr->sr_clientattr = fattr_decode(clientattr);
110186781Slulf		if (sr->sr_clientattr == NULL)
111186781Slulf			return (-1);
112186781Slulf		break;
113156230Smux	case SR_DIRDOWN:
114156230Smux		/* Nothing to do. */
115156230Smux		if (line != NULL)
116156230Smux			return (-1);
117156230Smux		break;
118156230Smux	case SR_CHECKOUTLIVE:
119156230Smux		sr->sr_tag = proto_get_ascii(&line);
120156230Smux		sr->sr_date = proto_get_ascii(&line);
121156230Smux		serverattr = proto_get_ascii(&line);
122156230Smux		sr->sr_revnum = proto_get_ascii(&line);
123156230Smux		sr->sr_revdate = proto_get_ascii(&line);
124156230Smux		clientattr = proto_get_ascii(&line);
125156230Smux		if (clientattr == NULL || line != NULL)
126156230Smux			return (-1);
127156230Smux		sr->sr_serverattr = fattr_decode(serverattr);
128156230Smux		if (sr->sr_serverattr == NULL)
129156230Smux			return (-1);
130156230Smux		sr->sr_clientattr = fattr_decode(clientattr);
131156230Smux		if (sr->sr_clientattr == NULL) {
132156230Smux			fattr_free(sr->sr_serverattr);
133156230Smux			return (-1);
134156230Smux		}
135156230Smux		break;
136156230Smux	case SR_CHECKOUTDEAD:
137156230Smux		sr->sr_tag = proto_get_ascii(&line);
138156230Smux		sr->sr_date = proto_get_ascii(&line);
139156230Smux		serverattr = proto_get_ascii(&line);
140156230Smux		if (serverattr == NULL || line != NULL)
141156230Smux			return (-1);
142156230Smux		sr->sr_serverattr = fattr_decode(serverattr);
143156230Smux		if (sr->sr_serverattr == NULL)
144156230Smux			return (-1);
145156230Smux		break;
146156230Smux	case SR_DIRUP:
147156230Smux		clientattr = proto_get_ascii(&line);
148156230Smux		if (clientattr == NULL || line != NULL)
149156230Smux			return (-1);
150156230Smux		sr->sr_clientattr = fattr_decode(clientattr);
151156230Smux		if (sr->sr_clientattr == NULL)
152156230Smux			return (-1);
153156230Smux		break;
154156230Smux	default:
155156230Smux		return (-1);
156156230Smux	}
157156230Smux	return (0);
158156230Smux}
159156230Smux
160156230Smuxstatic struct statusrec *
161156230Smuxstatus_rd(struct status *st)
162156230Smux{
163156230Smux	struct statusrec *sr;
164156230Smux	char *line;
165156230Smux	int error;
166156230Smux
167156230Smux	sr = status_rdraw(st, &line);
168156230Smux	if (sr == NULL)
169156230Smux		return (NULL);
170156230Smux	error = statusrec_cook(sr, line);
171156230Smux	if (error) {
172156230Smux		st->error = STATUS_ERR_PARSE;
173156230Smux		return (NULL);
174156230Smux	}
175156230Smux	return (sr);
176156230Smux}
177156230Smux
178156230Smuxstatic struct statusrec *
179156230Smuxstatus_rdraw(struct status *st, char **linep)
180156230Smux{
181156230Smux	struct statusrec sr;
182156230Smux	char *cmd, *line, *file;
183156230Smux
184156230Smux	if (st->rd == NULL || st->eof)
185156230Smux		return (NULL);
186156230Smux	line = stream_getln(st->rd, NULL);
187156230Smux	if (line == NULL) {
188156230Smux		if (stream_eof(st->rd)) {
189156230Smux			if (st->depth != 0) {
190156230Smux				st->error = STATUS_ERR_TRUNC;
191156230Smux				return (NULL);
192156230Smux			}
193156230Smux			st->eof = 1;
194156230Smux			return (NULL);
195156230Smux		}
196156230Smux		st->error = STATUS_ERR_READ;
197156230Smux		st->suberror = errno;
198156230Smux		return (NULL);
199156230Smux	}
200156230Smux	st->linenum++;
201156230Smux	cmd = proto_get_ascii(&line);
202156230Smux	file = proto_get_ascii(&line);
203156230Smux	if (file == NULL || strlen(cmd) != 1) {
204156230Smux		st->error = STATUS_ERR_PARSE;
205156230Smux		return (NULL);
206156230Smux	}
207156230Smux
208156230Smux	switch (cmd[0]) {
209186781Slulf	case 'A':
210186781Slulf		sr.sr_type = SR_FILELIVE;
211186781Slulf		break;
212156230Smux	case 'D':
213156230Smux		sr.sr_type = SR_DIRDOWN;
214156230Smux		st->depth++;
215156230Smux		break;
216156230Smux	case 'C':
217156230Smux		sr.sr_type = SR_CHECKOUTLIVE;
218156230Smux		break;
219156230Smux	case 'c':
220156230Smux		sr.sr_type = SR_CHECKOUTDEAD;
221156230Smux		break;
222156230Smux	case 'U':
223156230Smux		sr.sr_type = SR_DIRUP;
224156230Smux		if (st->depth <= 0) {
225156230Smux			st->error = STATUS_ERR_BOGUS_DIRUP;
226156230Smux			return (NULL);
227156230Smux		}
228156230Smux		st->depth--;
229156230Smux		break;
230186781Slulf	case 'V':
231186781Slulf		sr.sr_type = SR_FILELIVE;
232186781Slulf		break;
233186781Slulf	case 'v':
234186781Slulf		sr.sr_type = SR_FILEDEAD;
235186781Slulf		break;
236156230Smux	default:
237156230Smux		st->error = STATUS_ERR_BAD_TYPE;
238156230Smux		st->suberror = cmd[0];
239156230Smux		return (NULL);
240156230Smux	}
241156230Smux
242156230Smux	sr.sr_file = xstrdup(file);
243156230Smux	if (st->previous != NULL &&
244156230Smux	    statusrec_cmp(st->previous, &sr) >= 0) {
245156230Smux		st->error = STATUS_ERR_UNSORTED;
246156230Smux		free(sr.sr_file);
247156230Smux		return (NULL);
248156230Smux	}
249156230Smux
250156230Smux	if (st->previous == NULL) {
251156230Smux		st->previous = &st->buf;
252156230Smux	} else {
253156230Smux		statusrec_fini(st->previous);
254156230Smux		statusrec_init(st->previous);
255156230Smux	}
256156230Smux	st->previous->sr_type = sr.sr_type;
257156230Smux	st->previous->sr_file = sr.sr_file;
258156230Smux	*linep = line;
259156230Smux	return (st->previous);
260156230Smux}
261156230Smux
262156230Smuxstatic int
263156230Smuxstatus_wr(struct status *st, struct statusrec *sr)
264156230Smux{
265156230Smux	struct pathcomp *pc;
266156230Smux	const struct fattr *fa;
267156230Smux	char *name;
268156230Smux	int error, type, usedirupattr;
269156230Smux
270156230Smux	pc = st->pc;
271156230Smux	error = 0;
272156230Smux	usedirupattr = 0;
273156230Smux	if (sr->sr_type == SR_DIRDOWN) {
274156230Smux		pathcomp_put(pc, PC_DIRDOWN, sr->sr_file);
275156230Smux	} else if (sr->sr_type == SR_DIRUP) {
276156230Smux		pathcomp_put(pc, PC_DIRUP, sr->sr_file);
277156230Smux		usedirupattr = 1;
278156230Smux	} else {
279156230Smux		pathcomp_put(pc, PC_FILE, sr->sr_file);
280156230Smux	}
281156230Smux
282156230Smux	while (pathcomp_get(pc, &type, &name)) {
283156230Smux		if (type == PC_DIRDOWN) {
284156230Smux			error = proto_printf(st->wr, "D %s\n", name);
285156230Smux		} else if (type == PC_DIRUP) {
286156230Smux			if (usedirupattr)
287156230Smux				fa = sr->sr_clientattr;
288156230Smux			else
289156230Smux				fa = fattr_bogus;
290156230Smux			usedirupattr = 0;
291156230Smux			error = proto_printf(st->wr, "U %s %f\n", name, fa);
292156230Smux		}
293156230Smux		if (error)
294156230Smux			goto bad;
295156230Smux	}
296156230Smux
297156230Smux	switch (sr->sr_type) {
298156230Smux	case SR_DIRDOWN:
299156230Smux	case SR_DIRUP:
300156230Smux		/* Already emitted above. */
301156230Smux		break;
302156230Smux	case SR_CHECKOUTLIVE:
303156230Smux		error = proto_printf(st->wr, "C %s %s %s %f %s %s %f\n",
304156230Smux		    sr->sr_file, sr->sr_tag, sr->sr_date, sr->sr_serverattr,
305156230Smux		    sr->sr_revnum, sr->sr_revdate, sr->sr_clientattr);
306156230Smux		break;
307156230Smux	case SR_CHECKOUTDEAD:
308156230Smux		error = proto_printf(st->wr, "c %s %s %s %f\n", sr->sr_file,
309156230Smux		    sr->sr_tag, sr->sr_date, sr->sr_serverattr);
310156230Smux		break;
311186781Slulf	case SR_FILELIVE:
312186781Slulf		error = proto_printf(st->wr, "V %s %f\n", sr->sr_file,
313186781Slulf		    sr->sr_clientattr);
314186781Slulf		break;
315186781Slulf	case SR_FILEDEAD:
316186781Slulf		error = proto_printf(st->wr, "v %s %f\n", sr->sr_file,
317186781Slulf		    sr->sr_clientattr);
318186781Slulf		break;
319156230Smux	}
320156230Smux	if (error)
321156230Smux		goto bad;
322156230Smux	return (0);
323156230Smuxbad:
324156230Smux	st->error = STATUS_ERR_WRITE;
325156230Smux	st->suberror = errno;
326156230Smux	return (-1);
327156230Smux}
328156230Smux
329156230Smuxstatic int
330156230Smuxstatus_wrraw(struct status *st, struct statusrec *sr, char *line)
331156230Smux{
332156230Smux	char *name;
333156230Smux	char cmd;
334156230Smux	int error, ret, type;
335156230Smux
336156230Smux	if (st->wr == NULL)
337156230Smux		return (0);
338156230Smux
339156230Smux	/*
340156230Smux	 * Keep the compressor in sync.  At this point, the necessary
341156230Smux	 * DirDowns and DirUps should have already been emitted, so the
342156230Smux	 * compressor should return exactly one value in the PC_DIRDOWN
343156230Smux	 * and PC_DIRUP case and none in the PC_FILE case.
344156230Smux	 */
345156230Smux	if (sr->sr_type == SR_DIRDOWN)
346156230Smux		pathcomp_put(st->pc, PC_DIRDOWN, sr->sr_file);
347156230Smux	else if (sr->sr_type == SR_DIRUP)
348156230Smux		pathcomp_put(st->pc, PC_DIRUP, sr->sr_file);
349156230Smux	else
350156230Smux		pathcomp_put(st->pc, PC_FILE, sr->sr_file);
351156230Smux	if (sr->sr_type == SR_DIRDOWN || sr->sr_type == SR_DIRUP) {
352156230Smux		ret = pathcomp_get(st->pc, &type, &name);
353156230Smux		assert(ret);
354156230Smux		if (sr->sr_type == SR_DIRDOWN)
355156230Smux			assert(type == PC_DIRDOWN);
356156230Smux		else
357156230Smux			assert(type == PC_DIRUP);
358156230Smux	}
359156230Smux	ret = pathcomp_get(st->pc, &type, &name);
360156230Smux	assert(!ret);
361156230Smux
362156230Smux	switch (sr->sr_type) {
363156230Smux	case SR_DIRDOWN:
364156230Smux		cmd = 'D';
365156230Smux		break;
366156230Smux	case SR_DIRUP:
367156230Smux		cmd = 'U';
368156230Smux		break;
369156230Smux	case SR_CHECKOUTLIVE:
370156230Smux		cmd = 'C';
371156230Smux		break;
372156230Smux	case SR_CHECKOUTDEAD:
373156230Smux		cmd = 'c';
374156230Smux		break;
375186781Slulf	case SR_FILELIVE:
376186781Slulf		cmd = 'V';
377186781Slulf		break;
378186781Slulf	case SR_FILEDEAD:
379186781Slulf		cmd = 'v';
380186781Slulf		break;
381156230Smux	default:
382156230Smux		assert(0);
383156230Smux		return (-1);
384156230Smux	}
385156230Smux	if (sr->sr_type == SR_DIRDOWN)
386156230Smux		error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file);
387156230Smux	else
388156230Smux		error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file,
389156230Smux		    line);
390156230Smux	if (error) {
391156230Smux		st->error = STATUS_ERR_WRITE;
392156230Smux		st->suberror = errno;
393156230Smux		return (-1);
394156230Smux	}
395156230Smux	return (0);
396156230Smux}
397156230Smux
398156230Smuxstatic void
399156230Smuxstatusrec_fini(struct statusrec *sr)
400156230Smux{
401156230Smux
402156230Smux	fattr_free(sr->sr_serverattr);
403156230Smux	fattr_free(sr->sr_clientattr);
404156230Smux	free(sr->sr_file);
405156230Smux}
406156230Smux
407156230Smuxstatic int
408156230Smuxstatusrec_cmp(struct statusrec *a, struct statusrec *b)
409156230Smux{
410156230Smux	size_t lena, lenb;
411156230Smux
412156230Smux	if (a->sr_type == SR_DIRUP || b->sr_type == SR_DIRUP) {
413156230Smux		lena = strlen(a->sr_file);
414156230Smux		lenb = strlen(b->sr_file);
415156230Smux		if (a->sr_type == SR_DIRUP &&
416156230Smux		    ((lena < lenb && b->sr_file[lena] == '/') || lena == lenb)
417156230Smux		    && strncmp(a->sr_file, b->sr_file, lena) == 0)
418156230Smux			return (1);
419156230Smux		if (b->sr_type == SR_DIRUP &&
420156230Smux		    ((lenb < lena && a->sr_file[lenb] == '/') || lenb == lena)
421156230Smux		    && strncmp(a->sr_file, b->sr_file, lenb) == 0)
422156230Smux			return (-1);
423156230Smux	}
424156230Smux	return (pathcmp(a->sr_file, b->sr_file));
425156230Smux}
426156230Smux
427156230Smuxstatic struct status *
428156230Smuxstatus_new(char *path, time_t scantime, struct stream *file)
429156230Smux{
430156230Smux	struct status *st;
431156230Smux
432156230Smux	st = xmalloc(sizeof(struct status));
433156230Smux	st->path = path;
434156230Smux	st->error = 0;
435156230Smux	st->suberror = 0;
436156230Smux	st->tempfile = NULL;
437156230Smux	st->scantime = scantime;
438156230Smux	st->rd = file;
439156230Smux	st->wr = NULL;
440156230Smux	st->previous = NULL;
441156230Smux	st->current = NULL;
442156230Smux	st->dirty = 0;
443156230Smux	st->eof = 0;
444156230Smux	st->linenum = 0;
445156230Smux	st->depth = 0;
446156230Smux	st->pc = pathcomp_new();
447156230Smux	statusrec_init(&st->buf);
448156230Smux	return (st);
449156230Smux}
450156230Smux
451156230Smuxstatic void
452156230Smuxstatus_free(struct status *st)
453156230Smux{
454156230Smux
455156230Smux	if (st->previous != NULL)
456156230Smux		statusrec_fini(st->previous);
457156230Smux	if (st->rd != NULL)
458156230Smux		stream_close(st->rd);
459156230Smux	if (st->wr != NULL)
460156230Smux		stream_close(st->wr);
461156230Smux	if (st->tempfile != NULL)
462156230Smux		free(st->tempfile);
463156230Smux	free(st->path);
464156230Smux	pathcomp_free(st->pc);
465156230Smux	free(st);
466156230Smux}
467156230Smux
468156230Smuxstatic struct status *
469156230Smuxstatus_fromrd(char *path, struct stream *file)
470156230Smux{
471156230Smux	struct status *st;
472156230Smux	char *id, *line;
473156230Smux	time_t scantime;
474156230Smux	int error, ver;
475156230Smux
476156230Smux	/* Get the first line of the file and validate it. */
477156230Smux	line = stream_getln(file, NULL);
478156230Smux	if (line == NULL) {
479156230Smux		stream_close(file);
480156230Smux		return (NULL);
481156230Smux	}
482156230Smux	id = proto_get_ascii(&line);
483156230Smux	error = proto_get_int(&line, &ver, 10);
484156230Smux	if (error) {
485156230Smux		stream_close(file);
486156230Smux		return (NULL);
487156230Smux	}
488156230Smux	error = proto_get_time(&line, &scantime);
489156230Smux	if (error || line != NULL) {
490156230Smux		stream_close(file);
491156230Smux		return (NULL);
492156230Smux	}
493156230Smux
494156230Smux	if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) {
495156230Smux		stream_close(file);
496156230Smux		return (NULL);
497156230Smux	}
498156230Smux
499156230Smux	st = status_new(path, scantime, file);
500156230Smux	st->linenum = 1;
501156230Smux	return (st);
502156230Smux}
503156230Smux
504156230Smuxstatic struct status *
505156230Smuxstatus_fromnull(char *path)
506156230Smux{
507156230Smux	struct status *st;
508156230Smux
509156230Smux	st = status_new(path, -1, NULL);
510156230Smux	st->eof = 1;
511156230Smux	return (st);
512156230Smux}
513156230Smux
514156230Smux/*
515156230Smux * Open the status file.  If scantime is not -1, the file is opened
516156230Smux * for updating, otherwise, it is opened read-only. If the status file
517156230Smux * couldn't be opened, NULL is returned and errmsg is set to the error
518156230Smux * message.
519156230Smux */
520156230Smuxstruct status *
521156230Smuxstatus_open(struct coll *coll, time_t scantime, char **errmsg)
522156230Smux{
523156230Smux	struct status *st;
524156230Smux	struct stream *file;
525156230Smux	struct fattr *fa;
526156230Smux	char *destpath, *path;
527156230Smux	int error, rv;
528156230Smux
529156230Smux	path = coll_statuspath(coll);
530156230Smux	file = stream_open_file(path, O_RDONLY);
531156230Smux	if (file == NULL) {
532156230Smux		if (errno != ENOENT) {
533156230Smux			xasprintf(errmsg, "Could not open \"%s\": %s\n",
534156230Smux			    path, strerror(errno));
535156230Smux			free(path);
536156230Smux			return (NULL);
537156230Smux		}
538156230Smux		st = status_fromnull(path);
539156230Smux	} else {
540156230Smux		st = status_fromrd(path, file);
541156230Smux		if (st == NULL) {
542156230Smux			xasprintf(errmsg, "Error in \"%s\": Bad header line",
543156230Smux			    path);
544156230Smux			free(path);
545156230Smux			return (NULL);
546156230Smux		}
547156230Smux	}
548156230Smux
549156230Smux	if (scantime != -1) {
550156230Smux		/* Open for writing too. */
551156230Smux		xasprintf(&destpath, "%s/%s/%s/", coll->co_base,
552156230Smux		    coll->co_colldir, coll->co_name);
553156230Smux		st->tempfile = tempname(destpath);
554156230Smux		if (mkdirhier(destpath, coll->co_umask) != 0) {
555156230Smux			xasprintf(errmsg, "Cannot create directories leading "
556156230Smux			    "to \"%s\": %s", destpath, strerror(errno));
557156230Smux			free(destpath);
558156230Smux			status_free(st);
559156230Smux			return (NULL);
560156230Smux		}
561156230Smux		free(destpath);
562156230Smux		st->wr = stream_open_file(st->tempfile,
563156230Smux		    O_CREAT | O_TRUNC | O_WRONLY, 0644);
564156230Smux		if (st->wr == NULL) {
565156230Smux			xasprintf(errmsg, "Cannot create \"%s\": %s",
566156230Smux			    st->tempfile, strerror(errno));
567156230Smux			status_free(st);
568156230Smux			return (NULL);
569156230Smux		}
570156230Smux		fa = fattr_new(FT_FILE, -1);
571156230Smux		fattr_mergedefault(fa);
572156230Smux		fattr_umask(fa, coll->co_umask);
573156230Smux		rv = fattr_install(fa, st->tempfile, NULL);
574156230Smux		fattr_free(fa);
575156230Smux		if (rv == -1) {
576156230Smux			xasprintf(errmsg,
577156230Smux			    "Cannot set attributes for \"%s\": %s",
578156230Smux			    st->tempfile, strerror(errno));
579156230Smux			status_free(st);
580156230Smux			return (NULL);
581156230Smux		}
582156230Smux		if (scantime != st->scantime)
583156230Smux			st->dirty = 1;
584156230Smux		error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION,
585156230Smux		    scantime);
586156230Smux		if (error) {
587156230Smux			st->error = STATUS_ERR_WRITE;
588156230Smux			st->suberror = errno;
589156230Smux			*errmsg = status_errmsg(st);
590156230Smux			status_free(st);
591156230Smux			return (NULL);
592156230Smux		}
593156230Smux	}
594156230Smux	return (st);
595156230Smux}
596156230Smux
597156230Smux/*
598156230Smux * Get an entry from the status file.  If name is NULL, the next entry
599156230Smux * is returned.  If name is not NULL, the entry matching this name is
600156230Smux * returned, or NULL if it couldn't be found.  If deleteto is set to 1,
601156230Smux * all the entries read from the status file while looking for the
602156230Smux * given name are deleted.
603156230Smux */
604156230Smuxint
605156230Smuxstatus_get(struct status *st, char *name, int isdirup, int deleteto,
606156230Smux    struct statusrec **psr)
607156230Smux{
608156230Smux	struct statusrec key;
609156230Smux	struct statusrec *sr;
610156230Smux	char *line;
611156230Smux	int c, error;
612156230Smux
613156230Smux	if (st->eof)
614156230Smux		return (0);
615156230Smux
616156230Smux	if (st->error)
617156230Smux		return (-1);
618156230Smux
619156230Smux	if (name == NULL) {
620156230Smux		sr = status_rd(st);
621156230Smux		if (sr == NULL) {
622156230Smux			if (st->error)
623156230Smux				return (-1);
624156230Smux			return (0);
625156230Smux		}
626156230Smux		*psr = sr;
627156230Smux		return (1);
628156230Smux	}
629156230Smux
630156230Smux	if (st->current != NULL) {
631156230Smux		sr = st->current;
632156230Smux		st->current = NULL;
633156230Smux	} else {
634156230Smux		sr = status_rd(st);
635156230Smux		if (sr == NULL) {
636156230Smux			if (st->error)
637156230Smux				return (-1);
638156230Smux			return (0);
639156230Smux		}
640156230Smux	}
641156230Smux
642156230Smux	key.sr_file = name;
643156230Smux	if (isdirup)
644156230Smux		key.sr_type = SR_DIRUP;
645156230Smux	else
646156230Smux		key.sr_type = SR_CHECKOUTLIVE;
647156230Smux
648156230Smux	c = statusrec_cmp(sr, &key);
649156230Smux	if (c < 0) {
650156230Smux		if (st->wr != NULL && !deleteto) {
651156230Smux			error = status_wr(st, sr);
652156230Smux			if (error)
653156230Smux				return (-1);
654156230Smux		}
655156230Smux		/* Loop until we find the good entry. */
656156230Smux		for (;;) {
657156230Smux			sr = status_rdraw(st, &line);
658156230Smux			if (sr == NULL) {
659156230Smux				if (st->error)
660156230Smux					return (-1);
661156230Smux				return (0);
662156230Smux			}
663156230Smux			c = statusrec_cmp(sr, &key);
664156230Smux			if (c >= 0)
665156230Smux				break;
666156230Smux			if (st->wr != NULL && !deleteto) {
667156230Smux				error = status_wrraw(st, sr, line);
668156230Smux				if (error)
669156230Smux					return (-1);
670156230Smux			}
671156230Smux		}
672156230Smux		error = statusrec_cook(sr, line);
673156230Smux		if (error) {
674156230Smux			st->error = STATUS_ERR_PARSE;
675156230Smux			return (-1);
676156230Smux		}
677156230Smux	}
678156230Smux	st->current = sr;
679156230Smux	if (c != 0)
680156230Smux		return (0);
681156230Smux	*psr = sr;
682156230Smux	return (1);
683156230Smux}
684156230Smux
685156230Smux/*
686156230Smux * Put this entry into the status file.  If an entry with the same name
687156230Smux * existed in the status file, it is replaced by this one, otherwise,
688156230Smux * the entry is added to the status file.
689156230Smux */
690156230Smuxint
691156230Smuxstatus_put(struct status *st, struct statusrec *sr)
692156230Smux{
693156230Smux	struct statusrec *old;
694156230Smux	int error, ret;
695156230Smux
696156230Smux	ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old);
697156230Smux	if (ret == -1)
698156230Smux		return (-1);
699156230Smux	if (ret) {
700156230Smux		if (old->sr_type == SR_DIRDOWN) {
701156230Smux			/* DirUp should never match DirDown */
702156230Smux			assert(old->sr_type != SR_DIRUP);
703156230Smux			if (sr->sr_type == SR_CHECKOUTLIVE ||
704156230Smux			    sr->sr_type == SR_CHECKOUTDEAD) {
705156230Smux				/* We are replacing a directory with a file.
706156230Smux				   Delete all entries inside the directory we
707156230Smux				   are replacing. */
708156230Smux				ret = status_get(st, sr->sr_file, 1, 1, &old);
709156230Smux				if (ret == -1)
710156230Smux					return (-1);
711156230Smux				assert(ret);
712156230Smux			}
713156230Smux		} else
714156230Smux			st->current = NULL;
715156230Smux	}
716156230Smux	st->dirty = 1;
717156230Smux	error = status_wr(st, sr);
718156230Smux	if (error)
719156230Smux		return (-1);
720156230Smux	return (0);
721156230Smux}
722156230Smux
723156230Smux/*
724156230Smux * Delete the specified entry from the status file.
725156230Smux */
726156230Smuxint
727156230Smuxstatus_delete(struct status *st, char *name, int isdirup)
728156230Smux{
729156230Smux	struct statusrec *sr;
730156230Smux	int ret;
731156230Smux
732156230Smux	ret = status_get(st, name, isdirup, 0, &sr);
733156230Smux	if (ret == -1)
734156230Smux		return (-1);
735156230Smux	if (ret) {
736156230Smux		st->current = NULL;
737156230Smux		st->dirty = 1;
738156230Smux	}
739156230Smux	return (0);
740156230Smux}
741156230Smux
742156230Smux/*
743156230Smux * Check whether we hit the end of file.
744156230Smux */
745156230Smuxint
746156230Smuxstatus_eof(struct status *st)
747156230Smux{
748156230Smux
749156230Smux	return (st->eof);
750156230Smux}
751156230Smux
752156230Smux/*
753156230Smux * Returns the error message if there was an error, otherwise returns
754156230Smux * NULL.  The error message is allocated dynamically and needs to be
755156230Smux * freed by the caller after use.
756156230Smux */
757156230Smuxchar *
758156230Smuxstatus_errmsg(struct status *st)
759156230Smux{
760156230Smux	char *errmsg;
761156230Smux
762156230Smux	if (!st->error)
763156230Smux		return (NULL);
764156230Smux	switch (st->error) {
765156230Smux	case STATUS_ERR_READ:
766156230Smux		xasprintf(&errmsg, "Read failure on \"%s\": %s",
767156230Smux		    st->path, strerror(st->suberror));
768156230Smux		break;
769156230Smux	case STATUS_ERR_WRITE:
770156230Smux		xasprintf(&errmsg, "Write failure on \"%s\": %s",
771156230Smux		    st->tempfile, strerror(st->suberror));
772156230Smux		break;
773156230Smux	case STATUS_ERR_PARSE:
774156230Smux		xasprintf(&errmsg, "Error in \"%s\": %d: "
775156230Smux		    "Could not parse status record", st->path, st->linenum);
776156230Smux		break;
777156230Smux	case STATUS_ERR_UNSORTED:
778156230Smux		xasprintf(&errmsg, "Error in \"%s\": %d: "
779156230Smux		    "File is not sorted properly", st->path, st->linenum);
780156230Smux		break;
781156230Smux	case STATUS_ERR_TRUNC:
782156230Smux		xasprintf(&errmsg, "Error in \"%s\": "
783156230Smux		    "File is truncated", st->path);
784156230Smux		break;
785156230Smux	case STATUS_ERR_BOGUS_DIRUP:
786156230Smux		xasprintf(&errmsg, "Error in \"%s\": %d: "
787156230Smux		    "\"U\" entry has no matching \"D\"", st->path, st->linenum);
788156230Smux		break;
789156230Smux	case STATUS_ERR_BAD_TYPE:
790156230Smux		xasprintf(&errmsg, "Error in \"%s\": %d: "
791156230Smux		    "Invalid file type \"%c\"", st->path, st->linenum,
792156230Smux		    st->suberror);
793156230Smux		break;
794156230Smux	case STATUS_ERR_RENAME:
795156230Smux		xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s",
796156230Smux		    st->tempfile, st->path, strerror(st->suberror));
797156230Smux		break;
798156230Smux	default:
799156230Smux		assert(0);
800156230Smux		return (NULL);
801156230Smux	}
802156230Smux	return (errmsg);
803156230Smux}
804156230Smux
805156230Smux/*
806156230Smux * Close the status file and free any resource associated with it.
807156230Smux * It is OK to pass NULL for errmsg only if the status file was
808156230Smux * opened read-only.  If it wasn't opened read-only, status_close()
809156230Smux * can produce an error and errmsg is not allowed to be NULL.  If
810156230Smux * there was no errors, errmsg is set to NULL.
811156230Smux */
812156230Smuxvoid
813156230Smuxstatus_close(struct status *st, char **errmsg)
814156230Smux{
815156230Smux	struct statusrec *sr;
816156230Smux	char *line, *name;
817156230Smux	int error, type;
818156230Smux
819156230Smux	if (st->wr != NULL) {
820156230Smux		if (st->dirty) {
821156230Smux			if (st->current != NULL) {
822156230Smux				error = status_wr(st, st->current);
823156230Smux				if (error) {
824156230Smux					*errmsg = status_errmsg(st);
825156230Smux					goto bad;
826156230Smux				}
827156230Smux				st->current = NULL;
828156230Smux			}
829156230Smux			/* Copy the rest of the file. */
830156230Smux			while ((sr = status_rdraw(st, &line)) != NULL) {
831156230Smux				error = status_wrraw(st, sr, line);
832156230Smux				if (error) {
833156230Smux					*errmsg = status_errmsg(st);
834156230Smux					goto bad;
835156230Smux				}
836156230Smux			}
837156230Smux			if (st->error) {
838156230Smux				*errmsg = status_errmsg(st);
839156230Smux				goto bad;
840156230Smux			}
841156230Smux
842156230Smux			/* Close off all the open directories. */
843156230Smux			pathcomp_finish(st->pc);
844156230Smux			while (pathcomp_get(st->pc, &type, &name)) {
845156230Smux				assert(type == PC_DIRUP);
846156230Smux				error = proto_printf(st->wr, "U %s %f\n",
847156230Smux				    name, fattr_bogus);
848156230Smux				if (error) {
849156230Smux					st->error = STATUS_ERR_WRITE;
850156230Smux					st->suberror = errno;
851156230Smux					*errmsg = status_errmsg(st);
852156230Smux					goto bad;
853156230Smux				}
854156230Smux			}
855156230Smux
856156230Smux			/* Rename tempfile. */
857156230Smux			error = rename(st->tempfile, st->path);
858156230Smux			if (error) {
859156230Smux				st->error = STATUS_ERR_RENAME;
860156230Smux				st->suberror = errno;
861156230Smux				*errmsg = status_errmsg(st);
862156230Smux				goto bad;
863156230Smux			}
864156230Smux		} else {
865156230Smux			/* Just discard the tempfile. */
866156230Smux			unlink(st->tempfile);
867156230Smux		}
868156230Smux		*errmsg = NULL;
869156230Smux	}
870156230Smux	status_free(st);
871156230Smux	return;
872156230Smuxbad:
873156230Smux	status_free(st);
874156230Smux}
875