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