cvs.c revision 1.35
1/*	$OpenBSD: cvs.c,v 1.35 2005/01/14 18:02:04 jfb 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
46extern char *__progname;
47
48
49/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
50int verbosity = 2;
51
52/* compression level used with zlib, 0 meaning no compression taking place */
53int   cvs_compress = 0;
54int   cvs_readrc = 1;		/* read .cvsrc on startup */
55int   cvs_trace = 0;
56int   cvs_nolog = 0;
57int   cvs_readonly = 0;
58int   cvs_nocase = 0;   /* set to 1 to disable filename case sensitivity */
59
60char *cvs_defargs;		/* default global arguments from .cvsrc */
61char *cvs_command;		/* name of the command we are running */
62int   cvs_cmdop;
63char *cvs_rootstr;
64char *cvs_rsh = CVS_RSH_DEFAULT;
65char *cvs_editor = CVS_EDITOR_DEFAULT;
66
67char *cvs_msg = NULL;
68
69/* hierarchy of all the files affected by the command */
70CVSFILE *cvs_files;
71
72
73static 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 */
87static 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		"[-flR] [-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		"[-cilNpu] [-D date] [-r rev] ...",
135		"cD:ilNpr:u",
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" }, cvs_import,
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		"[-lRv]",
257		"",
258		"Display status information on checked out files",
259		NULL,
260	},
261	{
262		CVS_OP_TAG, "tag",      { "ta", "freeze" }, cvs_tag,
263		"[-bdl] [-D date | -r rev] tagname",
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
308void             usage        (void);
309void             sigchld_hdlr (int);
310void             cvs_read_rcfile   (void);
311struct cvs_cmd*  cvs_findcmd  (const char *);
312int              cvs_getopt   (int, char **);
313
314
315/*
316 * usage()
317 *
318 * Display usage information.
319 */
320void
321usage(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
329int
330main(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	if (cvs_msg != NULL)
436		free(cvs_msg);
437
438	return (ret);
439}
440
441
442int
443cvs_getopt(int argc, char **argv)
444{
445	int ret;
446	char *ep;
447
448	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) {
449		switch (ret) {
450		case 'b':
451			/*
452			 * We do not care about the bin directory for RCS files
453			 * as this program has no dependencies on RCS programs,
454			 * so it is only here for backwards compatibility.
455			 */
456			cvs_log(LP_NOTICE, "the -b argument is obsolete");
457			break;
458		case 'd':
459			cvs_rootstr = optarg;
460			break;
461		case 'e':
462			cvs_editor = optarg;
463			break;
464		case 'f':
465			cvs_readrc = 0;
466			break;
467		case 'l':
468			cvs_nolog = 1;
469			break;
470		case 'n':
471			break;
472		case 'Q':
473			verbosity = 0;
474			break;
475		case 'q':
476			/* don't override -Q */
477			if (verbosity > 1)
478				verbosity = 1;
479			break;
480		case 'r':
481			cvs_readonly = 1;
482			break;
483		case 's':
484			ep = strchr(optarg, '=');
485			if (ep == NULL) {
486				cvs_log(LP_ERR, "no = in variable assignment");
487				exit(EX_USAGE);
488			}
489			*(ep++) = '\0';
490			if (cvs_var_set(optarg, ep) < 0)
491				exit(EX_USAGE);
492			break;
493		case 't':
494			(void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
495			cvs_trace = 1;
496			break;
497		case 'v':
498			printf("%s\n", CVS_VERSION);
499			exit(0);
500			/* NOTREACHED */
501			break;
502		case 'x':
503			/*
504			 * Kerberos encryption support, kept for compatibility
505			 */
506			break;
507		case 'z':
508			cvs_compress = (int)strtol(optarg, &ep, 10);
509			if (*ep != '\0')
510				errx(1, "error parsing compression level");
511			if (cvs_compress < 0 || cvs_compress > 9)
512				errx(1, "gzip compression level must be "
513				    "between 0 and 9");
514			break;
515		default:
516			usage();
517			exit(EX_USAGE);
518		}
519	}
520
521	ret = optind;
522	optind = 1;
523	optreset = 1;	/* for next call */
524
525	return (ret);
526}
527
528
529/*
530 * cvs_findcmd()
531 *
532 * Find the entry in the command dispatch table whose name or one of its
533 * aliases matches <cmd>.
534 * Returns a pointer to the command entry on success, NULL on failure.
535 */
536struct cvs_cmd*
537cvs_findcmd(const char *cmd)
538{
539	u_int i, j;
540	struct cvs_cmd *cmdp;
541
542	cmdp = NULL;
543
544	for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
545		if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
546			cmdp = &cvs_cdt[i];
547		else {
548			for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
549				if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
550					cmdp = &cvs_cdt[i];
551					break;
552				}
553			}
554		}
555	}
556
557	return (cmdp);
558}
559
560
561/*
562 * cvs_read_rcfile()
563 *
564 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
565 * exists, it should contain a list of arguments that should always be given
566 * implicitly to the specified commands.
567 */
568void
569cvs_read_rcfile(void)
570{
571	char rcpath[MAXPATHLEN], linebuf[128], *lp;
572	int linenum = 0;
573	size_t len;
574	struct cvs_cmd *cmdp;
575	struct passwd *pw;
576	FILE *fp;
577
578	pw = getpwuid(getuid());
579	if (pw == NULL) {
580		cvs_log(LP_NOTICE, "failed to get user's password entry");
581		return;
582	}
583
584	snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
585
586	fp = fopen(rcpath, "r");
587	if (fp == NULL) {
588		if (errno != ENOENT)
589			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
590			    strerror(errno));
591		return;
592	}
593
594	while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
595		linenum++;
596		if ((len = strlen(linebuf)) == 0)
597			continue;
598		if (linebuf[len - 1] != '\n') {
599			cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
600				linenum);
601			break;
602		}
603		linebuf[--len] = '\0';
604
605		lp = strchr(linebuf, ' ');
606		if (lp == NULL)
607			continue;	/* ignore lines with no arguments */
608		*lp = '\0';
609		if (strcmp(linebuf, "cvs") == 0) {
610			/*
611			 * Global default options.  In the case of cvs only,
612			 * we keep the 'cvs' string as first argument because
613			 * getopt() does not like starting at index 0 for
614			 * argument processing.
615			 */
616			*lp = ' ';
617			cvs_defargs = strdup(linebuf);
618			if (cvs_defargs == NULL)
619				cvs_log(LP_ERRNO,
620				    "failed to copy global arguments");
621		} else {
622			lp++;
623			cmdp = cvs_findcmd(linebuf);
624			if (cmdp == NULL) {
625				cvs_log(LP_NOTICE,
626				    "unknown command `%s' in `%s:%d'",
627				    linebuf, rcpath, linenum);
628				continue;
629			}
630
631			cmdp->cmd_defargs = strdup(lp);
632			if (cmdp->cmd_defargs == NULL)
633				cvs_log(LP_ERRNO,
634				    "failed to copy default arguments for %s",
635				    cmdp->cmd_name);
636		}
637	}
638	if (ferror(fp)) {
639		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
640	}
641
642	(void)fclose(fp);
643}
644
645
646/*
647 * cvs_var_set()
648 *
649 * Set the value of the variable <var> to <val>.  If there is no such variable,
650 * a new entry is created, otherwise the old value is overwritten.
651 * Returns 0 on success, or -1 on failure.
652 */
653int
654cvs_var_set(const char *var, const char *val)
655{
656	char *valcp;
657	const char *cp;
658	struct cvs_var *vp;
659
660	if ((var == NULL) || (*var == '\0')) {
661		cvs_log(LP_ERR, "no variable name");
662		return (-1);
663	}
664
665	/* sanity check on the name */
666	for (cp = var; *cp != '\0'; cp++)
667		if (!isalnum(*cp) && (*cp != '_')) {
668			cvs_log(LP_ERR,
669			    "variable name `%s' contains invalid characters",
670			    var);
671			return (-1);
672		}
673
674	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
675		if (strcmp(vp->cv_name, var) == 0)
676			break;
677
678	valcp = strdup(val);
679	if (valcp == NULL) {
680		cvs_log(LP_ERRNO, "failed to allocate variable");
681		return (-1);
682	}
683
684	if (vp == NULL) {
685		vp = (struct cvs_var *)malloc(sizeof(*vp));
686		if (vp == NULL) {
687			cvs_log(LP_ERRNO, "failed to allocate variable");
688			free(valcp);
689			return (-1);
690		}
691		memset(vp, 0, sizeof(*vp));
692
693		vp->cv_name = strdup(var);
694		if (vp->cv_name == NULL) {
695			cvs_log(LP_ERRNO, "failed to allocate variable");
696			free(valcp);
697			free(vp);
698			return (-1);
699		}
700
701		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
702
703	} else	/* free the previous value */
704		free(vp->cv_val);
705
706	vp->cv_val = valcp;
707
708	return (0);
709}
710
711
712/*
713 * cvs_var_set()
714 *
715 * Remove any entry for the variable <var>.
716 * Returns 0 on success, or -1 on failure.
717 */
718int
719cvs_var_unset(const char *var)
720{
721	struct cvs_var *vp;
722
723	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
724		if (strcmp(vp->cv_name, var) == 0) {
725			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
726			free(vp->cv_name);
727			free(vp->cv_val);
728			free(vp);
729			return (0);
730		}
731
732	return (-1);
733
734}
735
736
737/*
738 * cvs_var_get()
739 *
740 * Get the value associated with the variable <var>.  Returns a pointer to the
741 * value string on success, or NULL if the variable does not exist.
742 */
743
744const char*
745cvs_var_get(const char *var)
746{
747	struct cvs_var *vp;
748
749	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
750		if (strcmp(vp->cv_name, var) == 0)
751			return (vp->cv_val);
752
753	return (NULL);
754}
755