Annotation of src/usr.bin/rcs/rcsprog.c, Revision 1.57
1.57 ! xsa 1: /* $OpenBSD: rcsprog.c,v 1.56 2005/12/24 03:48:09 joris Exp $ */
1.1 deraadt 2: /*
3: * Copyright (c) 2005 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:
1.6 joris 27: #include <sys/param.h>
1.1 deraadt 28: #include <sys/wait.h>
1.8 joris 29: #include <sys/stat.h>
1.1 deraadt 30:
1.3 joris 31: #include <ctype.h>
1.1 deraadt 32: #include <err.h>
1.3 joris 33: #include <errno.h>
1.1 deraadt 34: #include <pwd.h>
1.3 joris 35: #include <string.h>
1.1 deraadt 36: #include <stdio.h>
37: #include <stdlib.h>
38: #include <unistd.h>
39:
40: #include "log.h"
41: #include "rcs.h"
1.9 joris 42: #include "rcsprog.h"
1.1 deraadt 43:
1.29 joris 44: #define RCS_CMD_MAXARG 128
1.42 xsa 45: #define RCS_DEFAULT_SUFFIX ",v/"
1.56 joris 46: #define RCSPROG_OPTSTRING "A:a:b::c:e::hik:Lm:Mn:N:qt::TUVx:z:"
47:
48: #define DESC_PROMPT "enter description, terminated with single '.' " \
49: "or end of file:\nNOTE: This is NOT the log message!" \
50: "\n>> "
1.29 joris 51:
1.1 deraadt 52: const char rcs_version[] = "OpenCVS RCS version 3.6";
1.12 joris 53: int verbose = 1;
1.34 joris 54: int pipeout = 0;
1.1 deraadt 55:
1.56 joris 56: #define RCS_NFLAG 1
57: #define RCS_TFLAG 2
58: static int rcsflags = 0;
59:
1.36 xsa 60: int rcs_optind;
1.28 joris 61: char *rcs_optarg;
1.42 xsa 62: char *rcs_suffixes;
1.36 xsa 63: char *rcs_tmpdir = RCS_TMPDIR_DEFAULT;
1.28 joris 64:
1.1 deraadt 65: struct rcs_prog {
1.26 deraadt 66: char *prog_name;
67: int (*prog_hdlr)(int, char **);
68: void (*prog_usage)(void);
1.1 deraadt 69: } programs[] = {
1.2 deraadt 70: { "rcs", rcs_main, rcs_usage },
1.17 joris 71: { "ci", checkin_main, checkin_usage },
1.11 joris 72: { "co", checkout_main, checkout_usage },
1.20 joris 73: { "rcsclean", rcsclean_main, rcsclean_usage },
1.18 joris 74: { "rcsdiff", rcsdiff_main, rcsdiff_usage },
1.33 xsa 75: { "rcsmerge", rcsmerge_main, rcsmerge_usage },
1.21 joris 76: { "rlog", rlog_main, rlog_usage },
1.22 joris 77: { "ident", ident_main, ident_usage },
1.1 deraadt 78: };
1.31 joris 79:
1.56 joris 80: static void rcs_set_description(RCSFILE *, const char *);
81: static void rcs_attach_symbol(RCSFILE *, const char *);
82:
1.31 joris 83: void
84: rcs_set_rev(const char *str, RCSNUM **rev)
85: {
1.32 joris 86: if (str == NULL)
87: return;
88:
1.52 joris 89: if ((*rev != NULL) && (*rev != RCS_HEAD_REV))
1.31 joris 90: cvs_log(LP_WARN, "redefinition of revision number");
91:
1.55 xsa 92: if ((*rev = rcsnum_parse(str)) == NULL)
93: fatal("bad revision number '%s'", str);
1.47 xsa 94: }
95:
96: /*
97: * rcs_get_mtime()
98: *
99: * Get <filename> last modified time.
100: * Returns last modified time on success, or -1 on failure.
101: */
102: time_t
103: rcs_get_mtime(const char *filename)
104: {
105: struct stat st;
106: time_t mtime;
107:
108: if (stat(filename, &st) == -1) {
109: cvs_log(LP_ERRNO, "failed to stat `%s'", filename);
110: return (-1);
111: }
112: mtime = (time_t)st.st_mtimespec.tv_sec;
113:
114: return (mtime);
115: }
116:
117: /*
118: * rcs_set_mtime()
119: *
120: * Set <filename> last modified time to <mtime> if it's not set to -1.
121: * Returns 0 on success, or -1 on failure.
122: */
123: int
124: rcs_set_mtime(const char *filename, time_t mtime)
125: {
126: static struct timeval tv[2];
127:
128: if (mtime == -1)
129: return (0);
130:
131: tv[0].tv_sec = mtime;
132: tv[1].tv_sec = tv[0].tv_sec;
133:
134: if (utimes(filename, tv) == -1) {
135: cvs_log(LP_ERRNO, "error setting utimes");
136: return (-1);
137: }
138:
139: return (0);
1.31 joris 140: }
1.1 deraadt 141:
1.9 joris 142: int
1.29 joris 143: rcs_init(char *envstr, char **argv, int argvlen)
144: {
145: u_int i;
146: int argc, error;
147: char linebuf[256], *lp, *cp;
148:
149: strlcpy(linebuf, envstr, sizeof(linebuf));
150: memset(argv, 0, argvlen * sizeof(char *));
151:
152: error = argc = 0;
153: for (lp = linebuf; lp != NULL;) {
154: cp = strsep(&lp, " \t\b\f\n\r\t\v");;
155: if (cp == NULL)
156: break;
157: else if (*cp == '\0')
158: continue;
159:
160: if (argc == argvlen) {
161: error++;
162: break;
163: }
164:
1.53 joris 165: argv[argc] = xstrdup(cp);
1.29 joris 166: argc++;
167: }
168:
169: if (error != 0) {
170: for (i = 0; i < (u_int)argc; i++)
1.53 joris 171: xfree(argv[i]);
1.29 joris 172: argc = -1;
173: }
174:
175: return (argc);
176: }
177:
178: int
1.28 joris 179: rcs_getopt(int argc, char **argv, const char *optstr)
180: {
181: char *a;
182: const char *c;
183: static int i = 1;
184: int opt, hasargument, ret;
185:
186: hasargument = 0;
187: rcs_optarg = NULL;
188:
189: if (i >= argc)
190: return (-1);
191:
192: a = argv[i++];
193: if (*a++ != '-')
194: return (-1);
195:
196: ret = 0;
197: opt = *a;
198: for (c = optstr; *c != '\0'; c++) {
199: if (*c == opt) {
200: a++;
201: ret = opt;
202:
203: if (*(c + 1) == ':') {
204: if (*(c + 2) == ':') {
205: if (*a != '\0')
206: hasargument = 1;
207: } else {
208: if (*a != '\0') {
209: hasargument = 1;
210: } else {
211: ret = 1;
212: break;
213: }
214: }
215: }
216:
217: if (hasargument == 1)
218: rcs_optarg = a;
219:
220: if (ret == opt)
221: rcs_optind++;
222: break;
223: }
224: }
225:
226: if (ret == 0)
227: cvs_log(LP_ERR, "unknown option -%c", opt);
228: else if (ret == 1)
229: cvs_log(LP_ERR, "missing argument for option -%c", opt);
230:
231: return (ret);
232: }
233:
234: int
1.9 joris 235: rcs_statfile(char *fname, char *out, size_t len)
236: {
1.56 joris 237: size_t len1;
1.42 xsa 238: int l, found, strdir;
239: char defaultsuffix[] = RCS_DEFAULT_SUFFIX;
1.9 joris 240: char filev[MAXPATHLEN], fpath[MAXPATHLEN];
1.42 xsa 241: char *ext, *slash;
1.9 joris 242: struct stat st;
243:
1.42 xsa 244: strdir = found = 0;
245:
246: /* we might have gotten a RCS file as argument */
247: if ((ext = strchr(fname, ',')) != NULL)
248: *ext = '\0';
249:
250: /* we might have gotten the RCS/ dir in the argument string */
251: if (strstr(fname, RCSDIR) != NULL)
252: strdir = 1;
253:
254: if (rcs_suffixes != NULL)
255: ext = rcs_suffixes;
256: else
257: ext = defaultsuffix;
1.43 xsa 258:
1.42 xsa 259: for (;;) {
260: /*
261: * GNU documentation says -x,v/ specifies two suffixes,
262: * namely the ,v one and an empty one (which matches
263: * everything).
264: * The problem is that they don't follow this rule at
265: * all, and their documentation seems flawed.
266: * We try to be compatible, so let's do so.
267: */
268: if (*ext == '\0')
269: break;
270:
271: if ((slash = strchr(ext, '/')) != NULL)
272: *slash = '\0';
1.9 joris 273:
1.42 xsa 274: l = snprintf(filev, sizeof(filev), "%s%s", fname, ext);
1.55 xsa 275: if (l == -1 || l >= (int)sizeof(filev))
276: fatal("rcs_statfile: path truncation");
1.42 xsa 277:
278: if ((strdir == 0) &&
279: (stat(RCSDIR, &st) != -1) && (st.st_mode & S_IFDIR)) {
280: l = snprintf(fpath, sizeof(fpath), "%s/%s",
281: RCSDIR, filev);
1.55 xsa 282: if (l == -1 || l >= (int)sizeof(fpath))
283: fatal("rcs_statfile: path truncation");
1.42 xsa 284: } else {
1.56 joris 285: len1 = strlcpy(fpath, filev, sizeof(fpath));
286: if (len1 >= sizeof(fpath))
1.57 ! xsa 287: fatal("rcs_statfile: path truncation");
1.42 xsa 288: }
289:
1.56 joris 290: if ((stat(fpath, &st) != -1) || (rcsflags & RCS_CREATE)) {
1.42 xsa 291: found++;
292: break;
293: }
294:
295: if (slash == NULL)
296: break;
297:
298: *slash++ = '/';
299: ext = slash;
1.9 joris 300: }
301:
1.42 xsa 302: if (found != 1) {
1.44 niallo 303: if ((strcmp(__progname, "rcsclean") != 0)
304: && (strcmp(__progname, "ci") != 0))
1.20 joris 305: cvs_log(LP_ERRNO, "%s", fpath);
1.9 joris 306: return (-1);
307: }
308:
1.56 joris 309: len1 = strlcpy(out, fpath, len);
310: if (len1 >= len)
1.57 ! xsa 311: fatal("rcs_statfile: path truncation");
1.9 joris 312:
313: return (0);
314: }
1.1 deraadt 315:
316: int
317: main(int argc, char **argv)
318: {
319: u_int i;
1.29 joris 320: char *rcsinit, *cmd_argv[RCS_CMD_MAXARG];
321: int ret, cmd_argc;
1.1 deraadt 322:
323: ret = -1;
1.28 joris 324: rcs_optind = 1;
1.9 joris 325: cvs_log_init(LD_STD, 0);
1.1 deraadt 326:
1.29 joris 327: cmd_argc = 0;
1.30 joris 328: cmd_argv[cmd_argc++] = argv[0];
1.29 joris 329: if ((rcsinit = getenv("RCSINIT")) != NULL) {
330: ret = rcs_init(rcsinit, cmd_argv + 1,
331: RCS_CMD_MAXARG - 1);
332: if (ret < 0) {
333: cvs_log(LP_ERRNO, "failed to prepend RCSINIT options");
334: exit (1);
335: }
336:
337: cmd_argc += ret;
338: }
1.36 xsa 339:
340: if ((rcs_tmpdir = getenv("TMPDIR")) == NULL)
341: rcs_tmpdir = RCS_TMPDIR_DEFAULT;
1.29 joris 342:
343: for (ret = 1; ret < argc; ret++)
344: cmd_argv[cmd_argc++] = argv[ret];
345:
1.1 deraadt 346: for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++)
1.2 deraadt 347: if (strcmp(__progname, programs[i].prog_name) == 0) {
348: usage = programs[i].prog_usage;
1.29 joris 349: ret = programs[i].prog_hdlr(cmd_argc, cmd_argv);
1.2 deraadt 350: break;
351: }
1.1 deraadt 352:
1.23 niallo 353: exit(ret);
1.1 deraadt 354: }
355:
356:
357: void
358: rcs_usage(void)
359: {
360: fprintf(stderr,
1.50 xsa 361: "usage: rcs [-hiLMTUV] [-Aoldfile] [-ausers] [-b[rev]] [-cstring]\n"
1.54 xsa 362: " [-eusers] [-kmode] [-mrev:msg] [-xsuffixes] [-ztz] file ...\n");
1.1 deraadt 363: }
364:
365: /*
366: * rcs_main()
367: *
368: * Handler for the `rcs' program.
369: * Returns 0 on success, or >0 on error.
370: */
371: int
372: rcs_main(int argc, char **argv)
373: {
374: int i, ch, flags, kflag, lkmode;
1.39 xsa 375: char fpath[MAXPATHLEN], ofpath[MAXPATHLEN];
1.56 joris 376: char *logstr, *logmsg, *nflag, *descfile;
1.39 xsa 377: char *alist, *comment, *elist, *unp, *sp;
1.1 deraadt 378: mode_t fmode;
1.39 xsa 379: RCSFILE *file, *oldfile;
1.24 joris 380: RCSNUM *logrev;
1.39 xsa 381: struct rcs_access *acp;
1.48 xsa 382: time_t rcs_mtime = -1;
1.1 deraadt 383:
384: kflag = lkmode = -1;
385: fmode = 0;
386: flags = RCS_RDWR;
1.56 joris 387: descfile = nflag = NULL;
1.39 xsa 388: logstr = alist = comment = elist = NULL;
1.1 deraadt 389:
1.51 xsa 390: while ((ch = rcs_getopt(argc, argv, RCSPROG_OPTSTRING)) != -1) {
1.1 deraadt 391: switch (ch) {
392: case 'A':
1.39 xsa 393: if (rcs_statfile(rcs_optarg, ofpath, sizeof(ofpath)) < 0)
394: exit(1);
1.56 joris 395: rcsflags |= CO_ACLAPPEND;
1.1 deraadt 396: break;
397: case 'a':
1.28 joris 398: alist = rcs_optarg;
1.1 deraadt 399: break;
400: case 'c':
1.28 joris 401: comment = rcs_optarg;
1.1 deraadt 402: break;
403: case 'e':
1.28 joris 404: elist = rcs_optarg;
1.1 deraadt 405: break;
406: case 'h':
1.2 deraadt 407: (usage)();
1.1 deraadt 408: exit(0);
409: case 'i':
410: flags |= RCS_CREATE;
1.56 joris 411: rcsflags |= RCS_CREATE;
1.1 deraadt 412: break;
413: case 'k':
1.28 joris 414: kflag = rcs_kflag_get(rcs_optarg);
1.1 deraadt 415: if (RCS_KWEXP_INVAL(kflag)) {
416: cvs_log(LP_ERR,
417: "invalid keyword substitution mode `%s'",
1.28 joris 418: rcs_optarg);
1.1 deraadt 419: exit(1);
420: }
421: break;
422: case 'L':
423: if (lkmode == RCS_LOCK_LOOSE)
424: cvs_log(LP_WARN, "-U overriden by -L");
425: lkmode = RCS_LOCK_STRICT;
426: break;
1.24 joris 427: case 'm':
1.53 joris 428: logstr = xstrdup(rcs_optarg);
1.24 joris 429: break;
1.1 deraadt 430: case 'M':
431: /* ignore for the moment */
432: break;
1.56 joris 433: case 'n':
434: nflag = xstrdup(rcs_optarg);
435: break;
436: case 'N':
437: nflag = xstrdup(rcs_optarg);
438: rcsflags |= RCS_NFLAG;
439: break;
1.12 joris 440: case 'q':
441: verbose = 0;
1.46 xsa 442: break;
1.56 joris 443: case 't':
444: descfile = rcs_optarg;
445: rcsflags |= RCS_TFLAG;
446: break;
1.46 xsa 447: case 'T':
1.56 joris 448: rcsflags |= PRESERVETIME;
1.12 joris 449: break;
1.1 deraadt 450: case 'U':
451: if (lkmode == RCS_LOCK_STRICT)
452: cvs_log(LP_WARN, "-L overriden by -U");
453: lkmode = RCS_LOCK_LOOSE;
454: break;
455: case 'V':
456: printf("%s\n", rcs_version);
457: exit(0);
1.45 xsa 458: case 'x':
459: rcs_suffixes = rcs_optarg;
1.51 xsa 460: break;
461: case 'z':
462: /*
463: * kept for compatibility
464: */
1.45 xsa 465: break;
1.1 deraadt 466: default:
1.2 deraadt 467: (usage)();
1.1 deraadt 468: exit(1);
469: }
470: }
471:
1.28 joris 472: argc -= rcs_optind;
473: argv += rcs_optind;
1.30 joris 474:
1.1 deraadt 475: if (argc == 0) {
476: cvs_log(LP_ERR, "no input file");
1.5 joris 477: (usage)();
1.1 deraadt 478: exit(1);
479: }
480:
481: for (i = 0; i < argc; i++) {
1.9 joris 482: if (rcs_statfile(argv[i], fpath, sizeof(fpath)) < 0)
1.8 joris 483: continue;
1.6 joris 484:
1.35 niallo 485: if (verbose == 1)
486: printf("RCS file: %s\n", fpath);
1.48 xsa 487:
1.49 niallo 488: if ((file = rcs_open(fpath, flags, fmode)) == NULL)
1.6 joris 489: continue;
1.1 deraadt 490:
1.56 joris 491: if (rcsflags & RCS_CREATE)
492: rcs_set_description(file, NULL);
493:
494: if (rcsflags & RCS_TFLAG)
495: rcs_set_description(file, descfile);
496:
497: if (rcsflags & PRESERVETIME)
1.48 xsa 498: rcs_mtime = rcs_get_mtime(file->rf_path);
499:
1.56 joris 500: if (nflag != NULL)
501: rcs_attach_symbol(file, nflag);
502:
1.24 joris 503: if (logstr != NULL) {
504: if ((logmsg = strchr(logstr, ':')) == NULL) {
505: cvs_log(LP_ERR, "missing log message");
506: rcs_close(file);
507: continue;
508: }
509:
510: *logmsg++ = '\0';
511: if ((logrev = rcsnum_parse(logstr)) == NULL) {
1.48 xsa 512: cvs_log(LP_ERR,
513: "'%s' bad revision number", logstr);
1.24 joris 514: rcs_close(file);
515: continue;
516: }
517:
518: if (rcs_rev_setlog(file, logrev, logmsg) < 0) {
519: cvs_log(LP_ERR,
520: "failed to set logmsg for '%s' to '%s'",
521: logstr, logmsg);
522: rcs_close(file);
1.25 joris 523: rcsnum_free(logrev);
1.24 joris 524: continue;
525: }
526:
527: rcsnum_free(logrev);
1.39 xsa 528: }
529:
530: /* entries to add from <oldfile> */
1.56 joris 531: if (rcsflags & CO_ACLAPPEND) {
1.39 xsa 532: /* XXX */
533: if ((oldfile = rcs_open(ofpath, RCS_READ)) == NULL)
534: exit(1);
535:
536: TAILQ_FOREACH(acp, &(oldfile->rf_access), ra_list)
537: rcs_access_add(file, acp->ra_name);
538:
539: rcs_close(oldfile);
1.24 joris 540: }
541:
1.1 deraadt 542: /* entries to add to the access list */
543: if (alist != NULL) {
544: unp = alist;
545: do {
546: sp = strchr(unp, ',');
547: if (sp != NULL)
548: *(sp++) = '\0';
549:
550: rcs_access_add(file, unp);
551:
552: unp = sp;
553: } while (sp != NULL);
554: }
555:
556: if (comment != NULL)
557: rcs_comment_set(file, comment);
558:
559: if (kflag != -1)
560: rcs_kwexp_set(file, kflag);
561:
562: if (lkmode != -1)
563: rcs_lock_setmode(file, lkmode);
564:
565: rcs_close(file);
1.48 xsa 566:
1.56 joris 567: if (rcsflags & PRESERVETIME)
1.48 xsa 568: rcs_set_mtime(fpath, rcs_mtime);
1.9 joris 569:
1.14 xsa 570: if (verbose == 1)
1.12 joris 571: printf("done\n");
1.1 deraadt 572: }
1.24 joris 573:
574: if (logstr != NULL)
1.53 joris 575: xfree(logstr);
1.1 deraadt 576:
1.56 joris 577: if (nflag != NULL)
578: xfree(nflag);
579:
1.1 deraadt 580: return (0);
1.56 joris 581: }
582:
583: static void
584: rcs_attach_symbol(RCSFILE *file, const char *symname)
585: {
586: char *rnum;
587: RCSNUM *rev;
588: char rbuf[16];
589: int rm;
590:
591: rm = 0;
592: rev = NULL;
593: if ((rnum = strrchr(symname, ':')) != NULL) {
594: if (rnum[1] == '\0')
595: rev = file->rf_head;
596: *(rnum++) = '\0';
597: } else {
598: rm = 1;
599: }
600:
601: if (rev == NULL && rm != 1) {
602: if ((rev = rcsnum_parse(rnum)) == NULL)
603: fatal("bad revision %s", rnum);
604: }
605:
606: if (rcsflags & RCS_NFLAG)
607: rm = 1;
608:
609: if (rm == 1) {
610: if (rcs_sym_remove(file, symname) < 0) {
611: if ((rcs_errno == RCS_ERR_NOENT) &&
612: !(rcsflags & RCS_NFLAG))
613: cvs_log(LP_WARN,
614: "can't delete nonexisting symbol %s", symname);
615: } else {
616: if (rcsflags & RCS_NFLAG)
617: rm = 0;
618: }
619: }
620:
621: if (rm == 0) {
622: if ((rcs_sym_add(file, symname, rev) < 0) &&
623: (rcs_errno == RCS_ERR_DUPENT)) {
624: rcsnum_tostr(rcs_sym_getrev(file, symname),
625: rbuf, sizeof(rbuf));
626: fatal("symbolic name %s already bound to %s",
627: symname, rbuf);
628: }
629: }
630: }
631:
632: static void
633: rcs_set_description(RCSFILE *file, const char *in)
634: {
635: BUF *bp;
636: char *content, buf[128];
637:
638: content = NULL;
639: if (in != NULL) {
640: bp = cvs_buf_load(in, BUF_AUTOEXT);
641: } else {
642: bp = cvs_buf_alloc(64, BUF_AUTOEXT);
643:
644: printf(DESC_PROMPT);
645: for (;;) {
646: fgets(buf, sizeof(buf), stdin);
647: if (feof(stdin) || ferror(stdin) || buf[0] == '.')
648: break;
649: cvs_buf_append(bp, buf, strlen(buf));
650: printf(">> ");
651: }
652: }
653:
654: cvs_buf_putc(bp, '\0');
655: content = cvs_buf_release(bp);
656:
657: rcs_desc_set(file, content);
1.1 deraadt 658: }