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