Annotation of src/usr.bin/cvs/entries.c, Revision 1.52
1.52 ! joris 1: /* $OpenBSD: entries.c,v 1.51 2005/12/03 01:02:08 joris Exp $ */
1.1 jfb 2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
1.20 tedu 4: * All rights reserved.
1.1 jfb 5: *
1.20 tedu 6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
1.1 jfb 9: *
1.20 tedu 10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
1.1 jfb 12: * 2. The name of the author may not be used to endorse or promote products
1.20 tedu 13: * derived from this software without specific prior written permission.
1.1 jfb 14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
1.20 tedu 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 jfb 25: */
26:
27: #include <sys/stat.h>
28:
1.26 xsa 29: #include <errno.h>
30: #include <fcntl.h>
1.1 jfb 31: #include <stdio.h>
32: #include <stdlib.h>
1.26 xsa 33: #include <string.h>
1.1 jfb 34: #include <unistd.h>
35:
1.34 xsa 36: #include "cvs.h"
1.1 jfb 37: #include "log.h"
38:
39:
1.42 xsa 40: #define CVS_ENTRIES_NFIELDS 6
41: #define CVS_ENTRIES_DELIM '/'
1.1 jfb 42:
43:
44: /*
45: * cvs_ent_open()
46: *
47: * Open the CVS Entries file for the directory <dir>.
48: * Returns a pointer to the CVSENTRIES file structure on success, or NULL
49: * on failure.
50: */
1.42 xsa 51: CVSENTRIES *
1.2 jfb 52: cvs_ent_open(const char *dir, int flags)
1.1 jfb 53: {
54: size_t len;
1.36 xsa 55: int exists, nodir;
1.52 ! joris 56: char bpath[MAXPATHLEN], *p;
1.45 xsa 57: char cdpath[MAXPATHLEN], ebuf[CVS_ENT_MAXLINELEN], entpath[MAXPATHLEN];
58: char mode[4];
1.1 jfb 59: FILE *fp;
1.8 jfb 60: struct stat st;
1.1 jfb 61: struct cvs_ent *ent;
62: CVSENTRIES *ep;
63:
1.12 jfb 64: exists = 0;
1.36 xsa 65: nodir = 1;
1.3 jfb 66: memset(mode, 0, sizeof(mode));
1.8 jfb 67:
1.36 xsa 68: /*
69: * Check if the CVS/ dir does exist. If it does,
70: * maybe the Entries file was deleted by accident,
71: * display error message. Else we might be doing a fresh
72: * update or checkout of a module.
73: */
74: len = cvs_path_cat(dir, CVS_PATH_CVSDIR, cdpath, sizeof(cdpath));
1.37 xsa 75: if (len >= sizeof(cdpath))
1.36 xsa 76: return (NULL);
1.37 xsa 77:
1.36 xsa 78: if ((stat(cdpath, &st) == 0) && S_ISDIR(st.st_mode))
79: nodir = 0; /* the CVS/ directory does exist */
80:
1.52 ! joris 81: len = cvs_path_cat(dir, CVS_PATH_BACKUPENTRIES, bpath, sizeof(bpath));
! 82: if (len >= sizeof(entpath))
! 83: return (NULL);
! 84:
1.32 jfb 85: len = cvs_path_cat(dir, CVS_PATH_ENTRIES, entpath, sizeof(entpath));
1.35 joris 86: if (len >= sizeof(entpath))
1.26 xsa 87: return (NULL);
1.8 jfb 88:
1.4 jfb 89: switch (flags & O_ACCMODE) {
1.8 jfb 90: case O_WRONLY:
1.4 jfb 91: case O_RDWR:
1.8 jfb 92: /* we have to use append otherwise the file gets truncated */
93: mode[0] = 'w';
1.4 jfb 94: mode[1] = '+';
1.8 jfb 95: break;
1.4 jfb 96: case O_RDONLY:
1.3 jfb 97: mode[0] = 'r';
1.4 jfb 98: break;
1.3 jfb 99: }
100:
1.8 jfb 101: /* we can use 'r' if the file already exists */
1.12 jfb 102: if (stat(entpath, &st) == 0) {
103: exists = 1;
1.8 jfb 104: mode[0] = 'r';
1.12 jfb 105: }
1.8 jfb 106:
1.3 jfb 107: fp = fopen(entpath, mode);
1.1 jfb 108: if (fp == NULL) {
1.44 xsa 109: if (nodir == 0)
1.36 xsa 110: cvs_log(LP_ERRNO, "cannot open %s for %s", entpath,
111: mode[1] == '+' ? "writing" : "reading");
1.1 jfb 112: return (NULL);
113: }
114:
115: ep = (CVSENTRIES *)malloc(sizeof(CVSENTRIES));
116: if (ep == NULL) {
117: cvs_log(LP_ERRNO, "failed to allocate Entries data");
118: (void)fclose(fp);
119: return (NULL);
120: }
1.5 jfb 121: memset(ep, 0, sizeof(*ep));
122:
1.17 joris 123: ep->cef_path = strdup(entpath);
1.1 jfb 124: if (ep->cef_path == NULL) {
125: cvs_log(LP_ERRNO, "failed to copy Entries path");
126: free(ep);
127: (void)fclose(fp);
128: return (NULL);
129: }
130:
1.52 ! joris 131: ep->cef_bpath = strdup(bpath);
! 132: if (ep->cef_bpath == NULL) {
! 133: cvs_ent_close(ep);
! 134: (void)fclose(fp);
! 135: return (NULL);
! 136: }
! 137:
1.4 jfb 138: ep->cef_cur = NULL;
139: TAILQ_INIT(&(ep->cef_ent));
1.3 jfb 140:
1.43 xsa 141: while (fgets(ebuf, (int)sizeof(ebuf), fp) != NULL) {
1.1 jfb 142: len = strlen(ebuf);
143: if ((len > 0) && (ebuf[len - 1] == '\n'))
144: ebuf[--len] = '\0';
1.31 jfb 145: if ((ebuf[0] == 'D') && (ebuf[1] == '\0'))
1.7 jfb 146: break;
1.1 jfb 147: ent = cvs_ent_parse(ebuf);
148: if (ent == NULL)
149: continue;
150:
1.4 jfb 151: TAILQ_INSERT_TAIL(&(ep->cef_ent), ent, ce_list);
1.1 jfb 152: }
1.52 ! joris 153:
1.12 jfb 154: if (ferror(fp)) {
1.18 krapht 155: cvs_log(LP_ERRNO, "read error on %s", entpath);
1.25 jfb 156: (void)fclose(fp);
1.12 jfb 157: cvs_ent_close(ep);
158: return (NULL);
159: }
1.1 jfb 160:
1.4 jfb 161: /* only keep a pointer to the open file if we're in writing mode */
1.16 jfb 162: if ((flags & O_WRONLY) || (flags & O_RDWR))
1.8 jfb 163: ep->cef_flags |= CVS_ENTF_WR;
1.16 jfb 164:
165: (void)fclose(fp);
1.4 jfb 166:
1.52 ! joris 167: /*
! 168: * look for Entries.Log and add merge it together with our
! 169: * list of things.
! 170: */
! 171: len = cvs_path_cat(dir, CVS_PATH_LOGENTRIES, entpath, sizeof(entpath));
! 172: if (len >= sizeof(entpath)) {
! 173: cvs_ent_close(ep);
! 174: return (NULL);
! 175: }
! 176:
! 177: fp = fopen(entpath, "r");
! 178: if (fp != NULL) {
! 179: while (fgets(ebuf, (int)sizeof(ebuf), fp) != NULL) {
! 180: len = strlen(ebuf);
! 181: if ((len > 0) && (ebuf[len - 1] == '\n'))
! 182: ebuf[--len] = '\0';
! 183:
! 184: p = &ebuf[2];
! 185: ent = cvs_ent_parse(p);
! 186: if (ent == NULL)
! 187: continue;
! 188:
! 189: if (ebuf[0] == 'A')
! 190: cvs_ent_add(ep, ent);
! 191: else if (ebuf[0] == 'R')
! 192: cvs_ent_remove(ep, ent->ce_name, 0);
! 193: }
! 194: (void)fclose(fp);
! 195:
! 196: /* always un-synced here, because we
! 197: * just added or removed entries.
! 198: */
! 199: ep->cef_flags &= ~CVS_ENTF_SYNC;
! 200: } else {
! 201: if (exists == 1)
! 202: ep->cef_flags |= CVS_ENTF_SYNC;
! 203: }
1.12 jfb 204:
1.1 jfb 205: return (ep);
206: }
207:
208:
209: /*
210: * cvs_ent_close()
211: *
1.5 jfb 212: * Close the Entries file <ep> and free all data. Any reference to entries
213: * structure within that file become invalid.
1.1 jfb 214: */
215: void
216: cvs_ent_close(CVSENTRIES *ep)
217: {
1.5 jfb 218: struct cvs_ent *ent;
219:
1.41 xsa 220: if ((cvs_noexec == 0) && (ep->cef_flags & CVS_ENTF_WR) &&
1.8 jfb 221: !(ep->cef_flags & CVS_ENTF_SYNC)) {
222: /* implicit sync with disk */
223: (void)cvs_ent_write(ep);
224: }
225:
1.5 jfb 226: if (ep->cef_path != NULL)
227: free(ep->cef_path);
228:
1.52 ! joris 229: if (ep->cef_bpath != NULL)
! 230: free(ep->cef_bpath);
! 231:
1.5 jfb 232: while (!TAILQ_EMPTY(&(ep->cef_ent))) {
233: ent = TAILQ_FIRST(&(ep->cef_ent));
234: TAILQ_REMOVE(&(ep->cef_ent), ent, ce_list);
235: cvs_ent_free(ent);
236: }
237:
1.1 jfb 238: free(ep);
239: }
240:
241:
242: /*
243: * cvs_ent_add()
244: *
1.8 jfb 245: * Add the entry <ent> to the Entries file <ef>. The disk contents are not
246: * modified until a call to cvs_ent_write() is performed. This is done
247: * implicitly on a call to cvs_ent_close() on an Entries file that has been
248: * opened for writing.
1.7 jfb 249: * Returns 0 on success, or -1 on failure.
1.1 jfb 250: */
251: int
252: cvs_ent_add(CVSENTRIES *ef, struct cvs_ent *ent)
253: {
1.16 jfb 254: if (!(ef->cef_flags & CVS_ENTF_WR)) {
1.3 jfb 255: cvs_log(LP_ERR, "Entries file is opened in read-only mode");
256: return (-1);
257: }
1.1 jfb 258:
1.21 jfb 259: if (cvs_ent_get(ef, ent->ce_name) != NULL) {
260: cvs_log(LP_ERR, "attempt to add duplicate entry for `%s'",
261: ent->ce_name);
1.1 jfb 262: return (-1);
1.21 jfb 263: }
1.1 jfb 264:
1.8 jfb 265: TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list);
266:
267: ef->cef_flags &= ~CVS_ENTF_SYNC;
1.3 jfb 268:
269: return (0);
270: }
271:
272:
273: /*
274: * cvs_ent_addln()
275: *
276: * Add a line to the Entries file.
277: */
278: int
279: cvs_ent_addln(CVSENTRIES *ef, const char *line)
280: {
281: struct cvs_ent *ent;
282:
1.16 jfb 283: if (!(ef->cef_flags & CVS_ENTF_WR)) {
1.3 jfb 284: cvs_log(LP_ERR, "Entries file is opened in read-only mode");
285: return (-1);
286: }
287:
288: ent = cvs_ent_parse(line);
289: if (ent == NULL)
290: return (-1);
291:
292: if (cvs_ent_get(ef, ent->ce_name) != NULL)
293: return (-1);
1.1 jfb 294:
1.4 jfb 295: TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list);
1.8 jfb 296: ef->cef_flags &= ~CVS_ENTF_SYNC;
297:
1.1 jfb 298: return (0);
299: }
300:
301:
302: /*
1.9 jfb 303: * cvs_ent_remove()
304: *
305: * Remove an entry from the Entries file <ef>. The entry's name is given
306: * by <name>.
307: */
308: int
1.51 joris 309: cvs_ent_remove(CVSENTRIES *ef, const char *name, int useprev)
1.9 jfb 310: {
311: struct cvs_ent *ent;
1.44 xsa 312:
313: cvs_log(LP_TRACE, "cvs_ent_remove(%s)", name);
1.16 jfb 314:
1.9 jfb 315: ent = cvs_ent_get(ef, name);
316: if (ent == NULL)
317: return (-1);
318:
1.22 jfb 319: if (ef->cef_cur == ent) {
320: /* if this element was the last one retrieved through a
321: * call to cvs_ent_next(), point to the next element to avoid
322: * keeping an invalid reference.
323: */
1.51 joris 324: if (useprev) {
325: ef->cef_cur = TAILQ_PREV(ef->cef_cur,
326: cvsentrieshead, ce_list);
327: } else {
328: ef->cef_cur = TAILQ_NEXT(ef->cef_cur, ce_list);
329: }
1.22 jfb 330: }
1.9 jfb 331: TAILQ_REMOVE(&(ef->cef_ent), ent, ce_list);
332: cvs_ent_free(ent);
333:
334: ef->cef_flags &= ~CVS_ENTF_SYNC;
335:
336: return (0);
337: }
338:
339:
340: /*
1.1 jfb 341: * cvs_ent_get()
342: *
343: * Get the CVS entry from the Entries file <ef> whose 'name' portion matches
344: * <file>.
345: * Returns a pointer to the cvs entry structure on success, or NULL on failure.
346: */
1.42 xsa 347: struct cvs_ent *
1.1 jfb 348: cvs_ent_get(CVSENTRIES *ef, const char *file)
349: {
1.39 xsa 350: struct cvs_ent *ent;
1.1 jfb 351:
1.39 xsa 352: TAILQ_FOREACH(ent, &(ef->cef_ent), ce_list)
353: if (strcmp(ent->ce_name, file) == 0)
354: return (ent);
1.1 jfb 355:
356: return (NULL);
357: }
358:
359:
360: /*
361: * cvs_ent_next()
362: *
1.4 jfb 363: * This function is used to iterate over the entries in an Entries file. The
364: * first call will return the first entry of the file and each subsequent call
365: * will return the entry following the last one returned.
1.1 jfb 366: * Returns a pointer to the cvs entry structure on success, or NULL on failure.
367: */
1.42 xsa 368: struct cvs_ent *
1.1 jfb 369: cvs_ent_next(CVSENTRIES *ef)
370: {
1.5 jfb 371: if (ef->cef_cur == NULL)
1.4 jfb 372: ef->cef_cur = TAILQ_FIRST(&(ef->cef_ent));
1.5 jfb 373: else
374: ef->cef_cur = TAILQ_NEXT(ef->cef_cur, ce_list);
375: return (ef->cef_cur);
1.1 jfb 376: }
377:
378:
379: /*
380: * cvs_ent_parse()
381: *
1.25 jfb 382: * Parse a single line from a CVS/Entries file and return a cvs_ent structure
1.1 jfb 383: * containing all the parsed information.
384: */
385: struct cvs_ent*
386: cvs_ent_parse(const char *entry)
387: {
388: int i;
1.10 jfb 389: char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp;
1.39 xsa 390: struct cvs_ent *ent;
1.1 jfb 391:
1.10 jfb 392: buf = strdup(entry);
393: if (buf == NULL) {
394: cvs_log(LP_ERRNO, "failed to allocate entry copy");
395: return (NULL);
396: }
397:
398: sp = buf;
399: i = 0;
400: do {
401: dp = strchr(sp, CVS_ENTRIES_DELIM);
402: if (dp != NULL)
403: *(dp++) = '\0';
404: fields[i++] = sp;
405: sp = dp;
406: } while ((dp != NULL) && (i < CVS_ENTRIES_NFIELDS));
407:
408: if (i < CVS_ENTRIES_NFIELDS) {
409: cvs_log(LP_ERR, "missing fields in entry line `%s'", entry);
410: return (NULL);
411: }
412:
1.39 xsa 413: ent = (struct cvs_ent *)malloc(sizeof(*ent));
414: if (ent == NULL) {
1.1 jfb 415: cvs_log(LP_ERRNO, "failed to allocate CVS entry");
416: return (NULL);
417: }
1.39 xsa 418: memset(ent, 0, sizeof(*ent));
419: ent->ce_buf = buf;
1.1 jfb 420:
1.10 jfb 421: if (*fields[0] == '\0')
1.39 xsa 422: ent->ce_type = CVS_ENT_FILE;
1.10 jfb 423: else if (*fields[0] == 'D')
1.39 xsa 424: ent->ce_type = CVS_ENT_DIR;
1.10 jfb 425: else
1.39 xsa 426: ent->ce_type = CVS_ENT_NONE;
1.1 jfb 427:
1.39 xsa 428: ent->ce_status = CVS_ENT_REG;
429: ent->ce_name = fields[1];
1.48 joris 430: ent->processed = 0;
1.1 jfb 431:
1.39 xsa 432: if (ent->ce_type == CVS_ENT_FILE) {
1.24 jfb 433: if (*fields[2] == '-') {
1.39 xsa 434: ent->ce_status = CVS_ENT_REMOVED;
1.24 jfb 435: sp = fields[2] + 1;
436: } else {
437: sp = fields[2];
1.31 jfb 438: if ((fields[2][0] == '0') && (fields[2][1] == '\0'))
1.39 xsa 439: ent->ce_status = CVS_ENT_ADDED;
1.24 jfb 440: }
1.38 joris 441:
1.39 xsa 442: if ((ent->ce_rev = rcsnum_parse(sp)) == NULL) {
443: cvs_ent_free(ent);
1.29 jfb 444: return (NULL);
445: }
1.24 jfb 446:
1.38 joris 447: if (cvs_cmdop == CVS_OP_SERVER) {
448: if (!strcmp(fields[3], "up to date"))
1.39 xsa 449: ent->ce_status = CVS_ENT_UPTODATE;
1.38 joris 450: } else {
1.50 joris 451: if ((strcmp(fields[3], CVS_DATE_DUMMY) == 0) ||
452: (strncmp(fields[3], "Initial ", 8) == 0))
1.39 xsa 453: ent->ce_mtime = CVS_DATE_DMSEC;
1.38 joris 454: else
1.39 xsa 455: ent->ce_mtime = cvs_date_parse(fields[3]);
1.38 joris 456: }
1.29 jfb 457: }
1.24 jfb 458:
1.39 xsa 459: ent->ce_opts = fields[4];
460: ent->ce_tag = fields[5];
461: return (ent);
1.5 jfb 462: }
463:
464: /*
465: * cvs_ent_free()
466: *
467: * Free a single CVS entries structure.
468: */
469: void
470: cvs_ent_free(struct cvs_ent *ent)
471: {
472: if (ent->ce_rev != NULL)
473: rcsnum_free(ent->ce_rev);
474: if (ent->ce_buf != NULL)
475: free(ent->ce_buf);
476: free(ent);
477: }
1.8 jfb 478:
479: /*
480: * cvs_ent_write()
481: *
482: * Explicitly write the contents of the Entries file <ef> to disk.
483: * Returns 0 on success, or -1 on failure.
484: */
485: int
486: cvs_ent_write(CVSENTRIES *ef)
487: {
1.13 jfb 488: size_t len;
489: char revbuf[64], timebuf[32];
1.8 jfb 490: struct cvs_ent *ent;
1.32 jfb 491: FILE *fp;
1.8 jfb 492:
493: if (ef->cef_flags & CVS_ENTF_SYNC)
494: return (0);
495:
1.52 ! joris 496: if ((fp = fopen(ef->cef_bpath, "w")) == NULL) {
! 497: cvs_log(LP_ERRNO, "failed to open Entries `%s'", ef->cef_bpath);
1.32 jfb 498: return (-1);
1.16 jfb 499: }
500:
1.8 jfb 501: TAILQ_FOREACH(ent, &(ef->cef_ent), ce_list) {
1.15 jfb 502: if (ent->ce_type == CVS_ENT_DIR) {
1.32 jfb 503: putc('D', fp);
1.15 jfb 504: timebuf[0] = '\0';
505: revbuf[0] = '\0';
1.19 deraadt 506: } else {
1.15 jfb 507: rcsnum_tostr(ent->ce_rev, revbuf, sizeof(revbuf));
1.49 xsa 508: if ((ent->ce_mtime == CVS_DATE_DMSEC) &&
509: (ent->ce_status != CVS_ENT_ADDED))
1.15 jfb 510: strlcpy(timebuf, CVS_DATE_DUMMY,
511: sizeof(timebuf));
1.47 joris 512: else if (ent->ce_status == CVS_ENT_ADDED) {
513: strlcpy(timebuf, "Initial ", sizeof(timebuf));
514: strlcat(timebuf, ent->ce_name, sizeof(timebuf));
515: } else {
1.15 jfb 516: ctime_r(&(ent->ce_mtime), timebuf);
517: len = strlen(timebuf);
518: if ((len > 0) && (timebuf[len - 1] == '\n'))
519: timebuf[--len] = '\0';
520: }
1.38 joris 521: }
522:
523: if (cvs_cmdop == CVS_OP_SERVER) {
524: if (ent->ce_status == CVS_ENT_UPTODATE)
525: strlcpy(timebuf, "up to date", sizeof(timebuf));
526: else
527: timebuf[0] = '\0';
1.15 jfb 528: }
1.8 jfb 529:
1.32 jfb 530: fprintf(fp, "/%s/%s%s/%s/%s/%s\n", ent->ce_name,
1.27 joris 531: (ent->ce_status == CVS_ENT_REMOVED) ? "-" : "", revbuf,
1.49 xsa 532: timebuf, (ent->ce_opts != NULL) ? ent->ce_opts : "",
533: (ent->ce_tag != NULL) ? ent->ce_tag : "");
1.8 jfb 534: }
535:
536: /* terminating line */
1.32 jfb 537: putc('D', fp);
538: putc('\n', fp);
1.8 jfb 539:
540: ef->cef_flags |= CVS_ENTF_SYNC;
1.32 jfb 541: fclose(fp);
1.52 ! joris 542:
! 543: /* rename Entries.Backup to Entries */
! 544: cvs_rename(ef->cef_bpath, ef->cef_path);
! 545:
! 546: /* remove Entries.Log */
! 547: cvs_unlink(CVS_PATH_LOGENTRIES);
! 548:
1.8 jfb 549: return (0);
1.1 jfb 550: }