2 * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
46 #define STATUS_VERSION 5
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)
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 *,
64 static struct status *status_fromrd(char *, struct stream *);
65 static struct status *status_fromnull(char *);
66 static void status_free(struct status *);
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 *);
80 struct statusrec *previous;
81 struct statusrec *current;
92 statusrec_init(struct statusrec *sr)
95 memset(sr, 0, sizeof(*sr));
99 statusrec_cook(struct statusrec *sr, char *line)
101 char *clientattr, *serverattr;
103 switch (sr->sr_type) {
106 clientattr = proto_get_ascii(&line);
107 if (clientattr == NULL || line != NULL)
109 sr->sr_clientattr = fattr_decode(clientattr);
110 if (sr->sr_clientattr == NULL)
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)
127 sr->sr_serverattr = fattr_decode(serverattr);
128 if (sr->sr_serverattr == NULL)
130 sr->sr_clientattr = fattr_decode(clientattr);
131 if (sr->sr_clientattr == NULL) {
132 fattr_free(sr->sr_serverattr);
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)
142 sr->sr_serverattr = fattr_decode(serverattr);
143 if (sr->sr_serverattr == NULL)
147 clientattr = proto_get_ascii(&line);
148 if (clientattr == NULL || line != NULL)
150 sr->sr_clientattr = fattr_decode(clientattr);
151 if (sr->sr_clientattr == NULL)
160 static struct statusrec *
161 status_rd(struct status *st)
163 struct statusrec *sr;
167 sr = status_rdraw(st, &line);
170 error = statusrec_cook(sr, line);
172 st->error = STATUS_ERR_PARSE;
178 static struct statusrec *
179 status_rdraw(struct status *st, char **linep)
182 char *cmd, *line, *file;
184 if (st->rd == NULL || st->eof)
186 line = stream_getln(st->rd, NULL);
188 if (stream_eof(st->rd)) {
189 if (st->depth != 0) {
190 st->error = STATUS_ERR_TRUNC;
196 st->error = STATUS_ERR_READ;
197 st->suberror = errno;
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;
210 sr.sr_type = SR_FILELIVE;
213 sr.sr_type = SR_DIRDOWN;
217 sr.sr_type = SR_CHECKOUTLIVE;
220 sr.sr_type = SR_CHECKOUTDEAD;
223 sr.sr_type = SR_DIRUP;
224 if (st->depth <= 0) {
225 st->error = STATUS_ERR_BOGUS_DIRUP;
231 sr.sr_type = SR_FILELIVE;
234 sr.sr_type = SR_FILEDEAD;
237 st->error = STATUS_ERR_BAD_TYPE;
238 st->suberror = cmd[0];
242 sr.sr_file = xstrdup(file);
243 if (st->previous != NULL &&
244 statusrec_cmp(st->previous, &sr) >= 0) {
245 st->error = STATUS_ERR_UNSORTED;
250 if (st->previous == NULL) {
251 st->previous = &st->buf;
253 statusrec_fini(st->previous);
254 statusrec_init(st->previous);
256 st->previous->sr_type = sr.sr_type;
257 st->previous->sr_file = sr.sr_file;
259 return (st->previous);
263 status_wr(struct status *st, struct statusrec *sr)
266 const struct fattr *fa;
268 int error, type, usedirupattr;
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);
279 pathcomp_put(pc, PC_FILE, sr->sr_file);
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) {
287 fa = sr->sr_clientattr;
291 error = proto_printf(st->wr, "U %s %f\n", name, fa);
297 switch (sr->sr_type) {
300 /* Already emitted above. */
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);
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);
312 error = proto_printf(st->wr, "V %s %f\n", sr->sr_file,
316 error = proto_printf(st->wr, "v %s %f\n", sr->sr_file,
324 st->error = STATUS_ERR_WRITE;
325 st->suberror = errno;
330 status_wrraw(struct status *st, struct statusrec *sr, char *line)
334 int error, ret, type;
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.
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);
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);
354 if (sr->sr_type == SR_DIRDOWN)
355 assert(type == PC_DIRDOWN);
357 assert(type == PC_DIRUP);
359 ret = pathcomp_get(st->pc, &type, &name);
362 switch (sr->sr_type) {
369 case SR_CHECKOUTLIVE:
372 case SR_CHECKOUTDEAD:
385 if (sr->sr_type == SR_DIRDOWN)
386 error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file);
388 error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file,
391 st->error = STATUS_ERR_WRITE;
392 st->suberror = errno;
399 statusrec_fini(struct statusrec *sr)
402 fattr_free(sr->sr_serverattr);
403 fattr_free(sr->sr_clientattr);
408 statusrec_cmp(struct statusrec *a, struct statusrec *b)
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)
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)
424 return (pathcmp(a->sr_file, b->sr_file));
427 static struct status *
428 status_new(char *path, time_t scantime, struct stream *file)
432 st = xmalloc(sizeof(struct status));
437 st->scantime = scantime;
446 st->pc = pathcomp_new();
447 statusrec_init(&st->buf);
452 status_free(struct status *st)
455 if (st->previous != NULL)
456 statusrec_fini(st->previous);
458 stream_close(st->rd);
460 stream_close(st->wr);
461 if (st->tempfile != NULL)
464 pathcomp_free(st->pc);
468 static struct status *
469 status_fromrd(char *path, struct stream *file)
476 /* Get the first line of the file and validate it. */
477 line = stream_getln(file, NULL);
482 id = proto_get_ascii(&line);
483 error = proto_get_int(&line, &ver, 10);
488 error = proto_get_time(&line, &scantime);
489 if (error || line != NULL) {
494 if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) {
499 st = status_new(path, scantime, file);
504 static struct status *
505 status_fromnull(char *path)
509 st = status_new(path, -1, NULL);
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
521 status_open(struct coll *coll, time_t scantime, char **errmsg)
526 char *destpath, *path;
529 path = coll_statuspath(coll);
530 file = stream_open_file(path, O_RDONLY);
532 if (errno != ENOENT) {
533 xasprintf(errmsg, "Could not open \"%s\": %s\n",
534 path, strerror(errno));
538 st = status_fromnull(path);
540 st = status_fromrd(path, file);
542 xasprintf(errmsg, "Error in \"%s\": Bad header line",
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));
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));
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);
577 "Cannot set attributes for \"%s\": %s",
578 st->tempfile, strerror(errno));
582 if (scantime != st->scantime)
584 error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION,
587 st->error = STATUS_ERR_WRITE;
588 st->suberror = errno;
589 *errmsg = status_errmsg(st);
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.
605 status_get(struct status *st, char *name, int isdirup, int deleteto,
606 struct statusrec **psr)
608 struct statusrec key;
609 struct statusrec *sr;
630 if (st->current != NULL) {
644 key.sr_type = SR_DIRUP;
646 key.sr_type = SR_CHECKOUTLIVE;
648 c = statusrec_cmp(sr, &key);
650 if (st->wr != NULL && !deleteto) {
651 error = status_wr(st, sr);
655 /* Loop until we find the good entry. */
657 sr = status_rdraw(st, &line);
663 c = statusrec_cmp(sr, &key);
666 if (st->wr != NULL && !deleteto) {
667 error = status_wrraw(st, sr, line);
672 error = statusrec_cook(sr, line);
674 st->error = STATUS_ERR_PARSE;
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.
691 status_put(struct status *st, struct statusrec *sr)
693 struct statusrec *old;
696 ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old);
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
708 ret = status_get(st, sr->sr_file, 1, 1, &old);
717 error = status_wr(st, sr);
724 * Delete the specified entry from the status file.
727 status_delete(struct status *st, char *name, int isdirup)
729 struct statusrec *sr;
732 ret = status_get(st, name, isdirup, 0, &sr);
743 * Check whether we hit the end of file.
746 status_eof(struct status *st)
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.
758 status_errmsg(struct status *st)
765 case STATUS_ERR_READ:
766 xasprintf(&errmsg, "Read failure on \"%s\": %s",
767 st->path, strerror(st->suberror));
769 case STATUS_ERR_WRITE:
770 xasprintf(&errmsg, "Write failure on \"%s\": %s",
771 st->tempfile, strerror(st->suberror));
773 case STATUS_ERR_PARSE:
774 xasprintf(&errmsg, "Error in \"%s\": %d: "
775 "Could not parse status record", st->path, st->linenum);
777 case STATUS_ERR_UNSORTED:
778 xasprintf(&errmsg, "Error in \"%s\": %d: "
779 "File is not sorted properly", st->path, st->linenum);
781 case STATUS_ERR_TRUNC:
782 xasprintf(&errmsg, "Error in \"%s\": "
783 "File is truncated", st->path);
785 case STATUS_ERR_BOGUS_DIRUP:
786 xasprintf(&errmsg, "Error in \"%s\": %d: "
787 "\"U\" entry has no matching \"D\"", st->path, st->linenum);
789 case STATUS_ERR_BAD_TYPE:
790 xasprintf(&errmsg, "Error in \"%s\": %d: "
791 "Invalid file type \"%c\"", st->path, st->linenum,
794 case STATUS_ERR_RENAME:
795 xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s",
796 st->tempfile, st->path, strerror(st->suberror));
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.
813 status_close(struct status *st, char **errmsg)
815 struct statusrec *sr;
819 if (st->wr != NULL) {
821 if (st->current != NULL) {
822 error = status_wr(st, st->current);
824 *errmsg = status_errmsg(st);
829 /* Copy the rest of the file. */
830 while ((sr = status_rdraw(st, &line)) != NULL) {
831 error = status_wrraw(st, sr, line);
833 *errmsg = status_errmsg(st);
838 *errmsg = status_errmsg(st);
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",
849 st->error = STATUS_ERR_WRITE;
850 st->suberror = errno;
851 *errmsg = status_errmsg(st);
856 /* Rename tempfile. */
857 error = rename(st->tempfile, st->path);
859 st->error = STATUS_ERR_RENAME;
860 st->suberror = errno;
861 *errmsg = status_errmsg(st);
865 /* Just discard the tempfile. */
866 unlink(st->tempfile);