xref: /openbsd-src/usr.bin/rcs/rcsprog.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: rcsprog.c,v 1.147 2009/02/15 12:58:01 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 <sys/stat.h>
28 
29 #include <err.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "rcsprog.h"
36 
37 #define RCSPROG_OPTSTRING	"A:a:b::c:e::ik:Ll::m:Mn:N:o:qt::TUu::Vx::z::"
38 
39 const char rcs_version[] = "OpenRCS 4.5";
40 
41 int	 rcsflags;
42 int	 rcs_optind;
43 char	*rcs_optarg;
44 char	*rcs_suffixes;
45 char	*rcs_tmpdir = RCS_TMPDIR_DEFAULT;
46 
47 struct rcs_prog {
48 	char	*prog_name;
49 	int	(*prog_hdlr)(int, char **);
50 	void	(*prog_usage)(void);
51 } programs[] = {
52 	{ "rcs",	rcs_main,	rcs_usage	},
53 	{ "ci",		checkin_main,	checkin_usage   },
54 	{ "co",		checkout_main,	checkout_usage  },
55 	{ "rcsclean",	rcsclean_main,	rcsclean_usage	},
56 	{ "rcsdiff",	rcsdiff_main,	rcsdiff_usage	},
57 	{ "rcsmerge",	rcsmerge_main,	rcsmerge_usage	},
58 	{ "rlog",	rlog_main,	rlog_usage	},
59 	{ "ident",	ident_main,	ident_usage	},
60 	{ "merge",	merge_main,	merge_usage	},
61 };
62 
63 struct rcs_wklhead rcs_temp_files;
64 
65 void sighdlr(int);
66 static void  rcs_attach_symbol(RCSFILE *, const char *);
67 
68 /* ARGSUSED */
69 void
70 sighdlr(int sig)
71 {
72 	rcs_worklist_clean(&rcs_temp_files, rcs_worklist_unlink);
73 	_exit(1);
74 }
75 
76 int
77 build_cmd(char ***cmd_argv, char **argv, int argc)
78 {
79 	int cmd_argc, i, cur;
80 	char *cp, *rcsinit, *linebuf, *lp;
81 
82 	if ((rcsinit = getenv("RCSINIT")) == NULL) {
83 		*cmd_argv = argv;
84 		return argc;
85 	}
86 
87 	cur = argc + 2;
88 	cmd_argc = 0;
89 	*cmd_argv = xcalloc(cur, sizeof(char *));
90 	(*cmd_argv)[cmd_argc++] = argv[0];
91 
92 	linebuf = xstrdup(rcsinit);
93 	for (lp = linebuf; lp != NULL;) {
94 		cp = strsep(&lp, " \t\b\f\n\r\t\v");
95 		if (cp == NULL)
96 			break;
97 		if (*cp == '\0')
98 			continue;
99 
100 		if (cmd_argc == cur) {
101 			cur += 8;
102 			*cmd_argv = xrealloc(*cmd_argv, cur,
103 			    sizeof(char *));
104 		}
105 
106 		(*cmd_argv)[cmd_argc++] = cp;
107 	}
108 
109 	if (cmd_argc + argc > cur) {
110 		cur = cmd_argc + argc + 1;
111 		*cmd_argv = xrealloc(*cmd_argv, cur,
112 		    sizeof(char *));
113 	}
114 
115 	for (i = 1; i < argc; i++)
116 		(*cmd_argv)[cmd_argc++] = argv[i];
117 
118 	(*cmd_argv)[cmd_argc] = NULL;
119 
120 	return cmd_argc;
121 }
122 
123 int
124 main(int argc, char **argv)
125 {
126 	u_int i;
127 	char **cmd_argv;
128 	int ret, cmd_argc;
129 
130 	ret = -1;
131 	rcs_optind = 1;
132 	SLIST_INIT(&rcs_temp_files);
133 
134 	cmd_argc = build_cmd(&cmd_argv, argv, argc);
135 
136 	if ((rcs_tmpdir = getenv("TMPDIR")) == NULL)
137 		rcs_tmpdir = RCS_TMPDIR_DEFAULT;
138 
139 	signal(SIGHUP, sighdlr);
140 	signal(SIGINT, sighdlr);
141 	signal(SIGQUIT, sighdlr);
142 	signal(SIGABRT, sighdlr);
143 	signal(SIGALRM, sighdlr);
144 	signal(SIGTERM, sighdlr);
145 
146 	for (i = 0; i < (sizeof(programs)/sizeof(programs[0])); i++)
147 		if (strcmp(__progname, programs[i].prog_name) == 0) {
148 			usage = programs[i].prog_usage;
149 			ret = programs[i].prog_hdlr(cmd_argc, cmd_argv);
150 			break;
151 		}
152 
153 	/* clean up temporary files */
154 	rcs_worklist_run(&rcs_temp_files, rcs_worklist_unlink);
155 
156 	exit(ret);
157 }
158 
159 
160 void
161 rcs_usage(void)
162 {
163 	fprintf(stderr,
164 	    "usage: rcs [-IiLqTUV] [-Aoldfile] [-ausers] [-b[rev]]\n"
165 	    "           [-cstring] [-e[users]] [-kmode] [-l[rev]] [-mrev:msg]\n"
166 	    "           [-orev] [-tstr] [-u[rev]] [-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 overridden 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 overridden 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[RCS_REV_BUFSZ];
433 
434 			if (file->rf_head == NULL) {
435 				warnx("%s contains no revisions", fpath);
436 				rcs_close(file);
437 				continue;
438 			}
439 
440 			if ((username = getlogin()) == NULL)
441 				err(1, "getlogin");
442 			if (lrev == NULL) {
443 				rev = rcsnum_alloc();
444 				rcsnum_cpy(file->rf_head, rev, 0);
445 			} else if ((rev = rcsnum_parse(lrev)) == NULL) {
446 				warnx("unable to unlock file");
447 				rcs_close(file);
448 				continue;
449 			}
450 			rcsnum_tostr(rev, rev_str, sizeof(rev_str));
451 			/* Make sure revision exists. */
452 			if (rcs_findrev(file, rev) == NULL)
453 				errx(1, "%s: cannot lock nonexisting "
454 				    "revision %s", fpath, rev_str);
455 			if (rcs_lock_add(file, username, rev) != -1 &&
456 			    !(rcsflags & QUIET))
457 				(void)fprintf(stderr, "%s locked\n", rev_str);
458 			rcsnum_free(rev);
459 		}
460 
461 		if (rcsflags & RCSPROG_UFLAG) {
462 			RCSNUM *rev;
463 			const char *username;
464 			char rev_str[RCS_REV_BUFSZ];
465 
466 			if (file->rf_head == NULL) {
467 				warnx("%s contains no revisions", fpath);
468 				rcs_close(file);
469 				continue;
470 			}
471 
472 			if ((username = getlogin()) == NULL)
473 				err(1, "getlogin");
474 			if (urev == NULL) {
475 				rev = rcsnum_alloc();
476 				rcsnum_cpy(file->rf_head, rev, 0);
477 			} else if ((rev = rcsnum_parse(urev)) == NULL) {
478 				warnx("unable to unlock file");
479 				rcs_close(file);
480 				continue;
481 			}
482 			rcsnum_tostr(rev, rev_str, sizeof(rev_str));
483 			/* Make sure revision exists. */
484 			if (rcs_findrev(file, rev) == NULL)
485 				errx(1, "%s: cannot unlock nonexisting "
486 				    "revision %s", fpath, rev_str);
487 			if (rcs_lock_remove(file, username, rev) == -1 &&
488 			    !(rcsflags & QUIET))
489 				warnx("%s: warning: No locks are set.", fpath);
490 			else {
491 				if (!(rcsflags & QUIET))
492 					(void)fprintf(stderr,
493 					    "%s unlocked\n", rev_str);
494 			}
495 			rcsnum_free(rev);
496 		}
497 
498 		if (orange != NULL) {
499 			struct rcs_delta *rdp, *nrdp;
500 			char b[RCS_REV_BUFSZ];
501 
502 			rcs_rev_select(file, orange);
503 			for (rdp = TAILQ_FIRST(&(file->rf_delta));
504 			    rdp != NULL; rdp = nrdp) {
505 				nrdp = TAILQ_NEXT(rdp, rd_list);
506 
507 				/*
508 				 * Delete selected revisions.
509 				 */
510 				if (rdp->rd_flags & RCS_RD_SELECT) {
511 					rcsnum_tostr(rdp->rd_num, b, sizeof(b));
512 					if (!(rcsflags & QUIET)) {
513 						(void)fprintf(stderr, "deleting"
514 						    " revision %s\n", b);
515 					}
516 					(void)rcs_rev_remove(file, rdp->rd_num);
517 				}
518 			}
519 		}
520 
521 		rcs_write(file);
522 
523 		if (rcsflags & PRESERVETIME)
524 			rcs_set_mtime(file, rcs_mtime);
525 
526 		rcs_close(file);
527 
528 		if (!(rcsflags & QUIET))
529 			(void)fprintf(stderr, "done\n");
530 	}
531 
532 	return (0);
533 }
534 
535 static void
536 rcs_attach_symbol(RCSFILE *file, const char *symname)
537 {
538 	char *rnum;
539 	RCSNUM *rev;
540 	char rbuf[RCS_REV_BUFSZ];
541 	int rm;
542 
543 	rm = 0;
544 	rev = NULL;
545 	if ((rnum = strrchr(symname, ':')) != NULL) {
546 		if (rnum[1] == '\0')
547 			rev = file->rf_head;
548 		*(rnum++) = '\0';
549 	} else {
550 		rm = 1;
551 	}
552 
553 	if (rev == NULL && rm != 1) {
554 		if ((rev = rcsnum_parse(rnum)) == NULL)
555 			errx(1, "bad revision %s", rnum);
556 	}
557 
558 	if (rcsflags & RCSPROG_NFLAG)
559 		rm = 1;
560 
561 	if (rm == 1) {
562 		if (rcs_sym_remove(file, symname) < 0) {
563 			if (rcs_errno == RCS_ERR_NOENT &&
564 			    !(rcsflags & RCSPROG_NFLAG))
565 				warnx("cannot delete nonexisting symbol %s",
566 				    symname);
567 		} else {
568 			if (rcsflags & RCSPROG_NFLAG)
569 				rm = 0;
570 		}
571 	}
572 
573 	if (rm == 0) {
574 		if (rcs_sym_add(file, symname, rev) < 0 &&
575 		    rcs_errno == RCS_ERR_DUPENT) {
576 			rcsnum_tostr(rcs_sym_getrev(file, symname),
577 			    rbuf, sizeof(rbuf));
578 			errx(1, "symbolic name %s already bound to %s",
579 			    symname, rbuf);
580 		}
581 	}
582 }
583