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