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