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