cvs.c revision 1.115
1/*	$OpenBSD: cvs.c,v 1.115 2007/02/09 03:30:31 joris Exp $	*/
2/*
3 * Copyright (c) 2006, 2007 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#include "remote.h"
35
36extern char *__progname;
37
38/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
39int verbosity = 1;
40
41/* compression level used with zlib, 0 meaning no compression taking place */
42int	cvs_compress = 0;
43int	cvs_readrc = 1;		/* read .cvsrc on startup */
44int	cvs_trace = 0;
45int	cvs_nolog = 0;
46int	cvs_readonly = 0;
47int	cvs_readonlyfs = 0;
48int	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
49int	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
50int	cvs_error = -1;	/* set to the correct error code on failure */
51int	cvs_cmdop;
52int	cvs_umask = CVS_UMASK_DEFAULT;
53int	cvs_server_active = 0;
54
55char	*cvs_tagname = NULL;
56char	*cvs_defargs;		/* default global arguments from .cvsrc */
57char	*cvs_command;		/* name of the command we are running */
58char	*cvs_rootstr;
59char	*cvs_rsh = CVS_RSH_DEFAULT;
60char	*cvs_editor = CVS_EDITOR_DEFAULT;
61char	*cvs_homedir = NULL;
62char	*cvs_msg = NULL;
63char	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
64
65struct cvsroot *current_cvsroot = NULL;
66
67int		cvs_getopt(int, char **);
68void		usage(void);
69static void	cvs_read_rcfile(void);
70
71struct cvs_wklhead temp_files;
72
73void sighandler(int);
74volatile sig_atomic_t cvs_quit = 0;
75volatile sig_atomic_t sig_received = 0;
76
77void
78sighandler(int sig)
79{
80	sig_received = sig;
81
82	switch (sig) {
83	case SIGINT:
84	case SIGTERM:
85	case SIGPIPE:
86		cvs_quit = 1;
87		break;
88	default:
89		break;
90	}
91}
92
93void
94cvs_cleanup(void)
95{
96	cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
97	cvs_worklist_run(&repo_locks, cvs_worklist_unlink);
98
99	cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
100	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
101
102	if (cvs_server_active) {
103		if (cvs_rmdir(cvs_server_path) == -1)
104			cvs_log(LP_ERR,
105			    "warning: failed to remove server directory: %s",
106			    cvs_server_path);
107		xfree(cvs_server_path);
108	}
109}
110
111void
112usage(void)
113{
114	fprintf(stderr,
115	    "Usage: %s [-flnQqRrtVvw] [-d root] [-e editor] [-s var=val] "
116	    "[-T tmpdir] [-z level] command [...]\n", __progname);
117}
118
119int
120main(int argc, char **argv)
121{
122	char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
123	int i, ret, cmd_argc;
124	struct cvs_cmd *cmdp;
125	struct passwd *pw;
126	struct stat st;
127	char fpath[MAXPATHLEN];
128	char *root, *rootp;
129
130	tzset();
131
132	TAILQ_INIT(&cvs_variables);
133	SLIST_INIT(&repo_locks);
134	SLIST_INIT(&temp_files);
135
136	/* check environment so command-line options override it */
137	if ((envstr = getenv("CVS_RSH")) != NULL)
138		cvs_rsh = envstr;
139
140	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
141	    ((envstr = getenv("VISUAL")) != NULL) ||
142	    ((envstr = getenv("EDITOR")) != NULL))
143		cvs_editor = envstr;
144
145	if ((envstr = getenv("CVSREAD")) != NULL)
146		cvs_readonly = 1;
147
148	if ((envstr = getenv("CVSREADONLYFS")) != NULL) {
149		cvs_readonlyfs = 1;
150		cvs_nolog = 1;
151	}
152
153	if ((cvs_homedir = getenv("HOME")) == NULL) {
154		if ((pw = getpwuid(getuid())) == NULL)
155			fatal("getpwuid failed");
156		cvs_homedir = pw->pw_dir;
157	}
158
159	if ((envstr = getenv("TMPDIR")) != NULL)
160		cvs_tmpdir = envstr;
161
162	ret = cvs_getopt(argc, argv);
163
164	argc -= ret;
165	argv += ret;
166	if (argc == 0) {
167		usage();
168		exit(1);
169	}
170
171	cvs_command = argv[0];
172
173	/*
174	 * check the tmp dir, either specified through
175	 * the environment variable TMPDIR, or via
176	 * the global option -T <dir>
177	 */
178	if (stat(cvs_tmpdir, &st) == -1)
179		fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
180	else if (!S_ISDIR(st.st_mode))
181		fatal("`%s' is not valid temporary directory", cvs_tmpdir);
182
183	if (cvs_readrc == 1) {
184		cvs_read_rcfile();
185
186		if (cvs_defargs != NULL) {
187			if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
188				fatal("failed to load default arguments to %s",
189				    __progname);
190
191			cvs_getopt(i, targv);
192			cvs_freeargv(targv, i);
193			xfree(targv);
194		}
195	}
196
197	/* setup signal handlers */
198	signal(SIGTERM, sighandler);
199	signal(SIGINT, sighandler);
200	signal(SIGHUP, sighandler);
201	signal(SIGABRT, sighandler);
202	signal(SIGALRM, sighandler);
203	signal(SIGPIPE, sighandler);
204
205	cmdp = cvs_findcmd(cvs_command);
206	if (cmdp == NULL) {
207		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
208		fprintf(stderr, "CVS commands are:\n");
209		for (i = 0; cvs_cdt[i] != NULL; i++)
210			fprintf(stderr, "\t%-16s%s\n",
211			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
212		exit(1);
213	}
214
215	cvs_cmdop = cmdp->cmd_op;
216
217	cmd_argc = 0;
218	memset(cmd_argv, 0, sizeof(cmd_argv));
219
220	cmd_argv[cmd_argc++] = argv[0];
221	if (cmdp->cmd_defargs != NULL) {
222		/* transform into a new argument vector */
223		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
224		    CVS_CMD_MAXARG - 1);
225		if (ret < 0)
226			fatal("main: cvs_getargv failed");
227
228		cmd_argc += ret;
229	}
230
231	for (ret = 1; ret < argc; ret++)
232		cmd_argv[cmd_argc++] = argv[ret];
233
234	cvs_file_init();
235
236	if (cvs_cmdop == CVS_OP_SERVER) {
237		setvbuf(stdin, NULL, _IOLBF, 0);
238		setvbuf(stdout, NULL, _IOLBF, 0);
239
240		cvs_server_active = 1;
241		root = cvs_remote_input();
242		if ((rootp = strchr(root, ' ')) == NULL)
243			fatal("bad Root request");
244		cvs_rootstr = xstrdup(rootp + 1);
245		xfree(root);
246	}
247
248	if ((current_cvsroot = cvsroot_get(".")) == NULL) {
249		cvs_log(LP_ERR,
250		    "No CVSROOT specified! Please use the '-d' option");
251		fatal("or set the CVSROOT environment variable.");
252	}
253
254	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
255		cmdp->cmd(cmd_argc, cmd_argv);
256		cvs_cleanup();
257		return (0);
258	}
259
260	if (cvs_path_cat(current_cvsroot->cr_dir, CVS_PATH_ROOT,
261	    fpath, sizeof(fpath)) >= sizeof(fpath))
262		fatal("main: truncation");
263
264	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
265		if (errno == ENOENT)
266			fatal("repository '%s' does not exist",
267			    current_cvsroot->cr_dir);
268		else
269			fatal("%s: %s", current_cvsroot->cr_dir,
270			    strerror(errno));
271	} else {
272		if (!S_ISDIR(st.st_mode))
273			fatal("'%s' is not a directory",
274			    current_cvsroot->cr_dir);
275	}
276
277	if (cvs_cmdop != CVS_OP_INIT)
278		cvs_parse_configfile();
279
280	umask(cvs_umask);
281
282	cmdp->cmd(cmd_argc, cmd_argv);
283	cvs_cleanup();
284
285	return (0);
286}
287
288int
289cvs_getopt(int argc, char **argv)
290{
291	int ret;
292	char *ep;
293	const char *errstr;
294
295	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqRrs:T:tvVwz:")) != -1) {
296		switch (ret) {
297		case 'b':
298			/*
299			 * We do not care about the bin directory for RCS files
300			 * as this program has no dependencies on RCS programs,
301			 * so it is only here for backwards compatibility.
302			 */
303			cvs_log(LP_NOTICE, "the -b argument is obsolete");
304			break;
305		case 'd':
306			cvs_rootstr = optarg;
307			break;
308		case 'e':
309			cvs_editor = optarg;
310			break;
311		case 'f':
312			cvs_readrc = 0;
313			break;
314		case 'l':
315			cvs_nolog = 1;
316			break;
317		case 'n':
318			cvs_noexec = 1;
319			cvs_nolog = 1;
320			break;
321		case 'Q':
322			verbosity = 0;
323			break;
324		case 'q':
325			/*
326			 * Be quiet. This is the default in OpenCVS.
327			 */
328			break;
329		case 'R':
330			cvs_readonlyfs = 1;
331			cvs_nolog = 1;
332			break;
333		case 'r':
334			cvs_readonly = 1;
335			break;
336		case 's':
337			ep = strchr(optarg, '=');
338			if (ep == NULL) {
339				cvs_log(LP_ERR, "no = in variable assignment");
340				exit(1);
341			}
342			*(ep++) = '\0';
343			if (cvs_var_set(optarg, ep) < 0)
344				exit(1);
345			break;
346		case 'T':
347			cvs_tmpdir = optarg;
348			break;
349		case 't':
350			cvs_trace = 1;
351			break;
352		case 'V':
353			/* don't override -Q */
354			if (verbosity)
355				verbosity = 2;
356			break;
357		case 'v':
358			printf("%s\n", CVS_VERSION);
359			exit(0);
360			/* NOTREACHED */
361			break;
362		case 'w':
363			cvs_readonly = 0;
364			break;
365		case 'x':
366			/*
367			 * Kerberos encryption support, kept for compatibility
368			 */
369			break;
370		case 'z':
371			cvs_compress = strtonum(optarg, 0, 9, &errstr);
372			if (errstr != NULL)
373				fatal("cvs_compress: %s", errstr);
374			break;
375		default:
376			usage();
377			exit(1);
378		}
379	}
380
381	ret = optind;
382	optind = 1;
383	optreset = 1;	/* for next call */
384
385	return (ret);
386}
387
388/*
389 * cvs_read_rcfile()
390 *
391 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
392 * exists, it should contain a list of arguments that should always be given
393 * implicitly to the specified commands.
394 */
395static void
396cvs_read_rcfile(void)
397{
398	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
399	int linenum = 0;
400	size_t len;
401	struct cvs_cmd *cmdp;
402	FILE *fp;
403
404	if (cvs_path_cat(cvs_homedir, CVS_PATH_RC, rcpath, sizeof(rcpath))
405	    >= sizeof(rcpath)) {
406		cvs_log(LP_ERRNO, "%s", rcpath);
407		return;
408	}
409
410	fp = fopen(rcpath, "r");
411	if (fp == NULL) {
412		if (errno != ENOENT)
413			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
414			    strerror(errno));
415		return;
416	}
417
418	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
419		linenum++;
420		if ((len = strlen(linebuf)) == 0)
421			continue;
422		if (linebuf[len - 1] != '\n') {
423			cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
424				linenum);
425			break;
426		}
427		linebuf[--len] = '\0';
428
429		/* skip any whitespaces */
430		p = linebuf;
431		while (*p == ' ')
432			p++;
433
434		/* allow comments */
435		if (*p == '#')
436			continue;
437
438		lp = strchr(p, ' ');
439		if (lp == NULL)
440			continue;	/* ignore lines with no arguments */
441		*lp = '\0';
442		if (strcmp(p, "cvs") == 0) {
443			/*
444			 * Global default options.  In the case of cvs only,
445			 * we keep the 'cvs' string as first argument because
446			 * getopt() does not like starting at index 0 for
447			 * argument processing.
448			 */
449			*lp = ' ';
450			cvs_defargs = xstrdup(p);
451		} else {
452			lp++;
453			cmdp = cvs_findcmd(p);
454			if (cmdp == NULL) {
455				cvs_log(LP_NOTICE,
456				    "unknown command `%s' in `%s:%d'",
457				    p, rcpath, linenum);
458				continue;
459			}
460
461			cmdp->cmd_defargs = xstrdup(lp);
462		}
463	}
464
465	if (ferror(fp)) {
466		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
467	}
468
469	(void)fclose(fp);
470}
471
472/*
473 * cvs_var_set()
474 *
475 * Set the value of the variable <var> to <val>.  If there is no such variable,
476 * a new entry is created, otherwise the old value is overwritten.
477 * Returns 0 on success, or -1 on failure.
478 */
479int
480cvs_var_set(const char *var, const char *val)
481{
482	char *valcp;
483	const char *cp;
484	struct cvs_var *vp;
485
486	if (var == NULL || *var == '\0') {
487		cvs_log(LP_ERR, "no variable name");
488		return (-1);
489	}
490
491	/* sanity check on the name */
492	for (cp = var; *cp != '\0'; cp++)
493		if (!isalnum(*cp) && (*cp != '_')) {
494			cvs_log(LP_ERR,
495			    "variable name `%s' contains invalid characters",
496			    var);
497			return (-1);
498		}
499
500	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
501		if (strcmp(vp->cv_name, var) == 0)
502			break;
503
504	valcp = xstrdup(val);
505	if (vp == NULL) {
506		vp = xcalloc(1, sizeof(*vp));
507
508		vp->cv_name = xstrdup(var);
509		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
510
511	} else	/* free the previous value */
512		xfree(vp->cv_val);
513
514	vp->cv_val = valcp;
515
516	return (0);
517}
518
519/*
520 * cvs_var_set()
521 *
522 * Remove any entry for the variable <var>.
523 * Returns 0 on success, or -1 on failure.
524 */
525int
526cvs_var_unset(const char *var)
527{
528	struct cvs_var *vp;
529
530	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
531		if (strcmp(vp->cv_name, var) == 0) {
532			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
533			xfree(vp->cv_name);
534			xfree(vp->cv_val);
535			xfree(vp);
536			return (0);
537		}
538
539	return (-1);
540}
541
542/*
543 * cvs_var_get()
544 *
545 * Get the value associated with the variable <var>.  Returns a pointer to the
546 * value string on success, or NULL if the variable does not exist.
547 */
548
549const char *
550cvs_var_get(const char *var)
551{
552	struct cvs_var *vp;
553
554	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
555		if (strcmp(vp->cv_name, var) == 0)
556			return (vp->cv_val);
557
558	return (NULL);
559}
560