cvs.c revision 1.22
1/* $OpenBSD: cvs.c,v 1.22 2004/12/14 19:11:54 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 <stdlib.h> 35#include <unistd.h> 36#include <signal.h> 37#include <string.h> 38#include <sysexits.h> 39 40#include "cvs.h" 41#include "log.h" 42#include "file.h" 43 44 45extern char *__progname; 46 47 48/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 49int verbosity = 2; 50 51/* compression level used with zlib, 0 meaning no compression taking place */ 52int cvs_compress = 0; 53int cvs_readrc = 1; /* read .cvsrc on startup */ 54int cvs_trace = 0; 55int cvs_nolog = 0; 56int cvs_readonly = 0; 57int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 58 59char *cvs_defargs; /* default global arguments from .cvsrc */ 60char *cvs_command; /* name of the command we are running */ 61int cvs_cmdop; 62char *cvs_rootstr; 63char *cvs_rsh = CVS_RSH_DEFAULT; 64char *cvs_editor = CVS_EDITOR_DEFAULT; 65 66char *cvs_msg = NULL; 67 68/* hierarchy of all the files affected by the command */ 69CVSFILE *cvs_files; 70 71 72/* 73 * Command dispatch table 74 * ---------------------- 75 * 76 * The synopsis field should only contain the list of arguments that the 77 * command supports, without the actual command's name. 78 * 79 * Command handlers are expected to return 0 if no error occured, or one of 80 * the values known in sysexits.h in case of an error. In case the error 81 * returned is EX_USAGE, the command's usage string is printed to standard 82 * error before returning. 83 */ 84static struct cvs_cmd { 85 int cmd_op; 86 char cmd_name[CVS_CMD_MAXNAMELEN]; 87 char cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN]; 88 int (*cmd_hdlr)(int, char **); 89 char *cmd_synopsis; 90 char *cmd_opts; 91 char cmd_descr[CVS_CMD_MAXDESCRLEN]; 92 char *cmd_defargs; 93} cvs_cdt[] = { 94 { 95 CVS_OP_ADD, "add", { "ad", "new" }, cvs_add, 96 "[-m msg] file ...", 97 "", 98 "Add a new file/directory to the repository", 99 NULL, 100 }, 101 { 102 -1, "admin", { "adm", "rcs" }, NULL, 103 "", 104 "", 105 "Administration front end for rcs", 106 NULL, 107 }, 108 { 109 CVS_OP_ANNOTATE, "annotate", { "ann" }, cvs_annotate, 110 "[-FflR] [-D date | -r rev] file ...", 111 "", 112 "Show last revision where each line was modified", 113 NULL, 114 }, 115 { 116 CVS_OP_CHECKOUT, "checkout", { "co", "get" }, cvs_checkout, 117 "", 118 "", 119 "Checkout sources for editing", 120 NULL, 121 }, 122 { 123 CVS_OP_COMMIT, "commit", { "ci", "com" }, cvs_commit, 124 "[-flR] [-F logfile | -m msg] [-r rev] ...", 125 "F:flm:Rr:", 126 "Check files into the repository", 127 NULL, 128 }, 129 { 130 CVS_OP_DIFF, "diff", { "di", "dif" }, cvs_diff, 131 "[-cilu] [-D date] [-r rev] ...", 132 "cD:ilur:", 133 "Show differences between revisions", 134 NULL, 135 }, 136 { 137 -1, "edit", { }, NULL, 138 "", 139 "", 140 "Get ready to edit a watched file", 141 NULL, 142 }, 143 { 144 -1, "editors", { }, NULL, 145 "", 146 "", 147 "See who is editing a watched file", 148 NULL, 149 }, 150 { 151 -1, "export", { "ex", "exp" }, NULL, 152 "", 153 "", 154 "Export sources from CVS, similar to checkout", 155 NULL, 156 }, 157 { 158 CVS_OP_HISTORY, "history", { "hi", "his" }, cvs_history, 159 "", 160 "", 161 "Show repository access history", 162 NULL, 163 }, 164 { 165 CVS_OP_IMPORT, "import", { "im", "imp" }, NULL, 166 "[-d] [-b branch] [-I ign] [-k subst] [-m msg] " 167 "repository vendor-tag release-tags ...", 168 "b:dI:k:m:", 169 "Import sources into CVS, using vendor branches", 170 NULL, 171 }, 172 { 173 CVS_OP_INIT, "init", { }, cvs_init, 174 "", 175 "", 176 "Create a CVS repository if it doesn't exist", 177 NULL, 178 }, 179#if defined(HAVE_KERBEROS) 180 { 181 "kserver", {}, NULL 182 "", 183 "", 184 "Start a Kerberos authentication CVS server", 185 NULL, 186 }, 187#endif 188 { 189 CVS_OP_LOG, "log", { "lo" }, cvs_getlog, 190 "", 191 "", 192 "Print out history information for files", 193 NULL, 194 }, 195 { 196 -1, "login", {}, NULL, 197 "", 198 "", 199 "Prompt for password for authenticating server", 200 NULL, 201 }, 202 { 203 -1, "logout", {}, NULL, 204 "", 205 "", 206 "Removes entry in .cvspass for remote repository", 207 NULL, 208 }, 209 { 210 -1, "rdiff", {}, NULL, 211 "", 212 "", 213 "Create 'patch' format diffs between releases", 214 NULL, 215 }, 216 { 217 -1, "release", {}, NULL, 218 "", 219 "", 220 "Indicate that a Module is no longer in use", 221 NULL, 222 }, 223 { 224 CVS_OP_REMOVE, "remove", {}, NULL, 225 "", 226 "", 227 "Remove an entry from the repository", 228 NULL, 229 }, 230 { 231 -1, "rlog", {}, NULL, 232 "", 233 "", 234 "Print out history information for a module", 235 NULL, 236 }, 237 { 238 -1, "rtag", {}, NULL, 239 "", 240 "", 241 "Add a symbolic tag to a module", 242 NULL, 243 }, 244 { 245 CVS_OP_SERVER, "server", {}, cvs_server, 246 "", 247 "", 248 "Server mode", 249 NULL, 250 }, 251 { 252 CVS_OP_STATUS, "status", { "st", "stat" }, cvs_status, 253 "", 254 "", 255 "Display status information on checked out files", 256 NULL, 257 }, 258 { 259 CVS_OP_TAG, "tag", { "ta", "freeze" }, cvs_tag, 260 "", 261 "", 262 "Add a symbolic tag to checked out version of files", 263 NULL, 264 }, 265 { 266 -1, "unedit", {}, NULL, 267 "", 268 "", 269 "Undo an edit command", 270 NULL, 271 }, 272 { 273 CVS_OP_UPDATE, "update", { "up", "upd" }, cvs_update, 274 "", 275 "", 276 "Bring work tree in sync with repository", 277 NULL, 278 }, 279 { 280 CVS_OP_VERSION, "version", { "ve", "ver" }, cvs_version, 281 "", "", 282 "Show current CVS version(s)", 283 NULL, 284 }, 285 { 286 -1, "watch", {}, NULL, 287 "", 288 "", 289 "Set watches", 290 NULL, 291 }, 292 { 293 -1, "watchers", {}, NULL, 294 "", 295 "", 296 "See who is watching a file", 297 NULL, 298 }, 299}; 300 301#define CVS_NBCMD (sizeof(cvs_cdt)/sizeof(cvs_cdt[0])) 302 303 304 305void usage (void); 306void sigchld_hdlr (int); 307void cvs_read_rcfile (void); 308struct cvs_cmd* cvs_findcmd (const char *); 309int cvs_getopt (int, char **); 310 311 312/* 313 * usage() 314 * 315 * Display usage information. 316 */ 317void 318usage(void) 319{ 320 fprintf(stderr, 321 "Usage: %s [-flQqtv] [-d root] [-e editor] [-z level] command [...]\n", __progname); 322} 323 324 325int 326main(int argc, char **argv) 327{ 328 char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv; 329 int i, ret, cmd_argc; 330 struct cvs_cmd *cmdp; 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 /* check environment so command-line options override it */ 343 if ((envstr = getenv("CVS_RSH")) != NULL) 344 cvs_rsh = envstr; 345 346 if (((envstr = getenv("CVSEDITOR")) != NULL) || 347 ((envstr = getenv("VISUAL")) != NULL) || 348 ((envstr = getenv("EDITOR")) != NULL)) 349 cvs_editor = envstr; 350 351 ret = cvs_getopt(argc, argv); 352 353 argc -= ret; 354 argv += ret; 355 if (argc == 0) { 356 usage(); 357 exit(EX_USAGE); 358 } 359 cvs_command = argv[0]; 360 361 if (cvs_readrc) { 362 cvs_read_rcfile(); 363 364 if (cvs_defargs != NULL) { 365 targv = cvs_makeargv(cvs_defargs, &i); 366 if (targv == NULL) { 367 cvs_log(LP_ERR, 368 "failed to load default arguments to %s", 369 __progname); 370 exit(EX_OSERR); 371 } 372 373 cvs_getopt(i, targv); 374 cvs_freeargv(targv, i); 375 free(targv); 376 } 377 } 378 379 /* setup signal handlers */ 380 signal(SIGPIPE, SIG_IGN); 381 382 cvs_file_init(); 383 384 ret = -1; 385 386 cmdp = cvs_findcmd(cvs_command); 387 if (cmdp == NULL) { 388 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 389 fprintf(stderr, "CVS commands are:\n"); 390 for (i = 0; i < (int)CVS_NBCMD; i++) 391 fprintf(stderr, "\t%-16s%s\n", 392 cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr); 393 exit(EX_USAGE); 394 } 395 396 if (cmdp->cmd_hdlr == NULL) { 397 cvs_log(LP_ERR, "command `%s' not implemented", cvs_command); 398 exit(1); 399 } 400 401 cvs_cmdop = cmdp->cmd_op; 402 403 cmd_argc = 0; 404 memset(cmd_argv, 0, sizeof(cmd_argv)); 405 406 cmd_argv[cmd_argc++] = argv[0]; 407 if (cmdp->cmd_defargs != NULL) { 408 /* transform into a new argument vector */ 409 ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1, 410 CVS_CMD_MAXARG - 1); 411 if (ret < 0) { 412 cvs_log(LP_ERRNO, "failed to generate argument vector " 413 "from default arguments"); 414 exit(EX_DATAERR); 415 } 416 cmd_argc += ret; 417 } 418 for (ret = 1; ret < argc; ret++) 419 cmd_argv[cmd_argc++] = argv[ret]; 420 421 ret = (*cmdp->cmd_hdlr)(cmd_argc, cmd_argv); 422 if (ret == EX_USAGE) { 423 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 424 cmdp->cmd_synopsis); 425 } 426 427 if (cvs_files != NULL) 428 cvs_file_free(cvs_files); 429 430 return (ret); 431} 432 433 434int 435cvs_getopt(int argc, char **argv) 436{ 437 int ret; 438 char *ep; 439 440 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrtvz:")) != -1) { 441 switch (ret) { 442 case 'b': 443 /* 444 * We do not care about the bin directory for RCS files 445 * as this program has no dependencies on RCS programs, 446 * so it is only here for backwards compatibility. 447 */ 448 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 449 break; 450 case 'd': 451 cvs_rootstr = optarg; 452 break; 453 case 'e': 454 cvs_editor = optarg; 455 break; 456 case 'f': 457 cvs_readrc = 0; 458 break; 459 case 'l': 460 cvs_nolog = 1; 461 break; 462 case 'n': 463 break; 464 case 'Q': 465 verbosity = 0; 466 break; 467 case 'q': 468 /* don't override -Q */ 469 if (verbosity > 1) 470 verbosity = 1; 471 break; 472 case 'r': 473 cvs_readonly = 1; 474 break; 475 case 't': 476 cvs_trace = 1; 477 break; 478 case 'v': 479 printf("%s\n", CVS_VERSION); 480 exit(0); 481 /* NOTREACHED */ 482 break; 483 case 'x': 484 /* 485 * Kerberos encryption support, kept for compatibility 486 */ 487 break; 488 case 'z': 489 cvs_compress = (int)strtol(optarg, &ep, 10); 490 if (*ep != '\0') 491 errx(1, "error parsing compression level"); 492 if (cvs_compress < 0 || cvs_compress > 9) 493 errx(1, "gzip compression level must be " 494 "between 0 and 9"); 495 break; 496 default: 497 usage(); 498 exit(EX_USAGE); 499 } 500 } 501 502 ret = optind; 503 optind = 1; 504 optreset = 1; /* for next call */ 505 506 return (ret); 507} 508 509 510/* 511 * cvs_findcmd() 512 * 513 * Find the entry in the command dispatch table whose name or one of its 514 * aliases matches <cmd>. 515 * Returns a pointer to the command entry on success, NULL on failure. 516 */ 517struct cvs_cmd* 518cvs_findcmd(const char *cmd) 519{ 520 u_int i, j; 521 struct cvs_cmd *cmdp; 522 523 cmdp = NULL; 524 525 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 526 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 527 cmdp = &cvs_cdt[i]; 528 else { 529 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 530 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 531 cmdp = &cvs_cdt[i]; 532 break; 533 } 534 } 535 } 536 } 537 538 return (cmdp); 539} 540 541 542/* 543 * cvs_read_rcfile() 544 * 545 * Read the CVS `.cvsrc' file in the user's home directory. If the file 546 * exists, it should contain a list of arguments that should always be given 547 * implicitly to the specified commands. 548 */ 549void 550cvs_read_rcfile(void) 551{ 552 char rcpath[MAXPATHLEN], linebuf[128], *lp; 553 size_t len; 554 struct cvs_cmd *cmdp; 555 struct passwd *pw; 556 FILE *fp; 557 558 pw = getpwuid(getuid()); 559 if (pw == NULL) { 560 cvs_log(LP_NOTICE, "failed to get user's password entry"); 561 return; 562 } 563 564 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 565 566 fp = fopen(rcpath, "r"); 567 if (fp == NULL) { 568 if (errno != ENOENT) 569 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 570 strerror(errno)); 571 return; 572 } 573 574 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 575 if ((len = strlen(linebuf)) == 0) 576 continue; 577 if (linebuf[len - 1] != '\n') { 578 cvs_log(LP_WARN, ".cvsrc line too long"); 579 break; 580 } 581 linebuf[--len] = '\0'; 582 583 lp = strchr(linebuf, ' '); 584 if (lp == NULL) 585 continue; /* ignore lines with no arguments */ 586 *lp = '\0'; 587 if (strcmp(linebuf, "cvs") == 0) { 588 /* 589 * Global default options. In the case of cvs only, 590 * we keep the 'cvs' string as first argument because 591 * getopt() does not like starting at index 0 for 592 * argument processing. 593 */ 594 *lp = ' '; 595 cvs_defargs = strdup(linebuf); 596 if (cvs_defargs == NULL) 597 cvs_log(LP_ERRNO, 598 "failed to copy global arguments"); 599 } else { 600 lp++; 601 cmdp = cvs_findcmd(linebuf); 602 if (cmdp == NULL) { 603 cvs_log(LP_NOTICE, 604 "unknown command `%s' in `%s'", 605 linebuf, rcpath); 606 continue; 607 } 608 609 cmdp->cmd_defargs = strdup(lp); 610 if (cmdp->cmd_defargs == NULL) 611 cvs_log(LP_ERRNO, 612 "failed to copy default arguments for %s", 613 cmdp->cmd_name); 614 } 615 } 616 if (ferror(fp)) { 617 cvs_log(LP_NOTICE, "failed to read line from cvsrc"); 618 } 619 620 (void)fclose(fp); 621} 622