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