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