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