cvs.c revision 1.129
1219820Sjeff/* $OpenBSD: cvs.c,v 1.129 2007/08/06 19:16:06 sobrado Exp $ */ 2219820Sjeff/* 3219820Sjeff * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org> 4219820Sjeff * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 5219820Sjeff * All rights reserved. 6219820Sjeff * 7219820Sjeff * Redistribution and use in source and binary forms, with or without 8219820Sjeff * modification, are permitted provided that the following conditions 9219820Sjeff * are met: 10219820Sjeff * 11219820Sjeff * 1. Redistributions of source code must retain the above copyright 12219820Sjeff * notice, this list of conditions and the following disclaimer. 13219820Sjeff * 2. The name of the author may not be used to endorse or promote products 14219820Sjeff * derived from this software without specific prior written permission. 15219820Sjeff * 16219820Sjeff * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17219820Sjeff * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18219820Sjeff * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19219820Sjeff * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20219820Sjeff * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21219820Sjeff * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22219820Sjeff * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23219820Sjeff * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24219820Sjeff * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25219820Sjeff * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26219820Sjeff */ 27219820Sjeff 28219820Sjeff#include <sys/stat.h> 29219820Sjeff 30219820Sjeff#include <ctype.h> 31219820Sjeff#include <errno.h> 32219820Sjeff#include <pwd.h> 33219820Sjeff#include <stdlib.h> 34219820Sjeff#include <string.h> 35219820Sjeff#include <time.h> 36219820Sjeff#include <unistd.h> 37219820Sjeff 38219820Sjeff#include "cvs.h" 39219820Sjeff#include "remote.h" 40219820Sjeff 41219820Sjeffextern char *__progname; 42219820Sjeff 43219820Sjeff/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 44219820Sjeffint verbosity = 1; 45219820Sjeff 46219820Sjeff/* compression level used with zlib, 0 meaning no compression taking place */ 47219820Sjeffint cvs_compress = 0; 48219820Sjeffint cvs_readrc = 1; /* read .cvsrc on startup */ 49219820Sjeffint cvs_trace = 0; 50219820Sjeffint cvs_nolog = 0; 51219820Sjeffint cvs_readonly = 0; 52219820Sjeffint cvs_readonlyfs = 0; 53219820Sjeffint cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 54219820Sjeffint cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */ 55219820Sjeffint cvs_error = -1; /* set to the correct error code on failure */ 56219820Sjeffint cvs_cmdop; 57219820Sjeffint cvs_umask = CVS_UMASK_DEFAULT; 58219820Sjeffint cvs_server_active = 0; 59219820Sjeff 60219820Sjeffchar *cvs_tagname = NULL; 61219820Sjeffchar *cvs_defargs; /* default global arguments from .cvsrc */ 62219820Sjeffchar *cvs_command; /* name of the command we are running */ 63219820Sjeffchar *cvs_rootstr; 64219820Sjeffchar *cvs_rsh = CVS_RSH_DEFAULT; 65219820Sjeffchar *cvs_editor = CVS_EDITOR_DEFAULT; 66219820Sjeffchar *cvs_homedir = NULL; 67219820Sjeffchar *cvs_msg = NULL; 68219820Sjeffchar *cvs_tmpdir = CVS_TMPDIR_DEFAULT; 69219820Sjeff 70219820Sjeffstruct cvsroot *current_cvsroot = NULL; 71219820Sjeff 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 cvs_cmd *cmdp; 132 struct passwd *pw; 133 struct stat st; 134 char fpath[MAXPATHLEN]; 135 char *root, *rootp; 136 137 tzset(); 138 139 TAILQ_INIT(&cvs_variables); 140 SLIST_INIT(&repo_locks); 141 SLIST_INIT(&temp_files); 142 143 /* check environment so command-line options override it */ 144 if ((envstr = getenv("CVS_RSH")) != NULL) 145 cvs_rsh = envstr; 146 147 if (((envstr = getenv("CVSEDITOR")) != NULL) || 148 ((envstr = getenv("VISUAL")) != NULL) || 149 ((envstr = getenv("EDITOR")) != NULL)) 150 cvs_editor = envstr; 151 152 if ((envstr = getenv("CVSREAD")) != NULL) 153 cvs_readonly = 1; 154 155 if ((envstr = getenv("CVSREADONLYFS")) != NULL) { 156 cvs_readonlyfs = 1; 157 cvs_nolog = 1; 158 } 159 160 if ((cvs_homedir = getenv("HOME")) == NULL) { 161 if ((pw = getpwuid(getuid())) == NULL) 162 fatal("getpwuid failed"); 163 cvs_homedir = pw->pw_dir; 164 } 165 166 if ((envstr = getenv("TMPDIR")) != NULL) 167 cvs_tmpdir = envstr; 168 169 ret = cvs_getopt(argc, argv); 170 171 argc -= ret; 172 argv += ret; 173 if (argc == 0) 174 usage(); 175 176 cvs_command = argv[0]; 177 178 /* 179 * check the tmp dir, either specified through 180 * the environment variable TMPDIR, or via 181 * the global option -T <dir> 182 */ 183 if (stat(cvs_tmpdir, &st) == -1) 184 fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno)); 185 else if (!S_ISDIR(st.st_mode)) 186 fatal("`%s' is not valid temporary directory", cvs_tmpdir); 187 188 if (cvs_readrc == 1) { 189 cvs_read_rcfile(); 190 191 if (cvs_defargs != NULL) { 192 if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL) 193 fatal("failed to load default arguments to %s", 194 __progname); 195 196 cvs_getopt(i, targv); 197 cvs_freeargv(targv, i); 198 xfree(targv); 199 } 200 } 201 202 /* setup signal handlers */ 203 signal(SIGTERM, sighandler); 204 signal(SIGINT, sighandler); 205 signal(SIGHUP, sighandler); 206 signal(SIGABRT, sighandler); 207 signal(SIGALRM, sighandler); 208 signal(SIGPIPE, sighandler); 209 210 cmdp = cvs_findcmd(cvs_command); 211 if (cmdp == NULL) { 212 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 213 fprintf(stderr, "CVS commands are:\n"); 214 for (i = 0; cvs_cdt[i] != NULL; i++) 215 fprintf(stderr, "\t%-16s%s\n", 216 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 217 exit(1); 218 } 219 220 cvs_cmdop = cmdp->cmd_op; 221 222 cmd_argc = 0; 223 memset(cmd_argv, 0, sizeof(cmd_argv)); 224 225 cmd_argv[cmd_argc++] = argv[0]; 226 if (cmdp->cmd_defargs != NULL) { 227 /* transform into a new argument vector */ 228 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 229 CVS_CMD_MAXARG - 1); 230 if (ret < 0) 231 fatal("main: cvs_getargv failed"); 232 233 cmd_argc += ret; 234 } 235 236 if (argc + cmd_argc >= CVS_CMD_MAXARG) 237 fatal("main: too many arguments for `%s'", cmd_argv[0]); 238 for (ret = 1; ret < argc; ret++) 239 cmd_argv[cmd_argc++] = argv[ret]; 240 241 cvs_file_init(); 242 243 if (cvs_cmdop == CVS_OP_SERVER) { 244 if (cmd_argc > 1) 245 fatal("server does not take any extra arguments"); 246 247 setvbuf(stdin, NULL, _IOLBF, 0); 248 setvbuf(stdout, NULL, _IOLBF, 0); 249 250 cvs_server_active = 1; 251 root = cvs_remote_input(); 252 if ((rootp = strchr(root, ' ')) == NULL) 253 fatal("bad Root request"); 254 cvs_rootstr = xstrdup(rootp + 1); 255 xfree(root); 256 } 257 258 if ((current_cvsroot = cvsroot_get(".")) == NULL) { 259 cvs_log(LP_ERR, 260 "No CVSROOT specified! Please use the '-d' option"); 261 fatal("or set the CVSROOT environment variable."); 262 } 263 264 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 265 cmdp->cmd(cmd_argc, cmd_argv); 266 cvs_cleanup(); 267 return (0); 268 } 269 270 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", 271 current_cvsroot->cr_dir, CVS_PATH_ROOT); 272 273 if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) { 274 if (errno == ENOENT) 275 fatal("repository '%s' does not exist", 276 current_cvsroot->cr_dir); 277 else 278 fatal("%s: %s", current_cvsroot->cr_dir, 279 strerror(errno)); 280 } else { 281 if (!S_ISDIR(st.st_mode)) 282 fatal("'%s' is not a directory", 283 current_cvsroot->cr_dir); 284 } 285 286 if (cvs_cmdop != CVS_OP_INIT) 287 cvs_parse_configfile(); 288 289 umask(cvs_umask); 290 291 cmdp->cmd(cmd_argc, cmd_argv); 292 cvs_cleanup(); 293 294 return (0); 295} 296 297int 298cvs_getopt(int argc, char **argv) 299{ 300 int ret; 301 char *ep; 302 const char *errstr; 303 304 while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tVvwxz:")) != -1) { 305 switch (ret) { 306 case 'b': 307 /* 308 * We do not care about the bin directory for RCS files 309 * as this program has no dependencies on RCS programs, 310 * so it is only here for backwards compatibility. 311 */ 312 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 313 break; 314 case 'd': 315 cvs_rootstr = optarg; 316 break; 317 case 'e': 318 cvs_editor = optarg; 319 break; 320 case 'f': 321 cvs_readrc = 0; 322 break; 323 case 'l': 324 cvs_nolog = 1; 325 break; 326 case 'n': 327 cvs_noexec = 1; 328 cvs_nolog = 1; 329 break; 330 case 'Q': 331 verbosity = 0; 332 break; 333 case 'q': 334 /* 335 * Be quiet. This is the default in OpenCVS. 336 */ 337 break; 338 case 'R': 339 cvs_readonlyfs = 1; 340 cvs_nolog = 1; 341 break; 342 case 'r': 343 cvs_readonly = 1; 344 break; 345 case 's': 346 ep = strchr(optarg, '='); 347 if (ep == NULL) { 348 cvs_log(LP_ERR, "no = in variable assignment"); 349 exit(1); 350 } 351 *(ep++) = '\0'; 352 if (cvs_var_set(optarg, ep) < 0) 353 exit(1); 354 break; 355 case 'T': 356 cvs_tmpdir = optarg; 357 break; 358 case 't': 359 cvs_trace = 1; 360 break; 361 case 'V': 362 /* don't override -Q */ 363 if (verbosity) 364 verbosity = 2; 365 break; 366 case 'v': 367 printf("%s\n", CVS_VERSION); 368 exit(0); 369 /* NOTREACHED */ 370 break; 371 case 'w': 372 cvs_readonly = 0; 373 break; 374 case 'x': 375 /* 376 * Kerberos encryption support, kept for compatibility 377 */ 378 break; 379 case 'z': 380 cvs_compress = strtonum(optarg, 0, 9, &errstr); 381 if (errstr != NULL) 382 fatal("cvs_compress: %s", errstr); 383 break; 384 default: 385 usage(); 386 /* NOTREACHED */ 387 } 388 } 389 390 ret = optind; 391 optind = 1; 392 optreset = 1; /* for next call */ 393 394 return (ret); 395} 396 397/* 398 * cvs_read_rcfile() 399 * 400 * Read the CVS `.cvsrc' file in the user's home directory. If the file 401 * exists, it should contain a list of arguments that should always be given 402 * implicitly to the specified commands. 403 */ 404static void 405cvs_read_rcfile(void) 406{ 407 char rcpath[MAXPATHLEN], linebuf[128], *lp, *p; 408 int i, linenum; 409 size_t len; 410 struct cvs_cmd *cmdp; 411 FILE *fp; 412 413 linenum = 0; 414 415 i = snprintf(rcpath, MAXPATHLEN, "%s/%s", cvs_homedir, CVS_PATH_RC); 416 if (i < 0 || i >= MAXPATHLEN) { 417 cvs_log(LP_ERRNO, "%s", rcpath); 418 return; 419 } 420 421 fp = fopen(rcpath, "r"); 422 if (fp == NULL) { 423 if (errno != ENOENT) 424 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 425 strerror(errno)); 426 return; 427 } 428 429 while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) { 430 linenum++; 431 if ((len = strlen(linebuf)) == 0) 432 continue; 433 if (linebuf[len - 1] != '\n') { 434 cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath, 435 linenum); 436 break; 437 } 438 linebuf[--len] = '\0'; 439 440 /* skip any whitespaces */ 441 p = linebuf; 442 while (*p == ' ') 443 p++; 444 445 /* allow comments */ 446 if (*p == '#') 447 continue; 448 449 lp = strchr(p, ' '); 450 if (lp == NULL) 451 continue; /* ignore lines with no arguments */ 452 *lp = '\0'; 453 if (strcmp(p, "cvs") == 0) { 454 /* 455 * Global default options. In the case of cvs only, 456 * we keep the 'cvs' string as first argument because 457 * getopt() does not like starting at index 0 for 458 * argument processing. 459 */ 460 *lp = ' '; 461 cvs_defargs = xstrdup(p); 462 } else { 463 lp++; 464 cmdp = cvs_findcmd(p); 465 if (cmdp == NULL) { 466 cvs_log(LP_NOTICE, 467 "unknown command `%s' in `%s:%d'", 468 p, rcpath, linenum); 469 continue; 470 } 471 472 cmdp->cmd_defargs = xstrdup(lp); 473 } 474 } 475 476 if (ferror(fp)) { 477 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 478 } 479 480 (void)fclose(fp); 481} 482 483/* 484 * cvs_var_set() 485 * 486 * Set the value of the variable <var> to <val>. If there is no such variable, 487 * a new entry is created, otherwise the old value is overwritten. 488 * Returns 0 on success, or -1 on failure. 489 */ 490int 491cvs_var_set(const char *var, const char *val) 492{ 493 char *valcp; 494 const char *cp; 495 struct cvs_var *vp; 496 497 if (var == NULL || *var == '\0') { 498 cvs_log(LP_ERR, "no variable name"); 499 return (-1); 500 } 501 502 /* sanity check on the name */ 503 for (cp = var; *cp != '\0'; cp++) 504 if (!isalnum(*cp) && (*cp != '_')) { 505 cvs_log(LP_ERR, 506 "variable name `%s' contains invalid characters", 507 var); 508 return (-1); 509 } 510 511 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 512 if (strcmp(vp->cv_name, var) == 0) 513 break; 514 515 valcp = xstrdup(val); 516 if (vp == NULL) { 517 vp = xcalloc(1, sizeof(*vp)); 518 519 vp->cv_name = xstrdup(var); 520 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 521 522 } else /* free the previous value */ 523 xfree(vp->cv_val); 524 525 vp->cv_val = valcp; 526 527 return (0); 528} 529 530/* 531 * cvs_var_unset() 532 * 533 * Remove any entry for the variable <var>. 534 * Returns 0 on success, or -1 on failure. 535 */ 536int 537cvs_var_unset(const char *var) 538{ 539 struct cvs_var *vp; 540 541 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 542 if (strcmp(vp->cv_name, var) == 0) { 543 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 544 xfree(vp->cv_name); 545 xfree(vp->cv_val); 546 xfree(vp); 547 return (0); 548 } 549 550 return (-1); 551} 552 553/* 554 * cvs_var_get() 555 * 556 * Get the value associated with the variable <var>. Returns a pointer to the 557 * value string on success, or NULL if the variable does not exist. 558 */ 559 560const char * 561cvs_var_get(const char *var) 562{ 563 struct cvs_var *vp; 564 565 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 566 if (strcmp(vp->cv_name, var) == 0) 567 return (vp->cv_val); 568 569 return (NULL); 570} 571