xref: /openbsd-src/usr.bin/cvs/cvs.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: cvs.c,v 1.113 2007/01/11 02:35:55 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 
36 extern char *__progname;
37 
38 /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
39 int verbosity = 1;
40 
41 /* compression level used with zlib, 0 meaning no compression taking place */
42 int	cvs_compress = 0;
43 int	cvs_readrc = 1;		/* read .cvsrc on startup */
44 int	cvs_trace = 0;
45 int	cvs_nolog = 0;
46 int	cvs_readonly = 0;
47 int	cvs_readonlyfs = 0;
48 int	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
49 int	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
50 int	cvs_error = -1;	/* set to the correct error code on failure */
51 int	cvs_cmdop;
52 int	cvs_umask = CVS_UMASK_DEFAULT;
53 int	cvs_server_active = 0;
54 
55 char	*cvs_tagname = NULL;
56 char	*cvs_defargs;		/* default global arguments from .cvsrc */
57 char	*cvs_command;		/* name of the command we are running */
58 char	*cvs_rootstr;
59 char	*cvs_rsh = CVS_RSH_DEFAULT;
60 char	*cvs_editor = CVS_EDITOR_DEFAULT;
61 char	*cvs_homedir = NULL;
62 char	*cvs_msg = NULL;
63 char	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
64 
65 struct cvsroot *current_cvsroot = NULL;
66 
67 int		cvs_getopt(int, char **);
68 void		usage(void);
69 static void	cvs_read_rcfile(void);
70 
71 struct cvs_wklhead temp_files;
72 
73 void sighandler(int);
74 volatile sig_atomic_t cvs_quit = 0;
75 volatile sig_atomic_t sig_received = 0;
76 
77 void
78 sighandler(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 
93 void
94 cvs_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 
111 void
112 usage(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 
119 int
120 main(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 
288 int
289 cvs_getopt(int argc, char **argv)
290 {
291 	int ret;
292 	char *ep;
293 
294 	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqRrs:T:tvVwz:")) != -1) {
295 		switch (ret) {
296 		case 'b':
297 			/*
298 			 * We do not care about the bin directory for RCS files
299 			 * as this program has no dependencies on RCS programs,
300 			 * so it is only here for backwards compatibility.
301 			 */
302 			cvs_log(LP_NOTICE, "the -b argument is obsolete");
303 			break;
304 		case 'd':
305 			cvs_rootstr = optarg;
306 			break;
307 		case 'e':
308 			cvs_editor = optarg;
309 			break;
310 		case 'f':
311 			cvs_readrc = 0;
312 			break;
313 		case 'l':
314 			cvs_nolog = 1;
315 			break;
316 		case 'n':
317 			cvs_noexec = 1;
318 			cvs_nolog = 1;
319 			break;
320 		case 'Q':
321 			verbosity = 0;
322 			break;
323 		case 'q':
324 			/*
325 			 * Be quiet. This is the default in OpenCVS.
326 			 */
327 			break;
328 		case 'R':
329 			cvs_readonlyfs = 1;
330 			cvs_nolog = 1;
331 			break;
332 		case 'r':
333 			cvs_readonly = 1;
334 			break;
335 		case 's':
336 			ep = strchr(optarg, '=');
337 			if (ep == NULL) {
338 				cvs_log(LP_ERR, "no = in variable assignment");
339 				exit(1);
340 			}
341 			*(ep++) = '\0';
342 			if (cvs_var_set(optarg, ep) < 0)
343 				exit(1);
344 			break;
345 		case 'T':
346 			cvs_tmpdir = optarg;
347 			break;
348 		case 't':
349 			cvs_trace = 1;
350 			break;
351 		case 'V':
352 			/* don't override -Q */
353 			if (verbosity)
354 				verbosity = 2;
355 			break;
356 		case 'v':
357 			printf("%s\n", CVS_VERSION);
358 			exit(0);
359 			/* NOTREACHED */
360 			break;
361 		case 'w':
362 			cvs_readonly = 0;
363 			break;
364 		case 'x':
365 			/*
366 			 * Kerberos encryption support, kept for compatibility
367 			 */
368 			break;
369 		case 'z':
370 			cvs_compress = (int)strtol(optarg, &ep, 10);
371 			if (*ep != '\0')
372 				fatal("error parsing compression level");
373 			if (cvs_compress < 0 || cvs_compress > 9)
374 				fatal("gzip compression level must be "
375 				    "between 0 and 9");
376 			break;
377 		default:
378 			usage();
379 			exit(1);
380 		}
381 	}
382 
383 	ret = optind;
384 	optind = 1;
385 	optreset = 1;	/* for next call */
386 
387 	return (ret);
388 }
389 
390 /*
391  * cvs_read_rcfile()
392  *
393  * Read the CVS `.cvsrc' file in the user's home directory.  If the file
394  * exists, it should contain a list of arguments that should always be given
395  * implicitly to the specified commands.
396  */
397 static void
398 cvs_read_rcfile(void)
399 {
400 	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
401 	int linenum = 0;
402 	size_t len;
403 	struct cvs_cmd *cmdp;
404 	FILE *fp;
405 
406 	if (cvs_path_cat(cvs_homedir, CVS_PATH_RC, rcpath, sizeof(rcpath))
407 	    >= sizeof(rcpath)) {
408 		cvs_log(LP_ERRNO, "%s", rcpath);
409 		return;
410 	}
411 
412 	fp = fopen(rcpath, "r");
413 	if (fp == NULL) {
414 		if (errno != ENOENT)
415 			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
416 			    strerror(errno));
417 		return;
418 	}
419 
420 	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
421 		linenum++;
422 		if ((len = strlen(linebuf)) == 0)
423 			continue;
424 		if (linebuf[len - 1] != '\n') {
425 			cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
426 				linenum);
427 			break;
428 		}
429 		linebuf[--len] = '\0';
430 
431 		/* skip any whitespaces */
432 		p = linebuf;
433 		while (*p == ' ')
434 			p++;
435 
436 		/* allow comments */
437 		if (*p == '#')
438 			continue;
439 
440 		lp = strchr(p, ' ');
441 		if (lp == NULL)
442 			continue;	/* ignore lines with no arguments */
443 		*lp = '\0';
444 		if (strcmp(p, "cvs") == 0) {
445 			/*
446 			 * Global default options.  In the case of cvs only,
447 			 * we keep the 'cvs' string as first argument because
448 			 * getopt() does not like starting at index 0 for
449 			 * argument processing.
450 			 */
451 			*lp = ' ';
452 			cvs_defargs = xstrdup(p);
453 		} else {
454 			lp++;
455 			cmdp = cvs_findcmd(p);
456 			if (cmdp == NULL) {
457 				cvs_log(LP_NOTICE,
458 				    "unknown command `%s' in `%s:%d'",
459 				    p, rcpath, linenum);
460 				continue;
461 			}
462 
463 			cmdp->cmd_defargs = xstrdup(lp);
464 		}
465 	}
466 
467 	if (ferror(fp)) {
468 		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
469 	}
470 
471 	(void)fclose(fp);
472 }
473 
474 /*
475  * cvs_var_set()
476  *
477  * Set the value of the variable <var> to <val>.  If there is no such variable,
478  * a new entry is created, otherwise the old value is overwritten.
479  * Returns 0 on success, or -1 on failure.
480  */
481 int
482 cvs_var_set(const char *var, const char *val)
483 {
484 	char *valcp;
485 	const char *cp;
486 	struct cvs_var *vp;
487 
488 	if (var == NULL || *var == '\0') {
489 		cvs_log(LP_ERR, "no variable name");
490 		return (-1);
491 	}
492 
493 	/* sanity check on the name */
494 	for (cp = var; *cp != '\0'; cp++)
495 		if (!isalnum(*cp) && (*cp != '_')) {
496 			cvs_log(LP_ERR,
497 			    "variable name `%s' contains invalid characters",
498 			    var);
499 			return (-1);
500 		}
501 
502 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
503 		if (strcmp(vp->cv_name, var) == 0)
504 			break;
505 
506 	valcp = xstrdup(val);
507 	if (vp == NULL) {
508 		vp = xcalloc(1, sizeof(*vp));
509 
510 		vp->cv_name = xstrdup(var);
511 		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
512 
513 	} else	/* free the previous value */
514 		xfree(vp->cv_val);
515 
516 	vp->cv_val = valcp;
517 
518 	return (0);
519 }
520 
521 /*
522  * cvs_var_set()
523  *
524  * Remove any entry for the variable <var>.
525  * Returns 0 on success, or -1 on failure.
526  */
527 int
528 cvs_var_unset(const char *var)
529 {
530 	struct cvs_var *vp;
531 
532 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
533 		if (strcmp(vp->cv_name, var) == 0) {
534 			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
535 			xfree(vp->cv_name);
536 			xfree(vp->cv_val);
537 			xfree(vp);
538 			return (0);
539 		}
540 
541 	return (-1);
542 }
543 
544 /*
545  * cvs_var_get()
546  *
547  * Get the value associated with the variable <var>.  Returns a pointer to the
548  * value string on success, or NULL if the variable does not exist.
549  */
550 
551 const char *
552 cvs_var_get(const char *var)
553 {
554 	struct cvs_var *vp;
555 
556 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
557 		if (strcmp(vp->cv_name, var) == 0)
558 			return (vp->cv_val);
559 
560 	return (NULL);
561 }
562