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