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