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