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