cvs.c revision 1.48
1/*	$OpenBSD: cvs.c,v 1.48 2005/04/06 16:35:25 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#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",   {}, &cmd_server,
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);
303static void  cvs_read_rcfile (void);
304int          cvs_getopt      (int, char **);
305
306
307/*
308 * usage()
309 *
310 * Display usage information.
311 */
312void
313usage(void)
314{
315	fprintf(stderr,
316	    "Usage: %s [-flQqtv] [-d root] [-e editor] [-s var=val] [-z level] "
317	    "command [...]\n", __progname);
318}
319
320
321int
322main(int argc, char **argv)
323{
324	char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
325	int i, ret, cmd_argc;
326	struct cvs_cmd *cmdp;
327
328	TAILQ_INIT(&cvs_variables);
329
330	if (cvs_log_init(LD_STD, 0) < 0)
331		err(1, "failed to initialize logging");
332
333	/* by default, be very verbose */
334	(void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
335
336#ifdef DEBUG
337	(void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
338#endif
339
340	cvs_strtab_init();
341
342	/* check environment so command-line options override it */
343	if ((envstr = getenv("CVS_RSH")) != NULL)
344		cvs_rsh = envstr;
345
346	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
347	    ((envstr = getenv("VISUAL")) != NULL) ||
348	    ((envstr = getenv("EDITOR")) != NULL))
349		cvs_editor = envstr;
350
351	ret = cvs_getopt(argc, argv);
352
353	argc -= ret;
354	argv += ret;
355	if (argc == 0) {
356		usage();
357		exit(EX_USAGE);
358	}
359	cvs_command = argv[0];
360
361	if (cvs_readrc) {
362		cvs_read_rcfile();
363
364		if (cvs_defargs != NULL) {
365			targv = cvs_makeargv(cvs_defargs, &i);
366			if (targv == NULL) {
367				cvs_log(LP_ERR,
368				    "failed to load default arguments to %s",
369				    __progname);
370				exit(1);
371			}
372
373			cvs_getopt(i, targv);
374			cvs_freeargv(targv, i);
375			free(targv);
376		}
377	}
378
379	/* setup signal handlers */
380	signal(SIGPIPE, SIG_IGN);
381
382	if (cvs_file_init() < 0) {
383		cvs_log(LP_ERR, "failed to initialize file support");
384		exit(1);
385	}
386
387	ret = -1;
388
389	cmdp = cvs_findcmd(cvs_command);
390	if (cmdp == NULL) {
391		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
392		fprintf(stderr, "CVS commands are:\n");
393		for (i = 0; i < (int)CVS_NBCMD; i++)
394			fprintf(stderr, "\t%-16s%s\n",
395			    cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr);
396		exit(EX_USAGE);
397	}
398
399	if (cmdp->cmd_info == NULL) {
400		cvs_log(LP_ERR, "command `%s' not implemented", cvs_command);
401		exit(1);
402	}
403
404	cvs_cmdop = cmdp->cmd_op;
405
406	cmd_argc = 0;
407	memset(cmd_argv, 0, sizeof(cmd_argv));
408
409	cmd_argv[cmd_argc++] = argv[0];
410	if (cmdp->cmd_defargs != NULL) {
411		/* transform into a new argument vector */
412		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
413		    CVS_CMD_MAXARG - 1);
414		if (ret < 0) {
415			cvs_log(LP_ERRNO, "failed to generate argument vector "
416			    "from default arguments");
417			exit(1);
418		}
419		cmd_argc += ret;
420	}
421	for (ret = 1; ret < argc; ret++)
422		cmd_argv[cmd_argc++] = argv[ret];
423
424	ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
425	if (ret == EX_USAGE) {
426		fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
427		    cmdp->cmd_synopsis);
428	}
429
430	if (cmdp->cmd_info->cmd_cleanup != NULL)
431		cmdp->cmd_info->cmd_cleanup();
432	if (cvs_files != NULL)
433		cvs_file_free(cvs_files);
434	if (cvs_msg != NULL)
435		free(cvs_msg);
436
437	cvs_strtab_cleanup();
438
439	return (ret);
440}
441
442
443int
444cvs_getopt(int argc, char **argv)
445{
446	int ret;
447	char *ep;
448
449	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) {
450		switch (ret) {
451		case 'b':
452			/*
453			 * We do not care about the bin directory for RCS files
454			 * as this program has no dependencies on RCS programs,
455			 * so it is only here for backwards compatibility.
456			 */
457			cvs_log(LP_NOTICE, "the -b argument is obsolete");
458			break;
459		case 'd':
460			cvs_rootstr = optarg;
461			break;
462		case 'e':
463			cvs_editor = optarg;
464			break;
465		case 'f':
466			cvs_readrc = 0;
467			break;
468		case 'l':
469			cvs_nolog = 1;
470			break;
471		case 'n':
472			break;
473		case 'Q':
474			verbosity = 0;
475			break;
476		case 'q':
477			/* don't override -Q */
478			if (verbosity > 1)
479				verbosity = 1;
480			break;
481		case 'r':
482			cvs_readonly = 1;
483			break;
484		case 's':
485			ep = strchr(optarg, '=');
486			if (ep == NULL) {
487				cvs_log(LP_ERR, "no = in variable assignment");
488				exit(EX_USAGE);
489			}
490			*(ep++) = '\0';
491			if (cvs_var_set(optarg, ep) < 0)
492				exit(EX_USAGE);
493			break;
494		case 't':
495			(void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
496			cvs_trace = 1;
497			break;
498		case 'v':
499			printf("%s\n", CVS_VERSION);
500			exit(0);
501			/* NOTREACHED */
502			break;
503		case 'x':
504			/*
505			 * Kerberos encryption support, kept for compatibility
506			 */
507			break;
508		case 'z':
509			cvs_compress = (int)strtol(optarg, &ep, 10);
510			if (*ep != '\0')
511				errx(1, "error parsing compression level");
512			if (cvs_compress < 0 || cvs_compress > 9)
513				errx(1, "gzip compression level must be "
514				    "between 0 and 9");
515			break;
516		default:
517			usage();
518			exit(EX_USAGE);
519		}
520	}
521
522	ret = optind;
523	optind = 1;
524	optreset = 1;	/* for next call */
525
526	return (ret);
527}
528
529
530/*
531 * cvs_findcmd()
532 *
533 * Find the entry in the command dispatch table whose name or one of its
534 * aliases matches <cmd>.
535 * Returns a pointer to the command entry on success, NULL on failure.
536 */
537struct cvs_cmd*
538cvs_findcmd(const char *cmd)
539{
540	u_int i, j;
541	struct cvs_cmd *cmdp;
542
543	cmdp = NULL;
544
545	for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
546		if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
547			cmdp = &cvs_cdt[i];
548		else {
549			for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
550				if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
551					cmdp = &cvs_cdt[i];
552					break;
553				}
554			}
555		}
556	}
557
558	return (cmdp);
559}
560
561
562/*
563 * cvs_read_rcfile()
564 *
565 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
566 * exists, it should contain a list of arguments that should always be given
567 * implicitly to the specified commands.
568 */
569static void
570cvs_read_rcfile(void)
571{
572	char rcpath[MAXPATHLEN], linebuf[128], *lp;
573	int linenum = 0;
574	size_t len;
575	struct cvs_cmd *cmdp;
576	struct passwd *pw;
577	FILE *fp;
578
579	pw = getpwuid(getuid());
580	if (pw == NULL) {
581		cvs_log(LP_NOTICE, "failed to get user's password entry");
582		return;
583	}
584
585	snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
586
587	fp = fopen(rcpath, "r");
588	if (fp == NULL) {
589		if (errno != ENOENT)
590			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
591			    strerror(errno));
592		return;
593	}
594
595	while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
596		linenum++;
597		if ((len = strlen(linebuf)) == 0)
598			continue;
599		if (linebuf[len - 1] != '\n') {
600			cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
601				linenum);
602			break;
603		}
604		linebuf[--len] = '\0';
605
606		lp = strchr(linebuf, ' ');
607		if (lp == NULL)
608			continue;	/* ignore lines with no arguments */
609		*lp = '\0';
610		if (strcmp(linebuf, "cvs") == 0) {
611			/*
612			 * Global default options.  In the case of cvs only,
613			 * we keep the 'cvs' string as first argument because
614			 * getopt() does not like starting at index 0 for
615			 * argument processing.
616			 */
617			*lp = ' ';
618			cvs_defargs = strdup(linebuf);
619			if (cvs_defargs == NULL)
620				cvs_log(LP_ERRNO,
621				    "failed to copy global arguments");
622		} else {
623			lp++;
624			cmdp = cvs_findcmd(linebuf);
625			if (cmdp == NULL) {
626				cvs_log(LP_NOTICE,
627				    "unknown command `%s' in `%s:%d'",
628				    linebuf, rcpath, linenum);
629				continue;
630			}
631
632			cmdp->cmd_defargs = strdup(lp);
633			if (cmdp->cmd_defargs == NULL)
634				cvs_log(LP_ERRNO,
635				    "failed to copy default arguments for %s",
636				    cmdp->cmd_name);
637		}
638	}
639	if (ferror(fp)) {
640		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
641	}
642
643	(void)fclose(fp);
644}
645
646
647/*
648 * cvs_var_set()
649 *
650 * Set the value of the variable <var> to <val>.  If there is no such variable,
651 * a new entry is created, otherwise the old value is overwritten.
652 * Returns 0 on success, or -1 on failure.
653 */
654int
655cvs_var_set(const char *var, const char *val)
656{
657	char *valcp;
658	const char *cp;
659	struct cvs_var *vp;
660
661	if ((var == NULL) || (*var == '\0')) {
662		cvs_log(LP_ERR, "no variable name");
663		return (-1);
664	}
665
666	/* sanity check on the name */
667	for (cp = var; *cp != '\0'; cp++)
668		if (!isalnum(*cp) && (*cp != '_')) {
669			cvs_log(LP_ERR,
670			    "variable name `%s' contains invalid characters",
671			    var);
672			return (-1);
673		}
674
675	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
676		if (strcmp(vp->cv_name, var) == 0)
677			break;
678
679	valcp = strdup(val);
680	if (valcp == NULL) {
681		cvs_log(LP_ERRNO, "failed to allocate variable");
682		return (-1);
683	}
684
685	if (vp == NULL) {
686		vp = (struct cvs_var *)malloc(sizeof(*vp));
687		if (vp == NULL) {
688			cvs_log(LP_ERRNO, "failed to allocate variable");
689			free(valcp);
690			return (-1);
691		}
692		memset(vp, 0, sizeof(*vp));
693
694		vp->cv_name = strdup(var);
695		if (vp->cv_name == NULL) {
696			cvs_log(LP_ERRNO, "failed to allocate variable");
697			free(valcp);
698			free(vp);
699			return (-1);
700		}
701
702		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
703
704	} else	/* free the previous value */
705		free(vp->cv_val);
706
707	vp->cv_val = valcp;
708
709	return (0);
710}
711
712
713/*
714 * cvs_var_set()
715 *
716 * Remove any entry for the variable <var>.
717 * Returns 0 on success, or -1 on failure.
718 */
719int
720cvs_var_unset(const char *var)
721{
722	struct cvs_var *vp;
723
724	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
725		if (strcmp(vp->cv_name, var) == 0) {
726			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
727			free(vp->cv_name);
728			free(vp->cv_val);
729			free(vp);
730			return (0);
731		}
732
733	return (-1);
734
735}
736
737
738/*
739 * cvs_var_get()
740 *
741 * Get the value associated with the variable <var>.  Returns a pointer to the
742 * value string on success, or NULL if the variable does not exist.
743 */
744
745const char*
746cvs_var_get(const char *var)
747{
748	struct cvs_var *vp;
749
750	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
751		if (strcmp(vp->cv_name, var) == 0)
752			return (vp->cv_val);
753
754	return (NULL);
755}
756