Annotation of src/usr.bin/cvs/entries.c, Revision 1.17
1.1 jfb 1: /* $OpenBSD$ */
2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
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
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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:
1.3 jfb 44:
1.1 jfb 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:
53: CVSENTRIES*
1.2 jfb 54: cvs_ent_open(const char *dir, int flags)
1.1 jfb 55: {
56: size_t len;
1.12 jfb 57: int exists;
1.3 jfb 58: char entpath[MAXPATHLEN], ebuf[128], mode[4];
1.1 jfb 59: FILE *fp;
1.8 jfb 60: struct stat st;
1.1 jfb 61: struct cvs_ent *ent;
62: CVSENTRIES *ep;
63:
1.12 jfb 64: exists = 0;
1.3 jfb 65: memset(mode, 0, sizeof(mode));
1.8 jfb 66:
67: snprintf(entpath, sizeof(entpath), "%s/" CVS_PATH_ENTRIES, dir);
68:
1.4 jfb 69: switch (flags & O_ACCMODE) {
1.8 jfb 70: case O_WRONLY:
1.4 jfb 71: case O_RDWR:
1.8 jfb 72: /* we have to use append otherwise the file gets truncated */
73: mode[0] = 'w';
1.4 jfb 74: mode[1] = '+';
1.8 jfb 75: break;
1.4 jfb 76: case O_RDONLY:
1.3 jfb 77: mode[0] = 'r';
1.4 jfb 78: break;
1.3 jfb 79: }
80:
1.8 jfb 81: /* we can use 'r' if the file already exists */
1.12 jfb 82: if (stat(entpath, &st) == 0) {
83: exists = 1;
1.8 jfb 84: mode[0] = 'r';
1.12 jfb 85: }
1.8 jfb 86:
1.3 jfb 87: fp = fopen(entpath, mode);
1.1 jfb 88: if (fp == NULL) {
1.8 jfb 89: cvs_log(LP_ERRNO, "cannot open %s for %s", entpath,
90: mode[1] == '+' ? "writing" : "reading");
1.1 jfb 91: return (NULL);
92: }
93:
94: ep = (CVSENTRIES *)malloc(sizeof(CVSENTRIES));
95: if (ep == NULL) {
96: cvs_log(LP_ERRNO, "failed to allocate Entries data");
97: (void)fclose(fp);
98: return (NULL);
99: }
1.5 jfb 100: memset(ep, 0, sizeof(*ep));
101:
1.17 ! joris 102: ep->cef_path = strdup(entpath);
1.1 jfb 103: if (ep->cef_path == NULL) {
104: cvs_log(LP_ERRNO, "failed to copy Entries path");
105: free(ep);
106: (void)fclose(fp);
107: return (NULL);
108: }
109:
1.4 jfb 110: ep->cef_cur = NULL;
111: TAILQ_INIT(&(ep->cef_ent));
1.3 jfb 112:
1.1 jfb 113: while (fgets(ebuf, sizeof(ebuf), fp) != NULL) {
114: len = strlen(ebuf);
115: if ((len > 0) && (ebuf[len - 1] == '\n'))
116: ebuf[--len] = '\0';
1.7 jfb 117: if (strcmp(ebuf, "D") == 0)
118: break;
1.1 jfb 119: ent = cvs_ent_parse(ebuf);
120: if (ent == NULL)
121: continue;
122:
1.4 jfb 123: TAILQ_INSERT_TAIL(&(ep->cef_ent), ent, ce_list);
1.1 jfb 124: }
1.12 jfb 125: if (ferror(fp)) {
126: cvs_ent_close(ep);
127: return (NULL);
128: }
1.1 jfb 129:
1.4 jfb 130: /* only keep a pointer to the open file if we're in writing mode */
1.16 jfb 131: if ((flags & O_WRONLY) || (flags & O_RDWR))
1.8 jfb 132: ep->cef_flags |= CVS_ENTF_WR;
1.16 jfb 133:
134: (void)fclose(fp);
1.4 jfb 135:
1.12 jfb 136: if (exists)
137: ep->cef_flags |= CVS_ENTF_SYNC;
138:
1.1 jfb 139: return (ep);
140: }
141:
142:
143: /*
144: * cvs_ent_close()
145: *
1.5 jfb 146: * Close the Entries file <ep> and free all data. Any reference to entries
147: * structure within that file become invalid.
1.1 jfb 148: */
149:
150: void
151: cvs_ent_close(CVSENTRIES *ep)
152: {
1.5 jfb 153: struct cvs_ent *ent;
154:
1.8 jfb 155: if ((ep->cef_flags & CVS_ENTF_WR) &&
156: !(ep->cef_flags & CVS_ENTF_SYNC)) {
157: /* implicit sync with disk */
158: (void)cvs_ent_write(ep);
159: }
160:
1.5 jfb 161: if (ep->cef_file != NULL)
1.6 jfb 162: (void)fclose(ep->cef_file);
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:
186: int
187: cvs_ent_add(CVSENTRIES *ef, struct cvs_ent *ent)
188: {
1.16 jfb 189: if (!(ef->cef_flags & CVS_ENTF_WR)) {
1.3 jfb 190: cvs_log(LP_ERR, "Entries file is opened in read-only mode");
191: return (-1);
192: }
1.1 jfb 193:
194: if (cvs_ent_get(ef, ent->ce_name) != NULL)
195: return (-1);
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:
211: int
212: cvs_ent_addln(CVSENTRIES *ef, const char *line)
213: {
214: struct cvs_ent *ent;
215:
1.16 jfb 216: if (!(ef->cef_flags & CVS_ENTF_WR)) {
1.3 jfb 217: cvs_log(LP_ERR, "Entries file is opened in read-only mode");
218: return (-1);
219: }
220:
221: ent = cvs_ent_parse(line);
222: if (ent == NULL)
223: return (-1);
224:
225: if (cvs_ent_get(ef, ent->ce_name) != NULL)
226: return (-1);
1.1 jfb 227:
1.4 jfb 228: TAILQ_INSERT_TAIL(&(ef->cef_ent), ent, ce_list);
1.8 jfb 229: ef->cef_flags &= ~CVS_ENTF_SYNC;
230:
1.1 jfb 231: return (0);
232: }
233:
234:
235: /*
1.9 jfb 236: * cvs_ent_remove()
237: *
238: * Remove an entry from the Entries file <ef>. The entry's name is given
239: * by <name>.
240: */
241:
242: int
243: cvs_ent_remove(CVSENTRIES *ef, const char *name)
244: {
245: struct cvs_ent *ent;
246:
1.16 jfb 247: if (!(ef->cef_flags & CVS_ENTF_WR)) {
248: cvs_log(LP_ERR, "Entries file is opened in read-only mode");
249: return (-1);
250: }
251:
1.9 jfb 252: ent = cvs_ent_get(ef, name);
253: if (ent == NULL)
254: return (-1);
255:
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:
273: struct cvs_ent*
274: cvs_ent_get(CVSENTRIES *ef, const char *file)
275: {
1.4 jfb 276: struct cvs_ent *ep;
1.1 jfb 277:
1.4 jfb 278: TAILQ_FOREACH(ep, &(ef->cef_ent), ce_list)
279: if (strcmp(ep->ce_name, file) == 0)
280: return (ep);
1.1 jfb 281:
282: return (NULL);
283: }
284:
285:
286: /*
287: * cvs_ent_next()
288: *
1.4 jfb 289: * This function is used to iterate over the entries in an Entries file. The
290: * first call will return the first entry of the file and each subsequent call
291: * will return the entry following the last one returned.
1.1 jfb 292: * Returns a pointer to the cvs entry structure on success, or NULL on failure.
293: */
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: *
309: * Parse a single line from a CVS/Entries file and return a cvs_entry structure
310: * containing all the parsed information.
311: */
312:
313: struct cvs_ent*
314: cvs_ent_parse(const char *entry)
315: {
316: int i;
1.10 jfb 317: char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp;
1.1 jfb 318: struct cvs_ent *entp;
319:
1.10 jfb 320: buf = strdup(entry);
321: if (buf == NULL) {
322: cvs_log(LP_ERRNO, "failed to allocate entry copy");
323: return (NULL);
324: }
325:
326: sp = buf;
327: i = 0;
328: do {
329: dp = strchr(sp, CVS_ENTRIES_DELIM);
330: if (dp != NULL)
331: *(dp++) = '\0';
332: fields[i++] = sp;
333: sp = dp;
334: } while ((dp != NULL) && (i < CVS_ENTRIES_NFIELDS));
335:
336: if (i < CVS_ENTRIES_NFIELDS) {
337: cvs_log(LP_ERR, "missing fields in entry line `%s'", entry);
338: return (NULL);
339: }
340:
1.1 jfb 341: entp = (struct cvs_ent *)malloc(sizeof(*entp));
342: if (entp == NULL) {
343: cvs_log(LP_ERRNO, "failed to allocate CVS entry");
344: return (NULL);
345: }
1.5 jfb 346: memset(entp, 0, sizeof(*entp));
1.10 jfb 347: entp->ce_buf = buf;
1.1 jfb 348:
349: entp->ce_rev = rcsnum_alloc();
350: if (entp->ce_rev == NULL) {
1.10 jfb 351: cvs_ent_free(entp);
1.1 jfb 352: return (NULL);
353: }
354:
355: entp->ce_line = strdup(entry);
356: if (entp->ce_line == NULL) {
1.5 jfb 357: cvs_ent_free(entp);
1.1 jfb 358: return (NULL);
359: }
360:
1.10 jfb 361: if (*fields[0] == '\0')
1.1 jfb 362: entp->ce_type = CVS_ENT_FILE;
1.10 jfb 363: else if (*fields[0] == 'D')
1.1 jfb 364: entp->ce_type = CVS_ENT_DIR;
1.10 jfb 365: else
1.1 jfb 366: entp->ce_type = CVS_ENT_NONE;
367:
1.10 jfb 368: entp->ce_name = fields[1];
1.1 jfb 369:
370: if (entp->ce_type == CVS_ENT_FILE) {
1.10 jfb 371: rcsnum_aton(fields[2], NULL, entp->ce_rev);
1.13 jfb 372: entp->ce_mtime = cvs_datesec(fields[3], CVS_DATE_CTIME, 0);
1.10 jfb 373: entp->ce_opts = fields[4];
374: entp->ce_tag = fields[5];
1.1 jfb 375: }
376:
377: return (entp);
1.5 jfb 378: }
379:
380:
381: /*
382: * cvs_ent_free()
383: *
384: * Free a single CVS entries structure.
385: */
386:
387: void
388: cvs_ent_free(struct cvs_ent *ent)
389: {
390: if (ent->ce_rev != NULL)
391: rcsnum_free(ent->ce_rev);
392: if (ent->ce_line != NULL)
393: free(ent->ce_line);
394: if (ent->ce_buf != NULL)
395: free(ent->ce_buf);
396: free(ent);
397: }
398:
399:
400: /*
401: * cvs_ent_getent()
402: *
403: * Get a single entry from the CVS/Entries file of the basename portion of
404: * path <path> and return that entry. That entry must later be freed using
405: * cvs_ent_free().
406: */
407:
408: struct cvs_ent*
409: cvs_ent_getent(const char *path)
410: {
1.11 jfb 411: char base[MAXPATHLEN], *file;
1.5 jfb 412: CVSENTRIES *entf;
413: struct cvs_ent *ep;
414:
1.11 jfb 415: cvs_splitpath(path, base, sizeof(base), &file);
1.5 jfb 416:
417: entf = cvs_ent_open(base, O_RDONLY);
418: if (entf == NULL)
419: return (NULL);
420:
421: ep = cvs_ent_get(entf, file);
422: if (ep != NULL) {
423: /* take it out of the queue so it doesn't get freed */
424: TAILQ_REMOVE(&(entf->cef_ent), ep, ce_list);
425: }
426:
427: cvs_ent_close(entf);
428: return (ep);
1.8 jfb 429: }
430:
431:
432: /*
433: * cvs_ent_write()
434: *
435: * Explicitly write the contents of the Entries file <ef> to disk.
436: * Returns 0 on success, or -1 on failure.
437: */
438:
439: int
440: cvs_ent_write(CVSENTRIES *ef)
441: {
1.13 jfb 442: size_t len;
443: char revbuf[64], timebuf[32];
1.8 jfb 444: struct cvs_ent *ent;
445:
446: if (ef->cef_flags & CVS_ENTF_SYNC)
447: return (0);
448:
1.16 jfb 449: if (ef->cef_file == NULL) {
450: ef->cef_file = fopen(ef->cef_path, "w");
451: if (ef->cef_file == NULL) {
452: cvs_log(LP_ERRNO, "failed to open Entries `%s'",
453: ef->cef_path);
454: return (-1);
455: }
456: }
457:
458:
1.8 jfb 459: /* reposition ourself at beginning of file */
460: rewind(ef->cef_file);
461: TAILQ_FOREACH(ent, &(ef->cef_ent), ce_list) {
1.15 jfb 462: if (ent->ce_type == CVS_ENT_DIR) {
1.8 jfb 463: putc('D', ef->cef_file);
1.15 jfb 464: timebuf[0] = '\0';
465: revbuf[0] = '\0';
466: }
467: else {
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: }