cvs.c revision 1.106
1/* $OpenBSD: cvs.c,v 1.106 2006/07/07 17:37:17 joris Exp $ */ 2/* 3 * Copyright (c) 2006 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 36extern char *__progname; 37 38/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 39int verbosity = 1; 40 41/* compression level used with zlib, 0 meaning no compression taking place */ 42int cvs_compress = 0; 43int cvs_readrc = 1; /* read .cvsrc on startup */ 44int cvs_trace = 0; 45int cvs_nolog = 0; 46int cvs_readonly = 0; 47int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 48int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */ 49int cvs_error = -1; /* set to the correct error code on failure */ 50int cvs_cmdop; 51int cvs_umask = CVS_UMASK_DEFAULT; 52int cvs_server_active = 0; 53 54char *cvs_tagname = NULL; 55char *cvs_defargs; /* default global arguments from .cvsrc */ 56char *cvs_command; /* name of the command we are running */ 57char *cvs_rootstr; 58char *cvs_rsh = CVS_RSH_DEFAULT; 59char *cvs_editor = CVS_EDITOR_DEFAULT; 60char *cvs_homedir = NULL; 61char *cvs_msg = NULL; 62char *cvs_tmpdir = CVS_TMPDIR_DEFAULT; 63 64struct cvsroot *current_cvsroot = NULL; 65 66static TAILQ_HEAD(, cvs_var) cvs_variables; 67 68int cvs_getopt(int, char **); 69void usage(void); 70static void cvs_read_rcfile(void); 71 72struct cvs_wklhead temp_files; 73 74void sighandler(int); 75volatile sig_atomic_t cvs_quit = 0; 76volatile sig_atomic_t sig_received = 0; 77 78void 79sighandler(int sig) 80{ 81 sig_received = sig; 82 83 switch (sig) { 84 case SIGINT: 85 case SIGTERM: 86 cvs_quit = 1; 87 break; 88 default: 89 break; 90 } 91} 92 93void 94cvs_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 111void 112usage(void) 113{ 114 fprintf(stderr, 115 "Usage: %s [-flnQqrtvVw] [-d root] [-e editor] [-s var=val] " 116 "[-T tmpdir] [-z level] command [...]\n", __progname); 117} 118 119int 120main(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 ((cvs_homedir = getenv("HOME")) == NULL) { 149 if ((pw = getpwuid(getuid())) == NULL) 150 fatal("getpwuid failed"); 151 cvs_homedir = pw->pw_dir; 152 } 153 154 if ((envstr = getenv("TMPDIR")) != NULL) 155 cvs_tmpdir = envstr; 156 157 ret = cvs_getopt(argc, argv); 158 159 argc -= ret; 160 argv += ret; 161 if (argc == 0) { 162 usage(); 163 exit(1); 164 } 165 166 cvs_command = argv[0]; 167 168 /* 169 * check the tmp dir, either specified through 170 * the environment variable TMPDIR, or via 171 * the global option -T <dir> 172 */ 173 if (stat(cvs_tmpdir, &st) == -1) 174 fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno)); 175 else if (!S_ISDIR(st.st_mode)) 176 fatal("`%s' is not valid temporary directory", cvs_tmpdir); 177 178 if (cvs_readrc == 1) { 179 cvs_read_rcfile(); 180 181 if (cvs_defargs != NULL) { 182 if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL) 183 fatal("failed to load default arguments to %s", 184 __progname); 185 186 cvs_getopt(i, targv); 187 cvs_freeargv(targv, i); 188 xfree(targv); 189 } 190 } 191 192 /* setup signal handlers */ 193 signal(SIGTERM, sighandler); 194 signal(SIGINT, sighandler); 195 signal(SIGHUP, sighandler); 196 signal(SIGABRT, sighandler); 197 signal(SIGALRM, sighandler); 198 signal(SIGPIPE, sighandler); 199 200 cmdp = cvs_findcmd(cvs_command); 201 if (cmdp == NULL) { 202 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 203 fprintf(stderr, "CVS commands are:\n"); 204 for (i = 0; cvs_cdt[i] != NULL; i++) 205 fprintf(stderr, "\t%-16s%s\n", 206 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 207 exit(1); 208 } 209 210 cvs_cmdop = cmdp->cmd_op; 211 212 cmd_argc = 0; 213 memset(cmd_argv, 0, sizeof(cmd_argv)); 214 215 cmd_argv[cmd_argc++] = argv[0]; 216 if (cmdp->cmd_defargs != NULL) { 217 /* transform into a new argument vector */ 218 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 219 CVS_CMD_MAXARG - 1); 220 if (ret < 0) 221 fatal("main: cvs_getargv failed"); 222 223 cmd_argc += ret; 224 } 225 226 for (ret = 1; ret < argc; ret++) 227 cmd_argv[cmd_argc++] = argv[ret]; 228 229 cvs_file_init(); 230 231 if (cvs_cmdop == CVS_OP_SERVER) { 232 setvbuf(stdin, NULL, _IOLBF, 0); 233 setvbuf(stdout, NULL, _IOLBF, 0); 234 235 cvs_server_active = 1; 236 root = cvs_remote_input(); 237 if ((rootp = strchr(root, ' ')) == NULL) 238 fatal("bad Root request"); 239 cvs_rootstr = xstrdup(rootp + 1); 240 xfree(root); 241 } 242 243 if ((current_cvsroot = cvsroot_get(".")) == NULL) { 244 cvs_log(LP_ERR, 245 "No CVSROOT specified! Please use the '-d' option"); 246 fatal("or set the CVSROOT environment variable."); 247 } 248 249 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 250 if (cvs_server_active == 1) 251 fatal("remote Root while already running as server?"); 252 253 cvs_client_connect_to_server(); 254 cmdp->cmd(cmd_argc, cmd_argv); 255 cvs_cleanup(); 256 return (0); 257 } 258 259 i = snprintf(fpath, sizeof(fpath), "%s/%s", current_cvsroot->cr_dir, 260 CVS_PATH_ROOT); 261 if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) { 262 if (errno == ENOENT) 263 fatal("repository '%s' does not exist", 264 current_cvsroot->cr_dir); 265 else 266 fatal("%s: %s", current_cvsroot->cr_dir, 267 strerror(errno)); 268 } else { 269 if (!S_ISDIR(st.st_mode)) 270 fatal("'%s' is not a directory", 271 current_cvsroot->cr_dir); 272 } 273 274 if (cvs_cmdop != CVS_OP_INIT) 275 cvs_parse_configfile(); 276 277 umask(cvs_umask); 278 279 cmdp->cmd(cmd_argc, cmd_argv); 280 cvs_cleanup(); 281 282 return (0); 283} 284 285int 286cvs_getopt(int argc, char **argv) 287{ 288 int ret; 289 char *ep; 290 291 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:T:tvVwz:")) != -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 break; 316 case 'Q': 317 verbosity = 0; 318 break; 319 case 'q': 320 /* 321 * Be quiet. This is the default in OpenCVS. 322 */ 323 break; 324 case 'r': 325 cvs_readonly = 1; 326 break; 327 case 's': 328 ep = strchr(optarg, '='); 329 if (ep == NULL) { 330 cvs_log(LP_ERR, "no = in variable assignment"); 331 exit(1); 332 } 333 *(ep++) = '\0'; 334 if (cvs_var_set(optarg, ep) < 0) 335 exit(1); 336 break; 337 case 'T': 338 cvs_tmpdir = optarg; 339 break; 340 case 't': 341 cvs_trace = 1; 342 break; 343 case 'V': 344 /* don't override -Q */ 345 if (verbosity) 346 verbosity = 2; 347 break; 348 case 'v': 349 printf("%s\n", CVS_VERSION); 350 exit(0); 351 /* NOTREACHED */ 352 break; 353 case 'w': 354 cvs_readonly = 0; 355 break; 356 case 'x': 357 /* 358 * Kerberos encryption support, kept for compatibility 359 */ 360 break; 361 case 'z': 362 cvs_compress = (int)strtol(optarg, &ep, 10); 363 if (*ep != '\0') 364 fatal("error parsing compression level"); 365 if (cvs_compress < 0 || cvs_compress > 9) 366 fatal("gzip compression level must be " 367 "between 0 and 9"); 368 break; 369 default: 370 usage(); 371 exit(1); 372 } 373 } 374 375 ret = optind; 376 optind = 1; 377 optreset = 1; /* for next call */ 378 379 return (ret); 380} 381 382/* 383 * cvs_read_rcfile() 384 * 385 * Read the CVS `.cvsrc' file in the user's home directory. If the file 386 * exists, it should contain a list of arguments that should always be given 387 * implicitly to the specified commands. 388 */ 389static void 390cvs_read_rcfile(void) 391{ 392 char rcpath[MAXPATHLEN], linebuf[128], *lp, *p; 393 int linenum = 0; 394 size_t len; 395 struct cvs_cmd *cmdp; 396 FILE *fp; 397 398 if (strlcpy(rcpath, cvs_homedir, sizeof(rcpath)) >= sizeof(rcpath) || 399 strlcat(rcpath, "/", sizeof(rcpath)) >= sizeof(rcpath) || 400 strlcat(rcpath, CVS_PATH_RC, sizeof(rcpath)) >= sizeof(rcpath)) { 401 errno = ENAMETOOLONG; 402 cvs_log(LP_ERR, "%s", rcpath); 403 return; 404 } 405 406 fp = fopen(rcpath, "r"); 407 if (fp == NULL) { 408 if (errno != ENOENT) 409 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 410 strerror(errno)); 411 return; 412 } 413 414 while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) { 415 linenum++; 416 if ((len = strlen(linebuf)) == 0) 417 continue; 418 if (linebuf[len - 1] != '\n') { 419 cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath, 420 linenum); 421 break; 422 } 423 linebuf[--len] = '\0'; 424 425 /* skip any whitespaces */ 426 p = linebuf; 427 while (*p == ' ') 428 p++; 429 430 /* allow comments */ 431 if (*p == '#') 432 continue; 433 434 lp = strchr(p, ' '); 435 if (lp == NULL) 436 continue; /* ignore lines with no arguments */ 437 *lp = '\0'; 438 if (strcmp(p, "cvs") == 0) { 439 /* 440 * Global default options. In the case of cvs only, 441 * we keep the 'cvs' string as first argument because 442 * getopt() does not like starting at index 0 for 443 * argument processing. 444 */ 445 *lp = ' '; 446 cvs_defargs = xstrdup(p); 447 } else { 448 lp++; 449 cmdp = cvs_findcmd(p); 450 if (cmdp == NULL) { 451 cvs_log(LP_NOTICE, 452 "unknown command `%s' in `%s:%d'", 453 p, rcpath, linenum); 454 continue; 455 } 456 457 cmdp->cmd_defargs = xstrdup(lp); 458 } 459 } 460 461 if (ferror(fp)) { 462 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 463 } 464 465 (void)fclose(fp); 466} 467 468/* 469 * cvs_var_set() 470 * 471 * Set the value of the variable <var> to <val>. If there is no such variable, 472 * a new entry is created, otherwise the old value is overwritten. 473 * Returns 0 on success, or -1 on failure. 474 */ 475int 476cvs_var_set(const char *var, const char *val) 477{ 478 char *valcp; 479 const char *cp; 480 struct cvs_var *vp; 481 482 if (var == NULL || *var == '\0') { 483 cvs_log(LP_ERR, "no variable name"); 484 return (-1); 485 } 486 487 /* sanity check on the name */ 488 for (cp = var; *cp != '\0'; cp++) 489 if (!isalnum(*cp) && (*cp != '_')) { 490 cvs_log(LP_ERR, 491 "variable name `%s' contains invalid characters", 492 var); 493 return (-1); 494 } 495 496 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 497 if (strcmp(vp->cv_name, var) == 0) 498 break; 499 500 valcp = xstrdup(val); 501 if (vp == NULL) { 502 vp = xcalloc(1, sizeof(*vp)); 503 504 vp->cv_name = xstrdup(var); 505 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 506 507 } else /* free the previous value */ 508 xfree(vp->cv_val); 509 510 vp->cv_val = valcp; 511 512 return (0); 513} 514 515/* 516 * cvs_var_set() 517 * 518 * Remove any entry for the variable <var>. 519 * Returns 0 on success, or -1 on failure. 520 */ 521int 522cvs_var_unset(const char *var) 523{ 524 struct cvs_var *vp; 525 526 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 527 if (strcmp(vp->cv_name, var) == 0) { 528 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 529 xfree(vp->cv_name); 530 xfree(vp->cv_val); 531 xfree(vp); 532 return (0); 533 } 534 535 return (-1); 536} 537 538/* 539 * cvs_var_get() 540 * 541 * Get the value associated with the variable <var>. Returns a pointer to the 542 * value string on success, or NULL if the variable does not exist. 543 */ 544 545const char * 546cvs_var_get(const char *var) 547{ 548 struct cvs_var *vp; 549 550 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 551 if (strcmp(vp->cv_name, var) == 0) 552 return (vp->cv_val); 553 554 return (NULL); 555} 556