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