cvs.c revision 1.14
1/* $OpenBSD: cvs.c,v 1.14 2004/11/09 23:06:01 krapht 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 52 53/* compression level used with zlib, 0 meaning no compression taking place */ 54int cvs_compress = 0; 55int cvs_trace = 0; 56int cvs_nolog = 0; 57int cvs_readonly = 0; 58int cvs_nocase = 0; /* set to 1 to disable case sensitivity on filenames */ 59 60/* name of the command we are running */ 61char *cvs_command; 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 70/* hierarchy of all the files affected by the command */ 71CVSFILE *cvs_files; 72 73 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 */ 87 88static struct cvs_cmd { 89 int cmd_op; 90 char cmd_name[CVS_CMD_MAXNAMELEN]; 91 char cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN]; 92 int (*cmd_hdlr)(int, char **); 93 char *cmd_synopsis; 94 char *cmd_opts; 95 char cmd_descr[CVS_CMD_MAXDESCRLEN]; 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 }, 103 { 104 -1, "admin", { "adm", "rcs" }, NULL, 105 "", 106 "", 107 "Administration front end for rcs", 108 }, 109 { 110 CVS_OP_ANNOTATE, "annotate", { "ann" }, NULL, 111 "", 112 "", 113 "Show last revision where each line was modified", 114 }, 115 { 116 CVS_OP_CHECKOUT, "checkout", { "co", "get" }, cvs_checkout, 117 "", 118 "", 119 "Checkout sources for editing", 120 }, 121 { 122 CVS_OP_COMMIT, "commit", { "ci", "com" }, cvs_commit, 123 "[-flR] [-F logfile | -m msg] [-r rev] ...", 124 "F:flm:Rr:", 125 "Check files into the repository", 126 }, 127 { 128 CVS_OP_DIFF, "diff", { "di", "dif" }, cvs_diff, 129 "[-cilu] [-D date] [-r rev] ...", 130 "cD:ilur:", 131 "Show differences between revisions", 132 }, 133 { 134 -1, "edit", { }, NULL, 135 "", 136 "", 137 "Get ready to edit a watched file", 138 }, 139 { 140 -1, "editors", { }, NULL, 141 "", 142 "", 143 "See who is editing a watched file", 144 }, 145 { 146 -1, "export", { "ex", "exp" }, NULL, 147 "", 148 "", 149 "Export sources from CVS, similar to checkout", 150 }, 151 { 152 CVS_OP_HISTORY, "history", { "hi", "his" }, cvs_history, 153 "", 154 "", 155 "Show repository access history", 156 }, 157 { 158 CVS_OP_IMPORT, "import", { "im", "imp" }, NULL, 159 "[-d] [-b branch] [-I ign] [-k subst] [-m msg] " 160 "repository vendor-tag release-tags ...", 161 "b:dI:k:m:", 162 "Import sources into CVS, using vendor branches", 163 }, 164 { 165 CVS_OP_INIT, "init", { }, cvs_init, 166 "", 167 "", 168 "Create a CVS repository if it doesn't exist", 169 }, 170#if defined(HAVE_KERBEROS) 171 { 172 "kserver", {}, NULL 173 "", 174 "", 175 "Start a Kerberos authentication CVS server", 176 }, 177#endif 178 { 179 CVS_OP_LOG, "log", { "lo" }, cvs_getlog, 180 "", 181 "", 182 "Print out history information for files", 183 }, 184 { 185 -1, "login", {}, NULL, 186 "", 187 "", 188 "Prompt for password for authenticating server", 189 }, 190 { 191 -1, "logout", {}, NULL, 192 "", 193 "", 194 "Removes entry in .cvspass for remote repository", 195 }, 196 { 197 -1, "rdiff", {}, NULL, 198 "", 199 "", 200 "Create 'patch' format diffs between releases", 201 }, 202 { 203 -1, "release", {}, NULL, 204 "", 205 "", 206 "Indicate that a Module is no longer in use", 207 }, 208 { 209 CVS_OP_REMOVE, "remove", {}, NULL, 210 "", 211 "", 212 "Remove an entry from the repository", 213 }, 214 { 215 -1, "rlog", {}, NULL, 216 "", 217 "", 218 "Print out history information for a module", 219 }, 220 { 221 -1, "rtag", {}, NULL, 222 "", 223 "", 224 "Add a symbolic tag to a module", 225 }, 226 { 227 CVS_OP_SERVER, "server", {}, cvs_server, 228 "", 229 "", 230 "Server mode", 231 }, 232 { 233 CVS_OP_STATUS, "status", {}, cvs_status, 234 "", 235 "", 236 "Display status information on checked out files", 237 }, 238 { 239 CVS_OP_TAG, "tag", { "ta", }, NULL, 240 "", 241 "", 242 "Add a symbolic tag to checked out version of files", 243 }, 244 { 245 -1, "unedit", {}, NULL, 246 "", 247 "", 248 "Undo an edit command", 249 }, 250 { 251 CVS_OP_UPDATE, "update", {}, cvs_update, 252 "", 253 "", 254 "Bring work tree in sync with repository", 255 }, 256 { 257 CVS_OP_VERSION, "version", {}, cvs_version, 258 "", "", 259 "Show current CVS version(s)", 260 }, 261 { 262 -1, "watch", {}, NULL, 263 "", 264 "", 265 "Set watches", 266 }, 267 { 268 -1, "watchers", {}, NULL, 269 "", 270 "", 271 "See who is watching a file", 272 }, 273}; 274 275#define CVS_NBCMD (sizeof(cvs_cdt)/sizeof(cvs_cdt[0])) 276 277 278 279void usage (void); 280void sigchld_hdlr (int); 281void cvs_readrc (void); 282struct cvs_cmd* cvs_findcmd (const char *); 283 284 285/* 286 * usage() 287 * 288 * Display usage information. 289 */ 290 291void 292usage(void) 293{ 294 fprintf(stderr, 295 "Usage: %s [-lQqtvx] [-b bindir] [-d root] [-e editor] [-z level] " 296 "command [options] ...\n", 297 __progname); 298} 299 300 301int 302main(int argc, char **argv) 303{ 304 char *envstr, *ep; 305 int ret; 306 u_int i, readrc; 307 struct cvs_cmd *cmdp; 308 309 readrc = 1; 310 311 if (cvs_log_init(LD_STD, 0) < 0) 312 err(1, "failed to initialize logging"); 313 314 /* by default, be very verbose */ 315 (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO); 316 317#ifdef DEBUG 318 (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG); 319#endif 320 321 /* check environment so command-line options override it */ 322 if ((envstr = getenv("CVS_RSH")) != NULL) 323 cvs_rsh = envstr; 324 325 if (((envstr = getenv("CVSEDITOR")) != NULL) || 326 ((envstr = getenv("VISUAL")) != NULL) || 327 ((envstr = getenv("EDITOR")) != NULL)) 328 cvs_editor = envstr; 329 330 while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrtvz:")) != -1) { 331 switch (ret) { 332 case 'b': 333 /* 334 * We do not care about the bin directory for RCS files 335 * as this program has no dependencies on RCS programs, 336 * so it is only here for backwards compatibility. 337 */ 338 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 339 break; 340 case 'd': 341 cvs_rootstr = optarg; 342 break; 343 case 'e': 344 cvs_editor = optarg; 345 break; 346 case 'f': 347 readrc = 0; 348 break; 349 case 'l': 350 cvs_nolog = 1; 351 break; 352 case 'n': 353 break; 354 case 'Q': 355 verbosity = 0; 356 break; 357 case 'q': 358 /* don't override -Q */ 359 if (verbosity > 1) 360 verbosity = 1; 361 break; 362 case 'r': 363 cvs_readonly = 1; 364 break; 365 case 't': 366 cvs_trace = 1; 367 break; 368 case 'v': 369 printf("%s\n", CVS_VERSION); 370 exit(0); 371 /* NOTREACHED */ 372 break; 373 case 'x': 374 /* 375 * Kerberos encryption support, kept for compatibility 376 */ 377 break; 378 case 'z': 379 cvs_compress = (int)strtol(optarg, &ep, 10); 380 if (*ep != '\0') 381 errx(1, "error parsing compression level"); 382 if (cvs_compress < 0 || cvs_compress > 9) 383 errx(1, "gzip compression level must be " 384 "between 0 and 9"); 385 break; 386 default: 387 usage(); 388 exit(EX_USAGE); 389 } 390 } 391 392 argc -= optind; 393 argv += optind; 394 395 /* reset getopt() for use by commands */ 396 optind = 1; 397 optreset = 1; 398 399 if (argc == 0) { 400 usage(); 401 exit(EX_USAGE); 402 } 403 404 /* setup signal handlers */ 405 signal(SIGPIPE, SIG_IGN); 406 407 cvs_file_init(); 408 409 if (readrc) 410 cvs_readrc(); 411 412 cvs_command = argv[0]; 413 ret = -1; 414 415 cmdp = cvs_findcmd(cvs_command); 416 if (cmdp == NULL) { 417 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 418 fprintf(stderr, "CVS commands are:\n"); 419 for (i = 0; i < CVS_NBCMD; i++) 420 fprintf(stderr, "\t%-16s%s\n", 421 cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr); 422 exit(EX_USAGE); 423 } 424 425 if (cmdp->cmd_hdlr == NULL) { 426 cvs_log(LP_ERR, "command `%s' not implemented", cvs_command); 427 exit(1); 428 } 429 430 cvs_cmdop = cmdp->cmd_op; 431 432 ret = (*cmdp->cmd_hdlr)(argc, argv); 433 if (ret == EX_USAGE) { 434 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 435 cmdp->cmd_synopsis); 436 } 437 438 if (cvs_files != NULL) 439 cvs_file_free(cvs_files); 440 441 return (ret); 442} 443 444 445/* 446 * cvs_findcmd() 447 * 448 * Find the entry in the command dispatch table whose name or one of its 449 * aliases matches <cmd>. 450 * Returns a pointer to the command entry on success, NULL on failure. 451 */ 452 453struct cvs_cmd* 454cvs_findcmd(const char *cmd) 455{ 456 u_int i, j; 457 struct cvs_cmd *cmdp; 458 459 cmdp = NULL; 460 461 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 462 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 463 cmdp = &cvs_cdt[i]; 464 else { 465 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 466 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 467 cmdp = &cvs_cdt[i]; 468 break; 469 } 470 } 471 } 472 } 473 474 return (cmdp); 475} 476 477 478/* 479 * cvs_readrc() 480 * 481 * Read the CVS `.cvsrc' file in the user's home directory. If the file 482 * exists, it should contain a list of arguments that should always be given 483 * implicitly to the specified commands. 484 */ 485 486void 487cvs_readrc(void) 488{ 489 char rcpath[MAXPATHLEN], linebuf[128], *lp; 490 struct cvs_cmd *cmdp; 491 struct passwd *pw; 492 FILE *fp; 493 494 pw = getpwuid(getuid()); 495 if (pw == NULL) { 496 cvs_log(LP_NOTICE, "failed to get user's password entry"); 497 return; 498 } 499 500 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 501 502 fp = fopen(rcpath, "r"); 503 if (fp == NULL) { 504 if (errno != ENOENT) 505 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 506 strerror(errno)); 507 return; 508 } 509 510 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 511 lp = strchr(linebuf, ' '); 512 513 /* ignore lines with no arguments */ 514 if (lp == NULL) 515 continue; 516 517 *(lp++) = '\0'; 518 if (strcmp(linebuf, "cvs") == 0) { 519 /* global options */ 520 } 521 else { 522 cmdp = cvs_findcmd(linebuf); 523 if (cmdp == NULL) { 524 cvs_log(LP_NOTICE, 525 "unknown command `%s' in cvsrc", 526 linebuf); 527 continue; 528 } 529 } 530 } 531 if (ferror(fp)) { 532 cvs_log(LP_NOTICE, "failed to read line from cvsrc"); 533 } 534 535 (void)fclose(fp); 536} 537