1 /* $OpenBSD: cvs.c,v 1.150 2008/06/21 15:39:15 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 <sys/stat.h> 29 30 #include <ctype.h> 31 #include <errno.h> 32 #include <pwd.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <unistd.h> 37 38 #include "cvs.h" 39 #include "remote.h" 40 #include "hash.h" 41 42 extern char *__progname; 43 44 /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 45 int verbosity = 2; 46 47 /* compression level used with zlib, 0 meaning no compression taking place */ 48 int cvs_compress = 0; 49 int cvs_readrc = 1; /* read .cvsrc on startup */ 50 int cvs_trace = 0; 51 int cvs_nolog = 0; 52 int cvs_readonly = 0; 53 int cvs_readonlyfs = 0; 54 int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 55 int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */ 56 int cvs_cmdop; 57 int cvs_umask = CVS_UMASK_DEFAULT; 58 int cvs_server_active = 0; 59 60 char *cvs_tagname = NULL; 61 char *cvs_defargs; /* default global arguments from .cvsrc */ 62 char *cvs_rootstr; 63 char *cvs_rsh = CVS_RSH_DEFAULT; 64 char *cvs_editor = CVS_EDITOR_DEFAULT; 65 char *cvs_homedir = NULL; 66 char *cvs_msg = NULL; 67 char *cvs_tmpdir = CVS_TMPDIR_DEFAULT; 68 69 struct cvsroot *current_cvsroot = NULL; 70 struct cvs_cmd *cmdp; /* struct of command we are running */ 71 72 int cvs_getopt(int, char **); 73 __dead void usage(void); 74 static void cvs_read_rcfile(void); 75 76 struct cvs_wklhead temp_files; 77 78 void sighandler(int); 79 volatile sig_atomic_t cvs_quit = 0; 80 volatile sig_atomic_t sig_received = 0; 81 82 extern CVSENTRIES *current_list; 83 84 struct hash_table created_directories; 85 struct hash_table created_cvs_directories; 86 87 void 88 sighandler(int sig) 89 { 90 sig_received = sig; 91 92 switch (sig) { 93 case SIGINT: 94 case SIGTERM: 95 case SIGPIPE: 96 cvs_quit = 1; 97 break; 98 default: 99 break; 100 } 101 } 102 103 void 104 cvs_cleanup(void) 105 { 106 cvs_log(LP_TRACE, "cvs_cleanup: removing locks"); 107 cvs_worklist_run(&repo_locks, cvs_worklist_unlink); 108 109 cvs_log(LP_TRACE, "cvs_cleanup: removing temp files"); 110 cvs_worklist_run(&temp_files, cvs_worklist_unlink); 111 112 if (cvs_server_path != NULL) { 113 if (cvs_rmdir(cvs_server_path) == -1) 114 cvs_log(LP_ERR, 115 "warning: failed to remove server directory: %s", 116 cvs_server_path); 117 xfree(cvs_server_path); 118 cvs_server_path = NULL; 119 } 120 121 if (current_list != NULL) 122 cvs_ent_close(current_list, ENT_SYNC); 123 } 124 125 __dead void 126 usage(void) 127 { 128 (void)fprintf(stderr, 129 "usage: %s [-flnQqRrtvw] [-d root] [-e editor] [-s var=val]\n" 130 " [-T tmpdir] [-z level] command ...\n", __progname); 131 exit(1); 132 } 133 134 int 135 cvs_build_cmd(char ***cmd_argv, char **argv, int argc) 136 { 137 int cmd_argc, i, cur; 138 char *cp, *linebuf, *lp; 139 140 if (cmdp->cmd_defargs == NULL) { 141 *cmd_argv = argv; 142 return argc; 143 } 144 145 cur = argc + 2; 146 cmd_argc = 0; 147 *cmd_argv = xcalloc(cur, sizeof(char *)); 148 (*cmd_argv)[cmd_argc++] = argv[0]; 149 150 linebuf = xstrdup(cmdp->cmd_defargs); 151 for (lp = linebuf; lp != NULL;) { 152 cp = strsep(&lp, " \t\b\f\n\r\t\v"); 153 if (cp == NULL) 154 break; 155 if (*cp == '\0') 156 continue; 157 158 if (cmd_argc == cur) { 159 cur += 8; 160 *cmd_argv = xrealloc(*cmd_argv, cur, 161 sizeof(char *)); 162 } 163 164 (*cmd_argv)[cmd_argc++] = cp; 165 } 166 167 if (cmd_argc + argc > cur) { 168 cur = cmd_argc + argc + 1; 169 *cmd_argv = xrealloc(*cmd_argv, cur, 170 sizeof(char *)); 171 } 172 173 for (i = 1; i < argc; i++) 174 (*cmd_argv)[cmd_argc++] = argv[i]; 175 176 (*cmd_argv)[cmd_argc] = NULL; 177 178 return cmd_argc; 179 } 180 181 int 182 main(int argc, char **argv) 183 { 184 char *envstr, **cmd_argv, **targv; 185 int i, ret, cmd_argc; 186 struct passwd *pw; 187 struct stat st; 188 char fpath[MAXPATHLEN]; 189 190 tzset(); 191 192 TAILQ_INIT(&cvs_variables); 193 SLIST_INIT(&repo_locks); 194 SLIST_INIT(&temp_files); 195 196 hash_table_init(&created_directories, 100); 197 hash_table_init(&created_cvs_directories, 100); 198 199 /* check environment so command-line options override it */ 200 if ((envstr = getenv("CVS_RSH")) != NULL) 201 cvs_rsh = envstr; 202 203 if (((envstr = getenv("CVSEDITOR")) != NULL) || 204 ((envstr = getenv("VISUAL")) != NULL) || 205 ((envstr = getenv("EDITOR")) != NULL)) 206 cvs_editor = envstr; 207 208 if ((envstr = getenv("CVSREAD")) != NULL) 209 cvs_readonly = 1; 210 211 if ((envstr = getenv("CVSREADONLYFS")) != NULL) { 212 cvs_readonlyfs = 1; 213 cvs_nolog = 1; 214 } 215 216 if ((cvs_homedir = getenv("HOME")) == NULL) { 217 if ((pw = getpwuid(getuid())) != NULL) 218 cvs_homedir = pw->pw_dir; 219 } 220 221 if ((envstr = getenv("TMPDIR")) != NULL) 222 cvs_tmpdir = envstr; 223 224 ret = cvs_getopt(argc, argv); 225 226 argc -= ret; 227 argv += ret; 228 if (argc == 0) 229 usage(); 230 231 cmdp = cvs_findcmd(argv[0]); 232 if (cmdp == NULL) { 233 fprintf(stderr, "Unknown command: `%s'\n\n", argv[0]); 234 fprintf(stderr, "CVS commands are:\n"); 235 for (i = 0; cvs_cdt[i] != NULL; i++) 236 fprintf(stderr, "\t%-16s%s\n", 237 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 238 exit(1); 239 } 240 241 /* 242 * check the tmp dir, either specified through 243 * the environment variable TMPDIR, or via 244 * the global option -T <dir> 245 */ 246 if (stat(cvs_tmpdir, &st) == -1) 247 fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno)); 248 else if (!S_ISDIR(st.st_mode)) 249 fatal("`%s' is not valid temporary directory", cvs_tmpdir); 250 251 if (cvs_readrc == 1 && cvs_homedir != NULL) { 252 cvs_read_rcfile(); 253 254 if (cvs_defargs != NULL) { 255 if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL) 256 fatal("failed to load default arguments to %s", 257 __progname); 258 259 cvs_getopt(i, targv); 260 cvs_freeargv(targv, i); 261 xfree(targv); 262 } 263 } 264 265 /* setup signal handlers */ 266 signal(SIGTERM, sighandler); 267 signal(SIGINT, sighandler); 268 signal(SIGHUP, sighandler); 269 signal(SIGABRT, sighandler); 270 signal(SIGALRM, sighandler); 271 signal(SIGPIPE, sighandler); 272 273 cvs_cmdop = cmdp->cmd_op; 274 275 cmd_argc = cvs_build_cmd(&cmd_argv, argv, argc); 276 277 cvs_file_init(); 278 279 if (cvs_cmdop == CVS_OP_SERVER) { 280 cmdp->cmd(cmd_argc, cmd_argv); 281 cvs_cleanup(); 282 return (0); 283 } 284 285 cvs_umask = umask(0); 286 umask(cvs_umask); 287 288 if ((current_cvsroot = cvsroot_get(".")) == NULL) { 289 cvs_log(LP_ERR, 290 "No CVSROOT specified! Please use the '-d' option"); 291 fatal("or set the CVSROOT environment variable."); 292 } 293 294 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 295 cmdp->cmd(cmd_argc, cmd_argv); 296 cvs_cleanup(); 297 return (0); 298 } 299 300 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", 301 current_cvsroot->cr_dir, CVS_PATH_ROOT); 302 303 if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) { 304 if (errno == ENOENT) 305 fatal("repository '%s' does not exist", 306 current_cvsroot->cr_dir); 307 else 308 fatal("%s: %s", current_cvsroot->cr_dir, 309 strerror(errno)); 310 } else { 311 if (!S_ISDIR(st.st_mode)) 312 fatal("'%s' is not a directory", 313 current_cvsroot->cr_dir); 314 } 315 316 if (cvs_cmdop != CVS_OP_INIT) { 317 cvs_parse_configfile(); 318 cvs_parse_modules(); 319 } 320 321 cmdp->cmd(cmd_argc, cmd_argv); 322 cvs_cleanup(); 323 324 return (0); 325 } 326 327 int 328 cvs_getopt(int argc, char **argv) 329 { 330 int ret; 331 char *ep; 332 const char *errstr; 333 334 while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tvwxz:")) != -1) { 335 switch (ret) { 336 case 'b': 337 /* 338 * We do not care about the bin directory for RCS files 339 * as this program has no dependencies on RCS programs, 340 * so it is only here for backwards compatibility. 341 */ 342 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 343 break; 344 case 'd': 345 cvs_rootstr = optarg; 346 break; 347 case 'e': 348 cvs_editor = optarg; 349 break; 350 case 'f': 351 cvs_readrc = 0; 352 break; 353 case 'l': 354 cvs_nolog = 1; 355 break; 356 case 'n': 357 cvs_noexec = 1; 358 cvs_nolog = 1; 359 break; 360 case 'Q': 361 verbosity = 0; 362 break; 363 case 'q': 364 if (verbosity > 1) 365 verbosity = 1; 366 break; 367 case 'R': 368 cvs_readonlyfs = 1; 369 cvs_nolog = 1; 370 break; 371 case 'r': 372 cvs_readonly = 1; 373 break; 374 case 's': 375 ep = strchr(optarg, '='); 376 if (ep == NULL) { 377 cvs_log(LP_ERR, "no = in variable assignment"); 378 exit(1); 379 } 380 *(ep++) = '\0'; 381 if (cvs_var_set(optarg, ep) < 0) 382 exit(1); 383 break; 384 case 'T': 385 cvs_tmpdir = optarg; 386 break; 387 case 't': 388 cvs_trace = 1; 389 break; 390 case 'v': 391 printf("%s\n", CVS_VERSION); 392 exit(0); 393 /* NOTREACHED */ 394 case 'w': 395 cvs_readonly = 0; 396 break; 397 case 'x': 398 /* 399 * Kerberos encryption support, kept for compatibility 400 */ 401 break; 402 case 'z': 403 cvs_compress = strtonum(optarg, 0, 9, &errstr); 404 if (errstr != NULL) 405 fatal("cvs_compress: %s", errstr); 406 break; 407 default: 408 usage(); 409 /* NOTREACHED */ 410 } 411 } 412 413 ret = optind; 414 optind = 1; 415 optreset = 1; /* for next call */ 416 417 return (ret); 418 } 419 420 /* 421 * cvs_read_rcfile() 422 * 423 * Read the CVS `.cvsrc' file in the user's home directory. If the file 424 * exists, it should contain a list of arguments that should always be given 425 * implicitly to the specified commands. 426 */ 427 static void 428 cvs_read_rcfile(void) 429 { 430 char rcpath[MAXPATHLEN], *buf, *lbuf, *lp, *p; 431 int cmd_parsed, cvs_parsed, i, linenum; 432 size_t len, pos; 433 struct cvs_cmd *tcmdp; 434 FILE *fp; 435 436 linenum = 0; 437 438 i = snprintf(rcpath, MAXPATHLEN, "%s/%s", cvs_homedir, CVS_PATH_RC); 439 if (i < 0 || i >= MAXPATHLEN) { 440 cvs_log(LP_ERRNO, "%s", rcpath); 441 return; 442 } 443 444 fp = fopen(rcpath, "r"); 445 if (fp == NULL) { 446 if (errno != ENOENT) 447 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 448 strerror(errno)); 449 return; 450 } 451 452 cmd_parsed = cvs_parsed = 0; 453 lbuf = NULL; 454 while ((buf = fgetln(fp, &len)) != NULL) { 455 if (buf[len - 1] == '\n') { 456 buf[len - 1] = '\0'; 457 } else { 458 lbuf = xmalloc(len + 1); 459 memcpy(lbuf, buf, len); 460 lbuf[len] = '\0'; 461 buf = lbuf; 462 } 463 464 linenum++; 465 466 /* skip any whitespaces */ 467 p = buf; 468 while (*p == ' ') 469 p++; 470 471 /* 472 * Allow comments. 473 * GNU cvs stops parsing a line if it encounters a \t 474 * in front of a command, stick at this behaviour for 475 * compatibility. 476 */ 477 if (*p == '#' || *p == '\t') 478 continue; 479 480 pos = strcspn(p, " \t"); 481 if (pos == strlen(p)) { 482 lp = NULL; 483 } else { 484 lp = p + pos; 485 *lp = '\0'; 486 } 487 488 if (strcmp(p, "cvs") == 0 && !cvs_parsed) { 489 /* 490 * Global default options. In the case of cvs only, 491 * we keep the 'cvs' string as first argument because 492 * getopt() does not like starting at index 0 for 493 * argument processing. 494 */ 495 if (lp != NULL) { 496 *lp = ' '; 497 cvs_defargs = xstrdup(p); 498 } 499 cvs_parsed = 1; 500 } else { 501 tcmdp = cvs_findcmd(p); 502 if (tcmdp == NULL && verbosity == 2) 503 cvs_log(LP_NOTICE, 504 "unknown command `%s' in `%s:%d'", 505 p, rcpath, linenum); 506 507 if (tcmdp != cmdp || cmd_parsed) 508 continue; 509 510 if (lp != NULL) { 511 lp++; 512 cmdp->cmd_defargs = xstrdup(lp); 513 } 514 cmd_parsed = 1; 515 } 516 } 517 if (lbuf != NULL) 518 xfree(lbuf); 519 520 if (ferror(fp)) { 521 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 522 } 523 524 (void)fclose(fp); 525 } 526 527 /* 528 * cvs_var_set() 529 * 530 * Set the value of the variable <var> to <val>. If there is no such variable, 531 * a new entry is created, otherwise the old value is overwritten. 532 * Returns 0 on success, or -1 on failure. 533 */ 534 int 535 cvs_var_set(const char *var, const char *val) 536 { 537 const char *cp; 538 struct cvs_var *vp; 539 540 if (var == NULL || *var == '\0') { 541 cvs_log(LP_ERR, "no variable name"); 542 return (-1); 543 } 544 545 /* sanity check on the name */ 546 for (cp = var; *cp != '\0'; cp++) 547 if (!isalnum(*cp) && (*cp != '_')) { 548 cvs_log(LP_ERR, 549 "variable name `%s' contains invalid characters", 550 var); 551 return (-1); 552 } 553 554 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 555 if (strcmp(vp->cv_name, var) == 0) 556 break; 557 558 if (vp == NULL) { 559 vp = xcalloc(1, sizeof(*vp)); 560 561 vp->cv_name = xstrdup(var); 562 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 563 564 } else /* free the previous value */ 565 xfree(vp->cv_val); 566 567 vp->cv_val = xstrdup(val); 568 569 return (0); 570 } 571 572 /* 573 * cvs_var_unset() 574 * 575 * Remove any entry for the variable <var>. 576 * Returns 0 on success, or -1 on failure. 577 */ 578 int 579 cvs_var_unset(const char *var) 580 { 581 struct cvs_var *vp; 582 583 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 584 if (strcmp(vp->cv_name, var) == 0) { 585 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 586 xfree(vp->cv_name); 587 xfree(vp->cv_val); 588 xfree(vp); 589 return (0); 590 } 591 592 return (-1); 593 } 594 595 /* 596 * cvs_var_get() 597 * 598 * Get the value associated with the variable <var>. Returns a pointer to the 599 * value string on success, or NULL if the variable does not exist. 600 */ 601 602 const char * 603 cvs_var_get(const char *var) 604 { 605 struct cvs_var *vp; 606 607 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 608 if (strcmp(vp->cv_name, var) == 0) 609 return (vp->cv_val); 610 611 return (NULL); 612 } 613