]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.bin/csup/status.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / usr.bin / 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_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 *
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 *
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
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
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
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
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 *
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
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 *
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 *
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 *
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
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
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
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
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 *
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
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 }