1/*-
2 * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29#include <assert.h>
30#include <errno.h>
31#include <fcntl.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36
37#include "config.h"
38#include "fattr.h"
39#include "misc.h"
40#include "pathcomp.h"
41#include "proto.h"
42#include "queue.h"
43#include "status.h"
44#include "stream.h"
45
46#define	STATUS_VERSION	5
47
48/* Internal error codes. */
49#define	STATUS_ERR_READ		(-1)
50#define	STATUS_ERR_WRITE	(-2)
51#define	STATUS_ERR_PARSE	(-3)
52#define	STATUS_ERR_UNSORTED	(-4)
53#define	STATUS_ERR_TRUNC	(-5)
54#define	STATUS_ERR_BOGUS_DIRUP	(-6)
55#define	STATUS_ERR_BAD_TYPE	(-7)
56#define	STATUS_ERR_RENAME	(-8)
57
58static struct status	*status_new(char *, time_t, struct stream *);
59static struct statusrec	*status_rd(struct status *);
60static struct statusrec	*status_rdraw(struct status *, char **);
61static int		 status_wr(struct status *, struct statusrec *);
62static int		 status_wrraw(struct status *, struct statusrec *,
63			     char *);
64static struct status	*status_fromrd(char *, struct stream *);
65static struct status	*status_fromnull(char *);
66static void		 status_free(struct status *);
67
68static void		 statusrec_init(struct statusrec *);
69static void		 statusrec_fini(struct statusrec *);
70static int		 statusrec_cook(struct statusrec *, char *);
71static int		 statusrec_cmp(struct statusrec *, struct statusrec *);
72
73struct status {
74	char *path;
75	char *tempfile;
76	int error;
77	int suberror;
78	struct pathcomp *pc;
79	struct statusrec buf;
80	struct statusrec *previous;
81	struct statusrec *current;
82	struct stream *rd;
83	struct stream *wr;
84	time_t scantime;
85	int eof;
86	int linenum;
87	int depth;
88	int dirty;
89};
90
91static void
92statusrec_init(struct statusrec *sr)
93{
94
95	memset(sr, 0, sizeof(*sr));
96}
97
98static int
99statusrec_cook(struct statusrec *sr, char *line)
100{
101	char *clientattr, *serverattr;
102
103	switch (sr->sr_type) {
104	case SR_FILEDEAD:
105	case SR_FILELIVE:
106		clientattr = proto_get_ascii(&line);
107		if (clientattr == NULL || line != NULL)
108			return (-1);
109		sr->sr_clientattr = fattr_decode(clientattr);
110		if (sr->sr_clientattr == NULL)
111			return (-1);
112		break;
113	case SR_DIRDOWN:
114		/* Nothing to do. */
115		if (line != NULL)
116			return (-1);
117		break;
118	case SR_CHECKOUTLIVE:
119		sr->sr_tag = proto_get_ascii(&line);
120		sr->sr_date = proto_get_ascii(&line);
121		serverattr = proto_get_ascii(&line);
122		sr->sr_revnum = proto_get_ascii(&line);
123		sr->sr_revdate = proto_get_ascii(&line);
124		clientattr = proto_get_ascii(&line);
125		if (clientattr == NULL || line != NULL)
126			return (-1);
127		sr->sr_serverattr = fattr_decode(serverattr);
128		if (sr->sr_serverattr == NULL)
129			return (-1);
130		sr->sr_clientattr = fattr_decode(clientattr);
131		if (sr->sr_clientattr == NULL) {
132			fattr_free(sr->sr_serverattr);
133			return (-1);
134		}
135		break;
136	case SR_CHECKOUTDEAD:
137		sr->sr_tag = proto_get_ascii(&line);
138		sr->sr_date = proto_get_ascii(&line);
139		serverattr = proto_get_ascii(&line);
140		if (serverattr == NULL || line != NULL)
141			return (-1);
142		sr->sr_serverattr = fattr_decode(serverattr);
143		if (sr->sr_serverattr == NULL)
144			return (-1);
145		break;
146	case SR_DIRUP:
147		clientattr = proto_get_ascii(&line);
148		if (clientattr == NULL || line != NULL)
149			return (-1);
150		sr->sr_clientattr = fattr_decode(clientattr);
151		if (sr->sr_clientattr == NULL)
152			return (-1);
153		break;
154	default:
155		return (-1);
156	}
157	return (0);
158}
159
160static struct statusrec *
161status_rd(struct status *st)
162{
163	struct statusrec *sr;
164	char *line;
165	int error;
166
167	sr = status_rdraw(st, &line);
168	if (sr == NULL)
169		return (NULL);
170	error = statusrec_cook(sr, line);
171	if (error) {
172		st->error = STATUS_ERR_PARSE;
173		return (NULL);
174	}
175	return (sr);
176}
177
178static struct statusrec *
179status_rdraw(struct status *st, char **linep)
180{
181	struct statusrec sr;
182	char *cmd, *line, *file;
183
184	if (st->rd == NULL || st->eof)
185		return (NULL);
186	line = stream_getln(st->rd, NULL);
187	if (line == NULL) {
188		if (stream_eof(st->rd)) {
189			if (st->depth != 0) {
190				st->error = STATUS_ERR_TRUNC;
191				return (NULL);
192			}
193			st->eof = 1;
194			return (NULL);
195		}
196		st->error = STATUS_ERR_READ;
197		st->suberror = errno;
198		return (NULL);
199	}
200	st->linenum++;
201	cmd = proto_get_ascii(&line);
202	file = proto_get_ascii(&line);
203	if (file == NULL || strlen(cmd) != 1) {
204		st->error = STATUS_ERR_PARSE;
205		return (NULL);
206	}
207
208	switch (cmd[0]) {
209	case 'A':
210		sr.sr_type = SR_FILELIVE;
211		break;
212	case 'D':
213		sr.sr_type = SR_DIRDOWN;
214		st->depth++;
215		break;
216	case 'C':
217		sr.sr_type = SR_CHECKOUTLIVE;
218		break;
219	case 'c':
220		sr.sr_type = SR_CHECKOUTDEAD;
221		break;
222	case 'U':
223		sr.sr_type = SR_DIRUP;
224		if (st->depth <= 0) {
225			st->error = STATUS_ERR_BOGUS_DIRUP;
226			return (NULL);
227		}
228		st->depth--;
229		break;
230	case 'V':
231		sr.sr_type = SR_FILELIVE;
232		break;
233	case 'v':
234		sr.sr_type = SR_FILEDEAD;
235		break;
236	default:
237		st->error = STATUS_ERR_BAD_TYPE;
238		st->suberror = cmd[0];
239		return (NULL);
240	}
241
242	sr.sr_file = xstrdup(file);
243	if (st->previous != NULL &&
244	    statusrec_cmp(st->previous, &sr) >= 0) {
245		st->error = STATUS_ERR_UNSORTED;
246		free(sr.sr_file);
247		return (NULL);
248	}
249
250	if (st->previous == NULL) {
251		st->previous = &st->buf;
252	} else {
253		statusrec_fini(st->previous);
254		statusrec_init(st->previous);
255	}
256	st->previous->sr_type = sr.sr_type;
257	st->previous->sr_file = sr.sr_file;
258	*linep = line;
259	return (st->previous);
260}
261
262static int
263status_wr(struct status *st, struct statusrec *sr)
264{
265	struct pathcomp *pc;
266	const struct fattr *fa;
267	char *name;
268	int error, type, usedirupattr;
269
270	pc = st->pc;
271	error = 0;
272	usedirupattr = 0;
273	if (sr->sr_type == SR_DIRDOWN) {
274		pathcomp_put(pc, PC_DIRDOWN, sr->sr_file);
275	} else if (sr->sr_type == SR_DIRUP) {
276		pathcomp_put(pc, PC_DIRUP, sr->sr_file);
277		usedirupattr = 1;
278	} else {
279		pathcomp_put(pc, PC_FILE, sr->sr_file);
280	}
281
282	while (pathcomp_get(pc, &type, &name)) {
283		if (type == PC_DIRDOWN) {
284			error = proto_printf(st->wr, "D %s\n", name);
285		} else if (type == PC_DIRUP) {
286			if (usedirupattr)
287				fa = sr->sr_clientattr;
288			else
289				fa = fattr_bogus;
290			usedirupattr = 0;
291			error = proto_printf(st->wr, "U %s %f\n", name, fa);
292		}
293		if (error)
294			goto bad;
295	}
296
297	switch (sr->sr_type) {
298	case SR_DIRDOWN:
299	case SR_DIRUP:
300		/* Already emitted above. */
301		break;
302	case SR_CHECKOUTLIVE:
303		error = proto_printf(st->wr, "C %s %s %s %f %s %s %f\n",
304		    sr->sr_file, sr->sr_tag, sr->sr_date, sr->sr_serverattr,
305		    sr->sr_revnum, sr->sr_revdate, sr->sr_clientattr);
306		break;
307	case SR_CHECKOUTDEAD:
308		error = proto_printf(st->wr, "c %s %s %s %f\n", sr->sr_file,
309		    sr->sr_tag, sr->sr_date, sr->sr_serverattr);
310		break;
311	case SR_FILELIVE:
312		error = proto_printf(st->wr, "V %s %f\n", sr->sr_file,
313		    sr->sr_clientattr);
314		break;
315	case SR_FILEDEAD:
316		error = proto_printf(st->wr, "v %s %f\n", sr->sr_file,
317		    sr->sr_clientattr);
318		break;
319	}
320	if (error)
321		goto bad;
322	return (0);
323bad:
324	st->error = STATUS_ERR_WRITE;
325	st->suberror = errno;
326	return (-1);
327}
328
329static int
330status_wrraw(struct status *st, struct statusrec *sr, char *line)
331{
332	char *name;
333	char cmd;
334	int error, ret, type;
335
336	if (st->wr == NULL)
337		return (0);
338
339	/*
340	 * Keep the compressor in sync.  At this point, the necessary
341	 * DirDowns and DirUps should have already been emitted, so the
342	 * compressor should return exactly one value in the PC_DIRDOWN
343	 * and PC_DIRUP case and none in the PC_FILE case.
344	 */
345	if (sr->sr_type == SR_DIRDOWN)
346		pathcomp_put(st->pc, PC_DIRDOWN, sr->sr_file);
347	else if (sr->sr_type == SR_DIRUP)
348		pathcomp_put(st->pc, PC_DIRUP, sr->sr_file);
349	else
350		pathcomp_put(st->pc, PC_FILE, sr->sr_file);
351	if (sr->sr_type == SR_DIRDOWN || sr->sr_type == SR_DIRUP) {
352		ret = pathcomp_get(st->pc, &type, &name);
353		assert(ret);
354		if (sr->sr_type == SR_DIRDOWN)
355			assert(type == PC_DIRDOWN);
356		else
357			assert(type == PC_DIRUP);
358	}
359	ret = pathcomp_get(st->pc, &type, &name);
360	assert(!ret);
361
362	switch (sr->sr_type) {
363	case SR_DIRDOWN:
364		cmd = 'D';
365		break;
366	case SR_DIRUP:
367		cmd = 'U';
368		break;
369	case SR_CHECKOUTLIVE:
370		cmd = 'C';
371		break;
372	case SR_CHECKOUTDEAD:
373		cmd = 'c';
374		break;
375	case SR_FILELIVE:
376		cmd = 'V';
377		break;
378	case SR_FILEDEAD:
379		cmd = 'v';
380		break;
381	default:
382		assert(0);
383		return (-1);
384	}
385	if (sr->sr_type == SR_DIRDOWN)
386		error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file);
387	else
388		error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file,
389		    line);
390	if (error) {
391		st->error = STATUS_ERR_WRITE;
392		st->suberror = errno;
393		return (-1);
394	}
395	return (0);
396}
397
398static void
399statusrec_fini(struct statusrec *sr)
400{
401
402	fattr_free(sr->sr_serverattr);
403	fattr_free(sr->sr_clientattr);
404	free(sr->sr_file);
405}
406
407static int
408statusrec_cmp(struct statusrec *a, struct statusrec *b)
409{
410	size_t lena, lenb;
411
412	if (a->sr_type == SR_DIRUP || b->sr_type == SR_DIRUP) {
413		lena = strlen(a->sr_file);
414		lenb = strlen(b->sr_file);
415		if (a->sr_type == SR_DIRUP &&
416		    ((lena < lenb && b->sr_file[lena] == '/') || lena == lenb)
417		    && strncmp(a->sr_file, b->sr_file, lena) == 0)
418			return (1);
419		if (b->sr_type == SR_DIRUP &&
420		    ((lenb < lena && a->sr_file[lenb] == '/') || lenb == lena)
421		    && strncmp(a->sr_file, b->sr_file, lenb) == 0)
422			return (-1);
423	}
424	return (pathcmp(a->sr_file, b->sr_file));
425}
426
427static struct status *
428status_new(char *path, time_t scantime, struct stream *file)
429{
430	struct status *st;
431
432	st = xmalloc(sizeof(struct status));
433	st->path = path;
434	st->error = 0;
435	st->suberror = 0;
436	st->tempfile = NULL;
437	st->scantime = scantime;
438	st->rd = file;
439	st->wr = NULL;
440	st->previous = NULL;
441	st->current = NULL;
442	st->dirty = 0;
443	st->eof = 0;
444	st->linenum = 0;
445	st->depth = 0;
446	st->pc = pathcomp_new();
447	statusrec_init(&st->buf);
448	return (st);
449}
450
451static void
452status_free(struct status *st)
453{
454
455	if (st->previous != NULL)
456		statusrec_fini(st->previous);
457	if (st->rd != NULL)
458		stream_close(st->rd);
459	if (st->wr != NULL)
460		stream_close(st->wr);
461	if (st->tempfile != NULL)
462		free(st->tempfile);
463	free(st->path);
464	pathcomp_free(st->pc);
465	free(st);
466}
467
468static struct status *
469status_fromrd(char *path, struct stream *file)
470{
471	struct status *st;
472	char *id, *line;
473	time_t scantime;
474	int error, ver;
475
476	/* Get the first line of the file and validate it. */
477	line = stream_getln(file, NULL);
478	if (line == NULL) {
479		stream_close(file);
480		return (NULL);
481	}
482	id = proto_get_ascii(&line);
483	error = proto_get_int(&line, &ver, 10);
484	if (error) {
485		stream_close(file);
486		return (NULL);
487	}
488	error = proto_get_time(&line, &scantime);
489	if (error || line != NULL) {
490		stream_close(file);
491		return (NULL);
492	}
493
494	if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) {
495		stream_close(file);
496		return (NULL);
497	}
498
499	st = status_new(path, scantime, file);
500	st->linenum = 1;
501	return (st);
502}
503
504static struct status *
505status_fromnull(char *path)
506{
507	struct status *st;
508
509	st = status_new(path, -1, NULL);
510	st->eof = 1;
511	return (st);
512}
513
514/*
515 * Open the status file.  If scantime is not -1, the file is opened
516 * for updating, otherwise, it is opened read-only. If the status file
517 * couldn't be opened, NULL is returned and errmsg is set to the error
518 * message.
519 */
520struct status *
521status_open(struct coll *coll, time_t scantime, char **errmsg)
522{
523	struct status *st;
524	struct stream *file;
525	struct fattr *fa;
526	char *destpath, *path;
527	int error, rv;
528
529	path = coll_statuspath(coll);
530	file = stream_open_file(path, O_RDONLY);
531	if (file == NULL) {
532		if (errno != ENOENT) {
533			xasprintf(errmsg, "Could not open \"%s\": %s\n",
534			    path, strerror(errno));
535			free(path);
536			return (NULL);
537		}
538		st = status_fromnull(path);
539	} else {
540		st = status_fromrd(path, file);
541		if (st == NULL) {
542			xasprintf(errmsg, "Error in \"%s\": Bad header line",
543			    path);
544			free(path);
545			return (NULL);
546		}
547	}
548
549	if (scantime != -1) {
550		/* Open for writing too. */
551		xasprintf(&destpath, "%s/%s/%s/", coll->co_base,
552		    coll->co_colldir, coll->co_name);
553		st->tempfile = tempname(destpath);
554		if (mkdirhier(destpath, coll->co_umask) != 0) {
555			xasprintf(errmsg, "Cannot create directories leading "
556			    "to \"%s\": %s", destpath, strerror(errno));
557			free(destpath);
558			status_free(st);
559			return (NULL);
560		}
561		free(destpath);
562		st->wr = stream_open_file(st->tempfile,
563		    O_CREAT | O_TRUNC | O_WRONLY, 0644);
564		if (st->wr == NULL) {
565			xasprintf(errmsg, "Cannot create \"%s\": %s",
566			    st->tempfile, strerror(errno));
567			status_free(st);
568			return (NULL);
569		}
570		fa = fattr_new(FT_FILE, -1);
571		fattr_mergedefault(fa);
572		fattr_umask(fa, coll->co_umask);
573		rv = fattr_install(fa, st->tempfile, NULL);
574		fattr_free(fa);
575		if (rv == -1) {
576			xasprintf(errmsg,
577			    "Cannot set attributes for \"%s\": %s",
578			    st->tempfile, strerror(errno));
579			status_free(st);
580			return (NULL);
581		}
582		if (scantime != st->scantime)
583			st->dirty = 1;
584		error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION,
585		    scantime);
586		if (error) {
587			st->error = STATUS_ERR_WRITE;
588			st->suberror = errno;
589			*errmsg = status_errmsg(st);
590			status_free(st);
591			return (NULL);
592		}
593	}
594	return (st);
595}
596
597/*
598 * Get an entry from the status file.  If name is NULL, the next entry
599 * is returned.  If name is not NULL, the entry matching this name is
600 * returned, or NULL if it couldn't be found.  If deleteto is set to 1,
601 * all the entries read from the status file while looking for the
602 * given name are deleted.
603 */
604int
605status_get(struct status *st, char *name, int isdirup, int deleteto,
606    struct statusrec **psr)
607{
608	struct statusrec key;
609	struct statusrec *sr;
610	char *line;
611	int c, error;
612
613	if (st->eof)
614		return (0);
615
616	if (st->error)
617		return (-1);
618
619	if (name == NULL) {
620		sr = status_rd(st);
621		if (sr == NULL) {
622			if (st->error)
623				return (-1);
624			return (0);
625		}
626		*psr = sr;
627		return (1);
628	}
629
630	if (st->current != NULL) {
631		sr = st->current;
632		st->current = NULL;
633	} else {
634		sr = status_rd(st);
635		if (sr == NULL) {
636			if (st->error)
637				return (-1);
638			return (0);
639		}
640	}
641
642	key.sr_file = name;
643	if (isdirup)
644		key.sr_type = SR_DIRUP;
645	else
646		key.sr_type = SR_CHECKOUTLIVE;
647
648	c = statusrec_cmp(sr, &key);
649	if (c < 0) {
650		if (st->wr != NULL && !deleteto) {
651			error = status_wr(st, sr);
652			if (error)
653				return (-1);
654		}
655		/* Loop until we find the good entry. */
656		for (;;) {
657			sr = status_rdraw(st, &line);
658			if (sr == NULL) {
659				if (st->error)
660					return (-1);
661				return (0);
662			}
663			c = statusrec_cmp(sr, &key);
664			if (c >= 0)
665				break;
666			if (st->wr != NULL && !deleteto) {
667				error = status_wrraw(st, sr, line);
668				if (error)
669					return (-1);
670			}
671		}
672		error = statusrec_cook(sr, line);
673		if (error) {
674			st->error = STATUS_ERR_PARSE;
675			return (-1);
676		}
677	}
678	st->current = sr;
679	if (c != 0)
680		return (0);
681	*psr = sr;
682	return (1);
683}
684
685/*
686 * Put this entry into the status file.  If an entry with the same name
687 * existed in the status file, it is replaced by this one, otherwise,
688 * the entry is added to the status file.
689 */
690int
691status_put(struct status *st, struct statusrec *sr)
692{
693	struct statusrec *old;
694	int error, ret;
695
696	ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old);
697	if (ret == -1)
698		return (-1);
699	if (ret) {
700		if (old->sr_type == SR_DIRDOWN) {
701			/* DirUp should never match DirDown */
702			assert(old->sr_type != SR_DIRUP);
703			if (sr->sr_type == SR_CHECKOUTLIVE ||
704			    sr->sr_type == SR_CHECKOUTDEAD) {
705				/* We are replacing a directory with a file.
706				   Delete all entries inside the directory we
707				   are replacing. */
708				ret = status_get(st, sr->sr_file, 1, 1, &old);
709				if (ret == -1)
710					return (-1);
711				assert(ret);
712			}
713		} else
714			st->current = NULL;
715	}
716	st->dirty = 1;
717	error = status_wr(st, sr);
718	if (error)
719		return (-1);
720	return (0);
721}
722
723/*
724 * Delete the specified entry from the status file.
725 */
726int
727status_delete(struct status *st, char *name, int isdirup)
728{
729	struct statusrec *sr;
730	int ret;
731
732	ret = status_get(st, name, isdirup, 0, &sr);
733	if (ret == -1)
734		return (-1);
735	if (ret) {
736		st->current = NULL;
737		st->dirty = 1;
738	}
739	return (0);
740}
741
742/*
743 * Check whether we hit the end of file.
744 */
745int
746status_eof(struct status *st)
747{
748
749	return (st->eof);
750}
751
752/*
753 * Returns the error message if there was an error, otherwise returns
754 * NULL.  The error message is allocated dynamically and needs to be
755 * freed by the caller after use.
756 */
757char *
758status_errmsg(struct status *st)
759{
760	char *errmsg;
761
762	if (!st->error)
763		return (NULL);
764	switch (st->error) {
765	case STATUS_ERR_READ:
766		xasprintf(&errmsg, "Read failure on \"%s\": %s",
767		    st->path, strerror(st->suberror));
768		break;
769	case STATUS_ERR_WRITE:
770		xasprintf(&errmsg, "Write failure on \"%s\": %s",
771		    st->tempfile, strerror(st->suberror));
772		break;
773	case STATUS_ERR_PARSE:
774		xasprintf(&errmsg, "Error in \"%s\": %d: "
775		    "Could not parse status record", st->path, st->linenum);
776		break;
777	case STATUS_ERR_UNSORTED:
778		xasprintf(&errmsg, "Error in \"%s\": %d: "
779		    "File is not sorted properly", st->path, st->linenum);
780		break;
781	case STATUS_ERR_TRUNC:
782		xasprintf(&errmsg, "Error in \"%s\": "
783		    "File is truncated", st->path);
784		break;
785	case STATUS_ERR_BOGUS_DIRUP:
786		xasprintf(&errmsg, "Error in \"%s\": %d: "
787		    "\"U\" entry has no matching \"D\"", st->path, st->linenum);
788		break;
789	case STATUS_ERR_BAD_TYPE:
790		xasprintf(&errmsg, "Error in \"%s\": %d: "
791		    "Invalid file type \"%c\"", st->path, st->linenum,
792		    st->suberror);
793		break;
794	case STATUS_ERR_RENAME:
795		xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s",
796		    st->tempfile, st->path, strerror(st->suberror));
797		break;
798	default:
799		assert(0);
800		return (NULL);
801	}
802	return (errmsg);
803}
804
805/*
806 * Close the status file and free any resource associated with it.
807 * It is OK to pass NULL for errmsg only if the status file was
808 * opened read-only.  If it wasn't opened read-only, status_close()
809 * can produce an error and errmsg is not allowed to be NULL.  If
810 * there was no errors, errmsg is set to NULL.
811 */
812void
813status_close(struct status *st, char **errmsg)
814{
815	struct statusrec *sr;
816	char *line, *name;
817	int error, type;
818
819	if (st->wr != NULL) {
820		if (st->dirty) {
821			if (st->current != NULL) {
822				error = status_wr(st, st->current);
823				if (error) {
824					*errmsg = status_errmsg(st);
825					goto bad;
826				}
827				st->current = NULL;
828			}
829			/* Copy the rest of the file. */
830			while ((sr = status_rdraw(st, &line)) != NULL) {
831				error = status_wrraw(st, sr, line);
832				if (error) {
833					*errmsg = status_errmsg(st);
834					goto bad;
835				}
836			}
837			if (st->error) {
838				*errmsg = status_errmsg(st);
839				goto bad;
840			}
841
842			/* Close off all the open directories. */
843			pathcomp_finish(st->pc);
844			while (pathcomp_get(st->pc, &type, &name)) {
845				assert(type == PC_DIRUP);
846				error = proto_printf(st->wr, "U %s %f\n",
847				    name, fattr_bogus);
848				if (error) {
849					st->error = STATUS_ERR_WRITE;
850					st->suberror = errno;
851					*errmsg = status_errmsg(st);
852					goto bad;
853				}
854			}
855
856			/* Rename tempfile. */
857			error = rename(st->tempfile, st->path);
858			if (error) {
859				st->error = STATUS_ERR_RENAME;
860				st->suberror = errno;
861				*errmsg = status_errmsg(st);
862				goto bad;
863			}
864		} else {
865			/* Just discard the tempfile. */
866			unlink(st->tempfile);
867		}
868		*errmsg = NULL;
869	}
870	status_free(st);
871	return;
872bad:
873	status_free(st);
874}
875