cvs.c revision 1.88
1/*	$OpenBSD: cvs.c,v 1.88 2005/12/10 20:27:45 joris 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		pw = getpwuid(getuid());
133		if (pw == NULL) {
134			cvs_log(LP_NOTICE,
135				"failed to get user's password entry");
136			exit(CVS_EX_DATA);
137		}
138		cvs_homedir = pw->pw_dir;
139	}
140
141	if ((envstr = getenv("TMPDIR")) != NULL)
142		cvs_tmpdir = envstr;
143
144	ret = cvs_getopt(argc, argv);
145
146	argc -= ret;
147	argv += ret;
148	if (argc == 0) {
149		usage();
150		exit(CVS_EX_USAGE);
151	}
152
153	cvs_command = argv[0];
154
155	/*
156	 * check the tmp dir, either specified through
157	 * the environment variable TMPDIR, or via
158	 * the global option -T <dir>
159	 */
160	if (stat(cvs_tmpdir, &st) == -1) {
161		cvs_log(LP_ERR, "failed to stat `%s'", cvs_tmpdir);
162		exit(CVS_EX_FILE);
163	} else if (!S_ISDIR(st.st_mode)) {
164		cvs_log(LP_ERR, "`%s' is not valid temporary directory",
165		    cvs_tmpdir);
166		exit(CVS_EX_FILE);
167	}
168
169	if (cvs_readrc == 1) {
170		cvs_read_rcfile();
171
172		if (cvs_defargs != NULL) {
173			targv = cvs_makeargv(cvs_defargs, &i);
174			if (targv == NULL) {
175				cvs_log(LP_ERR,
176				    "failed to load default arguments to %s",
177				    __progname);
178				exit(CVS_EX_DATA);
179			}
180
181			cvs_getopt(i, targv);
182			cvs_freeargv(targv, i);
183			xfree(targv);
184		}
185	}
186
187	/* setup signal handlers */
188	signal(SIGPIPE, SIG_IGN);
189
190	if (cvs_file_init() < 0) {
191		cvs_log(LP_ERR, "failed to initialize file support");
192		exit(CVS_EX_FILE);
193	}
194
195	ret = -1;
196
197	cmdp = cvs_findcmd(cvs_command);
198	if (cmdp == NULL) {
199		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
200		fprintf(stderr, "CVS commands are:\n");
201		for (i = 0; cvs_cdt[i] != NULL; i++)
202			fprintf(stderr, "\t%-16s%s\n",
203			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
204		exit(CVS_EX_USAGE);
205	}
206
207	cvs_cmdop = cmdp->cmd_op;
208
209	cmd_argc = 0;
210	memset(cmd_argv, 0, sizeof(cmd_argv));
211
212	cmd_argv[cmd_argc++] = argv[0];
213	if (cmdp->cmd_defargs != NULL) {
214		/* transform into a new argument vector */
215		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
216		    CVS_CMD_MAXARG - 1);
217		if (ret < 0) {
218			cvs_log(LP_ERRNO, "failed to generate argument vector "
219			    "from default arguments");
220			exit(CVS_EX_DATA);
221		}
222		cmd_argc += ret;
223	}
224	for (ret = 1; ret < argc; ret++)
225		cmd_argv[cmd_argc++] = argv[ret];
226
227	ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
228	switch (ret) {
229	case CVS_EX_USAGE:
230		fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
231		    cmdp->cmd_synopsis);
232		break;
233	case CVS_EX_DATA:
234		cvs_log(LP_ABORT, "internal data error");
235		break;
236	case CVS_EX_PROTO:
237		cvs_log(LP_ABORT, "protocol error");
238		break;
239	case CVS_EX_FILE:
240		cvs_log(LP_ABORT, "an operation on a file or directory failed");
241		break;
242	case CVS_EX_BADROOT:
243		/* match GNU CVS output, thus the LP_ERR and LP_ABORT codes. */
244		cvs_log(LP_ERR,
245		    "No CVSROOT specified! Please use the `-d' option");
246		cvs_log(LP_ABORT,
247		    "or set the CVSROOT enviroment variable.");
248		break;
249	case CVS_EX_ERR:
250		cvs_log(LP_ABORT, "yeah, we failed, and we don't know why");
251		break;
252	default:
253		break;
254	}
255
256	if (cvs_files != NULL)
257		cvs_file_free(cvs_files);
258	if (cvs_msg != NULL)
259		xfree(cvs_msg);
260
261	return (ret);
262}
263
264
265int
266cvs_getopt(int argc, char **argv)
267{
268	int ret;
269	char *ep;
270
271	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:T:tvwz:")) != -1) {
272		switch (ret) {
273		case 'b':
274			/*
275			 * We do not care about the bin directory for RCS files
276			 * as this program has no dependencies on RCS programs,
277			 * so it is only here for backwards compatibility.
278			 */
279			cvs_log(LP_NOTICE, "the -b argument is obsolete");
280			break;
281		case 'd':
282			cvs_rootstr = optarg;
283			break;
284		case 'e':
285			cvs_editor = optarg;
286			break;
287		case 'f':
288			cvs_readrc = 0;
289			break;
290		case 'l':
291			cvs_nolog = 1;
292			break;
293		case 'n':
294			cvs_noexec = 1;
295			break;
296		case 'Q':
297			verbosity = 0;
298			break;
299		case 'q':
300			/* don't override -Q */
301			if (verbosity > 1)
302				verbosity = 1;
303			break;
304		case 'r':
305			cvs_readonly = 1;
306			break;
307		case 's':
308			ep = strchr(optarg, '=');
309			if (ep == NULL) {
310				cvs_log(LP_ERR, "no = in variable assignment");
311				exit(CVS_EX_USAGE);
312			}
313			*(ep++) = '\0';
314			if (cvs_var_set(optarg, ep) < 0)
315				exit(CVS_EX_USAGE);
316			break;
317		case 'T':
318			cvs_tmpdir = optarg;
319			break;
320		case 't':
321			(void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
322			cvs_trace = 1;
323			break;
324		case 'v':
325			printf("%s\n", CVS_VERSION);
326			exit(0);
327			/* NOTREACHED */
328			break;
329		case 'w':
330			cvs_readonly = 0;
331			break;
332		case 'x':
333			/*
334			 * Kerberos encryption support, kept for compatibility
335			 */
336			break;
337		case 'z':
338			cvs_compress = (int)strtol(optarg, &ep, 10);
339			if (*ep != '\0')
340				errx(1, "error parsing compression level");
341			if (cvs_compress < 0 || cvs_compress > 9)
342				errx(1, "gzip compression level must be "
343				    "between 0 and 9");
344			break;
345		default:
346			usage();
347			exit(CVS_EX_USAGE);
348		}
349	}
350
351	ret = optind;
352	optind = 1;
353	optreset = 1;	/* for next call */
354
355	return (ret);
356}
357
358
359/*
360 * cvs_read_rcfile()
361 *
362 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
363 * exists, it should contain a list of arguments that should always be given
364 * implicitly to the specified commands.
365 */
366static void
367cvs_read_rcfile(void)
368{
369	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
370	int l, linenum = 0;
371	size_t len;
372	struct cvs_cmd *cmdp;
373	FILE *fp;
374
375	l = snprintf(rcpath, sizeof(rcpath), "%s/%s", cvs_homedir, CVS_PATH_RC);
376	if (l == -1 || l >= (int)sizeof(rcpath)) {
377		errno = ENAMETOOLONG;
378		cvs_log(LP_ERRNO, "%s", rcpath);
379		return;
380	}
381
382	fp = fopen(rcpath, "r");
383	if (fp == NULL) {
384		if (errno != ENOENT)
385			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
386			    strerror(errno));
387		return;
388	}
389
390	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
391		linenum++;
392		if ((len = strlen(linebuf)) == 0)
393			continue;
394		if (linebuf[len - 1] != '\n') {
395			cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
396				linenum);
397			break;
398		}
399		linebuf[--len] = '\0';
400
401		/* skip any whitespaces */
402		p = linebuf;
403		while (*p == ' ')
404			*p++;
405
406		/* allow comments */
407		if (*p == '#')
408			continue;
409
410		lp = strchr(p, ' ');
411		if (lp == NULL)
412			continue;	/* ignore lines with no arguments */
413		*lp = '\0';
414		if (strcmp(p, "cvs") == 0) {
415			/*
416			 * Global default options.  In the case of cvs only,
417			 * we keep the 'cvs' string as first argument because
418			 * getopt() does not like starting at index 0 for
419			 * argument processing.
420			 */
421			*lp = ' ';
422			cvs_defargs = xstrdup(p);
423		} else {
424			lp++;
425			cmdp = cvs_findcmd(p);
426			if (cmdp == NULL) {
427				cvs_log(LP_NOTICE,
428				    "unknown command `%s' in `%s:%d'",
429				    p, rcpath, linenum);
430				continue;
431			}
432
433			cmdp->cmd_defargs = xstrdup(lp);
434		}
435	}
436	if (ferror(fp)) {
437		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
438	}
439
440	(void)fclose(fp);
441}
442
443
444/*
445 * cvs_var_set()
446 *
447 * Set the value of the variable <var> to <val>.  If there is no such variable,
448 * a new entry is created, otherwise the old value is overwritten.
449 * Returns 0 on success, or -1 on failure.
450 */
451int
452cvs_var_set(const char *var, const char *val)
453{
454	char *valcp;
455	const char *cp;
456	struct cvs_var *vp;
457
458	if ((var == NULL) || (*var == '\0')) {
459		cvs_log(LP_ERR, "no variable name");
460		return (-1);
461	}
462
463	/* sanity check on the name */
464	for (cp = var; *cp != '\0'; cp++)
465		if (!isalnum(*cp) && (*cp != '_')) {
466			cvs_log(LP_ERR,
467			    "variable name `%s' contains invalid characters",
468			    var);
469			return (-1);
470		}
471
472	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
473		if (strcmp(vp->cv_name, var) == 0)
474			break;
475
476	valcp = xstrdup(val);
477	if (vp == NULL) {
478		vp = (struct cvs_var *)xmalloc(sizeof(*vp));
479		memset(vp, 0, sizeof(*vp));
480
481		vp->cv_name = xstrdup(var);
482		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
483
484	} else	/* free the previous value */
485		xfree(vp->cv_val);
486
487	vp->cv_val = valcp;
488
489	return (0);
490}
491
492
493/*
494 * cvs_var_set()
495 *
496 * Remove any entry for the variable <var>.
497 * Returns 0 on success, or -1 on failure.
498 */
499int
500cvs_var_unset(const char *var)
501{
502	struct cvs_var *vp;
503
504	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
505		if (strcmp(vp->cv_name, var) == 0) {
506			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
507			xfree(vp->cv_name);
508			xfree(vp->cv_val);
509			xfree(vp);
510			return (0);
511		}
512
513	return (-1);
514
515}
516
517
518/*
519 * cvs_var_get()
520 *
521 * Get the value associated with the variable <var>.  Returns a pointer to the
522 * value string on success, or NULL if the variable does not exist.
523 */
524
525const char *
526cvs_var_get(const char *var)
527{
528	struct cvs_var *vp;
529
530	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
531		if (strcmp(vp->cv_name, var) == 0)
532			return (vp->cv_val);
533
534	return (NULL);
535}
536