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