cvs.c revision 1.96
1/*	$OpenBSD: cvs.c,v 1.96 2006/04/05 01:38:55 ray Exp $	*/
2/*
3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. The name of the author may not be used to endorse or promote products
13 *    derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "includes.h"
28
29#include "cvs.h"
30#include "log.h"
31#include "file.h"
32
33
34extern char *__progname;
35
36
37/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
38int verbosity = 2;
39
40/* compression level used with zlib, 0 meaning no compression taking place */
41int   cvs_compress = 0;
42int   cvs_readrc = 1;		/* read .cvsrc on startup */
43int   cvs_trace = 0;
44int   cvs_nolog = 0;
45int   cvs_readonly = 0;
46int   cvs_nocase = 0;   /* set to 1 to disable filename case sensitivity */
47int   cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
48int   cvs_error = -1;	/* set to the correct error code on failure */
49char *cvs_defargs;		/* default global arguments from .cvsrc */
50char *cvs_command;		/* name of the command we are running */
51int   cvs_cmdop;
52char *cvs_rootstr;
53char *cvs_rsh = CVS_RSH_DEFAULT;
54char *cvs_editor = CVS_EDITOR_DEFAULT;
55char *cvs_homedir = NULL;
56char *cvs_msg = NULL;
57char *cvs_repo_base = NULL;
58char *cvs_tmpdir = CVS_TMPDIR_DEFAULT;
59
60/* hierarchy of all the files affected by the command */
61CVSFILE *cvs_files;
62
63static TAILQ_HEAD(, cvs_var) cvs_variables;
64
65
66void		usage(void);
67static void	cvs_read_rcfile(void);
68int		cvs_getopt(int, char **);
69
70/*
71 * usage()
72 *
73 * Display usage information.
74 */
75void
76usage(void)
77{
78	fprintf(stderr,
79	    "Usage: %s [-flnQqrtvw] [-d root] [-e editor] [-s var=val] "
80	    "[-T tmpdir] [-z level] command [...]\n", __progname);
81}
82
83
84int
85main(int argc, char **argv)
86{
87	char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
88	int i, ret, cmd_argc;
89	struct cvs_cmd *cmdp;
90	struct passwd *pw;
91	struct stat st;
92
93	tzset();
94
95	TAILQ_INIT(&cvs_variables);
96
97	cvs_log_init(LD_STD, 0);
98
99	/* by default, be very verbose */
100	(void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
101
102#ifdef DEBUG
103	(void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
104#endif
105
106	/* check environment so command-line options override it */
107	if ((envstr = getenv("CVS_RSH")) != NULL)
108		cvs_rsh = envstr;
109
110	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
111	    ((envstr = getenv("VISUAL")) != NULL) ||
112	    ((envstr = getenv("EDITOR")) != NULL))
113		cvs_editor = envstr;
114
115	if ((envstr = getenv("CVSREAD")) != NULL)
116		cvs_readonly = 1;
117
118	if ((cvs_homedir = getenv("HOME")) == NULL) {
119		if ((pw = getpwuid(getuid())) == NULL)
120			fatal("getpwuid failed");
121		cvs_homedir = pw->pw_dir;
122	}
123
124	if ((envstr = getenv("TMPDIR")) != NULL)
125		cvs_tmpdir = envstr;
126
127	ret = cvs_getopt(argc, argv);
128
129	argc -= ret;
130	argv += ret;
131	if (argc == 0) {
132		usage();
133		exit(CVS_EX_USAGE);
134	}
135
136	cvs_command = argv[0];
137
138	/*
139	 * check the tmp dir, either specified through
140	 * the environment variable TMPDIR, or via
141	 * the global option -T <dir>
142	 */
143	if (stat(cvs_tmpdir, &st) == -1)
144		fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
145	else if (!S_ISDIR(st.st_mode))
146		fatal("`%s' is not valid temporary directory", cvs_tmpdir);
147
148	if (cvs_readrc == 1) {
149		cvs_read_rcfile();
150
151		if (cvs_defargs != NULL) {
152			if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
153				fatal("failed to load default arguments to %s",
154				    __progname);
155
156			cvs_getopt(i, targv);
157			cvs_freeargv(targv, i);
158			xfree(targv);
159		}
160	}
161
162	/* setup signal handlers */
163	signal(SIGPIPE, SIG_IGN);
164
165	if (cvs_file_init() < 0)
166		fatal("failed to initialize file support");
167
168	ret = -1;
169
170	cmdp = cvs_findcmd(cvs_command);
171	if (cmdp == NULL) {
172		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
173		fprintf(stderr, "CVS commands are:\n");
174		for (i = 0; cvs_cdt[i] != NULL; i++)
175			fprintf(stderr, "\t%-16s%s\n",
176			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
177		exit(CVS_EX_USAGE);
178	}
179
180	cvs_cmdop = cmdp->cmd_op;
181
182	cmd_argc = 0;
183	memset(cmd_argv, 0, sizeof(cmd_argv));
184
185	cmd_argv[cmd_argc++] = argv[0];
186	if (cmdp->cmd_defargs != NULL) {
187		/* transform into a new argument vector */
188		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
189		    CVS_CMD_MAXARG - 1);
190		if (ret < 0)
191			fatal("main: cvs_getargv failed");
192
193		cmd_argc += ret;
194	}
195	for (ret = 1; ret < argc; ret++)
196		cmd_argv[cmd_argc++] = argv[ret];
197
198	ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
199	switch (ret) {
200	case CVS_EX_USAGE:
201		fprintf(stderr, "Usage: %s %s %s\n", __progname,
202		    cmdp->cmd_name, cmdp->cmd_synopsis);
203		break;
204	case CVS_EX_DATA:
205		cvs_log(LP_ABORT, "internal data error");
206		break;
207	case CVS_EX_PROTO:
208		cvs_log(LP_ABORT, "protocol error");
209		break;
210	case CVS_EX_FILE:
211		cvs_log(LP_ABORT, "an operation on a file or directory failed");
212		break;
213	case CVS_EX_BADROOT:
214		/* match GNU CVS output, thus the LP_ERR and LP_ABORT codes. */
215		cvs_log(LP_ERR,
216		    "No CVSROOT specified! Please use the `-d' option");
217		cvs_log(LP_ABORT,
218		    "or set the CVSROOT enviroment variable.");
219		break;
220	case CVS_EX_ERR:
221		cvs_log(LP_ABORT, "yeah, we failed, and we don't know why");
222		break;
223	default:
224		break;
225	}
226
227	if (cvs_files != NULL)
228		cvs_file_free(cvs_files);
229	if (cvs_msg != NULL)
230		xfree(cvs_msg);
231
232	return (ret);
233}
234
235
236int
237cvs_getopt(int argc, char **argv)
238{
239	int ret;
240	char *ep;
241
242	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:T:tvwz:")) != -1) {
243		switch (ret) {
244		case 'b':
245			/*
246			 * We do not care about the bin directory for RCS files
247			 * as this program has no dependencies on RCS programs,
248			 * so it is only here for backwards compatibility.
249			 */
250			cvs_log(LP_NOTICE, "the -b argument is obsolete");
251			break;
252		case 'd':
253			cvs_rootstr = optarg;
254			break;
255		case 'e':
256			cvs_editor = optarg;
257			break;
258		case 'f':
259			cvs_readrc = 0;
260			break;
261		case 'l':
262			cvs_nolog = 1;
263			break;
264		case 'n':
265			cvs_noexec = 1;
266			break;
267		case 'Q':
268			verbosity = 0;
269			break;
270		case 'q':
271			/* don't override -Q */
272			if (verbosity > 1)
273				verbosity = 1;
274			break;
275		case 'r':
276			cvs_readonly = 1;
277			break;
278		case 's':
279			ep = strchr(optarg, '=');
280			if (ep == NULL) {
281				cvs_log(LP_ERR, "no = in variable assignment");
282				exit(CVS_EX_USAGE);
283			}
284			*(ep++) = '\0';
285			if (cvs_var_set(optarg, ep) < 0)
286				exit(CVS_EX_USAGE);
287			break;
288		case 'T':
289			cvs_tmpdir = optarg;
290			break;
291		case 't':
292			(void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
293			cvs_trace = 1;
294			break;
295		case 'v':
296			printf("%s\n", CVS_VERSION);
297			exit(0);
298			/* NOTREACHED */
299			break;
300		case 'w':
301			cvs_readonly = 0;
302			break;
303		case 'x':
304			/*
305			 * Kerberos encryption support, kept for compatibility
306			 */
307			break;
308		case 'z':
309			cvs_compress = (int)strtol(optarg, &ep, 10);
310			if (*ep != '\0')
311				fatal("error parsing compression level");
312			if (cvs_compress < 0 || cvs_compress > 9)
313				fatal("gzip compression level must be "
314				    "between 0 and 9");
315			break;
316		default:
317			usage();
318			exit(CVS_EX_USAGE);
319		}
320	}
321
322	ret = optind;
323	optind = 1;
324	optreset = 1;	/* for next call */
325
326	return (ret);
327}
328
329
330/*
331 * cvs_read_rcfile()
332 *
333 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
334 * exists, it should contain a list of arguments that should always be given
335 * implicitly to the specified commands.
336 */
337static void
338cvs_read_rcfile(void)
339{
340	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
341	int linenum = 0;
342	size_t len;
343	struct cvs_cmd *cmdp;
344	FILE *fp;
345
346	if (strlcpy(rcpath, cvs_homedir, sizeof(rcpath)) >= sizeof(rcpath) ||
347	    strlcat(rcpath, "/", sizeof(rcpath)) >= sizeof(rcpath) ||
348	    strlcat(rcpath, CVS_PATH_RC, sizeof(rcpath)) >= sizeof(rcpath)) {
349		errno = ENAMETOOLONG;
350		cvs_log(LP_ERRNO, "%s", rcpath);
351		return;
352	}
353
354	fp = fopen(rcpath, "r");
355	if (fp == NULL) {
356		if (errno != ENOENT)
357			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
358			    strerror(errno));
359		return;
360	}
361
362	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
363		linenum++;
364		if ((len = strlen(linebuf)) == 0)
365			continue;
366		if (linebuf[len - 1] != '\n') {
367			cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
368				linenum);
369			break;
370		}
371		linebuf[--len] = '\0';
372
373		/* skip any whitespaces */
374		p = linebuf;
375		while (*p == ' ')
376			p++;
377
378		/* allow comments */
379		if (*p == '#')
380			continue;
381
382		lp = strchr(p, ' ');
383		if (lp == NULL)
384			continue;	/* ignore lines with no arguments */
385		*lp = '\0';
386		if (strcmp(p, "cvs") == 0) {
387			/*
388			 * Global default options.  In the case of cvs only,
389			 * we keep the 'cvs' string as first argument because
390			 * getopt() does not like starting at index 0 for
391			 * argument processing.
392			 */
393			*lp = ' ';
394			cvs_defargs = xstrdup(p);
395		} else {
396			lp++;
397			cmdp = cvs_findcmd(p);
398			if (cmdp == NULL) {
399				cvs_log(LP_NOTICE,
400				    "unknown command `%s' in `%s:%d'",
401				    p, rcpath, linenum);
402				continue;
403			}
404
405			cmdp->cmd_defargs = xstrdup(lp);
406		}
407	}
408	if (ferror(fp)) {
409		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
410	}
411
412	(void)fclose(fp);
413}
414
415
416/*
417 * cvs_var_set()
418 *
419 * Set the value of the variable <var> to <val>.  If there is no such variable,
420 * a new entry is created, otherwise the old value is overwritten.
421 * Returns 0 on success, or -1 on failure.
422 */
423int
424cvs_var_set(const char *var, const char *val)
425{
426	char *valcp;
427	const char *cp;
428	struct cvs_var *vp;
429
430	if ((var == NULL) || (*var == '\0')) {
431		cvs_log(LP_ERR, "no variable name");
432		return (-1);
433	}
434
435	/* sanity check on the name */
436	for (cp = var; *cp != '\0'; cp++)
437		if (!isalnum(*cp) && (*cp != '_')) {
438			cvs_log(LP_ERR,
439			    "variable name `%s' contains invalid characters",
440			    var);
441			return (-1);
442		}
443
444	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
445		if (strcmp(vp->cv_name, var) == 0)
446			break;
447
448	valcp = xstrdup(val);
449	if (vp == NULL) {
450		vp = xcalloc(1, sizeof(*vp));
451
452		vp->cv_name = xstrdup(var);
453		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
454
455	} else	/* free the previous value */
456		xfree(vp->cv_val);
457
458	vp->cv_val = valcp;
459
460	return (0);
461}
462
463
464/*
465 * cvs_var_set()
466 *
467 * Remove any entry for the variable <var>.
468 * Returns 0 on success, or -1 on failure.
469 */
470int
471cvs_var_unset(const char *var)
472{
473	struct cvs_var *vp;
474
475	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
476		if (strcmp(vp->cv_name, var) == 0) {
477			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
478			xfree(vp->cv_name);
479			xfree(vp->cv_val);
480			xfree(vp);
481			return (0);
482		}
483
484	return (-1);
485
486}
487
488
489/*
490 * cvs_var_get()
491 *
492 * Get the value associated with the variable <var>.  Returns a pointer to the
493 * value string on success, or NULL if the variable does not exist.
494 */
495
496const char *
497cvs_var_get(const char *var)
498{
499	struct cvs_var *vp;
500
501	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
502		if (strcmp(vp->cv_name, var) == 0)
503			return (vp->cv_val);
504
505	return (NULL);
506}
507