cvs.c revision 1.69
1/*	$OpenBSD: cvs.c,v 1.69 2005/06/01 17:44:34 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/*
83 * usage()
84 *
85 * Display usage information.
86 */
87void
88usage(void)
89{
90	fprintf(stderr,
91	    "Usage: %s [-flnQqtv] [-d root] [-e editor] [-s var=val] [-z level] "
92	    "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
103	TAILQ_INIT(&cvs_variables);
104
105	if (cvs_log_init(LD_STD, 0) < 0)
106		err(1, "failed to initialize logging");
107
108	/* by default, be very verbose */
109	(void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
110
111#ifdef DEBUG
112	(void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
113#endif
114
115	cvs_strtab_init();
116
117	/* check environment so command-line options override it */
118	if ((envstr = getenv("CVS_RSH")) != NULL)
119		cvs_rsh = envstr;
120
121	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
122	    ((envstr = getenv("VISUAL")) != NULL) ||
123	    ((envstr = getenv("EDITOR")) != NULL))
124		cvs_editor = envstr;
125
126	ret = cvs_getopt(argc, argv);
127
128	argc -= ret;
129	argv += ret;
130	if (argc == 0) {
131		usage();
132		exit(1);
133	}
134	cvs_command = argv[0];
135
136	if (cvs_readrc) {
137		cvs_read_rcfile();
138
139		if (cvs_defargs != NULL) {
140			targv = cvs_makeargv(cvs_defargs, &i);
141			if (targv == NULL) {
142				cvs_log(LP_ERR,
143				    "failed to load default arguments to %s",
144				    __progname);
145				exit(1);
146			}
147
148			cvs_getopt(i, targv);
149			cvs_freeargv(targv, i);
150			free(targv);
151		}
152	}
153
154	/* setup signal handlers */
155	signal(SIGPIPE, SIG_IGN);
156
157	if (cvs_file_init() < 0) {
158		cvs_log(LP_ERR, "failed to initialize file support");
159		exit(1);
160	}
161
162	ret = -1;
163
164	cmdp = cvs_findcmd(cvs_command);
165	if (cmdp == NULL) {
166		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
167		fprintf(stderr, "CVS commands are:\n");
168		for (i = 0; cvs_cdt[i] != NULL; i++)
169			fprintf(stderr, "\t%-16s%s\n",
170			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
171		exit(CVS_EX_USAGE);
172	}
173
174	cvs_cmdop = cmdp->cmd_op;
175
176	cmd_argc = 0;
177	memset(cmd_argv, 0, sizeof(cmd_argv));
178
179	cmd_argv[cmd_argc++] = argv[0];
180	if (cmdp->cmd_defargs != NULL) {
181		/* transform into a new argument vector */
182		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
183		    CVS_CMD_MAXARG - 1);
184		if (ret < 0) {
185			cvs_log(LP_ERRNO, "failed to generate argument vector "
186			    "from default arguments");
187			exit(1);
188		}
189		cmd_argc += ret;
190	}
191	for (ret = 1; ret < argc; ret++)
192		cmd_argv[cmd_argc++] = argv[ret];
193
194	ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
195	switch (ret) {
196	case CVS_EX_USAGE:
197		fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
198		    cmdp->cmd_synopsis);
199		break;
200	case CVS_EX_DATA:
201		cvs_log(LP_ABORT, "internal data error");
202		break;
203	case CVS_EX_PROTO:
204		cvs_log(LP_ABORT, "protocol error");
205		break;
206	case CVS_EX_FILE:
207		cvs_log(LP_ABORT, "an operation on a file or directory failed");
208		break;
209	case CVS_EX_BADROOT:
210		cvs_log(LP_ABORT,
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;
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		lp = strchr(linebuf, ' ');
368		if (lp == NULL)
369			continue;	/* ignore lines with no arguments */
370		*lp = '\0';
371		if (strcmp(linebuf, "cvs") == 0) {
372			/*
373			 * Global default options.  In the case of cvs only,
374			 * we keep the 'cvs' string as first argument because
375			 * getopt() does not like starting at index 0 for
376			 * argument processing.
377			 */
378			*lp = ' ';
379			cvs_defargs = strdup(linebuf);
380			if (cvs_defargs == NULL)
381				cvs_log(LP_ERRNO,
382				    "failed to copy global arguments");
383		} else {
384			lp++;
385			cmdp = cvs_findcmd(linebuf);
386			if (cmdp == NULL) {
387				cvs_log(LP_NOTICE,
388				    "unknown command `%s' in `%s:%d'",
389				    linebuf, rcpath, linenum);
390				continue;
391			}
392
393			cmdp->cmd_defargs = strdup(lp);
394			if (cmdp->cmd_defargs == NULL)
395				cvs_log(LP_ERRNO,
396				    "failed to copy default arguments for %s",
397				    cmdp->cmd_name);
398		}
399	}
400	if (ferror(fp)) {
401		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
402	}
403
404	(void)fclose(fp);
405}
406
407
408/*
409 * cvs_var_set()
410 *
411 * Set the value of the variable <var> to <val>.  If there is no such variable,
412 * a new entry is created, otherwise the old value is overwritten.
413 * Returns 0 on success, or -1 on failure.
414 */
415int
416cvs_var_set(const char *var, const char *val)
417{
418	char *valcp;
419	const char *cp;
420	struct cvs_var *vp;
421
422	if ((var == NULL) || (*var == '\0')) {
423		cvs_log(LP_ERR, "no variable name");
424		return (-1);
425	}
426
427	/* sanity check on the name */
428	for (cp = var; *cp != '\0'; cp++)
429		if (!isalnum(*cp) && (*cp != '_')) {
430			cvs_log(LP_ERR,
431			    "variable name `%s' contains invalid characters",
432			    var);
433			return (-1);
434		}
435
436	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
437		if (strcmp(vp->cv_name, var) == 0)
438			break;
439
440	valcp = strdup(val);
441	if (valcp == NULL) {
442		cvs_log(LP_ERRNO, "failed to allocate variable");
443		return (-1);
444	}
445
446	if (vp == NULL) {
447		vp = (struct cvs_var *)malloc(sizeof(*vp));
448		if (vp == NULL) {
449			cvs_log(LP_ERRNO, "failed to allocate variable");
450			free(valcp);
451			return (-1);
452		}
453		memset(vp, 0, sizeof(*vp));
454
455		vp->cv_name = strdup(var);
456		if (vp->cv_name == NULL) {
457			cvs_log(LP_ERRNO, "failed to allocate variable");
458			free(valcp);
459			free(vp);
460			return (-1);
461		}
462
463		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
464
465	} else	/* free the previous value */
466		free(vp->cv_val);
467
468	vp->cv_val = valcp;
469
470	return (0);
471}
472
473
474/*
475 * cvs_var_set()
476 *
477 * Remove any entry for the variable <var>.
478 * Returns 0 on success, or -1 on failure.
479 */
480int
481cvs_var_unset(const char *var)
482{
483	struct cvs_var *vp;
484
485	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
486		if (strcmp(vp->cv_name, var) == 0) {
487			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
488			free(vp->cv_name);
489			free(vp->cv_val);
490			free(vp);
491			return (0);
492		}
493
494	return (-1);
495
496}
497
498
499/*
500 * cvs_var_get()
501 *
502 * Get the value associated with the variable <var>.  Returns a pointer to the
503 * value string on success, or NULL if the variable does not exist.
504 */
505
506const char*
507cvs_var_get(const char *var)
508{
509	struct cvs_var *vp;
510
511	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
512		if (strcmp(vp->cv_name, var) == 0)
513			return (vp->cv_val);
514
515	return (NULL);
516}
517