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