cvs.c revision 1.48
1/* $OpenBSD: cvs.c,v 1.48 2005/04/06 16:35:25 jfb Exp $ */ 2/* 3 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. The name of the author may not be used to endorse or promote products 13 * derived from this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 18 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <sys/types.h> 28#include <sys/wait.h> 29 30#include <err.h> 31#include <pwd.h> 32#include <errno.h> 33#include <stdio.h> 34#include <ctype.h> 35#include <stdlib.h> 36#include <unistd.h> 37#include <signal.h> 38#include <string.h> 39#include <sysexits.h> 40 41#include "cvs.h" 42#include "log.h" 43#include "file.h" 44#include "strtab.h" 45 46 47extern char *__progname; 48 49 50/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 51int verbosity = 2; 52 53/* compression level used with zlib, 0 meaning no compression taking place */ 54int cvs_compress = 0; 55int cvs_readrc = 1; /* read .cvsrc on startup */ 56int cvs_trace = 0; 57int cvs_nolog = 0; 58int cvs_readonly = 0; 59int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 60 61char *cvs_defargs; /* default global arguments from .cvsrc */ 62char *cvs_command; /* name of the command we are running */ 63int cvs_cmdop; 64char *cvs_rootstr; 65char *cvs_rsh = CVS_RSH_DEFAULT; 66char *cvs_editor = CVS_EDITOR_DEFAULT; 67 68char *cvs_msg = NULL; 69 70/* hierarchy of all the files affected by the command */ 71CVSFILE *cvs_files; 72 73 74static TAILQ_HEAD(, cvs_var) cvs_variables; 75 76/* 77 * Command dispatch table 78 * ---------------------- 79 * 80 * The synopsis field should only contain the list of arguments that the 81 * command supports, without the actual command's name. 82 * 83 * Command handlers are expected to return 0 if no error occurred, or one of 84 * the values known in sysexits.h in case of an error. In case the error 85 * returned is EX_USAGE, the command's usage string is printed to standard 86 * error before returning. 87 */ 88struct cvs_cmd cvs_cdt[] = { 89 { 90 CVS_OP_ADD, "add", { "ad", "new" }, &cvs_add, 91 "[-k opt] [-m msg] file ...", 92 "k:m:", 93 "Add a new file/directory to the repository", 94 NULL 95 }, 96 { 97 CVS_OP_ADMIN, "admin", { "adm", "rcs" }, &cvs_admin, 98 "", 99 "a:A:b::c:e::Ik:l::Lm:n:N:o:qs:t:u::U", 100 "Administration front end for rcs", 101 NULL 102 }, 103 { 104 CVS_OP_ANNOTATE, "annotate", { "ann" }, &cvs_annotate, 105 "[-flR] [-D date | -r rev] ...", 106 "D:flRr:", 107 "Show last revision where each line was modified", 108 NULL 109 }, 110 { 111 CVS_OP_CHECKOUT, "checkout", { "co", "get" }, &cvs_checkout, 112 "[-AcflNnPpRs] [-D date | -r rev] [-d dir] [-j rev] [-k mode] " 113 "[-t id] module ...", 114 "AcD:d:fj:k:lNnPRr:st:", 115 "Checkout sources for editing", 116 NULL 117 }, 118 { 119 CVS_OP_COMMIT, "commit", { "ci", "com" }, &cvs_commit, 120 "[-flR] [-F logfile | -m msg] [-r rev] ...", 121 "F:flm:Rr:", 122 "Check files into the repository", 123 NULL 124 }, 125 { 126 CVS_OP_DIFF, "diff", { "di", "dif" }, &cvs_diff, 127 "[-cilNpu] [-D date] [-r rev] ...", 128 "cD:ilNpr:u", 129 "Show differences between revisions", 130 NULL 131 }, 132 { 133 CVS_OP_EDIT, "edit", { }, NULL, 134 "", 135 "", 136 "Get ready to edit a watched file", 137 NULL 138 }, 139 { 140 CVS_OP_EDITORS, "editors", { }, NULL, 141 "", 142 "", 143 "See who is editing a watched file", 144 NULL 145 }, 146 { 147 CVS_OP_EXPORT, "export", { "ex", "exp" }, NULL, 148 "", 149 "", 150 "Export sources from CVS, similar to checkout", 151 NULL 152 }, 153 { 154 CVS_OP_HISTORY, "history", { "hi", "his" }, &cvs_history, 155 "", 156 "acelm:oTt:u:wx:z:", 157 "Show repository access history", 158 NULL 159 }, 160 { 161 CVS_OP_IMPORT, "import", { "im", "imp" }, &cvs_import, 162 "[-d] [-b branch] [-I ign] [-k subst] [-m msg] " 163 "repository vendor-tag release-tags ...", 164 "b:dI:k:m:", 165 "Import sources into CVS, using vendor branches", 166 NULL 167 }, 168 { 169 CVS_OP_INIT, "init", { }, &cvs_init, 170 "", 171 "", 172 "Create a CVS repository if it doesn't exist", 173 NULL 174 }, 175#if defined(HAVE_KERBEROS) 176 { 177 "kserver", {}, NULL 178 "", 179 "", 180 "Start a Kerberos authentication CVS server", 181 NULL 182 }, 183#endif 184 { 185 CVS_OP_LOG, "log", { "lo" }, &cvs_getlog, 186 "[-bhlNRt] [-d dates] [-r revisions] [-s states] [-w logins]", 187 "d:hlRr:", 188 "Print out history information for files", 189 NULL 190 }, 191 { 192 -1, "login", {}, NULL, 193 "", 194 "", 195 "Prompt for password for authenticating server", 196 NULL 197 }, 198 { 199 -1, "logout", {}, NULL, 200 "", 201 "", 202 "Removes entry in .cvspass for remote repository", 203 NULL 204 }, 205 { 206 CVS_OP_RDIFF, "rdiff", {}, NULL, 207 "", 208 "", 209 "Create 'patch' format diffs between releases", 210 NULL 211 }, 212 { 213 CVS_OP_RELEASE, "release", {}, NULL, 214 "[-d]", 215 "d", 216 "Indicate that a Module is no longer in use", 217 NULL 218 }, 219 { 220 CVS_OP_REMOVE, "remove", { "rm", "delete" }, &cvs_remove, 221 "[-flR] file ...", 222 "flR", 223 "Remove an entry from the repository", 224 NULL 225 }, 226 { 227 CVS_OP_RLOG, "rlog", {}, NULL, 228 "", 229 "", 230 "Print out history information for a module", 231 NULL 232 }, 233 { 234 CVS_OP_RTAG, "rtag", {}, NULL, 235 "", 236 "", 237 "Add a symbolic tag to a module", 238 NULL 239 }, 240 { 241 CVS_OP_SERVER, "server", {}, &cmd_server, 242 "", 243 "", 244 "Server mode", 245 NULL 246 }, 247 { 248 CVS_OP_STATUS, "status", { "st", "stat" }, &cvs_status, 249 "[-lRv]", 250 "lRv", 251 "Display status information on checked out files", 252 NULL 253 }, 254 { 255 CVS_OP_TAG, "tag", { "ta", "freeze" }, &cvs_tag, 256 "[-bcdFflR] [-D date | -r rev] tagname", 257 "bcD:dFflRr:", 258 "Add a symbolic tag to checked out version of files", 259 NULL 260 }, 261 { 262 CVS_OP_UNEDIT, "unedit", {}, NULL, 263 "", 264 "", 265 "Undo an edit command", 266 NULL 267 }, 268 { 269 CVS_OP_UPDATE, "update", { "up", "upd" }, &cvs_update, 270 "[-ACdflPpR] [-D date | -r rev] [-I ign] [-j rev] [-k mode] " 271 "[-t id] ...", 272 "ACD:dflPpQqRr:", 273 "Bring work tree in sync with repository", 274 NULL 275 }, 276 { 277 CVS_OP_VERSION, "version", { "ve", "ver" }, &cvs_version, 278 "", "", 279 "Show current CVS version(s)", 280 NULL 281 }, 282 { 283 CVS_OP_WATCH, "watch", {}, NULL, 284 "", 285 "", 286 "Set watches", 287 NULL 288 }, 289 { 290 CVS_OP_WATCHERS, "watchers", {}, NULL, 291 "", 292 "", 293 "See who is watching a file", 294 NULL 295 }, 296}; 297 298#define CVS_NBCMD (sizeof(cvs_cdt)/sizeof(cvs_cdt[0])) 299 300 301 302void usage (void); 303static void cvs_read_rcfile (void); 304int cvs_getopt (int, char **); 305 306 307/* 308 * usage() 309 * 310 * Display usage information. 311 */ 312void 313usage(void) 314{ 315 fprintf(stderr, 316 "Usage: %s [-flQqtv] [-d root] [-e editor] [-s var=val] [-z level] " 317 "command [...]\n", __progname); 318} 319 320 321int 322main(int argc, char **argv) 323{ 324 char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv; 325 int i, ret, cmd_argc; 326 struct cvs_cmd *cmdp; 327 328 TAILQ_INIT(&cvs_variables); 329 330 if (cvs_log_init(LD_STD, 0) < 0) 331 err(1, "failed to initialize logging"); 332 333 /* by default, be very verbose */ 334 (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO); 335 336#ifdef DEBUG 337 (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG); 338#endif 339 340 cvs_strtab_init(); 341 342 /* check environment so command-line options override it */ 343 if ((envstr = getenv("CVS_RSH")) != NULL) 344 cvs_rsh = envstr; 345 346 if (((envstr = getenv("CVSEDITOR")) != NULL) || 347 ((envstr = getenv("VISUAL")) != NULL) || 348 ((envstr = getenv("EDITOR")) != NULL)) 349 cvs_editor = envstr; 350 351 ret = cvs_getopt(argc, argv); 352 353 argc -= ret; 354 argv += ret; 355 if (argc == 0) { 356 usage(); 357 exit(EX_USAGE); 358 } 359 cvs_command = argv[0]; 360 361 if (cvs_readrc) { 362 cvs_read_rcfile(); 363 364 if (cvs_defargs != NULL) { 365 targv = cvs_makeargv(cvs_defargs, &i); 366 if (targv == NULL) { 367 cvs_log(LP_ERR, 368 "failed to load default arguments to %s", 369 __progname); 370 exit(1); 371 } 372 373 cvs_getopt(i, targv); 374 cvs_freeargv(targv, i); 375 free(targv); 376 } 377 } 378 379 /* setup signal handlers */ 380 signal(SIGPIPE, SIG_IGN); 381 382 if (cvs_file_init() < 0) { 383 cvs_log(LP_ERR, "failed to initialize file support"); 384 exit(1); 385 } 386 387 ret = -1; 388 389 cmdp = cvs_findcmd(cvs_command); 390 if (cmdp == NULL) { 391 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 392 fprintf(stderr, "CVS commands are:\n"); 393 for (i = 0; i < (int)CVS_NBCMD; i++) 394 fprintf(stderr, "\t%-16s%s\n", 395 cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr); 396 exit(EX_USAGE); 397 } 398 399 if (cmdp->cmd_info == NULL) { 400 cvs_log(LP_ERR, "command `%s' not implemented", cvs_command); 401 exit(1); 402 } 403 404 cvs_cmdop = cmdp->cmd_op; 405 406 cmd_argc = 0; 407 memset(cmd_argv, 0, sizeof(cmd_argv)); 408 409 cmd_argv[cmd_argc++] = argv[0]; 410 if (cmdp->cmd_defargs != NULL) { 411 /* transform into a new argument vector */ 412 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 413 CVS_CMD_MAXARG - 1); 414 if (ret < 0) { 415 cvs_log(LP_ERRNO, "failed to generate argument vector " 416 "from default arguments"); 417 exit(1); 418 } 419 cmd_argc += ret; 420 } 421 for (ret = 1; ret < argc; ret++) 422 cmd_argv[cmd_argc++] = argv[ret]; 423 424 ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv); 425 if (ret == EX_USAGE) { 426 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 427 cmdp->cmd_synopsis); 428 } 429 430 if (cmdp->cmd_info->cmd_cleanup != NULL) 431 cmdp->cmd_info->cmd_cleanup(); 432 if (cvs_files != NULL) 433 cvs_file_free(cvs_files); 434 if (cvs_msg != NULL) 435 free(cvs_msg); 436 437 cvs_strtab_cleanup(); 438 439 return (ret); 440} 441 442 443int 444cvs_getopt(int argc, char **argv) 445{ 446 int ret; 447 char *ep; 448 449 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) { 450 switch (ret) { 451 case 'b': 452 /* 453 * We do not care about the bin directory for RCS files 454 * as this program has no dependencies on RCS programs, 455 * so it is only here for backwards compatibility. 456 */ 457 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 458 break; 459 case 'd': 460 cvs_rootstr = optarg; 461 break; 462 case 'e': 463 cvs_editor = optarg; 464 break; 465 case 'f': 466 cvs_readrc = 0; 467 break; 468 case 'l': 469 cvs_nolog = 1; 470 break; 471 case 'n': 472 break; 473 case 'Q': 474 verbosity = 0; 475 break; 476 case 'q': 477 /* don't override -Q */ 478 if (verbosity > 1) 479 verbosity = 1; 480 break; 481 case 'r': 482 cvs_readonly = 1; 483 break; 484 case 's': 485 ep = strchr(optarg, '='); 486 if (ep == NULL) { 487 cvs_log(LP_ERR, "no = in variable assignment"); 488 exit(EX_USAGE); 489 } 490 *(ep++) = '\0'; 491 if (cvs_var_set(optarg, ep) < 0) 492 exit(EX_USAGE); 493 break; 494 case 't': 495 (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE); 496 cvs_trace = 1; 497 break; 498 case 'v': 499 printf("%s\n", CVS_VERSION); 500 exit(0); 501 /* NOTREACHED */ 502 break; 503 case 'x': 504 /* 505 * Kerberos encryption support, kept for compatibility 506 */ 507 break; 508 case 'z': 509 cvs_compress = (int)strtol(optarg, &ep, 10); 510 if (*ep != '\0') 511 errx(1, "error parsing compression level"); 512 if (cvs_compress < 0 || cvs_compress > 9) 513 errx(1, "gzip compression level must be " 514 "between 0 and 9"); 515 break; 516 default: 517 usage(); 518 exit(EX_USAGE); 519 } 520 } 521 522 ret = optind; 523 optind = 1; 524 optreset = 1; /* for next call */ 525 526 return (ret); 527} 528 529 530/* 531 * cvs_findcmd() 532 * 533 * Find the entry in the command dispatch table whose name or one of its 534 * aliases matches <cmd>. 535 * Returns a pointer to the command entry on success, NULL on failure. 536 */ 537struct cvs_cmd* 538cvs_findcmd(const char *cmd) 539{ 540 u_int i, j; 541 struct cvs_cmd *cmdp; 542 543 cmdp = NULL; 544 545 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 546 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 547 cmdp = &cvs_cdt[i]; 548 else { 549 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 550 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 551 cmdp = &cvs_cdt[i]; 552 break; 553 } 554 } 555 } 556 } 557 558 return (cmdp); 559} 560 561 562/* 563 * cvs_read_rcfile() 564 * 565 * Read the CVS `.cvsrc' file in the user's home directory. If the file 566 * exists, it should contain a list of arguments that should always be given 567 * implicitly to the specified commands. 568 */ 569static void 570cvs_read_rcfile(void) 571{ 572 char rcpath[MAXPATHLEN], linebuf[128], *lp; 573 int linenum = 0; 574 size_t len; 575 struct cvs_cmd *cmdp; 576 struct passwd *pw; 577 FILE *fp; 578 579 pw = getpwuid(getuid()); 580 if (pw == NULL) { 581 cvs_log(LP_NOTICE, "failed to get user's password entry"); 582 return; 583 } 584 585 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 586 587 fp = fopen(rcpath, "r"); 588 if (fp == NULL) { 589 if (errno != ENOENT) 590 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 591 strerror(errno)); 592 return; 593 } 594 595 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 596 linenum++; 597 if ((len = strlen(linebuf)) == 0) 598 continue; 599 if (linebuf[len - 1] != '\n') { 600 cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath, 601 linenum); 602 break; 603 } 604 linebuf[--len] = '\0'; 605 606 lp = strchr(linebuf, ' '); 607 if (lp == NULL) 608 continue; /* ignore lines with no arguments */ 609 *lp = '\0'; 610 if (strcmp(linebuf, "cvs") == 0) { 611 /* 612 * Global default options. In the case of cvs only, 613 * we keep the 'cvs' string as first argument because 614 * getopt() does not like starting at index 0 for 615 * argument processing. 616 */ 617 *lp = ' '; 618 cvs_defargs = strdup(linebuf); 619 if (cvs_defargs == NULL) 620 cvs_log(LP_ERRNO, 621 "failed to copy global arguments"); 622 } else { 623 lp++; 624 cmdp = cvs_findcmd(linebuf); 625 if (cmdp == NULL) { 626 cvs_log(LP_NOTICE, 627 "unknown command `%s' in `%s:%d'", 628 linebuf, rcpath, linenum); 629 continue; 630 } 631 632 cmdp->cmd_defargs = strdup(lp); 633 if (cmdp->cmd_defargs == NULL) 634 cvs_log(LP_ERRNO, 635 "failed to copy default arguments for %s", 636 cmdp->cmd_name); 637 } 638 } 639 if (ferror(fp)) { 640 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 641 } 642 643 (void)fclose(fp); 644} 645 646 647/* 648 * cvs_var_set() 649 * 650 * Set the value of the variable <var> to <val>. If there is no such variable, 651 * a new entry is created, otherwise the old value is overwritten. 652 * Returns 0 on success, or -1 on failure. 653 */ 654int 655cvs_var_set(const char *var, const char *val) 656{ 657 char *valcp; 658 const char *cp; 659 struct cvs_var *vp; 660 661 if ((var == NULL) || (*var == '\0')) { 662 cvs_log(LP_ERR, "no variable name"); 663 return (-1); 664 } 665 666 /* sanity check on the name */ 667 for (cp = var; *cp != '\0'; cp++) 668 if (!isalnum(*cp) && (*cp != '_')) { 669 cvs_log(LP_ERR, 670 "variable name `%s' contains invalid characters", 671 var); 672 return (-1); 673 } 674 675 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 676 if (strcmp(vp->cv_name, var) == 0) 677 break; 678 679 valcp = strdup(val); 680 if (valcp == NULL) { 681 cvs_log(LP_ERRNO, "failed to allocate variable"); 682 return (-1); 683 } 684 685 if (vp == NULL) { 686 vp = (struct cvs_var *)malloc(sizeof(*vp)); 687 if (vp == NULL) { 688 cvs_log(LP_ERRNO, "failed to allocate variable"); 689 free(valcp); 690 return (-1); 691 } 692 memset(vp, 0, sizeof(*vp)); 693 694 vp->cv_name = strdup(var); 695 if (vp->cv_name == NULL) { 696 cvs_log(LP_ERRNO, "failed to allocate variable"); 697 free(valcp); 698 free(vp); 699 return (-1); 700 } 701 702 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 703 704 } else /* free the previous value */ 705 free(vp->cv_val); 706 707 vp->cv_val = valcp; 708 709 return (0); 710} 711 712 713/* 714 * cvs_var_set() 715 * 716 * Remove any entry for the variable <var>. 717 * Returns 0 on success, or -1 on failure. 718 */ 719int 720cvs_var_unset(const char *var) 721{ 722 struct cvs_var *vp; 723 724 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 725 if (strcmp(vp->cv_name, var) == 0) { 726 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 727 free(vp->cv_name); 728 free(vp->cv_val); 729 free(vp); 730 return (0); 731 } 732 733 return (-1); 734 735} 736 737 738/* 739 * cvs_var_get() 740 * 741 * Get the value associated with the variable <var>. Returns a pointer to the 742 * value string on success, or NULL if the variable does not exist. 743 */ 744 745const char* 746cvs_var_get(const char *var) 747{ 748 struct cvs_var *vp; 749 750 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 751 if (strcmp(vp->cv_name, var) == 0) 752 return (vp->cv_val); 753 754 return (NULL); 755} 756