xref: /openbsd-src/usr.bin/cvs/cvs.c (revision 2f43a3f5488752f16be4ec77bf146703768436fa)
1*2f43a3f5Sderaadt /*	$OpenBSD: cvs.c,v 1.160 2021/01/27 07:18:16 deraadt Exp $	*/
26c121f58Sjfb /*
380f6ca9bSjoris  * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org>
46c121f58Sjfb  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
56c121f58Sjfb  * All rights reserved.
66c121f58Sjfb  *
76c121f58Sjfb  * Redistribution and use in source and binary forms, with or without
86c121f58Sjfb  * modification, are permitted provided that the following conditions
96c121f58Sjfb  * are met:
106c121f58Sjfb  *
116c121f58Sjfb  * 1. Redistributions of source code must retain the above copyright
126c121f58Sjfb  *    notice, this list of conditions and the following disclaimer.
136c121f58Sjfb  * 2. The name of the author may not be used to endorse or promote products
146c121f58Sjfb  *    derived from this software without specific prior written permission.
156c121f58Sjfb  *
166c121f58Sjfb  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
176c121f58Sjfb  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
186c121f58Sjfb  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
196c121f58Sjfb  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
206c121f58Sjfb  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
216c121f58Sjfb  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
226c121f58Sjfb  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
236c121f58Sjfb  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
246c121f58Sjfb  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
256c121f58Sjfb  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
266c121f58Sjfb  */
276c121f58Sjfb 
281f8531bdSotto #include <sys/stat.h>
291f8531bdSotto 
301f8531bdSotto #include <ctype.h>
311f8531bdSotto #include <errno.h>
321f8531bdSotto #include <pwd.h>
331f8531bdSotto #include <stdlib.h>
341f8531bdSotto #include <string.h>
351e29f7ddSxsa #include <time.h>
361f8531bdSotto #include <unistd.h>
377f453672Sderaadt #include <err.h>
386c121f58Sjfb 
396c121f58Sjfb #include "cvs.h"
409fac60a5Sjoris #include "remote.h"
418fbb14c0Sjoris #include "hash.h"
426c121f58Sjfb 
436c121f58Sjfb extern char *__progname;
446c121f58Sjfb 
456c121f58Sjfb /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
4660c775bfSjoris int verbosity = 2;
476c121f58Sjfb 
486c121f58Sjfb /* compression level used with zlib, 0 meaning no compression taking place */
496c121f58Sjfb int	cvs_compress = 0;
50ca1d3256Sjfb int	cvs_readrc = 1;		/* read .cvsrc on startup */
516c121f58Sjfb int	cvs_trace = 0;
526c121f58Sjfb int	cvs_nolog = 0;
536c121f58Sjfb int	cvs_readonly = 0;
543dcb84b2Sxsa int	cvs_readonlyfs = 0;
55ca1d3256Sjfb int	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
560bda20e2Sxsa int	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
573ad3fb45Sjoris int	cvs_cmdop;
5818911d1eSjoris int	cvs_umask = CVS_UMASK_DEFAULT;
599fac60a5Sjoris int	cvs_server_active = 0;
603ad3fb45Sjoris 
6118911d1eSjoris char	*cvs_tagname = NULL;
62ca1d3256Sjfb char	*cvs_defargs;		/* default global arguments from .cvsrc */
636c121f58Sjfb char	*cvs_rootstr;
646c121f58Sjfb char	*cvs_rsh = CVS_RSH_DEFAULT;
656c121f58Sjfb char	*cvs_editor = CVS_EDITOR_DEFAULT;
6658cbd44dSxsa char	*cvs_homedir = NULL;
67e5cbdd76Sxsa char	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
68cf397ae7Skrapht 
693ad3fb45Sjoris struct cvsroot *current_cvsroot = NULL;
709d091990Stobias struct cvs_cmd *cmdp;			/* struct of command we are running */
7193231021Sjfb 
723ad3fb45Sjoris int		cvs_getopt(int, char **);
7345357ec5Sxsa __dead void	usage(void);
745db46beeSjoris static void	cvs_read_rcfile(void);
756c121f58Sjfb 
76*2f43a3f5Sderaadt struct cvs_varhead cvs_variables;
77*2f43a3f5Sderaadt 
787a9e6d11Sray struct wklhead temp_files;
793ad3fb45Sjoris 
803ad3fb45Sjoris void sighandler(int);
813ad3fb45Sjoris volatile sig_atomic_t cvs_quit = 0;
823ad3fb45Sjoris volatile sig_atomic_t sig_received = 0;
833ad3fb45Sjoris 
847c1a09a6Sjoris extern CVSENTRIES *current_list;
857c1a09a6Sjoris 
868fbb14c0Sjoris struct hash_table created_directories;
878fbb14c0Sjoris struct hash_table created_cvs_directories;
888fbb14c0Sjoris 
893ad3fb45Sjoris void
sighandler(int sig)903ad3fb45Sjoris sighandler(int sig)
913ad3fb45Sjoris {
923ad3fb45Sjoris 	sig_received = sig;
933ad3fb45Sjoris 
943ad3fb45Sjoris 	switch (sig) {
953ad3fb45Sjoris 	case SIGINT:
963ad3fb45Sjoris 	case SIGTERM:
97d1d63b6eSjoris 	case SIGPIPE:
983ad3fb45Sjoris 		cvs_quit = 1;
993ad3fb45Sjoris 		break;
1003ad3fb45Sjoris 	default:
1013ad3fb45Sjoris 		break;
1023ad3fb45Sjoris 	}
1033ad3fb45Sjoris }
1043ad3fb45Sjoris 
1053ad3fb45Sjoris void
cvs_cleanup(void)1063ad3fb45Sjoris cvs_cleanup(void)
1073ad3fb45Sjoris {
1083ad3fb45Sjoris 	cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
1097a9e6d11Sray 	worklist_run(&repo_locks, worklist_unlink);
1103ad3fb45Sjoris 
1113ad3fb45Sjoris 	cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
1127a9e6d11Sray 	worklist_run(&temp_files, worklist_unlink);
1139fac60a5Sjoris 
114f7a8994eSray 	if (cvs_server_path != NULL) {
1159fac60a5Sjoris 		if (cvs_rmdir(cvs_server_path) == -1)
1169fac60a5Sjoris 			cvs_log(LP_ERR,
1179fac60a5Sjoris 			    "warning: failed to remove server directory: %s",
1189fac60a5Sjoris 			    cvs_server_path);
119397ddb8aSnicm 		free(cvs_server_path);
120f7a8994eSray 		cvs_server_path = NULL;
1219fac60a5Sjoris 	}
1227c1a09a6Sjoris 
1237c1a09a6Sjoris 	if (current_list != NULL)
1247c1a09a6Sjoris 		cvs_ent_close(current_list, ENT_SYNC);
1253ad3fb45Sjoris }
1263ad3fb45Sjoris 
12745357ec5Sxsa __dead void
usage(void)1286c121f58Sjfb usage(void)
1296c121f58Sjfb {
13045357ec5Sxsa 	(void)fprintf(stderr,
13160c775bfSjoris 	    "usage: %s [-flnQqRrtvw] [-d root] [-e editor] [-s var=val]\n"
13203cb09c0Ssobrado 	    "           [-T tmpdir] [-z level] command ...\n", __progname);
13345357ec5Sxsa 	exit(1);
1346c121f58Sjfb }
1356c121f58Sjfb 
1366c121f58Sjfb int
cvs_build_cmd(char *** cmd_argv,char ** argv,int argc)13710283864Stobias cvs_build_cmd(char ***cmd_argv, char **argv, int argc)
13810283864Stobias {
13910283864Stobias 	int cmd_argc, i, cur;
14010283864Stobias 	char *cp, *linebuf, *lp;
14110283864Stobias 
14210283864Stobias 	if (cmdp->cmd_defargs == NULL) {
14310283864Stobias 		*cmd_argv = argv;
14410283864Stobias 		return argc;
14510283864Stobias 	}
14610283864Stobias 
14710283864Stobias 	cur = argc + 2;
14810283864Stobias 	cmd_argc = 0;
14910283864Stobias 	*cmd_argv = xcalloc(cur, sizeof(char *));
15010283864Stobias 	(*cmd_argv)[cmd_argc++] = argv[0];
15110283864Stobias 
15210283864Stobias 	linebuf = xstrdup(cmdp->cmd_defargs);
15310283864Stobias 	for (lp = linebuf; lp != NULL;) {
15410283864Stobias 		cp = strsep(&lp, " \t\b\f\n\r\t\v");
15510283864Stobias 		if (cp == NULL)
15610283864Stobias 			break;
15710283864Stobias 		if (*cp == '\0')
15810283864Stobias 			continue;
15910283864Stobias 
16010283864Stobias 		if (cmd_argc == cur) {
16110283864Stobias 			cur += 8;
162caa2ffb0Sderaadt 			*cmd_argv = xreallocarray(*cmd_argv, cur,
16310283864Stobias 			    sizeof(char *));
16410283864Stobias 		}
16510283864Stobias 
16610283864Stobias 		(*cmd_argv)[cmd_argc++] = cp;
16710283864Stobias 	}
16810283864Stobias 
16910283864Stobias 	if (cmd_argc + argc > cur) {
17010283864Stobias 		cur = cmd_argc + argc + 1;
171caa2ffb0Sderaadt 		*cmd_argv = xreallocarray(*cmd_argv, cur,
17210283864Stobias 		    sizeof(char *));
17310283864Stobias         }
17410283864Stobias 
17510283864Stobias 	for (i = 1; i < argc; i++)
17610283864Stobias 		(*cmd_argv)[cmd_argc++] = argv[i];
17710283864Stobias 
17810283864Stobias 	(*cmd_argv)[cmd_argc] = NULL;
17910283864Stobias 
18010283864Stobias 	return cmd_argc;
18110283864Stobias }
18210283864Stobias 
18310283864Stobias int
main(int argc,char ** argv)1846c121f58Sjfb main(int argc, char **argv)
1856c121f58Sjfb {
18610283864Stobias 	char *envstr, **cmd_argv, **targv;
187ca1d3256Sjfb 	int i, ret, cmd_argc;
18858cbd44dSxsa 	struct passwd *pw;
189e5cbdd76Sxsa 	struct stat st;
190b9fc9a72Sderaadt 	char fpath[PATH_MAX];
1916c121f58Sjfb 
192fed64a08Stb 	if (pledge("stdio rpath wpath cpath fattr getpw proc exec", NULL) == -1)
1937f453672Sderaadt 		err(1, "pledge");
1947f453672Sderaadt 
195b1df6b0eSjoris 	tzset();
196b1df6b0eSjoris 
1972d540844Sjfb 	TAILQ_INIT(&cvs_variables);
1983ad3fb45Sjoris 	SLIST_INIT(&repo_locks);
1993ad3fb45Sjoris 	SLIST_INIT(&temp_files);
2006c121f58Sjfb 
2018fbb14c0Sjoris 	hash_table_init(&created_directories, 100);
2028fbb14c0Sjoris 	hash_table_init(&created_cvs_directories, 100);
2038fbb14c0Sjoris 
2046c121f58Sjfb 	/* check environment so command-line options override it */
2056c121f58Sjfb 	if ((envstr = getenv("CVS_RSH")) != NULL)
2066c121f58Sjfb 		cvs_rsh = envstr;
2076c121f58Sjfb 
2086c121f58Sjfb 	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
2096c121f58Sjfb 	    ((envstr = getenv("VISUAL")) != NULL) ||
2106c121f58Sjfb 	    ((envstr = getenv("EDITOR")) != NULL))
2116c121f58Sjfb 		cvs_editor = envstr;
2126c121f58Sjfb 
213c47e341fSxsa 	if ((envstr = getenv("CVSREAD")) != NULL)
214c47e341fSxsa 		cvs_readonly = 1;
215c47e341fSxsa 
2163dcb84b2Sxsa 	if ((envstr = getenv("CVSREADONLYFS")) != NULL) {
2173dcb84b2Sxsa 		cvs_readonlyfs = 1;
2183dcb84b2Sxsa 		cvs_nolog = 1;
2193dcb84b2Sxsa 	}
2203dcb84b2Sxsa 
22158cbd44dSxsa 	if ((cvs_homedir = getenv("HOME")) == NULL) {
222c5440612Stobias 		if ((pw = getpwuid(getuid())) != NULL)
22358cbd44dSxsa 			cvs_homedir = pw->pw_dir;
22458cbd44dSxsa 	}
22558cbd44dSxsa 
226e5cbdd76Sxsa 	if ((envstr = getenv("TMPDIR")) != NULL)
227e5cbdd76Sxsa 		cvs_tmpdir = envstr;
228e5cbdd76Sxsa 
229ca1d3256Sjfb 	ret = cvs_getopt(argc, argv);
230ca1d3256Sjfb 
231ca1d3256Sjfb 	argc -= ret;
232ca1d3256Sjfb 	argv += ret;
23345357ec5Sxsa 	if (argc == 0)
234ca1d3256Sjfb 		usage();
235e5cbdd76Sxsa 
2369d091990Stobias 	cmdp = cvs_findcmd(argv[0]);
237786b9ba3Stobias 	if (cmdp == NULL) {
2389d091990Stobias 		fprintf(stderr, "Unknown command: `%s'\n\n", argv[0]);
239786b9ba3Stobias 		fprintf(stderr, "CVS commands are:\n");
240786b9ba3Stobias 		for (i = 0; cvs_cdt[i] != NULL; i++)
241786b9ba3Stobias 			fprintf(stderr, "\t%-16s%s\n",
242786b9ba3Stobias 			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
243786b9ba3Stobias 		exit(1);
244786b9ba3Stobias 	}
245786b9ba3Stobias 
24696b1d6b8Stobias 	/*
24796b1d6b8Stobias 	 * check the tmp dir, either specified through
24896b1d6b8Stobias 	 * the environment variable TMPDIR, or via
24996b1d6b8Stobias 	 * the global option -T <dir>
25096b1d6b8Stobias 	 */
25196b1d6b8Stobias 	if (stat(cvs_tmpdir, &st) == -1)
25296b1d6b8Stobias 		fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
25396b1d6b8Stobias 	else if (!S_ISDIR(st.st_mode))
25496b1d6b8Stobias 		fatal("`%s' is not valid temporary directory", cvs_tmpdir);
25596b1d6b8Stobias 
256c5440612Stobias 	if (cvs_readrc == 1 && cvs_homedir != NULL) {
257ca1d3256Sjfb 		cvs_read_rcfile();
258ca1d3256Sjfb 
259ca1d3256Sjfb 		if (cvs_defargs != NULL) {
260fa2a9dfeSxsa 			if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
261fa2a9dfeSxsa 				fatal("failed to load default arguments to %s",
262ca1d3256Sjfb 				    __progname);
263ca1d3256Sjfb 
264ca1d3256Sjfb 			cvs_getopt(i, targv);
265ca1d3256Sjfb 			cvs_freeargv(targv, i);
266397ddb8aSnicm 			free(targv);
267ca1d3256Sjfb 		}
268ca1d3256Sjfb 	}
269ca1d3256Sjfb 
270ca1d3256Sjfb 	/* setup signal handlers */
2713ad3fb45Sjoris 	signal(SIGTERM, sighandler);
2723ad3fb45Sjoris 	signal(SIGINT, sighandler);
2733ad3fb45Sjoris 	signal(SIGHUP, sighandler);
2743ad3fb45Sjoris 	signal(SIGABRT, sighandler);
2753ad3fb45Sjoris 	signal(SIGALRM, sighandler);
2763ad3fb45Sjoris 	signal(SIGPIPE, sighandler);
277ca1d3256Sjfb 
278ca1d3256Sjfb 	cvs_cmdop = cmdp->cmd_op;
279ca1d3256Sjfb 
28010283864Stobias 	cmd_argc = cvs_build_cmd(&cmd_argv, argv, argc);
281ca1d3256Sjfb 
2823ad3fb45Sjoris 	cvs_file_init();
2833ad3fb45Sjoris 
2849fac60a5Sjoris 	if (cvs_cmdop == CVS_OP_SERVER) {
285b0d19690Stobias 		cmdp->cmd(cmd_argc, cmd_argv);
286b0d19690Stobias 		cvs_cleanup();
287b0d19690Stobias 		return (0);
2889fac60a5Sjoris 	}
2899fac60a5Sjoris 
29088b7ad1cStobias 	cvs_umask = umask(0);
29188b7ad1cStobias 	umask(cvs_umask);
29288b7ad1cStobias 
2933ad3fb45Sjoris 	if ((current_cvsroot = cvsroot_get(".")) == NULL) {
294a06e2578Sxsa 		cvs_log(LP_ERR,
2953ad3fb45Sjoris 		    "No CVSROOT specified! Please use the '-d' option");
2969a192d08Sdavid 		fatal("or set the CVSROOT environment variable.");
297ca1d3256Sjfb 	}
298ca1d3256Sjfb 
2994dcde513Sjoris 	if (cvsroot_is_remote()) {
3009fac60a5Sjoris 		cmdp->cmd(cmd_argc, cmd_argv);
3019fac60a5Sjoris 		cvs_cleanup();
3029fac60a5Sjoris 		return (0);
3039fac60a5Sjoris 	}
304ca1d3256Sjfb 
305e40de241Sxsa 	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
306e40de241Sxsa 	    current_cvsroot->cr_dir, CVS_PATH_ROOT);
307977ee386Sxsa 
308db68708cSxsa 	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
309b8b35aa3Sjoris 		if (errno == ENOENT)
31096276484Sjoris 			fatal("repository '%s' does not exist",
311b8b35aa3Sjoris 			    current_cvsroot->cr_dir);
312b8b35aa3Sjoris 		else
313b8b35aa3Sjoris 			fatal("%s: %s", current_cvsroot->cr_dir,
314b8b35aa3Sjoris 			    strerror(errno));
315b8b35aa3Sjoris 	} else {
316b8b35aa3Sjoris 		if (!S_ISDIR(st.st_mode))
317b8b35aa3Sjoris 			fatal("'%s' is not a directory",
318b8b35aa3Sjoris 			    current_cvsroot->cr_dir);
319b8b35aa3Sjoris 	}
320b8b35aa3Sjoris 
32175bebbccSjoris 	if (cvs_cmdop != CVS_OP_INIT) {
32218911d1eSjoris 		cvs_parse_configfile();
32375bebbccSjoris 		cvs_parse_modules();
32475bebbccSjoris 	}
32518911d1eSjoris 
3263ad3fb45Sjoris 	cmdp->cmd(cmd_argc, cmd_argv);
3273ad3fb45Sjoris 	cvs_cleanup();
3283ad3fb45Sjoris 
3293ad3fb45Sjoris 	return (0);
330ca1d3256Sjfb }
331ca1d3256Sjfb 
332ca1d3256Sjfb int
cvs_getopt(int argc,char ** argv)333ca1d3256Sjfb cvs_getopt(int argc, char **argv)
334ca1d3256Sjfb {
335ca1d3256Sjfb 	int ret;
336ca1d3256Sjfb 	char *ep;
337ff82c0d8Sjoris 	const char *errstr;
338ca1d3256Sjfb 
33960c775bfSjoris 	while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tvwxz:")) != -1) {
3406c121f58Sjfb 		switch (ret) {
341c68e1832Sjfb 		case 'b':
342c68e1832Sjfb 			/*
343c68e1832Sjfb 			 * We do not care about the bin directory for RCS files
344c68e1832Sjfb 			 * as this program has no dependencies on RCS programs,
345c68e1832Sjfb 			 * so it is only here for backwards compatibility.
346c68e1832Sjfb 			 */
347c68e1832Sjfb 			cvs_log(LP_NOTICE, "the -b argument is obsolete");
348c68e1832Sjfb 			break;
3496c121f58Sjfb 		case 'd':
3506c121f58Sjfb 			cvs_rootstr = optarg;
3516c121f58Sjfb 			break;
3526c121f58Sjfb 		case 'e':
3536c121f58Sjfb 			cvs_editor = optarg;
3546c121f58Sjfb 			break;
3556c121f58Sjfb 		case 'f':
356ca1d3256Sjfb 			cvs_readrc = 0;
3576c121f58Sjfb 			break;
3586c121f58Sjfb 		case 'l':
3596c121f58Sjfb 			cvs_nolog = 1;
3606c121f58Sjfb 			break;
3616c121f58Sjfb 		case 'n':
3620bda20e2Sxsa 			cvs_noexec = 1;
3636c1db92dSxsa 			cvs_nolog = 1;
3646c121f58Sjfb 			break;
3656c121f58Sjfb 		case 'Q':
3666c121f58Sjfb 			verbosity = 0;
3676c121f58Sjfb 			break;
3686c121f58Sjfb 		case 'q':
36960c775bfSjoris 			if (verbosity > 1)
37060c775bfSjoris 				verbosity = 1;
3716c121f58Sjfb 			break;
3723dcb84b2Sxsa 		case 'R':
3733dcb84b2Sxsa 			cvs_readonlyfs = 1;
3743dcb84b2Sxsa 			cvs_nolog = 1;
3753dcb84b2Sxsa 			break;
3766c121f58Sjfb 		case 'r':
3776c121f58Sjfb 			cvs_readonly = 1;
3786c121f58Sjfb 			break;
3792d540844Sjfb 		case 's':
3802d540844Sjfb 			ep = strchr(optarg, '=');
3812d540844Sjfb 			if (ep == NULL) {
3822d540844Sjfb 				cvs_log(LP_ERR, "no = in variable assignment");
3833ad3fb45Sjoris 				exit(1);
3842d540844Sjfb 			}
3852d540844Sjfb 			*(ep++) = '\0';
3862d540844Sjfb 			if (cvs_var_set(optarg, ep) < 0)
3873ad3fb45Sjoris 				exit(1);
3882d540844Sjfb 			break;
389e5cbdd76Sxsa 		case 'T':
390e5cbdd76Sxsa 			cvs_tmpdir = optarg;
391e5cbdd76Sxsa 			break;
3926c121f58Sjfb 		case 't':
3936c121f58Sjfb 			cvs_trace = 1;
3946c121f58Sjfb 			break;
3956c121f58Sjfb 		case 'v':
3966c121f58Sjfb 			printf("%s\n", CVS_VERSION);
3976c121f58Sjfb 			exit(0);
3986c121f58Sjfb 			/* NOTREACHED */
3990e60680aSxsa 		case 'w':
4000e60680aSxsa 			cvs_readonly = 0;
4010e60680aSxsa 			break;
402c68e1832Sjfb 		case 'x':
403c68e1832Sjfb 			/*
404c68e1832Sjfb 			 * Kerberos encryption support, kept for compatibility
405c68e1832Sjfb 			 */
406c68e1832Sjfb 			break;
4076c121f58Sjfb 		case 'z':
408ff82c0d8Sjoris 			cvs_compress = strtonum(optarg, 0, 9, &errstr);
409ff82c0d8Sjoris 			if (errstr != NULL)
410ff82c0d8Sjoris 				fatal("cvs_compress: %s", errstr);
4116c121f58Sjfb 			break;
4126c121f58Sjfb 		default:
4136c121f58Sjfb 			usage();
41445357ec5Sxsa 			/* NOTREACHED */
4156c121f58Sjfb 		}
4166c121f58Sjfb 	}
4176c121f58Sjfb 
418ca1d3256Sjfb 	ret = optind;
4196c121f58Sjfb 	optind = 1;
420ca1d3256Sjfb 	optreset = 1;	/* for next call */
42176a571ccSjfb 
4226c121f58Sjfb 	return (ret);
4236c121f58Sjfb }
4246c121f58Sjfb 
4256c121f58Sjfb /*
426ca1d3256Sjfb  * cvs_read_rcfile()
4276c121f58Sjfb  *
4286c121f58Sjfb  * Read the CVS `.cvsrc' file in the user's home directory.  If the file
4296c121f58Sjfb  * exists, it should contain a list of arguments that should always be given
4306c121f58Sjfb  * implicitly to the specified commands.
4316c121f58Sjfb  */
4325db46beeSjoris static void
cvs_read_rcfile(void)433ca1d3256Sjfb cvs_read_rcfile(void)
4346c121f58Sjfb {
435b9fc9a72Sderaadt 	char rcpath[PATH_MAX], *buf, *lbuf, *lp, *p;
43683bac5fbStobias 	int cmd_parsed, cvs_parsed, i, linenum;
4371f51c0c7Stobias 	size_t len, pos;
4389d091990Stobias 	struct cvs_cmd *tcmdp;
4396c121f58Sjfb 	FILE *fp;
4406c121f58Sjfb 
441e40de241Sxsa 	linenum = 0;
442e40de241Sxsa 
443b9fc9a72Sderaadt 	i = snprintf(rcpath, PATH_MAX, "%s/%s", cvs_homedir, CVS_PATH_RC);
444b9fc9a72Sderaadt 	if (i < 0 || i >= PATH_MAX) {
4459fe3180aSxsa 		cvs_log(LP_ERRNO, "%s", rcpath);
44627b85f85Sxsa 		return;
44727b85f85Sxsa 	}
4486c121f58Sjfb 
4496c121f58Sjfb 	fp = fopen(rcpath, "r");
4506c121f58Sjfb 	if (fp == NULL) {
4516c121f58Sjfb 		if (errno != ENOENT)
4526c121f58Sjfb 			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
4536c121f58Sjfb 			    strerror(errno));
4546c121f58Sjfb 		return;
4556c121f58Sjfb 	}
4566c121f58Sjfb 
45783bac5fbStobias 	cmd_parsed = cvs_parsed = 0;
458a1013731Stobias 	lbuf = NULL;
459a1013731Stobias 	while ((buf = fgetln(fp, &len)) != NULL) {
460a1013731Stobias 		if (buf[len - 1] == '\n') {
461a1013731Stobias 			buf[len - 1] = '\0';
462a1013731Stobias 		} else {
463a1013731Stobias 			lbuf = xmalloc(len + 1);
464a1013731Stobias 			memcpy(lbuf, buf, len);
465a1013731Stobias 			lbuf[len] = '\0';
466a1013731Stobias 			buf = lbuf;
467ca1d3256Sjfb 		}
468a1013731Stobias 
469a1013731Stobias 		linenum++;
4706c121f58Sjfb 
471099a59c1Sjoris 		/* skip any whitespaces */
472a1013731Stobias 		p = buf;
473099a59c1Sjoris 		while (*p == ' ')
474d30bb22aSderaadt 			p++;
475099a59c1Sjoris 
476c13d99f3Stobias 		/*
477c13d99f3Stobias 		 * Allow comments.
478c13d99f3Stobias 		 * GNU cvs stops parsing a line if it encounters a \t
479c13d99f3Stobias 		 * in front of a command, stick at this behaviour for
480c13d99f3Stobias 		 * compatibility.
481c13d99f3Stobias 		 */
482c13d99f3Stobias 		if (*p == '#' || *p == '\t')
483099a59c1Sjoris 			continue;
484099a59c1Sjoris 
4851f51c0c7Stobias 		pos = strcspn(p, " \t");
4861f51c0c7Stobias 		if (pos == strlen(p)) {
487c70dac2bStobias 			lp = NULL;
4881f51c0c7Stobias 		} else {
4891f51c0c7Stobias 			lp = p + pos;
490ca1d3256Sjfb 			*lp = '\0';
4911f51c0c7Stobias 		}
4921f51c0c7Stobias 
49383bac5fbStobias 		if (strcmp(p, "cvs") == 0 && !cvs_parsed) {
494ca1d3256Sjfb 			/*
495ca1d3256Sjfb 			 * Global default options.  In the case of cvs only,
496ca1d3256Sjfb 			 * we keep the 'cvs' string as first argument because
497ca1d3256Sjfb 			 * getopt() does not like starting at index 0 for
498ca1d3256Sjfb 			 * argument processing.
499ca1d3256Sjfb 			 */
500c70dac2bStobias 			if (lp != NULL) {
501ca1d3256Sjfb 				*lp = ' ';
5020450b43bSjoris 				cvs_defargs = xstrdup(p);
503c70dac2bStobias 			}
50483bac5fbStobias 			cvs_parsed = 1;
5053917c9bfSderaadt 		} else {
50683bac5fbStobias 			tcmdp = cvs_findcmd(p);
50783bac5fbStobias 			if (tcmdp == NULL && verbosity == 2)
5086c121f58Sjfb 				cvs_log(LP_NOTICE,
509e14657bfSxsa 				    "unknown command `%s' in `%s:%d'",
510099a59c1Sjoris 				    p, rcpath, linenum);
51183bac5fbStobias 
51283bac5fbStobias 			if (tcmdp != cmdp || cmd_parsed)
5136c121f58Sjfb 				continue;
514ca1d3256Sjfb 
515c70dac2bStobias 			if (lp != NULL) {
516c70dac2bStobias 				lp++;
5170450b43bSjoris 				cmdp->cmd_defargs = xstrdup(lp);
518c70dac2bStobias 			}
51983bac5fbStobias 			cmd_parsed = 1;
5206c121f58Sjfb 		}
5216c121f58Sjfb 	}
522397ddb8aSnicm 	free(lbuf);
5233ad3fb45Sjoris 
5246c121f58Sjfb 	if (ferror(fp)) {
5253b801bd8Sxsa 		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
5266c121f58Sjfb 	}
5276c121f58Sjfb 
5286c121f58Sjfb 	(void)fclose(fp);
5296c121f58Sjfb }
5302d540844Sjfb 
5312d540844Sjfb /*
5322d540844Sjfb  * cvs_var_set()
5332d540844Sjfb  *
5342d540844Sjfb  * Set the value of the variable <var> to <val>.  If there is no such variable,
5352d540844Sjfb  * a new entry is created, otherwise the old value is overwritten.
5362d540844Sjfb  * Returns 0 on success, or -1 on failure.
5372d540844Sjfb  */
5382d540844Sjfb int
cvs_var_set(const char * var,const char * val)5392d540844Sjfb cvs_var_set(const char *var, const char *val)
5402d540844Sjfb {
5412d540844Sjfb 	const char *cp;
5422d540844Sjfb 	struct cvs_var *vp;
5432d540844Sjfb 
544d593696fSderaadt 	if (var == NULL || *var == '\0') {
5452d540844Sjfb 		cvs_log(LP_ERR, "no variable name");
5462d540844Sjfb 		return (-1);
5472d540844Sjfb 	}
5482d540844Sjfb 
5492d540844Sjfb 	/* sanity check on the name */
5502d540844Sjfb 	for (cp = var; *cp != '\0'; cp++)
551f6ac027fSokan 		if (!isalnum((unsigned char)*cp) && (*cp != '_')) {
5522d540844Sjfb 			cvs_log(LP_ERR,
5532d540844Sjfb 			    "variable name `%s' contains invalid characters",
5542d540844Sjfb 			    var);
5552d540844Sjfb 			return (-1);
5562d540844Sjfb 		}
5572d540844Sjfb 
5582d540844Sjfb 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
5592d540844Sjfb 		if (strcmp(vp->cv_name, var) == 0)
5602d540844Sjfb 			break;
5612d540844Sjfb 
5622d540844Sjfb 	if (vp == NULL) {
5633b4c5c25Sray 		vp = xcalloc(1, sizeof(*vp));
5642d540844Sjfb 
5650450b43bSjoris 		vp->cv_name = xstrdup(var);
5662d540844Sjfb 		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
5672d540844Sjfb 
5682d540844Sjfb 	} else	/* free the previous value */
569397ddb8aSnicm 		free(vp->cv_val);
5702d540844Sjfb 
571b794e5f5Stobias 	vp->cv_val = xstrdup(val);
5722d540844Sjfb 
5732d540844Sjfb 	return (0);
5742d540844Sjfb }
5752d540844Sjfb 
5762d540844Sjfb /*
5772b2aca23Sotto  * cvs_var_unset()
5782d540844Sjfb  *
5792d540844Sjfb  * Remove any entry for the variable <var>.
5802d540844Sjfb  * Returns 0 on success, or -1 on failure.
5812d540844Sjfb  */
5822d540844Sjfb int
cvs_var_unset(const char * var)5832d540844Sjfb cvs_var_unset(const char *var)
5842d540844Sjfb {
5852d540844Sjfb 	struct cvs_var *vp;
5862d540844Sjfb 
5872d540844Sjfb 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
5882d540844Sjfb 		if (strcmp(vp->cv_name, var) == 0) {
5892d540844Sjfb 			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
590397ddb8aSnicm 			free(vp->cv_name);
591397ddb8aSnicm 			free(vp->cv_val);
592397ddb8aSnicm 			free(vp);
5932d540844Sjfb 			return (0);
5942d540844Sjfb 		}
5952d540844Sjfb 
5962d540844Sjfb 	return (-1);
5972d540844Sjfb }
5982d540844Sjfb 
5992d540844Sjfb /*
6002d540844Sjfb  * cvs_var_get()
6012d540844Sjfb  *
6022d540844Sjfb  * Get the value associated with the variable <var>.  Returns a pointer to the
6032d540844Sjfb  * value string on success, or NULL if the variable does not exist.
6042d540844Sjfb  */
6052d540844Sjfb 
6062d540844Sjfb const char *
cvs_var_get(const char * var)6072d540844Sjfb cvs_var_get(const char *var)
6082d540844Sjfb {
6092d540844Sjfb 	struct cvs_var *vp;
6102d540844Sjfb 
6112d540844Sjfb 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
6122d540844Sjfb 		if (strcmp(vp->cv_name, var) == 0)
6132d540844Sjfb 			return (vp->cv_val);
6142d540844Sjfb 
6152d540844Sjfb 	return (NULL);
6162d540844Sjfb }
617