xref: /openbsd-src/usr.bin/cvs/cvs.c (revision 11efff7f3ac2b3cfeff0c0cddc14294d9b3aca4f)
1 /*	$OpenBSD: cvs.c,v 1.29 2004/12/22 00:38:25 david Exp $	*/
2 /*
3  * Copyright (c) 2004 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/types.h>
28 #include <sys/wait.h>
29 
30 #include <err.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <signal.h>
38 #include <string.h>
39 #include <sysexits.h>
40 
41 #include "cvs.h"
42 #include "log.h"
43 #include "file.h"
44 
45 
46 extern char *__progname;
47 
48 
49 /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
50 int verbosity = 2;
51 
52 /* compression level used with zlib, 0 meaning no compression taking place */
53 int   cvs_compress = 0;
54 int   cvs_readrc = 1;		/* read .cvsrc on startup */
55 int   cvs_trace = 0;
56 int   cvs_nolog = 0;
57 int   cvs_readonly = 0;
58 int   cvs_nocase = 0;   /* set to 1 to disable filename case sensitivity */
59 
60 char *cvs_defargs;		/* default global arguments from .cvsrc */
61 char *cvs_command;		/* name of the command we are running */
62 int   cvs_cmdop;
63 char *cvs_rootstr;
64 char *cvs_rsh = CVS_RSH_DEFAULT;
65 char *cvs_editor = CVS_EDITOR_DEFAULT;
66 
67 char *cvs_msg = NULL;
68 
69 /* hierarchy of all the files affected by the command */
70 CVSFILE *cvs_files;
71 
72 
73 static TAILQ_HEAD(, cvs_var) cvs_variables;
74 
75 /*
76  * Command dispatch table
77  * ----------------------
78  *
79  * The synopsis field should only contain the list of arguments that the
80  * command supports, without the actual command's name.
81  *
82  * Command handlers are expected to return 0 if no error occurred, or one of
83  * the values known in sysexits.h in case of an error.  In case the error
84  * returned is EX_USAGE, the command's usage string is printed to standard
85  * error before returning.
86  */
87 static struct cvs_cmd {
88 	int     cmd_op;
89 	char    cmd_name[CVS_CMD_MAXNAMELEN];
90 	char    cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN];
91 	int   (*cmd_hdlr)(int, char **);
92 	char   *cmd_synopsis;
93 	char   *cmd_opts;
94 	char    cmd_descr[CVS_CMD_MAXDESCRLEN];
95 	char   *cmd_defargs;
96 } cvs_cdt[] = {
97 	{
98 		CVS_OP_ADD, "add",      { "ad",  "new" }, cvs_add,
99 		"[-m msg] file ...",
100 		"",
101 		"Add a new file/directory to the repository",
102 		NULL,
103 	},
104 	{
105 		-1, "admin",    { "adm", "rcs" }, NULL,
106 		"",
107 		"",
108 		"Administration front end for rcs",
109 		NULL,
110 	},
111 	{
112 		CVS_OP_ANNOTATE, "annotate", { "ann"        }, cvs_annotate,
113 		"[-FflR] [-D date | -r rev] file ...",
114 		"",
115 		"Show last revision where each line was modified",
116 		NULL,
117 	},
118 	{
119 		CVS_OP_CHECKOUT, "checkout", { "co",  "get" }, cvs_checkout,
120 		"",
121 		"",
122 		"Checkout sources for editing",
123 		NULL,
124 	},
125 	{
126 		CVS_OP_COMMIT, "commit",   { "ci",  "com" }, cvs_commit,
127 		"[-flR] [-F logfile | -m msg] [-r rev] ...",
128 		"F:flm:Rr:",
129 		"Check files into the repository",
130 		NULL,
131 	},
132 	{
133 		CVS_OP_DIFF, "diff",     { "di",  "dif" }, cvs_diff,
134 		"[-cilu] [-D date] [-r rev] ...",
135 		"cD:ilur:",
136 		"Show differences between revisions",
137 		NULL,
138 	},
139 	{
140 		-1, "edit",     {              }, NULL,
141 		"",
142 		"",
143 		"Get ready to edit a watched file",
144 		NULL,
145 	},
146 	{
147 		-1, "editors",  {              }, NULL,
148 		"",
149 		"",
150 		"See who is editing a watched file",
151 		NULL,
152 	},
153 	{
154 		-1, "export",   { "ex",  "exp" }, NULL,
155 		"",
156 		"",
157 		"Export sources from CVS, similar to checkout",
158 		NULL,
159 	},
160 	{
161 		CVS_OP_HISTORY, "history",  { "hi",  "his" }, cvs_history,
162 		"",
163 		"",
164 		"Show repository access history",
165 		NULL,
166 	},
167 	{
168 		CVS_OP_IMPORT, "import",   { "im",  "imp" }, NULL,
169 		"[-d] [-b branch] [-I ign] [-k subst] [-m msg] "
170 		"repository vendor-tag release-tags ...",
171 		"b:dI:k:m:",
172 		"Import sources into CVS, using vendor branches",
173 		NULL,
174 	},
175 	{
176 		CVS_OP_INIT, "init",     {              }, cvs_init,
177 		"",
178 		"",
179 		"Create a CVS repository if it doesn't exist",
180 		NULL,
181 	},
182 #if defined(HAVE_KERBEROS)
183 	{
184 		"kserver",  {}, NULL
185 		"",
186 		"",
187 		"Start a Kerberos authentication CVS server",
188 		NULL,
189 	},
190 #endif
191 	{
192 		CVS_OP_LOG, "log",      { "lo"         }, cvs_getlog,
193 		"",
194 		"",
195 		"Print out history information for files",
196 		NULL,
197 	},
198 	{
199 		-1, "login",    {}, NULL,
200 		"",
201 		"",
202 		"Prompt for password for authenticating server",
203 		NULL,
204 	},
205 	{
206 		-1, "logout",   {}, NULL,
207 		"",
208 		"",
209 		"Removes entry in .cvspass for remote repository",
210 		NULL,
211 	},
212 	{
213 		-1, "rdiff",    {}, NULL,
214 		"",
215 		"",
216 		"Create 'patch' format diffs between releases",
217 		NULL,
218 	},
219 	{
220 		-1, "release",  {}, NULL,
221 		"",
222 		"",
223 		"Indicate that a Module is no longer in use",
224 		NULL,
225 	},
226 	{
227 		CVS_OP_REMOVE, "remove",   { "rm", "delete" }, cvs_remove,
228 		"[-flR] file ...",
229 		"",
230 		"Remove an entry from the repository",
231 		NULL,
232 	},
233 	{
234 		-1, "rlog",     {}, NULL,
235 		"",
236 		"",
237 		"Print out history information for a module",
238 		NULL,
239 	},
240 	{
241 		-1, "rtag",     {}, NULL,
242 		"",
243 		"",
244 		"Add a symbolic tag to a module",
245 		NULL,
246 	},
247 	{
248 		CVS_OP_SERVER, "server",   {}, cvs_server,
249 		"",
250 		"",
251 		"Server mode",
252 		NULL,
253 	},
254 	{
255 		CVS_OP_STATUS, "status",   { "st", "stat" }, cvs_status,
256 		"",
257 		"",
258 		"Display status information on checked out files",
259 		NULL,
260 	},
261 	{
262 		CVS_OP_TAG, "tag",      { "ta", "freeze" }, cvs_tag,
263 		"",
264 		"",
265 		"Add a symbolic tag to checked out version of files",
266 		NULL,
267 	},
268 	{
269 		-1, "unedit",   {}, NULL,
270 		"",
271 		"",
272 		"Undo an edit command",
273 		NULL,
274 	},
275 	{
276 		CVS_OP_UPDATE, "update",   { "up", "upd" }, cvs_update,
277 		"",
278 		"",
279 		"Bring work tree in sync with repository",
280 		NULL,
281 	},
282 	{
283 		CVS_OP_VERSION, "version",  { "ve", "ver" }, cvs_version,
284 		"", "",
285 		"Show current CVS version(s)",
286 		NULL,
287 	},
288 	{
289 		-1, "watch",    {}, NULL,
290 		"",
291 		"",
292 		"Set watches",
293 		NULL,
294 	},
295 	{
296 		-1, "watchers", {}, NULL,
297 		"",
298 		"",
299 		"See who is watching a file",
300 		NULL,
301 	},
302 };
303 
304 #define CVS_NBCMD  (sizeof(cvs_cdt)/sizeof(cvs_cdt[0]))
305 
306 
307 
308 void             usage        (void);
309 void             sigchld_hdlr (int);
310 void             cvs_read_rcfile   (void);
311 struct cvs_cmd*  cvs_findcmd  (const char *);
312 int              cvs_getopt   (int, char **);
313 
314 
315 /*
316  * usage()
317  *
318  * Display usage information.
319  */
320 void
321 usage(void)
322 {
323 	fprintf(stderr,
324 	    "Usage: %s [-flQqtv] [-d root] [-e editor] [-s var=val] [-z level] "
325 	    "command [...]\n", __progname);
326 }
327 
328 
329 int
330 main(int argc, char **argv)
331 {
332 	char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
333 	int i, ret, cmd_argc;
334 	struct cvs_cmd *cmdp;
335 
336 	TAILQ_INIT(&cvs_variables);
337 
338 	if (cvs_log_init(LD_STD, 0) < 0)
339 		err(1, "failed to initialize logging");
340 
341 	/* by default, be very verbose */
342 	(void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
343 
344 #ifdef DEBUG
345 	(void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
346 #endif
347 
348 	/* check environment so command-line options override it */
349 	if ((envstr = getenv("CVS_RSH")) != NULL)
350 		cvs_rsh = envstr;
351 
352 	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
353 	    ((envstr = getenv("VISUAL")) != NULL) ||
354 	    ((envstr = getenv("EDITOR")) != NULL))
355 		cvs_editor = envstr;
356 
357 	ret = cvs_getopt(argc, argv);
358 
359 	argc -= ret;
360 	argv += ret;
361 	if (argc == 0) {
362 		usage();
363 		exit(EX_USAGE);
364 	}
365 	cvs_command = argv[0];
366 
367 	if (cvs_readrc) {
368 		cvs_read_rcfile();
369 
370 		if (cvs_defargs != NULL) {
371 			targv = cvs_makeargv(cvs_defargs, &i);
372 			if (targv == NULL) {
373 				cvs_log(LP_ERR,
374 				    "failed to load default arguments to %s",
375 				    __progname);
376 				exit(EX_OSERR);
377 			}
378 
379 			cvs_getopt(i, targv);
380 			cvs_freeargv(targv, i);
381 			free(targv);
382 		}
383 	}
384 
385 	/* setup signal handlers */
386 	signal(SIGPIPE, SIG_IGN);
387 
388 	cvs_file_init();
389 
390 	ret = -1;
391 
392 	cmdp = cvs_findcmd(cvs_command);
393 	if (cmdp == NULL) {
394 		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
395 		fprintf(stderr, "CVS commands are:\n");
396 		for (i = 0; i < (int)CVS_NBCMD; i++)
397 			fprintf(stderr, "\t%-16s%s\n",
398 			    cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr);
399 		exit(EX_USAGE);
400 	}
401 
402 	if (cmdp->cmd_hdlr == NULL) {
403 		cvs_log(LP_ERR, "command `%s' not implemented", cvs_command);
404 		exit(1);
405 	}
406 
407 	cvs_cmdop = cmdp->cmd_op;
408 
409 	cmd_argc = 0;
410 	memset(cmd_argv, 0, sizeof(cmd_argv));
411 
412 	cmd_argv[cmd_argc++] = argv[0];
413 	if (cmdp->cmd_defargs != NULL) {
414 		/* transform into a new argument vector */
415 		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
416 		    CVS_CMD_MAXARG - 1);
417 		if (ret < 0) {
418 			cvs_log(LP_ERRNO, "failed to generate argument vector "
419 			    "from default arguments");
420 			exit(EX_DATAERR);
421 		}
422 		cmd_argc += ret;
423 	}
424 	for (ret = 1; ret < argc; ret++)
425 		cmd_argv[cmd_argc++] = argv[ret];
426 
427 	ret = (*cmdp->cmd_hdlr)(cmd_argc, cmd_argv);
428 	if (ret == EX_USAGE) {
429 		fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
430 		    cmdp->cmd_synopsis);
431 	}
432 
433 	if (cvs_files != NULL)
434 		cvs_file_free(cvs_files);
435 
436 	return (ret);
437 }
438 
439 
440 int
441 cvs_getopt(int argc, char **argv)
442 {
443 	int ret;
444 	char *ep;
445 
446 	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) {
447 		switch (ret) {
448 		case 'b':
449 			/*
450 			 * We do not care about the bin directory for RCS files
451 			 * as this program has no dependencies on RCS programs,
452 			 * so it is only here for backwards compatibility.
453 			 */
454 			cvs_log(LP_NOTICE, "the -b argument is obsolete");
455 			break;
456 		case 'd':
457 			cvs_rootstr = optarg;
458 			break;
459 		case 'e':
460 			cvs_editor = optarg;
461 			break;
462 		case 'f':
463 			cvs_readrc = 0;
464 			break;
465 		case 'l':
466 			cvs_nolog = 1;
467 			break;
468 		case 'n':
469 			break;
470 		case 'Q':
471 			verbosity = 0;
472 			break;
473 		case 'q':
474 			/* don't override -Q */
475 			if (verbosity > 1)
476 				verbosity = 1;
477 			break;
478 		case 'r':
479 			cvs_readonly = 1;
480 			break;
481 		case 's':
482 			ep = strchr(optarg, '=');
483 			if (ep == NULL) {
484 				cvs_log(LP_ERR, "no = in variable assignment");
485 				exit(EX_USAGE);
486 			}
487 			*(ep++) = '\0';
488 			if (cvs_var_set(optarg, ep) < 0)
489 				exit(EX_USAGE);
490 			break;
491 		case 't':
492 			(void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
493 			cvs_trace = 1;
494 			break;
495 		case 'v':
496 			printf("%s\n", CVS_VERSION);
497 			exit(0);
498 			/* NOTREACHED */
499 			break;
500 		case 'x':
501 			/*
502 			 * Kerberos encryption support, kept for compatibility
503 			 */
504 			break;
505 		case 'z':
506 			cvs_compress = (int)strtol(optarg, &ep, 10);
507 			if (*ep != '\0')
508 				errx(1, "error parsing compression level");
509 			if (cvs_compress < 0 || cvs_compress > 9)
510 				errx(1, "gzip compression level must be "
511 				    "between 0 and 9");
512 			break;
513 		default:
514 			usage();
515 			exit(EX_USAGE);
516 		}
517 	}
518 
519 	ret = optind;
520 	optind = 1;
521 	optreset = 1;	/* for next call */
522 
523 	return (ret);
524 }
525 
526 
527 /*
528  * cvs_findcmd()
529  *
530  * Find the entry in the command dispatch table whose name or one of its
531  * aliases matches <cmd>.
532  * Returns a pointer to the command entry on success, NULL on failure.
533  */
534 struct cvs_cmd*
535 cvs_findcmd(const char *cmd)
536 {
537 	u_int i, j;
538 	struct cvs_cmd *cmdp;
539 
540 	cmdp = NULL;
541 
542 	for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
543 		if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
544 			cmdp = &cvs_cdt[i];
545 		else {
546 			for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
547 				if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
548 					cmdp = &cvs_cdt[i];
549 					break;
550 				}
551 			}
552 		}
553 	}
554 
555 	return (cmdp);
556 }
557 
558 
559 /*
560  * cvs_read_rcfile()
561  *
562  * Read the CVS `.cvsrc' file in the user's home directory.  If the file
563  * exists, it should contain a list of arguments that should always be given
564  * implicitly to the specified commands.
565  */
566 void
567 cvs_read_rcfile(void)
568 {
569 	char rcpath[MAXPATHLEN], linebuf[128], *lp;
570 	int linenum = 0;
571 	size_t len;
572 	struct cvs_cmd *cmdp;
573 	struct passwd *pw;
574 	FILE *fp;
575 
576 	pw = getpwuid(getuid());
577 	if (pw == NULL) {
578 		cvs_log(LP_NOTICE, "failed to get user's password entry");
579 		return;
580 	}
581 
582 	snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
583 
584 	fp = fopen(rcpath, "r");
585 	if (fp == NULL) {
586 		if (errno != ENOENT)
587 			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
588 			    strerror(errno));
589 		return;
590 	}
591 
592 	while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
593 		linenum++;
594 		if ((len = strlen(linebuf)) == 0)
595 			continue;
596 		if (linebuf[len - 1] != '\n') {
597 			cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
598 				linenum);
599 			break;
600 		}
601 		linebuf[--len] = '\0';
602 
603 		lp = strchr(linebuf, ' ');
604 		if (lp == NULL)
605 			continue;	/* ignore lines with no arguments */
606 		*lp = '\0';
607 		if (strcmp(linebuf, "cvs") == 0) {
608 			/*
609 			 * Global default options.  In the case of cvs only,
610 			 * we keep the 'cvs' string as first argument because
611 			 * getopt() does not like starting at index 0 for
612 			 * argument processing.
613 			 */
614 			*lp = ' ';
615 			cvs_defargs = strdup(linebuf);
616 			if (cvs_defargs == NULL)
617 				cvs_log(LP_ERRNO,
618 				    "failed to copy global arguments");
619 		} else {
620 			lp++;
621 			cmdp = cvs_findcmd(linebuf);
622 			if (cmdp == NULL) {
623 				cvs_log(LP_NOTICE,
624 				    "unknown command `%s' in `%s:%d'",
625 				    linebuf, rcpath, linenum);
626 				continue;
627 			}
628 
629 			cmdp->cmd_defargs = strdup(lp);
630 			if (cmdp->cmd_defargs == NULL)
631 				cvs_log(LP_ERRNO,
632 				    "failed to copy default arguments for %s",
633 				    cmdp->cmd_name);
634 		}
635 	}
636 	if (ferror(fp)) {
637 		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
638 	}
639 
640 	(void)fclose(fp);
641 }
642 
643 
644 /*
645  * cvs_var_set()
646  *
647  * Set the value of the variable <var> to <val>.  If there is no such variable,
648  * a new entry is created, otherwise the old value is overwritten.
649  * Returns 0 on success, or -1 on failure.
650  */
651 int
652 cvs_var_set(const char *var, const char *val)
653 {
654 	char *valcp;
655 	const char *cp;
656 	struct cvs_var *vp;
657 
658 	if ((var == NULL) || (*var == '\0')) {
659 		cvs_log(LP_ERR, "no variable name");
660 		return (-1);
661 	}
662 
663 	/* sanity check on the name */
664 	for (cp = var; *cp != '\0'; cp++)
665 		if (!isalnum(*cp) && (*cp != '_')) {
666 			cvs_log(LP_ERR,
667 			    "variable name `%s' contains invalid characters",
668 			    var);
669 			return (-1);
670 		}
671 
672 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
673 		if (strcmp(vp->cv_name, var) == 0)
674 			break;
675 
676 	valcp = strdup(val);
677 	if (valcp == NULL) {
678 		cvs_log(LP_ERRNO, "failed to allocate variable");
679 		return (-1);
680 	}
681 
682 	if (vp == NULL) {
683 		vp = (struct cvs_var *)malloc(sizeof(*vp));
684 		if (vp == NULL) {
685 			cvs_log(LP_ERRNO, "failed to allocate variable");
686 			free(valcp);
687 			return (-1);
688 		}
689 		memset(vp, 0, sizeof(*vp));
690 
691 		vp->cv_name = strdup(var);
692 		if (vp->cv_name == NULL) {
693 			cvs_log(LP_ERRNO, "failed to allocate variable");
694 			free(valcp);
695 			free(vp);
696 			return (-1);
697 		}
698 
699 		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
700 
701 	} else	/* free the previous value */
702 		free(vp->cv_val);
703 
704 	vp->cv_val = valcp;
705 
706 	return (0);
707 }
708 
709 
710 /*
711  * cvs_var_set()
712  *
713  * Remove any entry for the variable <var>.
714  * Returns 0 on success, or -1 on failure.
715  */
716 int
717 cvs_var_unset(const char *var)
718 {
719 	struct cvs_var *vp;
720 
721 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
722 		if (strcmp(vp->cv_name, var) == 0) {
723 			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
724 			free(vp->cv_name);
725 			free(vp->cv_val);
726 			free(vp);
727 			return (0);
728 		}
729 
730 	return (-1);
731 
732 }
733 
734 
735 /*
736  * cvs_var_get()
737  *
738  * Get the value associated with the variable <var>.  Returns a pointer to the
739  * value string on success, or NULL if the variable does not exist.
740  */
741 
742 const char*
743 cvs_var_get(const char *var)
744 {
745 	struct cvs_var *vp;
746 
747 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
748 		if (strcmp(vp->cv_name, var) == 0)
749 			return (vp->cv_val);
750 
751 	return (NULL);
752 }
753