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