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