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