cvs.c revision 1.129
1219820Sjeff/*	$OpenBSD: cvs.c,v 1.129 2007/08/06 19:16:06 sobrado Exp $	*/
2219820Sjeff/*
3219820Sjeff * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org>
4219820Sjeff * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
5219820Sjeff * All rights reserved.
6219820Sjeff *
7219820Sjeff * Redistribution and use in source and binary forms, with or without
8219820Sjeff * modification, are permitted provided that the following conditions
9219820Sjeff * are met:
10219820Sjeff *
11219820Sjeff * 1. Redistributions of source code must retain the above copyright
12219820Sjeff *    notice, this list of conditions and the following disclaimer.
13219820Sjeff * 2. The name of the author may not be used to endorse or promote products
14219820Sjeff *    derived from this software without specific prior written permission.
15219820Sjeff *
16219820Sjeff * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17219820Sjeff * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18219820Sjeff * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19219820Sjeff * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20219820Sjeff * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21219820Sjeff * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22219820Sjeff * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23219820Sjeff * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24219820Sjeff * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25219820Sjeff * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26219820Sjeff */
27219820Sjeff
28219820Sjeff#include <sys/stat.h>
29219820Sjeff
30219820Sjeff#include <ctype.h>
31219820Sjeff#include <errno.h>
32219820Sjeff#include <pwd.h>
33219820Sjeff#include <stdlib.h>
34219820Sjeff#include <string.h>
35219820Sjeff#include <time.h>
36219820Sjeff#include <unistd.h>
37219820Sjeff
38219820Sjeff#include "cvs.h"
39219820Sjeff#include "remote.h"
40219820Sjeff
41219820Sjeffextern char *__progname;
42219820Sjeff
43219820Sjeff/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
44219820Sjeffint verbosity = 1;
45219820Sjeff
46219820Sjeff/* compression level used with zlib, 0 meaning no compression taking place */
47219820Sjeffint	cvs_compress = 0;
48219820Sjeffint	cvs_readrc = 1;		/* read .cvsrc on startup */
49219820Sjeffint	cvs_trace = 0;
50219820Sjeffint	cvs_nolog = 0;
51219820Sjeffint	cvs_readonly = 0;
52219820Sjeffint	cvs_readonlyfs = 0;
53219820Sjeffint	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
54219820Sjeffint	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
55219820Sjeffint	cvs_error = -1;	/* set to the correct error code on failure */
56219820Sjeffint	cvs_cmdop;
57219820Sjeffint	cvs_umask = CVS_UMASK_DEFAULT;
58219820Sjeffint	cvs_server_active = 0;
59219820Sjeff
60219820Sjeffchar	*cvs_tagname = NULL;
61219820Sjeffchar	*cvs_defargs;		/* default global arguments from .cvsrc */
62219820Sjeffchar	*cvs_command;		/* name of the command we are running */
63219820Sjeffchar	*cvs_rootstr;
64219820Sjeffchar	*cvs_rsh = CVS_RSH_DEFAULT;
65219820Sjeffchar	*cvs_editor = CVS_EDITOR_DEFAULT;
66219820Sjeffchar	*cvs_homedir = NULL;
67219820Sjeffchar	*cvs_msg = NULL;
68219820Sjeffchar	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
69219820Sjeff
70219820Sjeffstruct cvsroot *current_cvsroot = NULL;
71219820Sjeff
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	if (argc + cmd_argc >= CVS_CMD_MAXARG)
237		fatal("main: too many arguments for `%s'", cmd_argv[0]);
238	for (ret = 1; ret < argc; ret++)
239		cmd_argv[cmd_argc++] = argv[ret];
240
241	cvs_file_init();
242
243	if (cvs_cmdop == CVS_OP_SERVER) {
244		if (cmd_argc > 1)
245			fatal("server does not take any extra arguments");
246
247		setvbuf(stdin, NULL, _IOLBF, 0);
248		setvbuf(stdout, NULL, _IOLBF, 0);
249
250		cvs_server_active = 1;
251		root = cvs_remote_input();
252		if ((rootp = strchr(root, ' ')) == NULL)
253			fatal("bad Root request");
254		cvs_rootstr = xstrdup(rootp + 1);
255		xfree(root);
256	}
257
258	if ((current_cvsroot = cvsroot_get(".")) == NULL) {
259		cvs_log(LP_ERR,
260		    "No CVSROOT specified! Please use the '-d' option");
261		fatal("or set the CVSROOT environment variable.");
262	}
263
264	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
265		cmdp->cmd(cmd_argc, cmd_argv);
266		cvs_cleanup();
267		return (0);
268	}
269
270	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
271	    current_cvsroot->cr_dir, CVS_PATH_ROOT);
272
273	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
274		if (errno == ENOENT)
275			fatal("repository '%s' does not exist",
276			    current_cvsroot->cr_dir);
277		else
278			fatal("%s: %s", current_cvsroot->cr_dir,
279			    strerror(errno));
280	} else {
281		if (!S_ISDIR(st.st_mode))
282			fatal("'%s' is not a directory",
283			    current_cvsroot->cr_dir);
284	}
285
286	if (cvs_cmdop != CVS_OP_INIT)
287		cvs_parse_configfile();
288
289	umask(cvs_umask);
290
291	cmdp->cmd(cmd_argc, cmd_argv);
292	cvs_cleanup();
293
294	return (0);
295}
296
297int
298cvs_getopt(int argc, char **argv)
299{
300	int ret;
301	char *ep;
302	const char *errstr;
303
304	while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tVvwxz:")) != -1) {
305		switch (ret) {
306		case 'b':
307			/*
308			 * We do not care about the bin directory for RCS files
309			 * as this program has no dependencies on RCS programs,
310			 * so it is only here for backwards compatibility.
311			 */
312			cvs_log(LP_NOTICE, "the -b argument is obsolete");
313			break;
314		case 'd':
315			cvs_rootstr = optarg;
316			break;
317		case 'e':
318			cvs_editor = optarg;
319			break;
320		case 'f':
321			cvs_readrc = 0;
322			break;
323		case 'l':
324			cvs_nolog = 1;
325			break;
326		case 'n':
327			cvs_noexec = 1;
328			cvs_nolog = 1;
329			break;
330		case 'Q':
331			verbosity = 0;
332			break;
333		case 'q':
334			/*
335			 * Be quiet. This is the default in OpenCVS.
336			 */
337			break;
338		case 'R':
339			cvs_readonlyfs = 1;
340			cvs_nolog = 1;
341			break;
342		case 'r':
343			cvs_readonly = 1;
344			break;
345		case 's':
346			ep = strchr(optarg, '=');
347			if (ep == NULL) {
348				cvs_log(LP_ERR, "no = in variable assignment");
349				exit(1);
350			}
351			*(ep++) = '\0';
352			if (cvs_var_set(optarg, ep) < 0)
353				exit(1);
354			break;
355		case 'T':
356			cvs_tmpdir = optarg;
357			break;
358		case 't':
359			cvs_trace = 1;
360			break;
361		case 'V':
362			/* don't override -Q */
363			if (verbosity)
364				verbosity = 2;
365			break;
366		case 'v':
367			printf("%s\n", CVS_VERSION);
368			exit(0);
369			/* NOTREACHED */
370			break;
371		case 'w':
372			cvs_readonly = 0;
373			break;
374		case 'x':
375			/*
376			 * Kerberos encryption support, kept for compatibility
377			 */
378			break;
379		case 'z':
380			cvs_compress = strtonum(optarg, 0, 9, &errstr);
381			if (errstr != NULL)
382				fatal("cvs_compress: %s", errstr);
383			break;
384		default:
385			usage();
386			/* NOTREACHED */
387		}
388	}
389
390	ret = optind;
391	optind = 1;
392	optreset = 1;	/* for next call */
393
394	return (ret);
395}
396
397/*
398 * cvs_read_rcfile()
399 *
400 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
401 * exists, it should contain a list of arguments that should always be given
402 * implicitly to the specified commands.
403 */
404static void
405cvs_read_rcfile(void)
406{
407	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
408	int i, linenum;
409	size_t len;
410	struct cvs_cmd *cmdp;
411	FILE *fp;
412
413	linenum = 0;
414
415	i = snprintf(rcpath, MAXPATHLEN, "%s/%s", cvs_homedir, CVS_PATH_RC);
416	if (i < 0 || i >= MAXPATHLEN) {
417		cvs_log(LP_ERRNO, "%s", rcpath);
418		return;
419	}
420
421	fp = fopen(rcpath, "r");
422	if (fp == NULL) {
423		if (errno != ENOENT)
424			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
425			    strerror(errno));
426		return;
427	}
428
429	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
430		linenum++;
431		if ((len = strlen(linebuf)) == 0)
432			continue;
433		if (linebuf[len - 1] != '\n') {
434			cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
435				linenum);
436			break;
437		}
438		linebuf[--len] = '\0';
439
440		/* skip any whitespaces */
441		p = linebuf;
442		while (*p == ' ')
443			p++;
444
445		/* allow comments */
446		if (*p == '#')
447			continue;
448
449		lp = strchr(p, ' ');
450		if (lp == NULL)
451			continue;	/* ignore lines with no arguments */
452		*lp = '\0';
453		if (strcmp(p, "cvs") == 0) {
454			/*
455			 * Global default options.  In the case of cvs only,
456			 * we keep the 'cvs' string as first argument because
457			 * getopt() does not like starting at index 0 for
458			 * argument processing.
459			 */
460			*lp = ' ';
461			cvs_defargs = xstrdup(p);
462		} else {
463			lp++;
464			cmdp = cvs_findcmd(p);
465			if (cmdp == NULL) {
466				cvs_log(LP_NOTICE,
467				    "unknown command `%s' in `%s:%d'",
468				    p, rcpath, linenum);
469				continue;
470			}
471
472			cmdp->cmd_defargs = xstrdup(lp);
473		}
474	}
475
476	if (ferror(fp)) {
477		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
478	}
479
480	(void)fclose(fp);
481}
482
483/*
484 * cvs_var_set()
485 *
486 * Set the value of the variable <var> to <val>.  If there is no such variable,
487 * a new entry is created, otherwise the old value is overwritten.
488 * Returns 0 on success, or -1 on failure.
489 */
490int
491cvs_var_set(const char *var, const char *val)
492{
493	char *valcp;
494	const char *cp;
495	struct cvs_var *vp;
496
497	if (var == NULL || *var == '\0') {
498		cvs_log(LP_ERR, "no variable name");
499		return (-1);
500	}
501
502	/* sanity check on the name */
503	for (cp = var; *cp != '\0'; cp++)
504		if (!isalnum(*cp) && (*cp != '_')) {
505			cvs_log(LP_ERR,
506			    "variable name `%s' contains invalid characters",
507			    var);
508			return (-1);
509		}
510
511	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
512		if (strcmp(vp->cv_name, var) == 0)
513			break;
514
515	valcp = xstrdup(val);
516	if (vp == NULL) {
517		vp = xcalloc(1, sizeof(*vp));
518
519		vp->cv_name = xstrdup(var);
520		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
521
522	} else	/* free the previous value */
523		xfree(vp->cv_val);
524
525	vp->cv_val = valcp;
526
527	return (0);
528}
529
530/*
531 * cvs_var_unset()
532 *
533 * Remove any entry for the variable <var>.
534 * Returns 0 on success, or -1 on failure.
535 */
536int
537cvs_var_unset(const char *var)
538{
539	struct cvs_var *vp;
540
541	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
542		if (strcmp(vp->cv_name, var) == 0) {
543			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
544			xfree(vp->cv_name);
545			xfree(vp->cv_val);
546			xfree(vp);
547			return (0);
548		}
549
550	return (-1);
551}
552
553/*
554 * cvs_var_get()
555 *
556 * Get the value associated with the variable <var>.  Returns a pointer to the
557 * value string on success, or NULL if the variable does not exist.
558 */
559
560const char *
561cvs_var_get(const char *var)
562{
563	struct cvs_var *vp;
564
565	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
566		if (strcmp(vp->cv_name, var) == 0)
567			return (vp->cv_val);
568
569	return (NULL);
570}
571