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