xref: /openbsd-src/usr.bin/rcs/rcsprog.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: rcsprog.c,v 1.136 2007/01/10 18:04:25 joris Exp $	*/
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 
27 #include "includes.h"
28 
29 #include "rcsprog.h"
30 
31 #define RCS_CMD_MAXARG	128
32 #define RCSPROG_OPTSTRING	"A:a:b::c:e::ik:Ll::m:Mn:N:o:qt::TUu::Vx::z::"
33 
34 const char rcs_version[] = "OpenRCS 4.1";
35 
36 int	 rcsflags;
37 int	 rcs_optind;
38 char	*rcs_optarg;
39 char	*rcs_suffixes;
40 char	*rcs_tmpdir = RCS_TMPDIR_DEFAULT;
41 
42 struct rcs_prog {
43 	char	*prog_name;
44 	int	(*prog_hdlr)(int, char **);
45 	void	(*prog_usage)(void);
46 } programs[] = {
47 	{ "rcs",	rcs_main,	rcs_usage	},
48 	{ "ci",		checkin_main,	checkin_usage   },
49 	{ "co",		checkout_main,	checkout_usage  },
50 	{ "rcsclean",	rcsclean_main,	rcsclean_usage	},
51 	{ "rcsdiff",	rcsdiff_main,	rcsdiff_usage	},
52 	{ "rcsmerge",	rcsmerge_main,	rcsmerge_usage	},
53 	{ "rlog",	rlog_main,	rlog_usage	},
54 	{ "ident",	ident_main,	ident_usage	},
55 	{ "merge",	merge_main,	merge_usage	},
56 };
57 
58 struct rcs_wklhead rcs_temp_files;
59 
60 void sighdlr(int);
61 static void  rcs_attach_symbol(RCSFILE *, const char *);
62 
63 /* ARGSUSED */
64 void
65 sighdlr(int sig)
66 {
67 	rcs_worklist_clean(&rcs_temp_files, rcs_worklist_unlink);
68 	_exit(1);
69 }
70 
71 int
72 rcs_init(char *envstr, char **argv, int argvlen)
73 {
74 	u_int i;
75 	int argc, error;
76 	char linebuf[256],  *lp, *cp;
77 
78 	if (strlcpy(linebuf, envstr, sizeof(linebuf)) >= sizeof(linebuf))
79 		errx(1, "rcs_init: string truncation");
80 	(void)memset(argv, 0, argvlen * sizeof(char *));
81 
82 	error = argc = 0;
83 	for (lp = linebuf; lp != NULL;) {
84 		cp = strsep(&lp, " \t\b\f\n\r\t\v");
85 		if (cp == NULL)
86 			break;
87 		else if (*cp == '\0')
88 			continue;
89 
90 		if (argc == argvlen) {
91 			error++;
92 			break;
93 		}
94 
95 		argv[argc] = xstrdup(cp);
96 		argc++;
97 	}
98 
99 	if (error != 0) {
100 		for (i = 0; i < (u_int)argc; i++)
101 			xfree(argv[i]);
102 		argc = -1;
103 	}
104 
105 	return (argc);
106 }
107 
108 int
109 main(int argc, char **argv)
110 {
111 	u_int i;
112 	char *rcsinit, *cmd_argv[RCS_CMD_MAXARG];
113 	int ret, cmd_argc;
114 
115 	ret = -1;
116 	rcs_optind = 1;
117 	SLIST_INIT(&rcs_temp_files);
118 
119 	cmd_argc = 0;
120 	cmd_argv[cmd_argc++] = argv[0];
121 	if ((rcsinit = getenv("RCSINIT")) != NULL) {
122 		ret = rcs_init(rcsinit, cmd_argv + 1,
123 		    RCS_CMD_MAXARG - 1);
124 		if (ret < 0) {
125 			warnx("failed to prepend RCSINIT options");
126 			exit (1);
127 		}
128 
129 		cmd_argc += ret;
130 	}
131 
132 	if ((rcs_tmpdir = getenv("TMPDIR")) == NULL)
133 		rcs_tmpdir = RCS_TMPDIR_DEFAULT;
134 
135 	for (ret = 1; ret < argc; ret++)
136 		cmd_argv[cmd_argc++] = argv[ret];
137 
138 	signal(SIGHUP, sighdlr);
139 	signal(SIGINT, sighdlr);
140 	signal(SIGQUIT, sighdlr);
141 	signal(SIGABRT, sighdlr);
142 	signal(SIGALRM, sighdlr);
143 	signal(SIGTERM, sighdlr);
144 
145 	for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++)
146 		if (strcmp(__progname, programs[i].prog_name) == 0) {
147 			usage = programs[i].prog_usage;
148 			ret = programs[i].prog_hdlr(cmd_argc, cmd_argv);
149 			break;
150 		}
151 
152 	/* clean up temporary files */
153 	rcs_worklist_run(&rcs_temp_files, rcs_worklist_unlink);
154 
155 	exit(ret);
156 }
157 
158 
159 void
160 rcs_usage(void)
161 {
162 	fprintf(stderr,
163 	    "usage: rcs [-IiLqTUV] [-Aoldfile] [-ausers] [-b[rev]]\n"
164 	    "           [-cstring] [-e[users]] [-kmode] [-l[rev]] [-mrev:msg]\n"
165 	    "           [-orev] [-sstate[:rev]] [-tstr] [-u[rev]]\n"
166 	    "           [-xsuffixes] file ...\n");
167 }
168 
169 /*
170  * rcs_main()
171  *
172  * Handler for the `rcs' program.
173  * Returns 0 on success, or >0 on error.
174  */
175 int
176 rcs_main(int argc, char **argv)
177 {
178 	int fd;
179 	int i, j, ch, flags, kflag, lkmode;
180 	const char *nflag, *oldfilename, *orange;
181 	char fpath[MAXPATHLEN];
182 	char *logstr, *logmsg, *descfile;
183 	char *alist, *comment, *elist, *lrev, *urev;
184 	mode_t fmode;
185 	RCSFILE *file;
186 	RCSNUM *logrev;
187 	struct rcs_access *acp;
188 	time_t rcs_mtime = -1;
189 
190 	kflag = RCS_KWEXP_ERR;
191 	lkmode = RCS_LOCK_INVAL;
192 	fmode =  S_IRUSR|S_IRGRP|S_IROTH;
193 	flags = RCS_RDWR|RCS_PARSE_FULLY;
194 	lrev = urev = descfile = NULL;
195 	logstr = alist = comment = elist = NULL;
196 	nflag = oldfilename = orange = NULL;
197 
198 	/* match GNU */
199 	if (1 < argc && argv[1][0] != '-')
200 		warnx("warning: No options were given; "
201 		    "this usage is obsolescent.");
202 
203 	while ((ch = rcs_getopt(argc, argv, RCSPROG_OPTSTRING)) != -1) {
204 		switch (ch) {
205 		case 'A':
206 			oldfilename = rcs_optarg;
207 			rcsflags |= CO_ACLAPPEND;
208 			break;
209 		case 'a':
210 			alist = rcs_optarg;
211 			break;
212 		case 'c':
213 			comment = rcs_optarg;
214 			break;
215 		case 'e':
216 			elist = rcs_optarg;
217 			rcsflags |= RCSPROG_EFLAG;
218 			break;
219 		case 'i':
220 			flags |= RCS_CREATE;
221 			break;
222 		case 'k':
223 			kflag = rcs_kflag_get(rcs_optarg);
224 			if (RCS_KWEXP_INVAL(kflag)) {
225 				warnx("invalid RCS keyword substitution mode");
226 				(usage)();
227 				exit(1);
228 			}
229 			break;
230 		case 'L':
231 			if (lkmode == RCS_LOCK_LOOSE)
232 				warnx("-U overriden by -L");
233 			lkmode = RCS_LOCK_STRICT;
234 			break;
235 		case 'l':
236 			/* XXX - Check with -u flag. */
237 			lrev = rcs_optarg;
238 			rcsflags |= RCSPROG_LFLAG;
239 			break;
240 		case 'm':
241 			if (logstr != NULL)
242 				xfree(logstr);
243 			logstr = xstrdup(rcs_optarg);
244 			break;
245 		case 'M':
246 			/* ignore for the moment */
247 			break;
248 		case 'n':
249 			nflag = rcs_optarg;
250 			break;
251 		case 'N':
252 			nflag = rcs_optarg;
253 			rcsflags |= RCSPROG_NFLAG;
254 			break;
255 		case 'o':
256 			orange = rcs_optarg;
257 			break;
258 		case 'q':
259 			rcsflags |= QUIET;
260 			break;
261 		case 't':
262 			descfile = rcs_optarg;
263 			rcsflags |= DESCRIPTION;
264 			break;
265 		case 'T':
266 			rcsflags |= PRESERVETIME;
267 			break;
268 		case 'U':
269 			if (lkmode == RCS_LOCK_STRICT)
270 				warnx("-L overriden by -U");
271 			lkmode = RCS_LOCK_LOOSE;
272 			break;
273 		case 'u':
274 			/* XXX - Check with -l flag. */
275 			urev = rcs_optarg;
276 			rcsflags |= RCSPROG_UFLAG;
277 			break;
278 		case 'V':
279 			printf("%s\n", rcs_version);
280 			exit(0);
281 		case 'x':
282 			/* Use blank extension if none given. */
283 			rcs_suffixes = rcs_optarg ? rcs_optarg : "";
284 			break;
285 		case 'z':
286 			/*
287 			 * kept for compatibility
288 			 */
289 			break;
290 		default:
291 			(usage)();
292 			exit(1);
293 		}
294 	}
295 
296 	argc -= rcs_optind;
297 	argv += rcs_optind;
298 
299 	if (argc == 0) {
300 		warnx("no input file");
301 		(usage)();
302 		exit(1);
303 	}
304 
305 	for (i = 0; i < argc; i++) {
306 		fd = rcs_choosefile(argv[i], fpath, sizeof(fpath));
307 		if (fd < 0 && !(flags & RCS_CREATE)) {
308 			warn("%s", fpath);
309 			continue;
310 		}
311 
312 		if (!(rcsflags & QUIET))
313 			(void)fprintf(stderr, "RCS file: %s\n", fpath);
314 
315 		if ((file = rcs_open(fpath, fd, flags, fmode)) == NULL) {
316 			close(fd);
317 			continue;
318 		}
319 
320 		if (rcsflags & DESCRIPTION) {
321 			if (rcs_set_description(file, descfile) == -1) {
322 				warn("%s", descfile);
323 				rcs_close(file);
324 				continue;
325 			}
326 		}
327 		else if (flags & RCS_CREATE) {
328 			if (rcs_set_description(file, NULL) == -1) {
329 				warn("stdin");
330 				rcs_close(file);
331 				continue;
332 			}
333 		}
334 
335 		if (rcsflags & PRESERVETIME)
336 			rcs_mtime = rcs_get_mtime(file);
337 
338 		if (nflag != NULL)
339 			rcs_attach_symbol(file, nflag);
340 
341 		if (logstr != NULL) {
342 			if ((logmsg = strchr(logstr, ':')) == NULL) {
343 				warnx("missing log message");
344 				rcs_close(file);
345 				continue;
346 			}
347 
348 			*logmsg++ = '\0';
349 			if ((logrev = rcsnum_parse(logstr)) == NULL) {
350 				warnx("`%s' bad revision number", logstr);
351 				rcs_close(file);
352 				continue;
353 			}
354 
355 			if (rcs_rev_setlog(file, logrev, logmsg) < 0) {
356 				warnx("failed to set logmsg for `%s' to `%s'",
357 				    logstr, logmsg);
358 				rcs_close(file);
359 				rcsnum_free(logrev);
360 				continue;
361 			}
362 
363 			rcsnum_free(logrev);
364 		}
365 
366 		/* entries to add from <oldfile> */
367 		if (rcsflags & CO_ACLAPPEND) {
368 			RCSFILE *oldfile;
369 			int ofd;
370 			char ofpath[MAXPATHLEN];
371 
372 			ofd = rcs_choosefile(oldfilename, ofpath, sizeof(ofpath));
373 			if (ofd < 0) {
374 				if (!(flags & RCS_CREATE))
375 					warn("%s", ofpath);
376 				exit(1);
377 			}
378 			if ((oldfile = rcs_open(ofpath, ofd, RCS_READ)) == NULL)
379 				exit(1);
380 
381 			TAILQ_FOREACH(acp, &(oldfile->rf_access), ra_list)
382 				rcs_access_add(file, acp->ra_name);
383 
384 			rcs_close(oldfile);
385 			(void)close(ofd);
386 		}
387 
388 		/* entries to add to the access list */
389 		if (alist != NULL) {
390 			struct rcs_argvector *aargv;
391 
392 			aargv = rcs_strsplit(alist, ",");
393 			for (j = 0; aargv->argv[j] != NULL; j++)
394 				rcs_access_add(file, aargv->argv[j]);
395 
396 			rcs_argv_destroy(aargv);
397 		}
398 
399 		if (comment != NULL)
400 			rcs_comment_set(file, comment);
401 
402 		if (elist != NULL) {
403 			struct rcs_argvector *eargv;
404 
405 			eargv = rcs_strsplit(elist, ",");
406 			for (j = 0; eargv->argv[j] != NULL; j++)
407 				rcs_access_remove(file, eargv->argv[j]);
408 
409 			rcs_argv_destroy(eargv);
410 		} else if (rcsflags & RCSPROG_EFLAG) {
411 			struct rcs_access *rap;
412 
413 			/* XXX rcs_access_remove(file, NULL); ?? */
414 			while (!TAILQ_EMPTY(&(file->rf_access))) {
415 				rap = TAILQ_FIRST(&(file->rf_access));
416 				TAILQ_REMOVE(&(file->rf_access), rap, ra_list);
417 				xfree(rap->ra_name);
418 				xfree(rap);
419 			}
420 			/* not synced anymore */
421 			file->rf_flags &= ~RCS_SYNCED;
422 		}
423 
424 		rcs_kwexp_set(file, kflag);
425 
426 		if (lkmode != RCS_LOCK_INVAL)
427 			(void)rcs_lock_setmode(file, lkmode);
428 
429 		if (rcsflags & RCSPROG_LFLAG) {
430 			RCSNUM *rev;
431 			const char *username;
432 			char rev_str[16];
433 
434 			if ((username = getlogin()) == NULL)
435 				err(1, "getlogin");
436 			if (lrev == NULL) {
437 				rev = rcsnum_alloc();
438 				rcsnum_cpy(file->rf_head, rev, 0);
439 			} else if ((rev = rcsnum_parse(lrev)) == NULL) {
440 				warnx("unable to unlock file");
441 				rcs_close(file);
442 				continue;
443 			}
444 			rcsnum_tostr(rev, rev_str, sizeof(rev_str));
445 			/* Make sure revision exists. */
446 			if (rcs_findrev(file, rev) == NULL)
447 				errx(1, "%s: cannot lock nonexisting "
448 				    "revision %s", fpath, rev_str);
449 			if (rcs_lock_add(file, username, rev) != -1 &&
450 			    !(rcsflags & QUIET))
451 				(void)fprintf(stderr, "%s locked\n", rev_str);
452 			rcsnum_free(rev);
453 		}
454 
455 		if (rcsflags & RCSPROG_UFLAG) {
456 			RCSNUM *rev;
457 			const char *username;
458 			char rev_str[16];
459 
460 			if ((username = getlogin()) == NULL)
461 				err(1, "getlogin");
462 			if (urev == NULL) {
463 				rev = rcsnum_alloc();
464 				rcsnum_cpy(file->rf_head, rev, 0);
465 			} else if ((rev = rcsnum_parse(urev)) == NULL) {
466 				warnx("unable to unlock file");
467 				rcs_close(file);
468 				continue;
469 			}
470 			rcsnum_tostr(rev, rev_str, sizeof(rev_str));
471 			/* Make sure revision exists. */
472 			if (rcs_findrev(file, rev) == NULL)
473 				errx(1, "%s: cannot unlock nonexisting "
474 				    "revision %s", fpath, rev_str);
475 			if (rcs_lock_remove(file, username, rev) == -1 &&
476 			    !(rcsflags & QUIET))
477 				warnx("%s: warning: No locks are set.", fpath);
478 			else {
479 				if (!(rcsflags & QUIET))
480 					(void)fprintf(stderr,
481 					    "%s unlocked\n", rev_str);
482 			}
483 			rcsnum_free(rev);
484 		}
485 
486 		if (orange != NULL) {
487 			struct rcs_delta *rdp, *nrdp;
488 			char b[16];
489 
490 			rcs_rev_select(file, orange);
491 			for (rdp = TAILQ_FIRST(&(file->rf_delta));
492 			    rdp != NULL; rdp = nrdp) {
493 				nrdp = TAILQ_NEXT(rdp, rd_list);
494 
495 				/*
496 				 * Delete selected revisions.
497 				 */
498 				if (rdp->rd_flags & RCS_RD_SELECT) {
499 					rcsnum_tostr(rdp->rd_num, b, sizeof(b));
500 					if (!(rcsflags & QUIET)) {
501 						(void)fprintf(stderr, "deleting"
502 						    " revision %s\n", b);
503 					}
504 					(void)rcs_rev_remove(file, rdp->rd_num);
505 				}
506 			}
507 		}
508 
509 		rcs_write(file);
510 
511 		if (rcsflags & PRESERVETIME)
512 			rcs_set_mtime(file, rcs_mtime);
513 
514 		rcs_close(file);
515 
516 		if (!(rcsflags & QUIET))
517 			(void)fprintf(stderr, "done\n");
518 	}
519 
520 	return (0);
521 }
522 
523 static void
524 rcs_attach_symbol(RCSFILE *file, const char *symname)
525 {
526 	char *rnum;
527 	RCSNUM *rev;
528 	char rbuf[16];
529 	int rm;
530 
531 	rm = 0;
532 	rev = NULL;
533 	if ((rnum = strrchr(symname, ':')) != NULL) {
534 		if (rnum[1] == '\0')
535 			rev = file->rf_head;
536 		*(rnum++) = '\0';
537 	} else {
538 		rm = 1;
539 	}
540 
541 	if (rev == NULL && rm != 1) {
542 		if ((rev = rcsnum_parse(rnum)) == NULL)
543 			errx(1, "bad revision %s", rnum);
544 	}
545 
546 	if (rcsflags & RCSPROG_NFLAG)
547 		rm = 1;
548 
549 	if (rm == 1) {
550 		if (rcs_sym_remove(file, symname) < 0) {
551 			if (rcs_errno == RCS_ERR_NOENT &&
552 			    !(rcsflags & RCSPROG_NFLAG))
553 				warnx("cannot delete nonexisting symbol %s",
554 				    symname);
555 		} else {
556 			if (rcsflags & RCSPROG_NFLAG)
557 				rm = 0;
558 		}
559 	}
560 
561 	if (rm == 0) {
562 		if (rcs_sym_add(file, symname, rev) < 0 &&
563 		    rcs_errno == RCS_ERR_DUPENT) {
564 			rcsnum_tostr(rcs_sym_getrev(file, symname),
565 			    rbuf, sizeof(rbuf));
566 			errx(1, "symbolic name %s already bound to %s",
567 			    symname, rbuf);
568 		}
569 	}
570 }
571