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