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