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