cvs.c revision 1.45
1/* $OpenBSD: cvs.c,v 1.45 2005/03/30 17:43:04 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#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", {}, NULL, 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); 303void sigchld_hdlr (int); 304static void cvs_read_rcfile (void); 305static struct cvs_cmd* cvs_findcmd (const char *); 306int cvs_getopt (int, char **); 307 308 309/* 310 * usage() 311 * 312 * Display usage information. 313 */ 314void 315usage(void) 316{ 317 fprintf(stderr, 318 "Usage: %s [-flQqtv] [-d root] [-e editor] [-s var=val] [-z level] " 319 "command [...]\n", __progname); 320} 321 322 323int 324main(int argc, char **argv) 325{ 326 char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv; 327 int i, ret, cmd_argc; 328 struct cvs_cmd *cmdp; 329 330 TAILQ_INIT(&cvs_variables); 331 332 if (cvs_log_init(LD_STD, 0) < 0) 333 err(1, "failed to initialize logging"); 334 335 /* by default, be very verbose */ 336 (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO); 337 338#ifdef DEBUG 339 (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG); 340#endif 341 342 cvs_strtab_init(); 343 344 /* check environment so command-line options override it */ 345 if ((envstr = getenv("CVS_RSH")) != NULL) 346 cvs_rsh = envstr; 347 348 if (((envstr = getenv("CVSEDITOR")) != NULL) || 349 ((envstr = getenv("VISUAL")) != NULL) || 350 ((envstr = getenv("EDITOR")) != NULL)) 351 cvs_editor = envstr; 352 353 ret = cvs_getopt(argc, argv); 354 355 argc -= ret; 356 argv += ret; 357 if (argc == 0) { 358 usage(); 359 exit(EX_USAGE); 360 } 361 cvs_command = argv[0]; 362 363 if (cvs_readrc) { 364 cvs_read_rcfile(); 365 366 if (cvs_defargs != NULL) { 367 targv = cvs_makeargv(cvs_defargs, &i); 368 if (targv == NULL) { 369 cvs_log(LP_ERR, 370 "failed to load default arguments to %s", 371 __progname); 372 exit(EX_OSERR); 373 } 374 375 cvs_getopt(i, targv); 376 cvs_freeargv(targv, i); 377 free(targv); 378 } 379 } 380 381 /* setup signal handlers */ 382 signal(SIGPIPE, SIG_IGN); 383 384 if (cvs_file_init() < 0) { 385 cvs_log(LP_ERR, "failed to initialize file support"); 386 exit(1); 387 } 388 389 ret = -1; 390 391 cmdp = cvs_findcmd(cvs_command); 392 if (cmdp == NULL) { 393 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 394 fprintf(stderr, "CVS commands are:\n"); 395 for (i = 0; i < (int)CVS_NBCMD; i++) 396 fprintf(stderr, "\t%-16s%s\n", 397 cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr); 398 exit(EX_USAGE); 399 } 400 401 if (cmdp->cmd_info == NULL) { 402 cvs_log(LP_ERR, "command `%s' not implemented", cvs_command); 403 exit(1); 404 } 405 406 cvs_cmdop = cmdp->cmd_op; 407 408 cmd_argc = 0; 409 memset(cmd_argv, 0, sizeof(cmd_argv)); 410 411 cmd_argv[cmd_argc++] = argv[0]; 412 if (cmdp->cmd_defargs != NULL) { 413 /* transform into a new argument vector */ 414 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 415 CVS_CMD_MAXARG - 1); 416 if (ret < 0) { 417 cvs_log(LP_ERRNO, "failed to generate argument vector " 418 "from default arguments"); 419 exit(EX_DATAERR); 420 } 421 cmd_argc += ret; 422 } 423 for (ret = 1; ret < argc; ret++) 424 cmd_argv[cmd_argc++] = argv[ret]; 425 426 ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv); 427 if (ret == EX_USAGE) { 428 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 429 cmdp->cmd_synopsis); 430 } 431 432 if (cmdp->cmd_info->cmd_cleanup != NULL) 433 cmdp->cmd_info->cmd_cleanup(); 434 if (cvs_files != NULL) 435 cvs_file_free(cvs_files); 436 if (cvs_msg != NULL) 437 free(cvs_msg); 438 439 cvs_strtab_cleanup(); 440 441 return (ret); 442} 443 444 445int 446cvs_getopt(int argc, char **argv) 447{ 448 int ret; 449 char *ep; 450 451 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) { 452 switch (ret) { 453 case 'b': 454 /* 455 * We do not care about the bin directory for RCS files 456 * as this program has no dependencies on RCS programs, 457 * so it is only here for backwards compatibility. 458 */ 459 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 460 break; 461 case 'd': 462 cvs_rootstr = optarg; 463 break; 464 case 'e': 465 cvs_editor = optarg; 466 break; 467 case 'f': 468 cvs_readrc = 0; 469 break; 470 case 'l': 471 cvs_nolog = 1; 472 break; 473 case 'n': 474 break; 475 case 'Q': 476 verbosity = 0; 477 break; 478 case 'q': 479 /* don't override -Q */ 480 if (verbosity > 1) 481 verbosity = 1; 482 break; 483 case 'r': 484 cvs_readonly = 1; 485 break; 486 case 's': 487 ep = strchr(optarg, '='); 488 if (ep == NULL) { 489 cvs_log(LP_ERR, "no = in variable assignment"); 490 exit(EX_USAGE); 491 } 492 *(ep++) = '\0'; 493 if (cvs_var_set(optarg, ep) < 0) 494 exit(EX_USAGE); 495 break; 496 case 't': 497 (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE); 498 cvs_trace = 1; 499 break; 500 case 'v': 501 printf("%s\n", CVS_VERSION); 502 exit(0); 503 /* NOTREACHED */ 504 break; 505 case 'x': 506 /* 507 * Kerberos encryption support, kept for compatibility 508 */ 509 break; 510 case 'z': 511 cvs_compress = (int)strtol(optarg, &ep, 10); 512 if (*ep != '\0') 513 errx(1, "error parsing compression level"); 514 if (cvs_compress < 0 || cvs_compress > 9) 515 errx(1, "gzip compression level must be " 516 "between 0 and 9"); 517 break; 518 default: 519 usage(); 520 exit(EX_USAGE); 521 } 522 } 523 524 ret = optind; 525 optind = 1; 526 optreset = 1; /* for next call */ 527 528 return (ret); 529} 530 531 532/* 533 * cvs_findcmd() 534 * 535 * Find the entry in the command dispatch table whose name or one of its 536 * aliases matches <cmd>. 537 * Returns a pointer to the command entry on success, NULL on failure. 538 */ 539static struct cvs_cmd* 540cvs_findcmd(const char *cmd) 541{ 542 u_int i, j; 543 struct cvs_cmd *cmdp; 544 545 cmdp = NULL; 546 547 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 548 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 549 cmdp = &cvs_cdt[i]; 550 else { 551 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 552 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 553 cmdp = &cvs_cdt[i]; 554 break; 555 } 556 } 557 } 558 } 559 560 return (cmdp); 561} 562 563 564/* 565 * cvs_read_rcfile() 566 * 567 * Read the CVS `.cvsrc' file in the user's home directory. If the file 568 * exists, it should contain a list of arguments that should always be given 569 * implicitly to the specified commands. 570 */ 571static void 572cvs_read_rcfile(void) 573{ 574 char rcpath[MAXPATHLEN], linebuf[128], *lp; 575 int linenum = 0; 576 size_t len; 577 struct cvs_cmd *cmdp; 578 struct passwd *pw; 579 FILE *fp; 580 581 pw = getpwuid(getuid()); 582 if (pw == NULL) { 583 cvs_log(LP_NOTICE, "failed to get user's password entry"); 584 return; 585 } 586 587 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 588 589 fp = fopen(rcpath, "r"); 590 if (fp == NULL) { 591 if (errno != ENOENT) 592 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 593 strerror(errno)); 594 return; 595 } 596 597 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 598 linenum++; 599 if ((len = strlen(linebuf)) == 0) 600 continue; 601 if (linebuf[len - 1] != '\n') { 602 cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath, 603 linenum); 604 break; 605 } 606 linebuf[--len] = '\0'; 607 608 lp = strchr(linebuf, ' '); 609 if (lp == NULL) 610 continue; /* ignore lines with no arguments */ 611 *lp = '\0'; 612 if (strcmp(linebuf, "cvs") == 0) { 613 /* 614 * Global default options. In the case of cvs only, 615 * we keep the 'cvs' string as first argument because 616 * getopt() does not like starting at index 0 for 617 * argument processing. 618 */ 619 *lp = ' '; 620 cvs_defargs = strdup(linebuf); 621 if (cvs_defargs == NULL) 622 cvs_log(LP_ERRNO, 623 "failed to copy global arguments"); 624 } else { 625 lp++; 626 cmdp = cvs_findcmd(linebuf); 627 if (cmdp == NULL) { 628 cvs_log(LP_NOTICE, 629 "unknown command `%s' in `%s:%d'", 630 linebuf, rcpath, linenum); 631 continue; 632 } 633 634 cmdp->cmd_defargs = strdup(lp); 635 if (cmdp->cmd_defargs == NULL) 636 cvs_log(LP_ERRNO, 637 "failed to copy default arguments for %s", 638 cmdp->cmd_name); 639 } 640 } 641 if (ferror(fp)) { 642 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 643 } 644 645 (void)fclose(fp); 646} 647 648 649/* 650 * cvs_var_set() 651 * 652 * Set the value of the variable <var> to <val>. If there is no such variable, 653 * a new entry is created, otherwise the old value is overwritten. 654 * Returns 0 on success, or -1 on failure. 655 */ 656int 657cvs_var_set(const char *var, const char *val) 658{ 659 char *valcp; 660 const char *cp; 661 struct cvs_var *vp; 662 663 if ((var == NULL) || (*var == '\0')) { 664 cvs_log(LP_ERR, "no variable name"); 665 return (-1); 666 } 667 668 /* sanity check on the name */ 669 for (cp = var; *cp != '\0'; cp++) 670 if (!isalnum(*cp) && (*cp != '_')) { 671 cvs_log(LP_ERR, 672 "variable name `%s' contains invalid characters", 673 var); 674 return (-1); 675 } 676 677 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 678 if (strcmp(vp->cv_name, var) == 0) 679 break; 680 681 valcp = strdup(val); 682 if (valcp == NULL) { 683 cvs_log(LP_ERRNO, "failed to allocate variable"); 684 return (-1); 685 } 686 687 if (vp == NULL) { 688 vp = (struct cvs_var *)malloc(sizeof(*vp)); 689 if (vp == NULL) { 690 cvs_log(LP_ERRNO, "failed to allocate variable"); 691 free(valcp); 692 return (-1); 693 } 694 memset(vp, 0, sizeof(*vp)); 695 696 vp->cv_name = strdup(var); 697 if (vp->cv_name == NULL) { 698 cvs_log(LP_ERRNO, "failed to allocate variable"); 699 free(valcp); 700 free(vp); 701 return (-1); 702 } 703 704 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 705 706 } else /* free the previous value */ 707 free(vp->cv_val); 708 709 vp->cv_val = valcp; 710 711 return (0); 712} 713 714 715/* 716 * cvs_var_set() 717 * 718 * Remove any entry for the variable <var>. 719 * Returns 0 on success, or -1 on failure. 720 */ 721int 722cvs_var_unset(const char *var) 723{ 724 struct cvs_var *vp; 725 726 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 727 if (strcmp(vp->cv_name, var) == 0) { 728 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 729 free(vp->cv_name); 730 free(vp->cv_val); 731 free(vp); 732 return (0); 733 } 734 735 return (-1); 736 737} 738 739 740/* 741 * cvs_var_get() 742 * 743 * Get the value associated with the variable <var>. Returns a pointer to the 744 * value string on success, or NULL if the variable does not exist. 745 */ 746 747const char* 748cvs_var_get(const char *var) 749{ 750 struct cvs_var *vp; 751 752 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 753 if (strcmp(vp->cv_name, var) == 0) 754 return (vp->cv_val); 755 756 return (NULL); 757} 758