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