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