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