cvs.c revision 1.27
1/* $OpenBSD: cvs.c,v 1.27 2004/12/21 18:47:58 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 occured, 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 "[-FflR] [-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 "[-cilu] [-D date] [-r rev] ...", 135 "cD:ilur:", 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" }, NULL, 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] files ...", 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 "", 257 "", 258 "Display status information on checked out files", 259 NULL, 260 }, 261 { 262 CVS_OP_TAG, "tag", { "ta", "freeze" }, cvs_tag, 263 "", 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 436 return (ret); 437} 438 439 440int 441cvs_getopt(int argc, char **argv) 442{ 443 int ret; 444 char *ep; 445 446 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) { 447 switch (ret) { 448 case 'b': 449 /* 450 * We do not care about the bin directory for RCS files 451 * as this program has no dependencies on RCS programs, 452 * so it is only here for backwards compatibility. 453 */ 454 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 455 break; 456 case 'd': 457 cvs_rootstr = optarg; 458 break; 459 case 'e': 460 cvs_editor = optarg; 461 break; 462 case 'f': 463 cvs_readrc = 0; 464 break; 465 case 'l': 466 cvs_nolog = 1; 467 break; 468 case 'n': 469 break; 470 case 'Q': 471 verbosity = 0; 472 break; 473 case 'q': 474 /* don't override -Q */ 475 if (verbosity > 1) 476 verbosity = 1; 477 break; 478 case 'r': 479 cvs_readonly = 1; 480 break; 481 case 's': 482 ep = strchr(optarg, '='); 483 if (ep == NULL) { 484 cvs_log(LP_ERR, "no = in variable assignment"); 485 exit(EX_USAGE); 486 } 487 *(ep++) = '\0'; 488 if (cvs_var_set(optarg, ep) < 0) 489 exit(EX_USAGE); 490 break; 491 case 't': 492 (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE); 493 cvs_trace = 1; 494 break; 495 case 'v': 496 printf("%s\n", CVS_VERSION); 497 exit(0); 498 /* NOTREACHED */ 499 break; 500 case 'x': 501 /* 502 * Kerberos encryption support, kept for compatibility 503 */ 504 break; 505 case 'z': 506 cvs_compress = (int)strtol(optarg, &ep, 10); 507 if (*ep != '\0') 508 errx(1, "error parsing compression level"); 509 if (cvs_compress < 0 || cvs_compress > 9) 510 errx(1, "gzip compression level must be " 511 "between 0 and 9"); 512 break; 513 default: 514 usage(); 515 exit(EX_USAGE); 516 } 517 } 518 519 ret = optind; 520 optind = 1; 521 optreset = 1; /* for next call */ 522 523 return (ret); 524} 525 526 527/* 528 * cvs_findcmd() 529 * 530 * Find the entry in the command dispatch table whose name or one of its 531 * aliases matches <cmd>. 532 * Returns a pointer to the command entry on success, NULL on failure. 533 */ 534struct cvs_cmd* 535cvs_findcmd(const char *cmd) 536{ 537 u_int i, j; 538 struct cvs_cmd *cmdp; 539 540 cmdp = NULL; 541 542 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 543 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 544 cmdp = &cvs_cdt[i]; 545 else { 546 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 547 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 548 cmdp = &cvs_cdt[i]; 549 break; 550 } 551 } 552 } 553 } 554 555 return (cmdp); 556} 557 558 559/* 560 * cvs_read_rcfile() 561 * 562 * Read the CVS `.cvsrc' file in the user's home directory. If the file 563 * exists, it should contain a list of arguments that should always be given 564 * implicitly to the specified commands. 565 */ 566void 567cvs_read_rcfile(void) 568{ 569 char rcpath[MAXPATHLEN], linebuf[128], *lp; 570 int linenum = 0; 571 size_t len; 572 struct cvs_cmd *cmdp; 573 struct passwd *pw; 574 FILE *fp; 575 576 pw = getpwuid(getuid()); 577 if (pw == NULL) { 578 cvs_log(LP_NOTICE, "failed to get user's password entry"); 579 return; 580 } 581 582 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 583 584 fp = fopen(rcpath, "r"); 585 if (fp == NULL) { 586 if (errno != ENOENT) 587 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 588 strerror(errno)); 589 return; 590 } 591 592 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 593 linenum++; 594 if ((len = strlen(linebuf)) == 0) 595 continue; 596 if (linebuf[len - 1] != '\n') { 597 cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath, 598 linenum); 599 break; 600 } 601 linebuf[--len] = '\0'; 602 603 lp = strchr(linebuf, ' '); 604 if (lp == NULL) 605 continue; /* ignore lines with no arguments */ 606 *lp = '\0'; 607 if (strcmp(linebuf, "cvs") == 0) { 608 /* 609 * Global default options. In the case of cvs only, 610 * we keep the 'cvs' string as first argument because 611 * getopt() does not like starting at index 0 for 612 * argument processing. 613 */ 614 *lp = ' '; 615 cvs_defargs = strdup(linebuf); 616 if (cvs_defargs == NULL) 617 cvs_log(LP_ERRNO, 618 "failed to copy global arguments"); 619 } else { 620 lp++; 621 cmdp = cvs_findcmd(linebuf); 622 if (cmdp == NULL) { 623 cvs_log(LP_NOTICE, 624 "unknown command `%s' in `%s:%d'", 625 linebuf, rcpath, linenum); 626 continue; 627 } 628 629 cmdp->cmd_defargs = strdup(lp); 630 if (cmdp->cmd_defargs == NULL) 631 cvs_log(LP_ERRNO, 632 "failed to copy default arguments for %s", 633 cmdp->cmd_name); 634 } 635 } 636 if (ferror(fp)) { 637 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 638 } 639 640 (void)fclose(fp); 641} 642 643 644/* 645 * cvs_var_set() 646 * 647 * Set the value of the variable <var> to <val>. If there is no such variable, 648 * a new entry is created, otherwise the old value is overwritten. 649 * Returns 0 on success, or -1 on failure. 650 */ 651int 652cvs_var_set(const char *var, const char *val) 653{ 654 char *valcp; 655 const char *cp; 656 struct cvs_var *vp; 657 658 if ((var == NULL) || (*var == '\0')) { 659 cvs_log(LP_ERR, "no variable name"); 660 return (-1); 661 } 662 663 /* sanity check on the name */ 664 for (cp = var; *cp != '\0'; cp++) 665 if (!isalnum(*cp) && (*cp != '_')) { 666 cvs_log(LP_ERR, 667 "variable name `%s' contains invalid characters", 668 var); 669 return (-1); 670 } 671 672 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 673 if (strcmp(vp->cv_name, var) == 0) 674 break; 675 676 valcp = strdup(val); 677 if (valcp == NULL) { 678 cvs_log(LP_ERRNO, "failed to allocate variable"); 679 return (-1); 680 } 681 682 if (vp == NULL) { 683 vp = (struct cvs_var *)malloc(sizeof(*vp)); 684 if (vp == NULL) { 685 cvs_log(LP_ERRNO, "failed to allocate variable"); 686 free(valcp); 687 return (-1); 688 } 689 memset(vp, 0, sizeof(*vp)); 690 691 vp->cv_name = strdup(var); 692 if (vp->cv_name == NULL) { 693 cvs_log(LP_ERRNO, "failed to allocate variable"); 694 free(valcp); 695 free(vp); 696 return (-1); 697 } 698 699 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 700 701 } else /* free the previous value */ 702 free(vp->cv_val); 703 704 vp->cv_val = valcp; 705 706 return (0); 707} 708 709 710/* 711 * cvs_var_set() 712 * 713 * Remove any entry for the variable <var>. 714 * Returns 0 on success, or -1 on failure. 715 */ 716int 717cvs_var_unset(const char *var) 718{ 719 struct cvs_var *vp; 720 721 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 722 if (strcmp(vp->cv_name, var) == 0) { 723 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 724 free(vp->cv_name); 725 free(vp->cv_val); 726 free(vp); 727 return (0); 728 } 729 730 return (-1); 731 732} 733 734 735/* 736 * cvs_var_get() 737 * 738 * Get the value associated with the variable <var>. Returns a pointer to the 739 * value string on success, or NULL if the variable does not exist. 740 */ 741 742const char* 743cvs_var_get(const char *var) 744{ 745 struct cvs_var *vp; 746 747 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 748 if (strcmp(vp->cv_name, var) == 0) 749 return (vp->cv_val); 750 751 return (NULL); 752} 753