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