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