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