cvs.c revision 1.107
11573Srgrimes/*	$OpenBSD: cvs.c,v 1.107 2006/07/09 01:57:51 joris Exp $	*/
214287Spst/*
31573Srgrimes * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
41573Srgrimes * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
51573Srgrimes * All rights reserved.
61573Srgrimes *
71573Srgrimes * Redistribution and use in source and binary forms, with or without
81573Srgrimes * modification, are permitted provided that the following conditions
91573Srgrimes * are met:
101573Srgrimes *
111573Srgrimes * 1. Redistributions of source code must retain the above copyright
121573Srgrimes *    notice, this list of conditions and the following disclaimer.
131573Srgrimes * 2. The name of the author may not be used to endorse or promote products
141573Srgrimes *    derived from this software without specific prior written permission.
151573Srgrimes *
161573Srgrimes * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
171573Srgrimes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
181573Srgrimes * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
191573Srgrimes * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
201573Srgrimes * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
211573Srgrimes * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
221573Srgrimes * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
231573Srgrimes * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
241573Srgrimes * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
251573Srgrimes * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
261573Srgrimes */
271573Srgrimes
281573Srgrimes#include "includes.h"
291573Srgrimes
301573Srgrimes#include "cvs.h"
311573Srgrimes#include "config.h"
321573Srgrimes#include "log.h"
331573Srgrimes#include "file.h"
3414287Spst#include "remote.h"
351573Srgrimes
3692986Sobrienextern char *__progname;
3792986Sobrien
381573Srgrimes/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
391573Srgrimesint verbosity = 1;
401573Srgrimes
411573Srgrimes/* compression level used with zlib, 0 meaning no compression taking place */
421573Srgrimesint	cvs_compress = 0;
431573Srgrimesint	cvs_readrc = 1;		/* read .cvsrc on startup */
441573Srgrimesint	cvs_trace = 0;
451573Srgrimesint	cvs_nolog = 0;
461573Srgrimesint	cvs_readonly = 0;
4771579Sdeischenint	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
481573Srgrimesint	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
491573Srgrimesint	cvs_error = -1;	/* set to the correct error code on failure */
501573Srgrimesint	cvs_cmdop;
511573Srgrimesint	cvs_umask = CVS_UMASK_DEFAULT;
521573Srgrimesint	cvs_server_active = 0;
531573Srgrimes
541573Srgrimeschar	*cvs_tagname = NULL;
551573Srgrimeschar	*cvs_defargs;		/* default global arguments from .cvsrc */
561573Srgrimeschar	*cvs_command;		/* name of the command we are running */
571573Srgrimeschar	*cvs_rootstr;
581573Srgrimeschar	*cvs_rsh = CVS_RSH_DEFAULT;
5971579Sdeischenchar	*cvs_editor = CVS_EDITOR_DEFAULT;
601573Srgrimeschar	*cvs_homedir = NULL;
611573Srgrimeschar	*cvs_msg = NULL;
621573Srgrimeschar	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
631573Srgrimes
6414287Spststruct cvsroot *current_cvsroot = NULL;
6514287Spst
6614287Spststatic TAILQ_HEAD(, cvs_var) cvs_variables;
6714287Spst
6814287Spstint		cvs_getopt(int, char **);
6992905Sobrienvoid		usage(void);
7092905Sobrienstatic void	cvs_read_rcfile(void);
7192905Sobrien
721573Srgrimesstruct cvs_wklhead temp_files;
731573Srgrimes
741573Srgrimesvoid sighandler(int);
751573Srgrimesvolatile sig_atomic_t cvs_quit = 0;
761573Srgrimesvolatile sig_atomic_t sig_received = 0;
771573Srgrimes
781573Srgrimesvoid
791573Srgrimessighandler(int sig)
801573Srgrimes{
811573Srgrimes	sig_received = sig;
821573Srgrimes
831573Srgrimes	switch (sig) {
841573Srgrimes	case SIGINT:
851573Srgrimes	case SIGTERM:
861573Srgrimes	case SIGPIPE:
871573Srgrimes		cvs_quit = 1;
881573Srgrimes		break;
891573Srgrimes	default:
90189291Sdelphij		break;
911573Srgrimes	}
921573Srgrimes}
931573Srgrimes
941573Srgrimesvoid
951573Srgrimescvs_cleanup(void)
961573Srgrimes{
971573Srgrimes	cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
981573Srgrimes	cvs_worklist_run(&repo_locks, cvs_worklist_unlink);
99190344Sdelphij
1001573Srgrimes	cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
1011573Srgrimes	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
1021573Srgrimes
1031573Srgrimes	if (cvs_server_active) {
1041573Srgrimes		if (cvs_rmdir(cvs_server_path) == -1)
1051573Srgrimes			cvs_log(LP_ERR,
1061573Srgrimes			    "warning: failed to remove server directory: %s",
1071573Srgrimes			    cvs_server_path);
1081573Srgrimes		xfree(cvs_server_path);
1091573Srgrimes	}
1101573Srgrimes}
1111573Srgrimes
1121573Srgrimesvoid
1131573Srgrimesusage(void)
1141573Srgrimes{
1151573Srgrimes	fprintf(stderr,
1161573Srgrimes	    "Usage: %s [-flnQqrtvVw] [-d root] [-e editor] [-s var=val] "
1171573Srgrimes	    "[-T tmpdir] [-z level] command [...]\n", __progname);
1181573Srgrimes}
1191573Srgrimes
1201573Srgrimesint
1211573Srgrimesmain(int argc, char **argv)
1221573Srgrimes{
1231573Srgrimes	char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
1241573Srgrimes	int i, ret, cmd_argc;
12517141Sjkh	struct cvs_cmd *cmdp;
1261573Srgrimes	struct passwd *pw;
1271573Srgrimes	struct stat st;
1281573Srgrimes	char fpath[MAXPATHLEN];
1291573Srgrimes	char *root, *rootp;
1301573Srgrimes
1311573Srgrimes	tzset();
1321573Srgrimes
1331573Srgrimes	TAILQ_INIT(&cvs_variables);
1341573Srgrimes	SLIST_INIT(&repo_locks);
1351573Srgrimes	SLIST_INIT(&temp_files);
1361573Srgrimes
1371573Srgrimes	/* check environment so command-line options override it */
1381573Srgrimes	if ((envstr = getenv("CVS_RSH")) != NULL)
1391573Srgrimes		cvs_rsh = envstr;
1401573Srgrimes
1411573Srgrimes	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
1421573Srgrimes	    ((envstr = getenv("VISUAL")) != NULL) ||
1431573Srgrimes	    ((envstr = getenv("EDITOR")) != NULL))
1441573Srgrimes		cvs_editor = envstr;
1451573Srgrimes
1461573Srgrimes	if ((envstr = getenv("CVSREAD")) != NULL)
1471573Srgrimes		cvs_readonly = 1;
1481573Srgrimes
1491573Srgrimes	if ((cvs_homedir = getenv("HOME")) == NULL) {
1501573Srgrimes		if ((pw = getpwuid(getuid())) == NULL)
1511573Srgrimes			fatal("getpwuid failed");
1521573Srgrimes		cvs_homedir = pw->pw_dir;
1531573Srgrimes	}
1541573Srgrimes
1551573Srgrimes	if ((envstr = getenv("TMPDIR")) != NULL)
1561573Srgrimes		cvs_tmpdir = envstr;
1571573Srgrimes
1581573Srgrimes	ret = cvs_getopt(argc, argv);
1591573Srgrimes
1601573Srgrimes	argc -= ret;
1611573Srgrimes	argv += ret;
1621573Srgrimes	if (argc == 0) {
1631573Srgrimes		usage();
1641573Srgrimes		exit(1);
1651573Srgrimes	}
1661573Srgrimes
1671573Srgrimes	cvs_command = argv[0];
1681573Srgrimes
1691573Srgrimes	/*
1701573Srgrimes	 * check the tmp dir, either specified through
17114287Spst	 * the environment variable TMPDIR, or via
1721573Srgrimes	 * the global option -T <dir>
17314287Spst	 */
1741573Srgrimes	if (stat(cvs_tmpdir, &st) == -1)
1751573Srgrimes		fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
1761573Srgrimes	else if (!S_ISDIR(st.st_mode))
1771573Srgrimes		fatal("`%s' is not valid temporary directory", cvs_tmpdir);
1781573Srgrimes
1791573Srgrimes	if (cvs_readrc == 1) {
1801573Srgrimes		cvs_read_rcfile();
1811573Srgrimes
1821573Srgrimes		if (cvs_defargs != NULL) {
1831573Srgrimes			if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
1841573Srgrimes				fatal("failed to load default arguments to %s",
1851573Srgrimes				    __progname);
1861573Srgrimes
1871573Srgrimes			cvs_getopt(i, targv);
1881573Srgrimes			cvs_freeargv(targv, i);
1891573Srgrimes			xfree(targv);
19014287Spst		}
1911573Srgrimes	}
19214287Spst
1931573Srgrimes	/* setup signal handlers */
1941573Srgrimes	signal(SIGTERM, sighandler);
1951573Srgrimes	signal(SIGINT, sighandler);
1961573Srgrimes	signal(SIGHUP, sighandler);
1971573Srgrimes	signal(SIGABRT, sighandler);
1981573Srgrimes	signal(SIGALRM, sighandler);
1991573Srgrimes	signal(SIGPIPE, sighandler);
200189327Sdelphij
20156698Sjasone	cmdp = cvs_findcmd(cvs_command);
2021573Srgrimes	if (cmdp == NULL) {
2031573Srgrimes		fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
2041573Srgrimes		fprintf(stderr, "CVS commands are:\n");
2051573Srgrimes		for (i = 0; cvs_cdt[i] != NULL; i++)
2061573Srgrimes			fprintf(stderr, "\t%-16s%s\n",
2071573Srgrimes			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
2081573Srgrimes		exit(1);
20914287Spst	}
2101573Srgrimes
2111573Srgrimes	cvs_cmdop = cmdp->cmd_op;
21256698Sjasone
2131573Srgrimes	cmd_argc = 0;
2141573Srgrimes	memset(cmd_argv, 0, sizeof(cmd_argv));
21571579Sdeischen
2161573Srgrimes	cmd_argv[cmd_argc++] = argv[0];
2171573Srgrimes	if (cmdp->cmd_defargs != NULL) {
21856698Sjasone		/* transform into a new argument vector */
2191573Srgrimes		ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
2201573Srgrimes		    CVS_CMD_MAXARG - 1);
2211573Srgrimes		if (ret < 0)
2221573Srgrimes			fatal("main: cvs_getargv failed");
2231573Srgrimes
2241573Srgrimes		cmd_argc += ret;
2251573Srgrimes	}
2261573Srgrimes
2271573Srgrimes	for (ret = 1; ret < argc; ret++)
2281573Srgrimes		cmd_argv[cmd_argc++] = argv[ret];
2291573Srgrimes
2301573Srgrimes	cvs_file_init();
23114287Spst
23214287Spst	if (cvs_cmdop == CVS_OP_SERVER) {
2331573Srgrimes		setvbuf(stdin, NULL, _IOLBF, 0);
23414287Spst		setvbuf(stdout, NULL, _IOLBF, 0);
23514287Spst
23614287Spst		cvs_server_active = 1;
23714287Spst		root = cvs_remote_input();
23814287Spst		if ((rootp = strchr(root, ' ')) == NULL)
23914287Spst			fatal("bad Root request");
24014287Spst		cvs_rootstr = xstrdup(rootp + 1);
2411573Srgrimes		xfree(root);
24214287Spst	}
2431573Srgrimes
24414287Spst	if ((current_cvsroot = cvsroot_get(".")) == NULL) {
24517141Sjkh		cvs_log(LP_ERR,
2461573Srgrimes		    "No CVSROOT specified! Please use the '-d' option");
24714287Spst		fatal("or set the CVSROOT environment variable.");
2481573Srgrimes	}
24914287Spst
25014287Spst	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
25114287Spst		if (cvs_server_active == 1)
25214287Spst			fatal("remote Root while already running as server?");
2531573Srgrimes
2541573Srgrimes		cvs_client_connect_to_server();
2551573Srgrimes		cmdp->cmd(cmd_argc, cmd_argv);
2561573Srgrimes		cvs_cleanup();
2571573Srgrimes		return (0);
2581573Srgrimes	}
2591573Srgrimes
2601573Srgrimes	i = snprintf(fpath, sizeof(fpath), "%s/%s", current_cvsroot->cr_dir,
2611573Srgrimes	    CVS_PATH_ROOT);
2621573Srgrimes	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
2631573Srgrimes		if (errno == ENOENT)
2641573Srgrimes			fatal("repository '%s' does not exist",
2651573Srgrimes			    current_cvsroot->cr_dir);
2661573Srgrimes		else
2671573Srgrimes			fatal("%s: %s", current_cvsroot->cr_dir,
26814287Spst			    strerror(errno));
2691573Srgrimes	} else {
2701573Srgrimes		if (!S_ISDIR(st.st_mode))
2711573Srgrimes			fatal("'%s' is not a directory",
27214287Spst			    current_cvsroot->cr_dir);
2731573Srgrimes	}
2741573Srgrimes
2751573Srgrimes	if (cvs_cmdop != CVS_OP_INIT)
2761573Srgrimes		cvs_parse_configfile();
2771573Srgrimes
27817141Sjkh	umask(cvs_umask);
27917141Sjkh
2801573Srgrimes	cmdp->cmd(cmd_argc, cmd_argv);
2811573Srgrimes	cvs_cleanup();
2821573Srgrimes
2831573Srgrimes	return (0);
2841573Srgrimes}
2851573Srgrimes
2861573Srgrimesint
2871573Srgrimescvs_getopt(int argc, char **argv)
2881573Srgrimes{
2891573Srgrimes	int ret;
2901573Srgrimes	char *ep;
2911573Srgrimes
2921573Srgrimes	while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:T:tvVwz:")) != -1) {
2931573Srgrimes		switch (ret) {
2941573Srgrimes		case 'b':
2951573Srgrimes			/*
2961573Srgrimes			 * We do not care about the bin directory for RCS files
2971573Srgrimes			 * as this program has no dependencies on RCS programs,
2981573Srgrimes			 * so it is only here for backwards compatibility.
2991573Srgrimes			 */
3001573Srgrimes			cvs_log(LP_NOTICE, "the -b argument is obsolete");
3011573Srgrimes			break;
3021573Srgrimes		case 'd':
3031573Srgrimes			cvs_rootstr = optarg;
3041573Srgrimes			break;
3051573Srgrimes		case 'e':
3061573Srgrimes			cvs_editor = optarg;
30714287Spst			break;
3081573Srgrimes		case 'f':
3091573Srgrimes			cvs_readrc = 0;
3101573Srgrimes			break;
3111573Srgrimes		case 'l':
3121573Srgrimes			cvs_nolog = 1;
3131573Srgrimes			break;
3141573Srgrimes		case 'n':
3151573Srgrimes			cvs_noexec = 1;
31614287Spst			break;
3171573Srgrimes		case 'Q':
31814287Spst			verbosity = 0;
3191573Srgrimes			break;
32014287Spst		case 'q':
3211573Srgrimes			/*
3221573Srgrimes			 * Be quiet. This is the default in OpenCVS.
3231573Srgrimes			 */
3241573Srgrimes			break;
3251573Srgrimes		case 'r':
3261573Srgrimes			cvs_readonly = 1;
3271573Srgrimes			break;
3281573Srgrimes		case 's':
3291573Srgrimes			ep = strchr(optarg, '=');
330190344Sdelphij			if (ep == NULL) {
331190344Sdelphij				cvs_log(LP_ERR, "no = in variable assignment");
3321573Srgrimes				exit(1);
3331573Srgrimes			}
3341573Srgrimes			*(ep++) = '\0';
33556698Sjasone			if (cvs_var_set(optarg, ep) < 0)
3361573Srgrimes				exit(1);
3371573Srgrimes			break;
338190344Sdelphij		case 'T':
3391573Srgrimes			cvs_tmpdir = optarg;
3401573Srgrimes			break;
3411573Srgrimes		case 't':
3421573Srgrimes			cvs_trace = 1;
3431573Srgrimes			break;
3441573Srgrimes		case 'V':
3451573Srgrimes			/* don't override -Q */
3461573Srgrimes			if (verbosity)
3471573Srgrimes				verbosity = 2;
3481573Srgrimes			break;
3491573Srgrimes		case 'v':
3501573Srgrimes			printf("%s\n", CVS_VERSION);
3511573Srgrimes			exit(0);
352189291Sdelphij			/* NOTREACHED */
3531573Srgrimes			break;
3541573Srgrimes		case 'w':
3551573Srgrimes			cvs_readonly = 0;
3561573Srgrimes			break;
3571573Srgrimes		case 'x':
3581573Srgrimes			/*
3591573Srgrimes			 * Kerberos encryption support, kept for compatibility
3601573Srgrimes			 */
36114287Spst			break;
3621573Srgrimes		case 'z':
36314287Spst			cvs_compress = (int)strtol(optarg, &ep, 10);
3641573Srgrimes			if (*ep != '\0')
3651573Srgrimes				fatal("error parsing compression level");
3661573Srgrimes			if (cvs_compress < 0 || cvs_compress > 9)
3671573Srgrimes				fatal("gzip compression level must be "
3681573Srgrimes				    "between 0 and 9");
3691573Srgrimes			break;
3701573Srgrimes		default:
3711573Srgrimes			usage();
3721573Srgrimes			exit(1);
3731573Srgrimes		}
3741573Srgrimes	}
3751573Srgrimes
3761573Srgrimes	ret = optind;
3771573Srgrimes	optind = 1;
3781573Srgrimes	optreset = 1;	/* for next call */
3791573Srgrimes
3801573Srgrimes	return (ret);
3811573Srgrimes}
3821573Srgrimes
3831573Srgrimes/*
3841573Srgrimes * cvs_read_rcfile()
385189291Sdelphij *
3861573Srgrimes * Read the CVS `.cvsrc' file in the user's home directory.  If the file
3871573Srgrimes * exists, it should contain a list of arguments that should always be given
3881573Srgrimes * implicitly to the specified commands.
38939058Simp */
3901573Srgrimesstatic void
3911573Srgrimescvs_read_rcfile(void)
39239058Simp{
39339058Simp	char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
3941573Srgrimes	int linenum = 0;
39568234Skris	size_t len;
3961573Srgrimes	struct cvs_cmd *cmdp;
3971573Srgrimes	FILE *fp;
39871579Sdeischen
3991573Srgrimes	if (strlcpy(rcpath, cvs_homedir, sizeof(rcpath)) >= sizeof(rcpath) ||
4001573Srgrimes	    strlcat(rcpath, "/", sizeof(rcpath)) >= sizeof(rcpath) ||
40171579Sdeischen	    strlcat(rcpath, CVS_PATH_RC, sizeof(rcpath)) >= sizeof(rcpath)) {
4021573Srgrimes		errno = ENAMETOOLONG;
4031573Srgrimes		cvs_log(LP_ERR, "%s", rcpath);
4041573Srgrimes		return;
4051573Srgrimes	}
406189291Sdelphij
4071573Srgrimes	fp = fopen(rcpath, "r");
4081573Srgrimes	if (fp == NULL) {
4091573Srgrimes		if (errno != ENOENT)
4101573Srgrimes			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
4111573Srgrimes			    strerror(errno));
4121573Srgrimes		return;
4131573Srgrimes	}
4141573Srgrimes
4151573Srgrimes	while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
4161573Srgrimes		linenum++;
4171573Srgrimes		if ((len = strlen(linebuf)) == 0)
4181573Srgrimes			continue;
4191573Srgrimes		if (linebuf[len - 1] != '\n') {
4201573Srgrimes			cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
4211573Srgrimes				linenum);
4221573Srgrimes			break;
4231573Srgrimes		}
424189291Sdelphij		linebuf[--len] = '\0';
4251573Srgrimes
4261573Srgrimes		/* skip any whitespaces */
4271573Srgrimes		p = linebuf;
4281573Srgrimes		while (*p == ' ')
4291573Srgrimes			p++;
4301573Srgrimes
4311573Srgrimes		/* allow comments */
4321573Srgrimes		if (*p == '#')
4331573Srgrimes			continue;
4341573Srgrimes
4351573Srgrimes		lp = strchr(p, ' ');
4361573Srgrimes		if (lp == NULL)
43714287Spst			continue;	/* ignore lines with no arguments */
4381573Srgrimes		*lp = '\0';
4391573Srgrimes		if (strcmp(p, "cvs") == 0) {
4401573Srgrimes			/*
4411573Srgrimes			 * Global default options.  In the case of cvs only,
4421573Srgrimes			 * we keep the 'cvs' string as first argument because
443			 * getopt() does not like starting at index 0 for
444			 * argument processing.
445			 */
446			*lp = ' ';
447			cvs_defargs = xstrdup(p);
448		} else {
449			lp++;
450			cmdp = cvs_findcmd(p);
451			if (cmdp == NULL) {
452				cvs_log(LP_NOTICE,
453				    "unknown command `%s' in `%s:%d'",
454				    p, rcpath, linenum);
455				continue;
456			}
457
458			cmdp->cmd_defargs = xstrdup(lp);
459		}
460	}
461
462	if (ferror(fp)) {
463		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
464	}
465
466	(void)fclose(fp);
467}
468
469/*
470 * cvs_var_set()
471 *
472 * Set the value of the variable <var> to <val>.  If there is no such variable,
473 * a new entry is created, otherwise the old value is overwritten.
474 * Returns 0 on success, or -1 on failure.
475 */
476int
477cvs_var_set(const char *var, const char *val)
478{
479	char *valcp;
480	const char *cp;
481	struct cvs_var *vp;
482
483	if (var == NULL || *var == '\0') {
484		cvs_log(LP_ERR, "no variable name");
485		return (-1);
486	}
487
488	/* sanity check on the name */
489	for (cp = var; *cp != '\0'; cp++)
490		if (!isalnum(*cp) && (*cp != '_')) {
491			cvs_log(LP_ERR,
492			    "variable name `%s' contains invalid characters",
493			    var);
494			return (-1);
495		}
496
497	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
498		if (strcmp(vp->cv_name, var) == 0)
499			break;
500
501	valcp = xstrdup(val);
502	if (vp == NULL) {
503		vp = xcalloc(1, sizeof(*vp));
504
505		vp->cv_name = xstrdup(var);
506		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
507
508	} else	/* free the previous value */
509		xfree(vp->cv_val);
510
511	vp->cv_val = valcp;
512
513	return (0);
514}
515
516/*
517 * cvs_var_set()
518 *
519 * Remove any entry for the variable <var>.
520 * Returns 0 on success, or -1 on failure.
521 */
522int
523cvs_var_unset(const char *var)
524{
525	struct cvs_var *vp;
526
527	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
528		if (strcmp(vp->cv_name, var) == 0) {
529			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
530			xfree(vp->cv_name);
531			xfree(vp->cv_val);
532			xfree(vp);
533			return (0);
534		}
535
536	return (-1);
537}
538
539/*
540 * cvs_var_get()
541 *
542 * Get the value associated with the variable <var>.  Returns a pointer to the
543 * value string on success, or NULL if the variable does not exist.
544 */
545
546const char *
547cvs_var_get(const char *var)
548{
549	struct cvs_var *vp;
550
551	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
552		if (strcmp(vp->cv_name, var) == 0)
553			return (vp->cv_val);
554
555	return (NULL);
556}
557