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