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