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