Annotation of src/usr.bin/rcs/rcsutil.c, Revision 1.29
1.29 ! xsa 1: /* $OpenBSD: rcsutil.c,v 1.28 2007/02/22 19:11:13 otto Exp $ */
1.1 xsa 2: /*
3: * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
4: * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org>
5: * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org>
6: * Copyright (c) 2006 Ray Lai <ray@openbsd.org>
7: * All rights reserved.
8: *
9: * Redistribution and use in source and binary forms, with or without
10: * modification, are permitted provided that the following conditions
11: * are met:
12: *
13: * 1. Redistributions of source code must retain the above copyright
14: * notice, this list of conditions and the following disclaimer.
15: * 2. The name of the author may not be used to endorse or promote products
16: * derived from this software without specific prior written permission.
17: *
18: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28: */
29:
1.29 ! xsa 30: #include <sys/stat.h>
! 31:
! 32: #include <ctype.h>
! 33: #include <err.h>
! 34: #include <fcntl.h>
! 35: #include <stdio.h>
! 36: #include <string.h>
! 37: #include <unistd.h>
1.1 xsa 38:
39: #include "rcsprog.h"
40:
41: /*
42: * rcs_get_mtime()
43: *
44: * Get <filename> last modified time.
45: * Returns last modified time on success, or -1 on failure.
46: */
47: time_t
1.6 joris 48: rcs_get_mtime(RCSFILE *file)
1.1 xsa 49: {
50: struct stat st;
51: time_t mtime;
52:
1.13 ray 53: if (fstat(file->rf_fd, &st) == -1) {
1.6 joris 54: warn("%s", file->rf_path);
1.1 xsa 55: return (-1);
56: }
1.6 joris 57:
1.1 xsa 58: mtime = (time_t)st.st_mtimespec.tv_sec;
59:
60: return (mtime);
61: }
62:
63: /*
64: * rcs_set_mtime()
65: *
66: * Set <filename> last modified time to <mtime> if it's not set to -1.
67: */
68: void
1.6 joris 69: rcs_set_mtime(RCSFILE *file, time_t mtime)
1.1 xsa 70: {
71: static struct timeval tv[2];
72:
73: if (mtime == -1)
74: return;
75:
76: tv[0].tv_sec = mtime;
77: tv[1].tv_sec = tv[0].tv_sec;
78:
1.13 ray 79: if (futimes(file->rf_fd, tv) == -1)
1.3 xsa 80: err(1, "utimes");
1.1 xsa 81: }
82:
83: int
84: rcs_getopt(int argc, char **argv, const char *optstr)
85: {
86: char *a;
87: const char *c;
88: static int i = 1;
89: int opt, hasargument, ret;
90:
91: hasargument = 0;
92: rcs_optarg = NULL;
93:
94: if (i >= argc)
95: return (-1);
96:
97: a = argv[i++];
98: if (*a++ != '-')
99: return (-1);
100:
101: ret = 0;
102: opt = *a;
103: for (c = optstr; *c != '\0'; c++) {
104: if (*c == opt) {
105: a++;
106: ret = opt;
107:
108: if (*(c + 1) == ':') {
109: if (*(c + 2) == ':') {
110: if (*a != '\0')
111: hasargument = 1;
112: } else {
113: if (*a != '\0') {
114: hasargument = 1;
115: } else {
116: ret = 1;
117: break;
118: }
119: }
120: }
121:
122: if (hasargument == 1)
123: rcs_optarg = a;
124:
125: if (ret == opt)
126: rcs_optind++;
127: break;
128: }
129: }
130:
131: if (ret == 0)
132: warnx("unknown option -%c", opt);
133: else if (ret == 1)
134: warnx("missing argument for option -%c", opt);
135:
136: return (ret);
137: }
138:
139: /*
140: * rcs_choosefile()
141: *
142: * Given a relative filename, decide where the corresponding RCS file
143: * should be. Tries each extension until a file is found. If no file
144: * was found, returns a path with the first extension.
145: *
1.11 ray 146: * Opens and returns file descriptor to RCS file.
1.1 xsa 147: */
1.6 joris 148: int
149: rcs_choosefile(const char *filename, char *out, size_t len)
1.1 xsa 150: {
1.6 joris 151: int fd;
1.1 xsa 152: struct stat sb;
153: char *p, *ext, name[MAXPATHLEN], *next, *ptr, rcsdir[MAXPATHLEN],
1.10 ray 154: *suffixes, rcspath[MAXPATHLEN];
1.1 xsa 155:
156: /* If -x flag was not given, use default. */
157: if (rcs_suffixes == NULL)
158: rcs_suffixes = RCS_DEFAULT_SUFFIX;
159:
1.6 joris 160: fd = -1;
161:
1.1 xsa 162: /*
163: * If `filename' contains a directory, `rcspath' contains that
164: * directory, including a trailing slash. Otherwise `rcspath'
165: * contains an empty string.
166: */
167: if (strlcpy(rcspath, filename, sizeof(rcspath)) >= sizeof(rcspath))
1.6 joris 168: errx(1, "rcs_choosefile: truncation");
169:
1.1 xsa 170: /* If `/' is found, end string after `/'. */
171: if ((ptr = strrchr(rcspath, '/')) != NULL)
172: *(++ptr) = '\0';
173: else
174: rcspath[0] = '\0';
175:
176: /* Append RCS/ to `rcspath' if it exists. */
177: if (strlcpy(rcsdir, rcspath, sizeof(rcsdir)) >= sizeof(rcsdir) ||
178: strlcat(rcsdir, RCSDIR, sizeof(rcsdir)) >= sizeof(rcsdir))
1.6 joris 179: errx(1, "rcs_choosefile: truncation");
180:
1.19 otto 181: if (stat(rcsdir, &sb) == 0 && S_ISDIR(sb.st_mode))
1.6 joris 182: if (strlcpy(rcspath, rcsdir, sizeof(rcspath))
183: >= sizeof(rcspath) ||
1.1 xsa 184: strlcat(rcspath, "/", sizeof(rcspath)) >= sizeof(rcspath))
1.6 joris 185: errx(1, "rcs_choosefile: truncation");
1.1 xsa 186:
187: /* Name of file without path. */
188: if ((ptr = strrchr(filename, '/')) == NULL) {
189: if (strlcpy(name, filename, sizeof(name)) >= sizeof(name))
1.6 joris 190: errx(1, "rcs_choosefile: truncation");
1.1 xsa 191: } else {
192: /* Skip `/'. */
193: if (strlcpy(name, ptr + 1, sizeof(name)) >= sizeof(name))
1.6 joris 194: errx(1, "rcs_choosefile: truncation");
1.1 xsa 195: }
196:
197: /* Name of RCS file without an extension. */
198: if (strlcat(rcspath, name, sizeof(rcspath)) >= sizeof(rcspath))
1.6 joris 199: errx(1, "rcs_choosefile: truncation");
1.1 xsa 200:
201: /*
202: * If only the empty suffix was given, use existing rcspath.
203: * This ensures that there is at least one suffix for strsep().
204: */
205: if (strcmp(rcs_suffixes, "") == 0) {
1.8 ray 206: if (strlcpy(out, rcspath, len) >= len)
207: errx(1, "rcs_choosefile: truncation");
1.22 niallo 208: fd = open(rcspath, O_RDONLY);
1.6 joris 209: return (fd);
1.1 xsa 210: }
211:
212: /*
213: * Cycle through slash-separated `rcs_suffixes', appending each
214: * extension to `rcspath' and testing if the file exists. If it
215: * does, return that string. Otherwise return path with first
216: * extension.
217: */
218: suffixes = xstrdup(rcs_suffixes);
1.10 ray 219: for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) {
1.1 xsa 220: char fpath[MAXPATHLEN];
221:
222: if ((p = strrchr(rcspath, ',')) != NULL) {
223: if (!strcmp(p, ext)) {
1.6 joris 224: if ((fd = open(rcspath, O_RDONLY)) == -1)
225: continue;
226:
227: if (fstat(fd, &sb) == -1)
228: err(1, "%s", rcspath);
229:
230: if (strlcpy(out, rcspath, len) >= len)
231: errx(1, "rcs_choosefile; truncation");
232:
233: return (fd);
1.1 xsa 234: }
235:
236: continue;
237: }
238:
239: /* Construct RCS file path. */
240: if (strlcpy(fpath, rcspath, sizeof(fpath)) >= sizeof(fpath) ||
241: strlcat(fpath, ext, sizeof(fpath)) >= sizeof(fpath))
1.6 joris 242: errx(1, "rcs_choosefile: truncation");
1.1 xsa 243:
244: /* Don't use `filename' as RCS file. */
245: if (strcmp(fpath, filename) == 0)
246: continue;
247:
1.6 joris 248: if ((fd = open(fpath, O_RDONLY)) == -1)
249: continue;
250:
251: if (fstat(fd, &sb) == -1)
252: err(1, "%s", fpath);
253:
254: if (strlcpy(out, fpath, len) >= len)
255: errx(1, "rcs_choosefile: truncation");
256:
257: return (fd);
1.1 xsa 258: }
259:
260: /*
261: * `suffixes' should now be NUL separated, so the first
262: * extension can be read just by reading `suffixes'.
263: */
1.11 ray 264: if (strlcat(rcspath, suffixes, sizeof(rcspath)) >= sizeof(rcspath))
1.6 joris 265: errx(1, "rcs_choosefile: truncation");
1.1 xsa 266:
267: xfree(suffixes);
1.6 joris 268:
1.8 ray 269: if (strlcpy(out, rcspath, len) >= len)
270: errx(1, "rcs_choosefile: truncation");
1.22 niallo 271:
272: fd = open(rcspath, O_RDONLY);
1.1 xsa 273:
1.6 joris 274: return (fd);
1.1 xsa 275: }
276:
277: /*
278: * Allocate an RCSNUM and store in <rev>.
279: */
280: void
281: rcs_set_rev(const char *str, RCSNUM **rev)
282: {
283: if (str == NULL || (*rev = rcsnum_parse(str)) == NULL)
1.4 xsa 284: errx(1, "bad revision number `%s'", str);
1.1 xsa 285: }
286:
287: /*
288: * Set <str> to <new_str>. Print warning if <str> is redefined.
289: */
290: void
291: rcs_setrevstr(char **str, char *new_str)
292: {
293: if (new_str == NULL)
294: return;
295: if (*str != NULL)
296: warnx("redefinition of revision number");
297: *str = new_str;
298: }
299:
300: /*
301: * Set <str1> or <str2> to <new_str>, depending on which is not set.
302: * If both are set, error out.
303: */
304: void
305: rcs_setrevstr2(char **str1, char **str2, char *new_str)
306: {
307: if (new_str == NULL)
308: return;
309: if (*str1 == NULL)
310: *str1 = new_str;
311: else if (*str2 == NULL)
312: *str2 = new_str;
313: else
1.3 xsa 314: errx(1, "too many revision numbers");
1.1 xsa 315: }
316:
317: /*
318: * Get revision from file. The revision can be specified as a symbol or
319: * a revision number.
320: */
321: RCSNUM *
322: rcs_getrevnum(const char *rev_str, RCSFILE *file)
323: {
324: RCSNUM *rev;
325:
326: /* Search for symbol. */
327: rev = rcs_sym_getrev(file, rev_str);
328:
329: /* Search for revision number. */
330: if (rev == NULL)
331: rev = rcsnum_parse(rev_str);
332:
333: return (rev);
334: }
335:
336: /*
337: * Prompt for and store user's input in an allocated string.
338: *
339: * Returns the string's pointer.
340: */
341: char *
342: rcs_prompt(const char *prompt)
343: {
344: BUF *bp;
345: size_t len;
346: char *buf;
347:
1.5 joris 348: bp = rcs_buf_alloc(0, BUF_AUTOEXT);
1.1 xsa 349: if (isatty(STDIN_FILENO))
350: (void)fprintf(stderr, "%s", prompt);
351: if (isatty(STDIN_FILENO))
352: (void)fprintf(stderr, ">> ");
1.25 ray 353: clearerr(stdin);
1.1 xsa 354: while ((buf = fgetln(stdin, &len)) != NULL) {
355: /* The last line may not be EOL terminated. */
356: if (buf[0] == '.' && (len == 1 || buf[1] == '\n'))
357: break;
358: else
1.5 joris 359: rcs_buf_append(bp, buf, len);
1.1 xsa 360:
361: if (isatty(STDIN_FILENO))
362: (void)fprintf(stderr, ">> ");
363: }
1.5 joris 364: rcs_buf_putc(bp, '\0');
1.1 xsa 365:
1.5 joris 366: return (rcs_buf_release(bp));
1.1 xsa 367: }
368:
369: u_int
1.14 ray 370: rcs_rev_select(RCSFILE *file, const char *range)
1.1 xsa 371: {
372: int i;
373: u_int nrev;
374: char *ep;
375: char *lstr, *rstr;
376: struct rcs_delta *rdp;
1.5 joris 377: struct rcs_argvector *revargv, *revrange;
1.1 xsa 378: RCSNUM lnum, rnum;
379:
380: nrev = 0;
381: (void)memset(&lnum, 0, sizeof(lnum));
382: (void)memset(&rnum, 0, sizeof(rnum));
383:
384: if (range == NULL) {
385: TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
386: if (rcsnum_cmp(rdp->rd_num, file->rf_head, 0) == 0) {
387: rdp->rd_flags |= RCS_RD_SELECT;
388: return (1);
389: }
390: return (0);
391: }
392:
1.5 joris 393: revargv = rcs_strsplit(range, ",");
1.1 xsa 394: for (i = 0; revargv->argv[i] != NULL; i++) {
1.5 joris 395: revrange = rcs_strsplit(revargv->argv[i], ":");
1.1 xsa 396: if (revrange->argv[0] == NULL)
397: /* should not happen */
1.3 xsa 398: errx(1, "invalid revision range: %s", revargv->argv[i]);
1.1 xsa 399: else if (revrange->argv[1] == NULL)
400: lstr = rstr = revrange->argv[0];
401: else {
402: if (revrange->argv[2] != NULL)
1.3 xsa 403: errx(1, "invalid revision range: %s",
404: revargv->argv[i]);
1.1 xsa 405: lstr = revrange->argv[0];
406: rstr = revrange->argv[1];
407: if (strcmp(lstr, "") == 0)
408: lstr = NULL;
409: if (strcmp(rstr, "") == 0)
410: rstr = NULL;
411: }
412:
413: if (lstr == NULL)
414: lstr = RCS_HEAD_INIT;
415: if (rcsnum_aton(lstr, &ep, &lnum) == 0 || (*ep != '\0'))
1.3 xsa 416: errx(1, "invalid revision: %s", lstr);
1.1 xsa 417:
418: if (rstr != NULL) {
419: if (rcsnum_aton(rstr, &ep, &rnum) == 0 || (*ep != '\0'))
1.3 xsa 420: errx(1, "invalid revision: %s", rstr);
1.1 xsa 421: } else
422: rcsnum_cpy(file->rf_head, &rnum, 0);
423:
1.5 joris 424: rcs_argv_destroy(revrange);
1.1 xsa 425:
426: TAILQ_FOREACH(rdp, &file->rf_delta, rd_list)
427: if (rcsnum_cmp(rdp->rd_num, &lnum, 0) <= 0 &&
428: rcsnum_cmp(rdp->rd_num, &rnum, 0) >= 0 &&
429: !(rdp->rd_flags & RCS_RD_SELECT)) {
430: rdp->rd_flags |= RCS_RD_SELECT;
431: nrev++;
432: }
433: }
1.5 joris 434: rcs_argv_destroy(revargv);
1.1 xsa 435:
436: if (lnum.rn_id != NULL)
437: xfree(lnum.rn_id);
438: if (rnum.rn_id != NULL)
439: xfree(rnum.rn_id);
440:
441: return (nrev);
1.2 ray 442: }
443:
444: /*
445: * Load description from <in> to <file>.
446: * If <in> starts with a `-', <in> is taken as the description.
447: * Otherwise <in> is the name of the file containing the description.
448: * If <in> is NULL, the description is read from stdin.
1.18 ray 449: * Returns 0 on success, -1 on failure, setting errno.
1.2 ray 450: */
1.18 ray 451: int
1.2 ray 452: rcs_set_description(RCSFILE *file, const char *in)
453: {
454: BUF *bp;
455: char *content;
456: const char *prompt =
457: "enter description, terminated with single '.' or end of file:\n"
458: "NOTE: This is NOT the log message!\n";
459:
460: /* Description is in file <in>. */
461: if (in != NULL && *in != '-') {
1.18 ray 462: if ((bp = rcs_buf_load(in, BUF_AUTOEXT)) == NULL)
463: return (-1);
1.5 joris 464: rcs_buf_putc(bp, '\0');
465: content = rcs_buf_release(bp);
1.2 ray 466: /* Description is in <in>. */
467: } else if (in != NULL)
468: /* Skip leading `-'. */
469: content = xstrdup(in + 1);
470: /* Get description from stdin. */
471: else
472: content = rcs_prompt(prompt);
473:
474: rcs_desc_set(file, content);
475: xfree(content);
1.18 ray 476: return (0);
1.7 xsa 477: }
478:
479: /*
480: * Split the contents of a file into a list of lines.
481: */
482: struct rcs_lines *
1.27 xsa 483: rcs_splitlines(u_char *data, size_t len)
1.7 xsa 484: {
1.15 niallo 485: u_char *c, *p;
1.7 xsa 486: struct rcs_lines *lines;
487: struct rcs_line *lp;
1.24 niallo 488: size_t i, tlen;
1.7 xsa 489:
490: lines = xmalloc(sizeof(*lines));
1.24 niallo 491: memset(lines, 0, sizeof(*lines));
1.7 xsa 492: TAILQ_INIT(&(lines->l_lines));
493:
494: lp = xmalloc(sizeof(*lp));
1.24 niallo 495: memset(lp, 0, sizeof(*lp));
1.7 xsa 496: TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
497:
498:
1.24 niallo 499: p = c = data;
500: for (i = 0; i < len; i++) {
501: if (*p == '\n' || (i == len - 1)) {
1.26 niallo 502: tlen = p - c + 1;
1.15 niallo 503: lp = xmalloc(sizeof(*lp));
1.24 niallo 504: lp->l_line = c;
505: lp->l_len = tlen;
1.15 niallo 506: lp->l_lineno = ++(lines->l_nblines);
507: TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
508: c = p + 1;
509: }
510: p++;
1.7 xsa 511: }
512:
513: return (lines);
514: }
515:
516: void
517: rcs_freelines(struct rcs_lines *lines)
518: {
519: struct rcs_line *lp;
520:
521: while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
522: TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
523: xfree(lp);
524: }
525:
526: xfree(lines);
527: }
528:
529: BUF *
1.27 xsa 530: rcs_patchfile(u_char *data, size_t dlen, u_char *patch, size_t plen,
1.7 xsa 531: int (*p)(struct rcs_lines *, struct rcs_lines *))
532: {
533: struct rcs_lines *dlines, *plines;
534: struct rcs_line *lp;
535: BUF *res;
536:
1.24 niallo 537: dlines = rcs_splitlines(data, dlen);
538: plines = rcs_splitlines(patch, plen);
1.7 xsa 539:
540: if (p(dlines, plines) < 0) {
541: rcs_freelines(dlines);
542: rcs_freelines(plines);
543: return (NULL);
544: }
545:
1.24 niallo 546: res = rcs_buf_alloc(1024, BUF_AUTOEXT);
1.7 xsa 547: TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
1.24 niallo 548: if (lp->l_line == NULL)
549: continue;
550: rcs_buf_append(res, lp->l_line, lp->l_len);
1.7 xsa 551: }
552:
553: rcs_freelines(dlines);
554: rcs_freelines(plines);
555: return (res);
556: }
557:
558: /*
559: * rcs_yesno()
560: *
1.23 millert 561: * Read a char from standard input, returns defc if the
562: * user enters an equivalent to defc, else whatever char
563: * was entered. Converts input to lower case.
1.7 xsa 564: */
565: int
1.23 millert 566: rcs_yesno(int defc)
1.7 xsa 567: {
568: int c, ret;
569:
570: fflush(stderr);
571: fflush(stdout);
572:
1.25 ray 573: clearerr(stdin);
1.23 millert 574: if (isalpha(c = getchar()))
575: c = tolower(c);
576: if (c == defc || c == '\n' || (c == EOF && feof(stdin)))
577: ret = defc;
1.7 xsa 578: else
1.23 millert 579: ret = c;
580:
581: while (c != EOF && c != '\n')
582: c = getchar();
1.7 xsa 583:
584: return (ret);
585: }
586:
587: /*
588: * rcs_strsplit()
589: *
590: * Split a string <str> of <sep>-separated values and allocate
591: * an argument vector for the values found.
592: */
593: struct rcs_argvector *
1.14 ray 594: rcs_strsplit(const char *str, const char *sep)
1.7 xsa 595: {
596: struct rcs_argvector *av;
597: size_t i = 0;
598: char **nargv;
599: char *cp, *p;
600:
601: cp = xstrdup(str);
602: av = xmalloc(sizeof(*av));
603: av->str = cp;
604: av->argv = xcalloc(i + 1, sizeof(*(av->argv)));
605:
606: while ((p = strsep(&cp, sep)) != NULL) {
607: av->argv[i++] = p;
608: nargv = xrealloc(av->argv,
609: i + 1, sizeof(*(av->argv)));
610: av->argv = nargv;
611: }
612: av->argv[i] = NULL;
613:
614: return (av);
615: }
616:
617: /*
618: * rcs_argv_destroy()
619: *
620: * Free an argument vector previously allocated by rcs_strsplit().
621: */
622: void
623: rcs_argv_destroy(struct rcs_argvector *av)
624: {
625: xfree(av->str);
626: xfree(av->argv);
627: xfree(av);
1.28 otto 628: }
629:
630: /*
631: * Strip suffix from filename.
632: */
633: void
634: rcs_strip_suffix(char *filename)
635: {
636: char *p, *suffixes, *next, *ext;
637:
638: if ((p = strrchr(filename, ',')) != NULL) {
639: suffixes = xstrdup(rcs_suffixes);
640: for (next = suffixes; (ext = strsep(&next, "/")) != NULL;) {
641: if (!strcmp(p, ext)) {
642: *p = '\0';
643: break;
644: }
645: }
646: xfree(suffixes);
647: }
1.1 xsa 648: }