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