cvs.c revision 1.105
1/*	$OpenBSD: cvs.c,v 1.105 2006/06/28 20:19:05 reyk Exp $	*/
2/*
3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. The name of the author may not be used to endorse or promote products
14 *    derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "includes.h"
29
30#include "cvs.h"
31#include "config.h"
32#include "log.h"
33#include "file.h"
34
35extern char *__progname;
36
37/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
38int verbosity = 1;
39
40/* compression level used with zlib, 0 meaning no compression taking place */
41int	cvs_compress = 0;
42int	cvs_readrc = 1;		/* read .cvsrc on startup */
43int	cvs_trace = 0;
44int	cvs_nolog = 0;
45int	cvs_readonly = 0;
46int	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
47int	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
48int	cvs_error = -1;	/* set to the correct error code on failure */
49int	cvs_cmdop;
50int	cvs_umask = CVS_UMASK_DEFAULT;
51
52char	*cvs_tagname = NULL;
53char	*cvs_defargs;		/* default global arguments from .cvsrc */
54char	*cvs_command;		/* name of the command we are running */
55char	*cvs_rootstr;
56char	*cvs_rsh = CVS_RSH_DEFAULT;
57char	*cvs_editor = CVS_EDITOR_DEFAULT;
58char	*cvs_homedir = NULL;
59char	*cvs_msg = NULL;
60char	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
61
62struct cvsroot *current_cvsroot = NULL;
63
64static TAILQ_HEAD(, cvs_var) cvs_variables;
65
66int		cvs_getopt(int, char **);
67void		usage(void);
68static void	cvs_read_rcfile(void);
69
70struct cvs_wklhead temp_files;
71
72void sighandler(int);
73volatile sig_atomic_t cvs_quit = 0;
74volatile sig_atomic_t sig_received = 0;
75
76void
77sighandler(int sig)
78{
79	sig_received = sig;
80
81	switch (sig) {
82	case SIGINT:
83	case SIGTERM:
84		cvs_quit = 1;
85		break;
86	default:
87		break;
88	}
89}
90
91void
92cvs_cleanup(void)
93{
94	cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
95	cvs_worklist_run(&repo_locks, cvs_worklist_unlink);
96
97	cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
98	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
99}
100
101void
102usage(void)
103{
104	fprintf(stderr,
105	    "Usage: %s [-flnQqrtvVw] [-d root] [-e editor] [-s var=val] "
106	    "[-T tmpdir] [-z level] command [...]\n", __progname);
107}
108
109int
110main(int argc, char **argv)
111{
112	char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
113	int i, ret, cmd_argc;
114	struct cvs_cmd *cmdp;
115	struct passwd *pw;
116	struct stat st;
117	char fpath[MAXPATHLEN];
118
119	tzset();
120
121	TAILQ_INIT(&cvs_variables);
122	SLIST_INIT(&repo_locks);
123	SLIST_INIT(&temp_files);
124
125	/* check environment so command-line options override it */
126	if ((envstr = getenv("CVS_RSH")) != NULL)
127		cvs_rsh = envstr;
128
129	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
130	    ((envstr = getenv("VISUAL")) != NULL) ||
131	    ((envstr = getenv("EDITOR")) != NULL))
132		cvs_editor = envstr;
133
134	if ((envstr = getenv("CVSREAD")) != NULL)
135		cvs_readonly = 1;
136
137	if ((cvs_homedir = getenv("HOME")) == NULL) {
138		if ((pw = getpwuid(getuid())) == NULL)
139			fatal("getpwuid failed");
140		cvs_homedir = pw->pw_dir;
141	}
142
143	if ((envstr = getenv("TMPDIR")) != NULL)
144		cvs_tmpdir = envstr;
145
146	ret = cvs_getopt(argc, argv);
147
148	argc -= ret;
149	argv += ret;
150	if (argc == 0) {
151		usage();
152		exit(1);
153	}
154
155	cvs_command = argv[0];
156
157	/*
158	 * check the tmp dir, either specified through
159	 * the environment variable TMPDIR, or via
160	 * the global option -T <dir>
161	 */
162	if (stat(cvs_tmpdir, &st) == -1)
163		fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
164	else if (!S_ISDIR(st.st_mode))
165		fatal("`%s' is not valid temporary directory", cvs_tmpdir);
166
167	if (cvs_readrc == 1) {
168		cvs_read_rcfile();
169
170		if (cvs_defargs != NULL) {
171			if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
172				fatal("failed to load default arguments to %s",
173				    __progname);
174
175			cvs_getopt(i, targv);
176			cvs_freeargv(targv, i);
177			xfree(targv);
178		}
179	}
180
181	/* setup signal handlers */
182	signal(SIGTERM, sighandler);
183	signal(SIGINT, sighandler);
184	signal(SIGHUP, sighandler);
185	signal(SIGABRT, sighandler);
186	signal(SIGALRM, sighandler);
187	signal(SIGPIPE, sighandler);
188
189	cmdp = cvs_findcmd(cvs_command);
190	if (cmdp == NULL) {
191		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
192		fprintf(stderr, "CVS commands are:\n");
193		for (i = 0; cvs_cdt[i] != NULL; i++)
194			fprintf(stderr, "\t%-16s%s\n",
195			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
196		exit(1);
197	}
198
199	cvs_cmdop = cmdp->cmd_op;
200
201	cmd_argc = 0;
202	memset(cmd_argv, 0, sizeof(cmd_argv));
203
204	cmd_argv[cmd_argc++] = argv[0];
205	if (cmdp->cmd_defargs != NULL) {
206		/* transform into a new argument vector */
207		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
208		    CVS_CMD_MAXARG - 1);
209		if (ret < 0)
210			fatal("main: cvs_getargv failed");
211
212		cmd_argc += ret;
213	}
214
215	for (ret = 1; ret < argc; ret++)
216		cmd_argv[cmd_argc++] = argv[ret];
217
218	cvs_file_init();
219
220	if ((current_cvsroot = cvsroot_get(".")) == NULL) {
221		cvs_log(LP_ERR,
222		    "No CVSROOT specified! Please use the '-d' option");
223		fatal("or set the CVSROOT environment variable.");
224	}
225
226	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL)
227		fatal("remote setups are not supported yet");
228
229	i = snprintf(fpath, sizeof(fpath), "%s/%s", current_cvsroot->cr_dir,
230	    CVS_PATH_ROOT);
231	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
232		if (errno == ENOENT)
233			fatal("repository '%s' does not exist",
234			    current_cvsroot->cr_dir);
235		else
236			fatal("%s: %s", current_cvsroot->cr_dir,
237			    strerror(errno));
238	} else {
239		if (!S_ISDIR(st.st_mode))
240			fatal("'%s' is not a directory",
241			    current_cvsroot->cr_dir);
242	}
243
244	if (cvs_cmdop != CVS_OP_INIT)
245		cvs_parse_configfile();
246
247	umask(cvs_umask);
248
249	cmdp->cmd(cmd_argc, cmd_argv);
250	cvs_cleanup();
251
252	return (0);
253}
254
255int
256cvs_getopt(int argc, char **argv)
257{
258	int ret;
259	char *ep;
260
261	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:T:tvVwz:")) != -1) {
262		switch (ret) {
263		case 'b':
264			/*
265			 * We do not care about the bin directory for RCS files
266			 * as this program has no dependencies on RCS programs,
267			 * so it is only here for backwards compatibility.
268			 */
269			cvs_log(LP_NOTICE, "the -b argument is obsolete");
270			break;
271		case 'd':
272			cvs_rootstr = optarg;
273			break;
274		case 'e':
275			cvs_editor = optarg;
276			break;
277		case 'f':
278			cvs_readrc = 0;
279			break;
280		case 'l':
281			cvs_nolog = 1;
282			break;
283		case 'n':
284			cvs_noexec = 1;
285			break;
286		case 'Q':
287			verbosity = 0;
288			break;
289		case 'q':
290			/*
291			 * Be quiet. This is the default in OpenCVS.
292			 */
293			break;
294		case 'r':
295			cvs_readonly = 1;
296			break;
297		case 's':
298			ep = strchr(optarg, '=');
299			if (ep == NULL) {
300				cvs_log(LP_ERR, "no = in variable assignment");
301				exit(1);
302			}
303			*(ep++) = '\0';
304			if (cvs_var_set(optarg, ep) < 0)
305				exit(1);
306			break;
307		case 'T':
308			cvs_tmpdir = optarg;
309			break;
310		case 't':
311			cvs_trace = 1;
312			break;
313		case 'V':
314			/* don't override -Q */
315			if (verbosity)
316				verbosity = 2;
317			break;
318		case 'v':
319			printf("%s\n", CVS_VERSION);
320			exit(0);
321			/* NOTREACHED */
322			break;
323		case 'w':
324			cvs_readonly = 0;
325			break;
326		case 'x':
327			/*
328			 * Kerberos encryption support, kept for compatibility
329			 */
330			break;
331		case 'z':
332			cvs_compress = (int)strtol(optarg, &ep, 10);
333			if (*ep != '\0')
334				fatal("error parsing compression level");
335			if (cvs_compress < 0 || cvs_compress > 9)
336				fatal("gzip compression level must be "
337				    "between 0 and 9");
338			break;
339		default:
340			usage();
341			exit(1);
342		}
343	}
344
345	ret = optind;
346	optind = 1;
347	optreset = 1;	/* for next call */
348
349	return (ret);
350}
351
352/*
353 * cvs_read_rcfile()
354 *
355 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
356 * exists, it should contain a list of arguments that should always be given
357 * implicitly to the specified commands.
358 */
359static void
360cvs_read_rcfile(void)
361{
362	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
363	int linenum = 0;
364	size_t len;
365	struct cvs_cmd *cmdp;
366	FILE *fp;
367
368	if (strlcpy(rcpath, cvs_homedir, sizeof(rcpath)) >= sizeof(rcpath) ||
369	    strlcat(rcpath, "/", sizeof(rcpath)) >= sizeof(rcpath) ||
370	    strlcat(rcpath, CVS_PATH_RC, sizeof(rcpath)) >= sizeof(rcpath)) {
371		errno = ENAMETOOLONG;
372		cvs_log(LP_ERR, "%s", rcpath);
373		return;
374	}
375
376	fp = fopen(rcpath, "r");
377	if (fp == NULL) {
378		if (errno != ENOENT)
379			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
380			    strerror(errno));
381		return;
382	}
383
384	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
385		linenum++;
386		if ((len = strlen(linebuf)) == 0)
387			continue;
388		if (linebuf[len - 1] != '\n') {
389			cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
390				linenum);
391			break;
392		}
393		linebuf[--len] = '\0';
394
395		/* skip any whitespaces */
396		p = linebuf;
397		while (*p == ' ')
398			p++;
399
400		/* allow comments */
401		if (*p == '#')
402			continue;
403
404		lp = strchr(p, ' ');
405		if (lp == NULL)
406			continue;	/* ignore lines with no arguments */
407		*lp = '\0';
408		if (strcmp(p, "cvs") == 0) {
409			/*
410			 * Global default options.  In the case of cvs only,
411			 * we keep the 'cvs' string as first argument because
412			 * getopt() does not like starting at index 0 for
413			 * argument processing.
414			 */
415			*lp = ' ';
416			cvs_defargs = xstrdup(p);
417		} else {
418			lp++;
419			cmdp = cvs_findcmd(p);
420			if (cmdp == NULL) {
421				cvs_log(LP_NOTICE,
422				    "unknown command `%s' in `%s:%d'",
423				    p, rcpath, linenum);
424				continue;
425			}
426
427			cmdp->cmd_defargs = xstrdup(lp);
428		}
429	}
430
431	if (ferror(fp)) {
432		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
433	}
434
435	(void)fclose(fp);
436}
437
438/*
439 * cvs_var_set()
440 *
441 * Set the value of the variable <var> to <val>.  If there is no such variable,
442 * a new entry is created, otherwise the old value is overwritten.
443 * Returns 0 on success, or -1 on failure.
444 */
445int
446cvs_var_set(const char *var, const char *val)
447{
448	char *valcp;
449	const char *cp;
450	struct cvs_var *vp;
451
452	if (var == NULL || *var == '\0') {
453		cvs_log(LP_ERR, "no variable name");
454		return (-1);
455	}
456
457	/* sanity check on the name */
458	for (cp = var; *cp != '\0'; cp++)
459		if (!isalnum(*cp) && (*cp != '_')) {
460			cvs_log(LP_ERR,
461			    "variable name `%s' contains invalid characters",
462			    var);
463			return (-1);
464		}
465
466	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
467		if (strcmp(vp->cv_name, var) == 0)
468			break;
469
470	valcp = xstrdup(val);
471	if (vp == NULL) {
472		vp = xcalloc(1, sizeof(*vp));
473
474		vp->cv_name = xstrdup(var);
475		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
476
477	} else	/* free the previous value */
478		xfree(vp->cv_val);
479
480	vp->cv_val = valcp;
481
482	return (0);
483}
484
485/*
486 * cvs_var_set()
487 *
488 * Remove any entry for the variable <var>.
489 * Returns 0 on success, or -1 on failure.
490 */
491int
492cvs_var_unset(const char *var)
493{
494	struct cvs_var *vp;
495
496	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
497		if (strcmp(vp->cv_name, var) == 0) {
498			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
499			xfree(vp->cv_name);
500			xfree(vp->cv_val);
501			xfree(vp);
502			return (0);
503		}
504
505	return (-1);
506}
507
508/*
509 * cvs_var_get()
510 *
511 * Get the value associated with the variable <var>.  Returns a pointer to the
512 * value string on success, or NULL if the variable does not exist.
513 */
514
515const char *
516cvs_var_get(const char *var)
517{
518	struct cvs_var *vp;
519
520	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
521		if (strcmp(vp->cv_name, var) == 0)
522			return (vp->cv_val);
523
524	return (NULL);
525}
526