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