xref: /trueos/usr.bin/csup/status.c (revision 834fb25a9ed2240101506d137b5be7d71c75f306)
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 
58 static struct status	*status_new(char *, time_t, struct stream *);
59 static struct statusrec	*status_rd(struct status *);
60 static struct statusrec	*status_rdraw(struct status *, char **);
61 static int		 status_wr(struct status *, struct statusrec *);
62 static int		 status_wrraw(struct status *, struct statusrec *,
63 			     char *);
64 static struct status	*status_fromrd(char *, struct stream *);
65 static struct status	*status_fromnull(char *);
66 static void		 status_free(struct status *);
67 
68 static void		 statusrec_init(struct statusrec *);
69 static void		 statusrec_fini(struct statusrec *);
70 static int		 statusrec_cook(struct statusrec *, char *);
71 static int		 statusrec_cmp(struct statusrec *, struct statusrec *);
72 
73 struct 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 
91 static void
statusrec_init(struct statusrec * sr)92 statusrec_init(struct statusrec *sr)
93 {
94 
95 	memset(sr, 0, sizeof(*sr));
96 }
97 
98 static int
statusrec_cook(struct statusrec * sr,char * line)99 statusrec_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 
160 static struct statusrec *
status_rd(struct status * st)161 status_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 
178 static struct statusrec *
status_rdraw(struct status * st,char ** linep)179 status_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 
262 static int
status_wr(struct status * st,struct statusrec * sr)263 status_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);
323 bad:
324 	st->error = STATUS_ERR_WRITE;
325 	st->suberror = errno;
326 	return (-1);
327 }
328 
329 static int
status_wrraw(struct status * st,struct statusrec * sr,char * line)330 status_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 
398 static void
statusrec_fini(struct statusrec * sr)399 statusrec_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 
407 static int
statusrec_cmp(struct statusrec * a,struct statusrec * b)408 statusrec_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 
427 static struct status *
status_new(char * path,time_t scantime,struct stream * file)428 status_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 
451 static void
status_free(struct status * st)452 status_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 
468 static struct status *
status_fromrd(char * path,struct stream * file)469 status_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 
504 static struct status *
status_fromnull(char * path)505 status_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  */
520 struct status *
status_open(struct coll * coll,time_t scantime,char ** errmsg)521 status_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  */
604 int
status_get(struct status * st,char * name,int isdirup,int deleteto,struct statusrec ** psr)605 status_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  */
690 int
status_put(struct status * st,struct statusrec * sr)691 status_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  */
726 int
status_delete(struct status * st,char * name,int isdirup)727 status_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  */
745 int
status_eof(struct status * st)746 status_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  */
757 char *
status_errmsg(struct status * st)758 status_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  */
812 void
status_close(struct status * st,char ** errmsg)813 status_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;
872 bad:
873 	status_free(st);
874 }
875