cvs.c revision 1.2
1#define DEBUG 2/* $OpenBSD: cvs.c,v 1.2 2004/07/14 03:33:09 jfb Exp $ */ 3/* 4 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#include <sys/types.h> 29#include <sys/wait.h> 30 31#include <err.h> 32#include <pwd.h> 33#include <errno.h> 34#include <stdio.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 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; 58 59/* name of the command we are running */ 60char *cvs_command; 61char *cvs_rootstr; 62char *cvs_rsh = CVS_RSH_DEFAULT; 63char *cvs_editor = CVS_EDITOR_DEFAULT; 64 65struct cvsroot *cvs_root = NULL; 66 67 68/* 69 * Command dispatch table 70 * ---------------------- 71 * 72 * The synopsis field should only contain the list of arguments that the 73 * command supports, without the actual command's name. 74 * 75 * Command handlers are expected to return 0 if no error occured, or one of 76 * the values known in sysexits.h in case of an error. In case the error 77 * returned is EX_USAGE, the command's usage string is printed to standard 78 * error before returning. 79 */ 80 81static struct cvs_cmd { 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 "add", { "ad", "new" }, cvs_add, 90 "[-m msg] file ...", 91 "Add a new file/directory to the repository", 92 }, 93 { 94 "admin", { "adm", "rcs" }, NULL, 95 "", 96 "Administration front end for rcs", 97 }, 98 { 99 "annotate", { "ann" }, NULL, 100 "", 101 "Show last revision where each line was modified", 102 }, 103 { 104 "checkout", { "co", "get" }, NULL, 105 "", 106 "Checkout sources for editing", 107 }, 108 { 109 "commit", { "ci", "com" }, cvs_commit, 110 "", 111 "Check files into the repository", 112 }, 113 { 114 "diff", { "di", "dif" }, cvs_diff, 115 "", 116 "Show differences between revisions", 117 }, 118 { 119 "edit", { }, NULL, 120 "", 121 "Get ready to edit a watched file", 122 }, 123 { 124 "editors", { }, NULL, 125 "", 126 "See who is editing a watched file", 127 }, 128 { 129 "export", { "ex", "exp" }, NULL, 130 "", 131 "Export sources from CVS, similar to checkout", 132 }, 133 { 134 "history", { "hi", "his" }, cvs_history, 135 "", 136 "Show repository access history", 137 }, 138 { 139 "import", { "im", "imp" }, NULL, 140 "", 141 "Import sources into CVS, using vendor branches", 142 }, 143 { 144 "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 "log", { "lo" }, cvs_getlog, 157 "", 158 "Print out history information for files", 159 }, 160 { 161 "login", {}, NULL, 162 "", 163 "Prompt for password for authenticating server", 164 }, 165 { 166 "logout", {}, NULL, 167 "", 168 "Removes entry in .cvspass for remote repository", 169 }, 170 { 171 "rdiff", {}, NULL, 172 "", 173 "Create 'patch' format diffs between releases", 174 }, 175 { 176 "release", {}, NULL, 177 "", 178 "Indicate that a Module is no longer in use", 179 }, 180 { 181 "remove", {}, NULL, 182 "", 183 "Remove an entry from the repository", 184 }, 185 { 186 "rlog", {}, NULL, 187 "", 188 "Print out history information for a module", 189 }, 190 { 191 "rtag", {}, NULL, 192 "", 193 "Add a symbolic tag to a module", 194 }, 195 { 196 "server", {}, cvs_server, 197 "", 198 "Server mode", 199 }, 200 { 201 "status", {}, NULL, 202 "", 203 "Display status information on checked out files", 204 }, 205 { 206 "tag", { "ta", }, NULL, 207 "", 208 "Add a symbolic tag to checked out version of files", 209 }, 210 { 211 "unedit", {}, NULL, 212 "", 213 "Undo an edit command", 214 }, 215 { 216 "update", {}, cvs_update, 217 "", 218 "Bring work tree in sync with repository", 219 }, 220 { 221 "version", {}, cvs_version, 222 "", 223 "Show current CVS version(s)", 224 }, 225 { 226 "watch", {}, NULL, 227 "", 228 "Set watches", 229 }, 230 { 231 "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 241void usage (void); 242void sigchld_hdlr (int); 243void cvs_readrc (void); 244struct 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 255void 256sigchld_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 272void 273usage(void) 274{ 275 fprintf(stderr, 276 "Usage: %s [-lQqtv] [-d root] [-e editor] [-z level] " 277 "command [options] ...\n", 278 __progname); 279} 280 281 282int 283main(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 ret = (*cmdp->cmd_hdlr)(argc, argv); 399 if (ret == EX_USAGE) { 400 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 401 cmdp->cmd_synopsis); 402 } 403 404 return (ret); 405} 406 407 408/* 409 * cvs_findcmd() 410 * 411 * Find the entry in the command dispatch table whose name or one of its 412 * aliases matches <cmd>. 413 * Returns a pointer to the command entry on success, NULL on failure. 414 */ 415 416struct cvs_cmd* 417cvs_findcmd(const char *cmd) 418{ 419 u_int i, j; 420 struct cvs_cmd *cmdp; 421 422 cmdp = NULL; 423 424 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 425 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 426 cmdp = &cvs_cdt[i]; 427 else { 428 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 429 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 430 cmdp = &cvs_cdt[i]; 431 break; 432 } 433 } 434 } 435 } 436 437 return (cmdp); 438} 439 440 441/* 442 * cvs_readrc() 443 * 444 * Read the CVS `.cvsrc' file in the user's home directory. If the file 445 * exists, it should contain a list of arguments that should always be given 446 * implicitly to the specified commands. 447 */ 448 449void 450cvs_readrc(void) 451{ 452 char rcpath[MAXPATHLEN], linebuf[128], *lp; 453 struct cvs_cmd *cmdp; 454 struct passwd *pw; 455 FILE *fp; 456 457 pw = getpwuid(getuid()); 458 if (pw == NULL) { 459 cvs_log(LP_NOTICE, "failed to get user's password entry"); 460 return; 461 } 462 463 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 464 465 fp = fopen(rcpath, "r"); 466 if (fp == NULL) { 467 if (errno != ENOENT) 468 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 469 strerror(errno)); 470 return; 471 } 472 473 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 474 lp = strchr(linebuf, ' '); 475 476 /* ignore lines with no arguments */ 477 if (lp == NULL) 478 continue; 479 480 *(lp++) = '\0'; 481 if (strcmp(linebuf, "cvs") == 0) { 482 /* global options */ 483 } 484 else { 485 cmdp = cvs_findcmd(linebuf); 486 if (cmdp == NULL) { 487 cvs_log(LP_NOTICE, 488 "unknown command `%s' in cvsrc", 489 linebuf); 490 continue; 491 } 492 } 493 } 494 if (ferror(fp)) { 495 cvs_log(LP_NOTICE, "failed to read line from cvsrc"); 496 } 497 498 (void)fclose(fp); 499} 500