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) {
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)
118 sr->sr_serverattr = fattr_decode(serverattr);
119 if (sr->sr_serverattr == NULL)
121 sr->sr_clientattr = fattr_decode(clientattr);
122 if (sr->sr_clientattr == NULL) {
123 fattr_free(sr->sr_serverattr);
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)
133 sr->sr_serverattr = fattr_decode(serverattr);
134 if (sr->sr_serverattr == NULL)
138 clientattr = proto_get_ascii(&line);
139 if (clientattr == NULL || line != NULL)
141 sr->sr_clientattr = fattr_decode(clientattr);
142 if (sr->sr_clientattr == NULL)
151 static struct statusrec *
152 status_rd(struct status *st)
154 struct statusrec *sr;
158 sr = status_rdraw(st, &line);
161 error = statusrec_cook(sr, line);
163 st->error = STATUS_ERR_PARSE;
169 static struct statusrec *
170 status_rdraw(struct status *st, char **linep)
173 char *cmd, *line, *file;
175 if (st->rd == NULL || st->eof)
177 line = stream_getln(st->rd, NULL);
179 if (stream_eof(st->rd)) {
180 if (st->depth != 0) {
181 st->error = STATUS_ERR_TRUNC;
187 st->error = STATUS_ERR_READ;
188 st->suberror = errno;
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;
201 sr.sr_type = SR_DIRDOWN;
205 sr.sr_type = SR_CHECKOUTLIVE;
208 sr.sr_type = SR_CHECKOUTDEAD;
211 sr.sr_type = SR_DIRUP;
212 if (st->depth <= 0) {
213 st->error = STATUS_ERR_BOGUS_DIRUP;
219 st->error = STATUS_ERR_BAD_TYPE;
220 st->suberror = cmd[0];
224 sr.sr_file = xstrdup(file);
225 if (st->previous != NULL &&
226 statusrec_cmp(st->previous, &sr) >= 0) {
227 st->error = STATUS_ERR_UNSORTED;
232 if (st->previous == NULL) {
233 st->previous = &st->buf;
235 statusrec_fini(st->previous);
236 statusrec_init(st->previous);
238 st->previous->sr_type = sr.sr_type;
239 st->previous->sr_file = sr.sr_file;
241 return (st->previous);
245 status_wr(struct status *st, struct statusrec *sr)
248 const struct fattr *fa;
250 int error, type, usedirupattr;
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);
261 pathcomp_put(pc, PC_FILE, sr->sr_file);
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) {
269 fa = sr->sr_clientattr;
273 error = proto_printf(st->wr, "U %s %f\n", name, fa);
279 switch (sr->sr_type) {
282 /* Already emitted above. */
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);
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);
298 st->error = STATUS_ERR_WRITE;
299 st->suberror = errno;
304 status_wrraw(struct status *st, struct statusrec *sr, char *line)
308 int error, ret, type;
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.
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);
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);
328 if (sr->sr_type == SR_DIRDOWN)
329 assert(type == PC_DIRDOWN);
331 assert(type == PC_DIRUP);
333 ret = pathcomp_get(st->pc, &type, &name);
336 switch (sr->sr_type) {
343 case SR_CHECKOUTLIVE:
346 case SR_CHECKOUTDEAD:
353 if (sr->sr_type == SR_DIRDOWN)
354 error = proto_printf(st->wr, "%c %S\n", cmd, sr->sr_file);
356 error = proto_printf(st->wr, "%c %s %S\n", cmd, sr->sr_file,
359 st->error = STATUS_ERR_WRITE;
360 st->suberror = errno;
367 statusrec_fini(struct statusrec *sr)
370 fattr_free(sr->sr_serverattr);
371 fattr_free(sr->sr_clientattr);
376 statusrec_cmp(struct statusrec *a, struct statusrec *b)
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)
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)
392 return (pathcmp(a->sr_file, b->sr_file));
395 static struct status *
396 status_new(char *path, time_t scantime, struct stream *file)
400 st = xmalloc(sizeof(struct status));
405 st->scantime = scantime;
414 st->pc = pathcomp_new();
415 statusrec_init(&st->buf);
420 status_free(struct status *st)
423 if (st->previous != NULL)
424 statusrec_fini(st->previous);
426 stream_close(st->rd);
428 stream_close(st->wr);
429 if (st->tempfile != NULL)
432 pathcomp_free(st->pc);
436 static struct status *
437 status_fromrd(char *path, struct stream *file)
444 /* Get the first line of the file and validate it. */
445 line = stream_getln(file, NULL);
450 id = proto_get_ascii(&line);
451 error = proto_get_int(&line, &ver, 10);
456 error = proto_get_time(&line, &scantime);
457 if (error || line != NULL) {
462 if (strcmp(id, "F") != 0 || ver != STATUS_VERSION) {
467 st = status_new(path, scantime, file);
472 static struct status *
473 status_fromnull(char *path)
477 st = status_new(path, -1, NULL);
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
489 status_open(struct coll *coll, time_t scantime, char **errmsg)
494 char *destpath, *path;
497 path = coll_statuspath(coll);
498 file = stream_open_file(path, O_RDONLY);
500 if (errno != ENOENT) {
501 xasprintf(errmsg, "Could not open \"%s\": %s\n",
502 path, strerror(errno));
506 st = status_fromnull(path);
508 st = status_fromrd(path, file);
510 xasprintf(errmsg, "Error in \"%s\": Bad header line",
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));
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));
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);
545 "Cannot set attributes for \"%s\": %s",
546 st->tempfile, strerror(errno));
550 if (scantime != st->scantime)
552 error = proto_printf(st->wr, "F %d %t\n", STATUS_VERSION,
555 st->error = STATUS_ERR_WRITE;
556 st->suberror = errno;
557 *errmsg = status_errmsg(st);
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.
573 status_get(struct status *st, char *name, int isdirup, int deleteto,
574 struct statusrec **psr)
576 struct statusrec key;
577 struct statusrec *sr;
598 if (st->current != NULL) {
612 key.sr_type = SR_DIRUP;
614 key.sr_type = SR_CHECKOUTLIVE;
616 c = statusrec_cmp(sr, &key);
618 if (st->wr != NULL && !deleteto) {
619 error = status_wr(st, sr);
623 /* Loop until we find the good entry. */
625 sr = status_rdraw(st, &line);
631 c = statusrec_cmp(sr, &key);
634 if (st->wr != NULL && !deleteto) {
635 error = status_wrraw(st, sr, line);
640 error = statusrec_cook(sr, line);
642 st->error = STATUS_ERR_PARSE;
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.
659 status_put(struct status *st, struct statusrec *sr)
661 struct statusrec *old;
664 ret = status_get(st, sr->sr_file, sr->sr_type == SR_DIRUP, 0, &old);
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
676 ret = status_get(st, sr->sr_file, 1, 1, &old);
685 error = status_wr(st, sr);
692 * Delete the specified entry from the status file.
695 status_delete(struct status *st, char *name, int isdirup)
697 struct statusrec *sr;
700 ret = status_get(st, name, isdirup, 0, &sr);
711 * Check whether we hit the end of file.
714 status_eof(struct status *st)
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.
726 status_errmsg(struct status *st)
733 case STATUS_ERR_READ:
734 xasprintf(&errmsg, "Read failure on \"%s\": %s",
735 st->path, strerror(st->suberror));
737 case STATUS_ERR_WRITE:
738 xasprintf(&errmsg, "Write failure on \"%s\": %s",
739 st->tempfile, strerror(st->suberror));
741 case STATUS_ERR_PARSE:
742 xasprintf(&errmsg, "Error in \"%s\": %d: "
743 "Could not parse status record", st->path, st->linenum);
745 case STATUS_ERR_UNSORTED:
746 xasprintf(&errmsg, "Error in \"%s\": %d: "
747 "File is not sorted properly", st->path, st->linenum);
749 case STATUS_ERR_TRUNC:
750 xasprintf(&errmsg, "Error in \"%s\": "
751 "File is truncated", st->path);
753 case STATUS_ERR_BOGUS_DIRUP:
754 xasprintf(&errmsg, "Error in \"%s\": %d: "
755 "\"U\" entry has no matching \"D\"", st->path, st->linenum);
757 case STATUS_ERR_BAD_TYPE:
758 xasprintf(&errmsg, "Error in \"%s\": %d: "
759 "Invalid file type \"%c\"", st->path, st->linenum,
762 case STATUS_ERR_RENAME:
763 xasprintf(&errmsg, "Cannot rename \"%s\" to \"%s\": %s",
764 st->tempfile, st->path, strerror(st->suberror));
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.
781 status_close(struct status *st, char **errmsg)
783 struct statusrec *sr;
787 if (st->wr != NULL) {
789 if (st->current != NULL) {
790 error = status_wr(st, st->current);
792 *errmsg = status_errmsg(st);
797 /* Copy the rest of the file. */
798 while ((sr = status_rdraw(st, &line)) != NULL) {
799 error = status_wrraw(st, sr, line);
801 *errmsg = status_errmsg(st);
806 *errmsg = status_errmsg(st);
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",
817 st->error = STATUS_ERR_WRITE;
818 st->suberror = errno;
819 *errmsg = status_errmsg(st);
824 /* Rename tempfile. */
825 error = rename(st->tempfile, st->path);
827 st->error = STATUS_ERR_RENAME;
828 st->suberror = errno;
829 *errmsg = status_errmsg(st);
833 /* Just discard the tempfile. */
834 unlink(st->tempfile);