cvs.c revision 1.112
1/*	$OpenBSD: cvs.c,v 1.112 2006/12/11 10:53:00 xsa 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#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		if (cvs_server_active == 1)
256			fatal("remote Root while already running as server?");
257
258		cvs_client_connect_to_server();
259		cmdp->cmd(cmd_argc, cmd_argv);
260		cvs_cleanup();
261		return (0);
262	}
263
264	if (cvs_path_cat(current_cvsroot->cr_dir, CVS_PATH_ROOT,
265	    fpath, sizeof(fpath)) >= sizeof(fpath))
266		fatal("main: truncation");
267
268	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
269		if (errno == ENOENT)
270			fatal("repository '%s' does not exist",
271			    current_cvsroot->cr_dir);
272		else
273			fatal("%s: %s", current_cvsroot->cr_dir,
274			    strerror(errno));
275	} else {
276		if (!S_ISDIR(st.st_mode))
277			fatal("'%s' is not a directory",
278			    current_cvsroot->cr_dir);
279	}
280
281	if (cvs_cmdop != CVS_OP_INIT)
282		cvs_parse_configfile();
283
284	umask(cvs_umask);
285
286	cmdp->cmd(cmd_argc, cmd_argv);
287	cvs_cleanup();
288
289	return (0);
290}
291
292int
293cvs_getopt(int argc, char **argv)
294{
295	int ret;
296	char *ep;
297
298	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqRrs:T:tvVwz:")) != -1) {
299		switch (ret) {
300		case 'b':
301			/*
302			 * We do not care about the bin directory for RCS files
303			 * as this program has no dependencies on RCS programs,
304			 * so it is only here for backwards compatibility.
305			 */
306			cvs_log(LP_NOTICE, "the -b argument is obsolete");
307			break;
308		case 'd':
309			cvs_rootstr = optarg;
310			break;
311		case 'e':
312			cvs_editor = optarg;
313			break;
314		case 'f':
315			cvs_readrc = 0;
316			break;
317		case 'l':
318			cvs_nolog = 1;
319			break;
320		case 'n':
321			cvs_noexec = 1;
322			cvs_nolog = 1;
323			break;
324		case 'Q':
325			verbosity = 0;
326			break;
327		case 'q':
328			/*
329			 * Be quiet. This is the default in OpenCVS.
330			 */
331			break;
332		case 'R':
333			cvs_readonlyfs = 1;
334			cvs_nolog = 1;
335			break;
336		case 'r':
337			cvs_readonly = 1;
338			break;
339		case 's':
340			ep = strchr(optarg, '=');
341			if (ep == NULL) {
342				cvs_log(LP_ERR, "no = in variable assignment");
343				exit(1);
344			}
345			*(ep++) = '\0';
346			if (cvs_var_set(optarg, ep) < 0)
347				exit(1);
348			break;
349		case 'T':
350			cvs_tmpdir = optarg;
351			break;
352		case 't':
353			cvs_trace = 1;
354			break;
355		case 'V':
356			/* don't override -Q */
357			if (verbosity)
358				verbosity = 2;
359			break;
360		case 'v':
361			printf("%s\n", CVS_VERSION);
362			exit(0);
363			/* NOTREACHED */
364			break;
365		case 'w':
366			cvs_readonly = 0;
367			break;
368		case 'x':
369			/*
370			 * Kerberos encryption support, kept for compatibility
371			 */
372			break;
373		case 'z':
374			cvs_compress = (int)strtol(optarg, &ep, 10);
375			if (*ep != '\0')
376				fatal("error parsing compression level");
377			if (cvs_compress < 0 || cvs_compress > 9)
378				fatal("gzip compression level must be "
379				    "between 0 and 9");
380			break;
381		default:
382			usage();
383			exit(1);
384		}
385	}
386
387	ret = optind;
388	optind = 1;
389	optreset = 1;	/* for next call */
390
391	return (ret);
392}
393
394/*
395 * cvs_read_rcfile()
396 *
397 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
398 * exists, it should contain a list of arguments that should always be given
399 * implicitly to the specified commands.
400 */
401static void
402cvs_read_rcfile(void)
403{
404	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
405	int linenum = 0;
406	size_t len;
407	struct cvs_cmd *cmdp;
408	FILE *fp;
409
410	if (cvs_path_cat(cvs_homedir, CVS_PATH_RC, rcpath, sizeof(rcpath))
411	    >= sizeof(rcpath)) {
412		cvs_log(LP_ERRNO, "%s", rcpath);
413		return;
414	}
415
416	fp = fopen(rcpath, "r");
417	if (fp == NULL) {
418		if (errno != ENOENT)
419			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
420			    strerror(errno));
421		return;
422	}
423
424	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
425		linenum++;
426		if ((len = strlen(linebuf)) == 0)
427			continue;
428		if (linebuf[len - 1] != '\n') {
429			cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
430				linenum);
431			break;
432		}
433		linebuf[--len] = '\0';
434
435		/* skip any whitespaces */
436		p = linebuf;
437		while (*p == ' ')
438			p++;
439
440		/* allow comments */
441		if (*p == '#')
442			continue;
443
444		lp = strchr(p, ' ');
445		if (lp == NULL)
446			continue;	/* ignore lines with no arguments */
447		*lp = '\0';
448		if (strcmp(p, "cvs") == 0) {
449			/*
450			 * Global default options.  In the case of cvs only,
451			 * we keep the 'cvs' string as first argument because
452			 * getopt() does not like starting at index 0 for
453			 * argument processing.
454			 */
455			*lp = ' ';
456			cvs_defargs = xstrdup(p);
457		} else {
458			lp++;
459			cmdp = cvs_findcmd(p);
460			if (cmdp == NULL) {
461				cvs_log(LP_NOTICE,
462				    "unknown command `%s' in `%s:%d'",
463				    p, rcpath, linenum);
464				continue;
465			}
466
467			cmdp->cmd_defargs = xstrdup(lp);
468		}
469	}
470
471	if (ferror(fp)) {
472		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
473	}
474
475	(void)fclose(fp);
476}
477
478/*
479 * cvs_var_set()
480 *
481 * Set the value of the variable <var> to <val>.  If there is no such variable,
482 * a new entry is created, otherwise the old value is overwritten.
483 * Returns 0 on success, or -1 on failure.
484 */
485int
486cvs_var_set(const char *var, const char *val)
487{
488	char *valcp;
489	const char *cp;
490	struct cvs_var *vp;
491
492	if (var == NULL || *var == '\0') {
493		cvs_log(LP_ERR, "no variable name");
494		return (-1);
495	}
496
497	/* sanity check on the name */
498	for (cp = var; *cp != '\0'; cp++)
499		if (!isalnum(*cp) && (*cp != '_')) {
500			cvs_log(LP_ERR,
501			    "variable name `%s' contains invalid characters",
502			    var);
503			return (-1);
504		}
505
506	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
507		if (strcmp(vp->cv_name, var) == 0)
508			break;
509
510	valcp = xstrdup(val);
511	if (vp == NULL) {
512		vp = xcalloc(1, sizeof(*vp));
513
514		vp->cv_name = xstrdup(var);
515		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
516
517	} else	/* free the previous value */
518		xfree(vp->cv_val);
519
520	vp->cv_val = valcp;
521
522	return (0);
523}
524
525/*
526 * cvs_var_set()
527 *
528 * Remove any entry for the variable <var>.
529 * Returns 0 on success, or -1 on failure.
530 */
531int
532cvs_var_unset(const char *var)
533{
534	struct cvs_var *vp;
535
536	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
537		if (strcmp(vp->cv_name, var) == 0) {
538			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
539			xfree(vp->cv_name);
540			xfree(vp->cv_val);
541			xfree(vp);
542			return (0);
543		}
544
545	return (-1);
546}
547
548/*
549 * cvs_var_get()
550 *
551 * Get the value associated with the variable <var>.  Returns a pointer to the
552 * value string on success, or NULL if the variable does not exist.
553 */
554
555const char *
556cvs_var_get(const char *var)
557{
558	struct cvs_var *vp;
559
560	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
561		if (strcmp(vp->cv_name, var) == 0)
562			return (vp->cv_val);
563
564	return (NULL);
565}
566