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