1 /* $OpenBSD: cvs.c,v 1.113 2007/01/11 02:35:55 joris Exp $ */ 2 /* 3 * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org> 4 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "includes.h" 29 30 #include "cvs.h" 31 #include "config.h" 32 #include "log.h" 33 #include "file.h" 34 #include "remote.h" 35 36 extern char *__progname; 37 38 /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 39 int verbosity = 1; 40 41 /* compression level used with zlib, 0 meaning no compression taking place */ 42 int cvs_compress = 0; 43 int cvs_readrc = 1; /* read .cvsrc on startup */ 44 int cvs_trace = 0; 45 int cvs_nolog = 0; 46 int cvs_readonly = 0; 47 int cvs_readonlyfs = 0; 48 int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 49 int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */ 50 int cvs_error = -1; /* set to the correct error code on failure */ 51 int cvs_cmdop; 52 int cvs_umask = CVS_UMASK_DEFAULT; 53 int cvs_server_active = 0; 54 55 char *cvs_tagname = NULL; 56 char *cvs_defargs; /* default global arguments from .cvsrc */ 57 char *cvs_command; /* name of the command we are running */ 58 char *cvs_rootstr; 59 char *cvs_rsh = CVS_RSH_DEFAULT; 60 char *cvs_editor = CVS_EDITOR_DEFAULT; 61 char *cvs_homedir = NULL; 62 char *cvs_msg = NULL; 63 char *cvs_tmpdir = CVS_TMPDIR_DEFAULT; 64 65 struct cvsroot *current_cvsroot = NULL; 66 67 int cvs_getopt(int, char **); 68 void usage(void); 69 static void cvs_read_rcfile(void); 70 71 struct cvs_wklhead temp_files; 72 73 void sighandler(int); 74 volatile sig_atomic_t cvs_quit = 0; 75 volatile sig_atomic_t sig_received = 0; 76 77 void 78 sighandler(int sig) 79 { 80 sig_received = sig; 81 82 switch (sig) { 83 case SIGINT: 84 case SIGTERM: 85 case SIGPIPE: 86 cvs_quit = 1; 87 break; 88 default: 89 break; 90 } 91 } 92 93 void 94 cvs_cleanup(void) 95 { 96 cvs_log(LP_TRACE, "cvs_cleanup: removing locks"); 97 cvs_worklist_run(&repo_locks, cvs_worklist_unlink); 98 99 cvs_log(LP_TRACE, "cvs_cleanup: removing temp files"); 100 cvs_worklist_run(&temp_files, cvs_worklist_unlink); 101 102 if (cvs_server_active) { 103 if (cvs_rmdir(cvs_server_path) == -1) 104 cvs_log(LP_ERR, 105 "warning: failed to remove server directory: %s", 106 cvs_server_path); 107 xfree(cvs_server_path); 108 } 109 } 110 111 void 112 usage(void) 113 { 114 fprintf(stderr, 115 "Usage: %s [-flnQqRrtvVw] [-d root] [-e editor] [-s var=val] " 116 "[-T tmpdir] [-z level] command [...]\n", __progname); 117 } 118 119 int 120 main(int argc, char **argv) 121 { 122 char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv; 123 int i, ret, cmd_argc; 124 struct cvs_cmd *cmdp; 125 struct passwd *pw; 126 struct stat st; 127 char fpath[MAXPATHLEN]; 128 char *root, *rootp; 129 130 tzset(); 131 132 TAILQ_INIT(&cvs_variables); 133 SLIST_INIT(&repo_locks); 134 SLIST_INIT(&temp_files); 135 136 /* check environment so command-line options override it */ 137 if ((envstr = getenv("CVS_RSH")) != NULL) 138 cvs_rsh = envstr; 139 140 if (((envstr = getenv("CVSEDITOR")) != NULL) || 141 ((envstr = getenv("VISUAL")) != NULL) || 142 ((envstr = getenv("EDITOR")) != NULL)) 143 cvs_editor = envstr; 144 145 if ((envstr = getenv("CVSREAD")) != NULL) 146 cvs_readonly = 1; 147 148 if ((envstr = getenv("CVSREADONLYFS")) != NULL) { 149 cvs_readonlyfs = 1; 150 cvs_nolog = 1; 151 } 152 153 if ((cvs_homedir = getenv("HOME")) == NULL) { 154 if ((pw = getpwuid(getuid())) == NULL) 155 fatal("getpwuid failed"); 156 cvs_homedir = pw->pw_dir; 157 } 158 159 if ((envstr = getenv("TMPDIR")) != NULL) 160 cvs_tmpdir = envstr; 161 162 ret = cvs_getopt(argc, argv); 163 164 argc -= ret; 165 argv += ret; 166 if (argc == 0) { 167 usage(); 168 exit(1); 169 } 170 171 cvs_command = argv[0]; 172 173 /* 174 * check the tmp dir, either specified through 175 * the environment variable TMPDIR, or via 176 * the global option -T <dir> 177 */ 178 if (stat(cvs_tmpdir, &st) == -1) 179 fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno)); 180 else if (!S_ISDIR(st.st_mode)) 181 fatal("`%s' is not valid temporary directory", cvs_tmpdir); 182 183 if (cvs_readrc == 1) { 184 cvs_read_rcfile(); 185 186 if (cvs_defargs != NULL) { 187 if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL) 188 fatal("failed to load default arguments to %s", 189 __progname); 190 191 cvs_getopt(i, targv); 192 cvs_freeargv(targv, i); 193 xfree(targv); 194 } 195 } 196 197 /* setup signal handlers */ 198 signal(SIGTERM, sighandler); 199 signal(SIGINT, sighandler); 200 signal(SIGHUP, sighandler); 201 signal(SIGABRT, sighandler); 202 signal(SIGALRM, sighandler); 203 signal(SIGPIPE, sighandler); 204 205 cmdp = cvs_findcmd(cvs_command); 206 if (cmdp == NULL) { 207 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 208 fprintf(stderr, "CVS commands are:\n"); 209 for (i = 0; cvs_cdt[i] != NULL; i++) 210 fprintf(stderr, "\t%-16s%s\n", 211 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 212 exit(1); 213 } 214 215 cvs_cmdop = cmdp->cmd_op; 216 217 cmd_argc = 0; 218 memset(cmd_argv, 0, sizeof(cmd_argv)); 219 220 cmd_argv[cmd_argc++] = argv[0]; 221 if (cmdp->cmd_defargs != NULL) { 222 /* transform into a new argument vector */ 223 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 224 CVS_CMD_MAXARG - 1); 225 if (ret < 0) 226 fatal("main: cvs_getargv failed"); 227 228 cmd_argc += ret; 229 } 230 231 for (ret = 1; ret < argc; ret++) 232 cmd_argv[cmd_argc++] = argv[ret]; 233 234 cvs_file_init(); 235 236 if (cvs_cmdop == CVS_OP_SERVER) { 237 setvbuf(stdin, NULL, _IOLBF, 0); 238 setvbuf(stdout, NULL, _IOLBF, 0); 239 240 cvs_server_active = 1; 241 root = cvs_remote_input(); 242 if ((rootp = strchr(root, ' ')) == NULL) 243 fatal("bad Root request"); 244 cvs_rootstr = xstrdup(rootp + 1); 245 xfree(root); 246 } 247 248 if ((current_cvsroot = cvsroot_get(".")) == NULL) { 249 cvs_log(LP_ERR, 250 "No CVSROOT specified! Please use the '-d' option"); 251 fatal("or set the CVSROOT environment variable."); 252 } 253 254 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 255 cmdp->cmd(cmd_argc, cmd_argv); 256 cvs_cleanup(); 257 return (0); 258 } 259 260 if (cvs_path_cat(current_cvsroot->cr_dir, CVS_PATH_ROOT, 261 fpath, sizeof(fpath)) >= sizeof(fpath)) 262 fatal("main: truncation"); 263 264 if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) { 265 if (errno == ENOENT) 266 fatal("repository '%s' does not exist", 267 current_cvsroot->cr_dir); 268 else 269 fatal("%s: %s", current_cvsroot->cr_dir, 270 strerror(errno)); 271 } else { 272 if (!S_ISDIR(st.st_mode)) 273 fatal("'%s' is not a directory", 274 current_cvsroot->cr_dir); 275 } 276 277 if (cvs_cmdop != CVS_OP_INIT) 278 cvs_parse_configfile(); 279 280 umask(cvs_umask); 281 282 cmdp->cmd(cmd_argc, cmd_argv); 283 cvs_cleanup(); 284 285 return (0); 286 } 287 288 int 289 cvs_getopt(int argc, char **argv) 290 { 291 int ret; 292 char *ep; 293 294 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqRrs:T:tvVwz:")) != -1) { 295 switch (ret) { 296 case 'b': 297 /* 298 * We do not care about the bin directory for RCS files 299 * as this program has no dependencies on RCS programs, 300 * so it is only here for backwards compatibility. 301 */ 302 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 303 break; 304 case 'd': 305 cvs_rootstr = optarg; 306 break; 307 case 'e': 308 cvs_editor = optarg; 309 break; 310 case 'f': 311 cvs_readrc = 0; 312 break; 313 case 'l': 314 cvs_nolog = 1; 315 break; 316 case 'n': 317 cvs_noexec = 1; 318 cvs_nolog = 1; 319 break; 320 case 'Q': 321 verbosity = 0; 322 break; 323 case 'q': 324 /* 325 * Be quiet. This is the default in OpenCVS. 326 */ 327 break; 328 case 'R': 329 cvs_readonlyfs = 1; 330 cvs_nolog = 1; 331 break; 332 case 'r': 333 cvs_readonly = 1; 334 break; 335 case 's': 336 ep = strchr(optarg, '='); 337 if (ep == NULL) { 338 cvs_log(LP_ERR, "no = in variable assignment"); 339 exit(1); 340 } 341 *(ep++) = '\0'; 342 if (cvs_var_set(optarg, ep) < 0) 343 exit(1); 344 break; 345 case 'T': 346 cvs_tmpdir = optarg; 347 break; 348 case 't': 349 cvs_trace = 1; 350 break; 351 case 'V': 352 /* don't override -Q */ 353 if (verbosity) 354 verbosity = 2; 355 break; 356 case 'v': 357 printf("%s\n", CVS_VERSION); 358 exit(0); 359 /* NOTREACHED */ 360 break; 361 case 'w': 362 cvs_readonly = 0; 363 break; 364 case 'x': 365 /* 366 * Kerberos encryption support, kept for compatibility 367 */ 368 break; 369 case 'z': 370 cvs_compress = (int)strtol(optarg, &ep, 10); 371 if (*ep != '\0') 372 fatal("error parsing compression level"); 373 if (cvs_compress < 0 || cvs_compress > 9) 374 fatal("gzip compression level must be " 375 "between 0 and 9"); 376 break; 377 default: 378 usage(); 379 exit(1); 380 } 381 } 382 383 ret = optind; 384 optind = 1; 385 optreset = 1; /* for next call */ 386 387 return (ret); 388 } 389 390 /* 391 * cvs_read_rcfile() 392 * 393 * Read the CVS `.cvsrc' file in the user's home directory. If the file 394 * exists, it should contain a list of arguments that should always be given 395 * implicitly to the specified commands. 396 */ 397 static void 398 cvs_read_rcfile(void) 399 { 400 char rcpath[MAXPATHLEN], linebuf[128], *lp, *p; 401 int linenum = 0; 402 size_t len; 403 struct cvs_cmd *cmdp; 404 FILE *fp; 405 406 if (cvs_path_cat(cvs_homedir, CVS_PATH_RC, rcpath, sizeof(rcpath)) 407 >= sizeof(rcpath)) { 408 cvs_log(LP_ERRNO, "%s", rcpath); 409 return; 410 } 411 412 fp = fopen(rcpath, "r"); 413 if (fp == NULL) { 414 if (errno != ENOENT) 415 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 416 strerror(errno)); 417 return; 418 } 419 420 while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) { 421 linenum++; 422 if ((len = strlen(linebuf)) == 0) 423 continue; 424 if (linebuf[len - 1] != '\n') { 425 cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath, 426 linenum); 427 break; 428 } 429 linebuf[--len] = '\0'; 430 431 /* skip any whitespaces */ 432 p = linebuf; 433 while (*p == ' ') 434 p++; 435 436 /* allow comments */ 437 if (*p == '#') 438 continue; 439 440 lp = strchr(p, ' '); 441 if (lp == NULL) 442 continue; /* ignore lines with no arguments */ 443 *lp = '\0'; 444 if (strcmp(p, "cvs") == 0) { 445 /* 446 * Global default options. In the case of cvs only, 447 * we keep the 'cvs' string as first argument because 448 * getopt() does not like starting at index 0 for 449 * argument processing. 450 */ 451 *lp = ' '; 452 cvs_defargs = xstrdup(p); 453 } else { 454 lp++; 455 cmdp = cvs_findcmd(p); 456 if (cmdp == NULL) { 457 cvs_log(LP_NOTICE, 458 "unknown command `%s' in `%s:%d'", 459 p, rcpath, linenum); 460 continue; 461 } 462 463 cmdp->cmd_defargs = xstrdup(lp); 464 } 465 } 466 467 if (ferror(fp)) { 468 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 469 } 470 471 (void)fclose(fp); 472 } 473 474 /* 475 * cvs_var_set() 476 * 477 * Set the value of the variable <var> to <val>. If there is no such variable, 478 * a new entry is created, otherwise the old value is overwritten. 479 * Returns 0 on success, or -1 on failure. 480 */ 481 int 482 cvs_var_set(const char *var, const char *val) 483 { 484 char *valcp; 485 const char *cp; 486 struct cvs_var *vp; 487 488 if (var == NULL || *var == '\0') { 489 cvs_log(LP_ERR, "no variable name"); 490 return (-1); 491 } 492 493 /* sanity check on the name */ 494 for (cp = var; *cp != '\0'; cp++) 495 if (!isalnum(*cp) && (*cp != '_')) { 496 cvs_log(LP_ERR, 497 "variable name `%s' contains invalid characters", 498 var); 499 return (-1); 500 } 501 502 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 503 if (strcmp(vp->cv_name, var) == 0) 504 break; 505 506 valcp = xstrdup(val); 507 if (vp == NULL) { 508 vp = xcalloc(1, sizeof(*vp)); 509 510 vp->cv_name = xstrdup(var); 511 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 512 513 } else /* free the previous value */ 514 xfree(vp->cv_val); 515 516 vp->cv_val = valcp; 517 518 return (0); 519 } 520 521 /* 522 * cvs_var_set() 523 * 524 * Remove any entry for the variable <var>. 525 * Returns 0 on success, or -1 on failure. 526 */ 527 int 528 cvs_var_unset(const char *var) 529 { 530 struct cvs_var *vp; 531 532 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 533 if (strcmp(vp->cv_name, var) == 0) { 534 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 535 xfree(vp->cv_name); 536 xfree(vp->cv_val); 537 xfree(vp); 538 return (0); 539 } 540 541 return (-1); 542 } 543 544 /* 545 * cvs_var_get() 546 * 547 * Get the value associated with the variable <var>. Returns a pointer to the 548 * value string on success, or NULL if the variable does not exist. 549 */ 550 551 const char * 552 cvs_var_get(const char *var) 553 { 554 struct cvs_var *vp; 555 556 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 557 if (strcmp(vp->cv_name, var) == 0) 558 return (vp->cv_val); 559 560 return (NULL); 561 } 562