cvs.c revision 1.7
1107120Sjulian/* $OpenBSD: cvs.c,v 1.7 2004/07/30 17:44:48 jfb Exp $ */ 2107120Sjulian/* 3139823Simp * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 4139823Simp * All rights reserved. 5139823Simp * 6107120Sjulian * Redistribution and use in source and binary forms, with or without 7107120Sjulian * modification, are permitted provided that the following conditions 8107120Sjulian * are met: 9107120Sjulian * 10107120Sjulian * 1. Redistributions of source code must retain the above copyright 11107120Sjulian * notice, this list of conditions and the following disclaimer. 12107120Sjulian * 2. The name of the author may not be used to endorse or promote products 13107120Sjulian * derived from this software without specific prior written permission. 14107120Sjulian * 15107120Sjulian * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16107120Sjulian * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17107120Sjulian * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 18107120Sjulian * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19107120Sjulian * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20107120Sjulian * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21107120Sjulian * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22107120Sjulian * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23107120Sjulian * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24107120Sjulian * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25107120Sjulian */ 26107120Sjulian 27107120Sjulian#include <sys/types.h> 28107120Sjulian#include <sys/wait.h> 29107120Sjulian 30114878Sjulian#include <err.h> 31107120Sjulian#include <pwd.h> 32107120Sjulian#include <errno.h> 33107120Sjulian#include <stdio.h> 34107120Sjulian#include <stdlib.h> 35107120Sjulian#include <unistd.h> 36107120Sjulian#include <signal.h> 37107120Sjulian#include <string.h> 38107120Sjulian#include <sysexits.h> 39107120Sjulian 40107120Sjulian#include "cvs.h" 41107120Sjulian#include "log.h" 42107120Sjulian 43107120Sjulian 44107120Sjulianextern char *__progname; 45107120Sjulian 46107120Sjulian 47107120Sjulian/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 48107120Sjulianint verbosity = 2; 49107120Sjulian 50107120Sjulian 51107120Sjulian 52107120Sjulian/* compression level used with zlib, 0 meaning no compression taking place */ 53107120Sjulianint cvs_compress = 0; 54107120Sjulianint cvs_trace = 0; 55107120Sjulianint cvs_nolog = 0; 56107120Sjulianint cvs_readonly = 0; 57107120Sjulian 58107120Sjulian/* name of the command we are running */ 59107120Sjulianchar *cvs_command; 60107120Sjulianint cvs_cmdop; 61107120Sjulianchar *cvs_rootstr; 62107120Sjulianchar *cvs_rsh = CVS_RSH_DEFAULT; 63107120Sjulianchar *cvs_editor = CVS_EDITOR_DEFAULT; 64107120Sjulian 65107120Sjulianstruct cvsroot *cvs_root = NULL; 66107120Sjulian 67107120Sjulian 68107120Sjulian/* 69107120Sjulian * Command dispatch table 70107120Sjulian * ---------------------- 71107120Sjulian * 72107120Sjulian * The synopsis field should only contain the list of arguments that the 73107120Sjulian * command supports, without the actual command's name. 74107120Sjulian * 75107120Sjulian * Command handlers are expected to return 0 if no error occured, or one of 76107120Sjulian * the values known in sysexits.h in case of an error. In case the error 77107120Sjulian * returned is EX_USAGE, the command's usage string is printed to standard 78107120Sjulian * error before returning. 79107120Sjulian */ 80 81static struct cvs_cmd { 82 int cmd_op; 83 char cmd_name[CVS_CMD_MAXNAMELEN]; 84 char cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN]; 85 int (*cmd_hdlr)(int, char **); 86 char *cmd_synopsis; 87 char cmd_descr[CVS_CMD_MAXDESCRLEN]; 88} cvs_cdt[] = { 89 { 90 CVS_OP_ADD, "add", { "ad", "new" }, cvs_add, 91 "[-m msg] file ...", 92 "Add a new file/directory to the repository", 93 }, 94 { 95 -1, "admin", { "adm", "rcs" }, NULL, 96 "", 97 "Administration front end for rcs", 98 }, 99 { 100 CVS_OP_ANNOTATE, "annotate", { "ann" }, NULL, 101 "", 102 "Show last revision where each line was modified", 103 }, 104 { 105 CVS_OP_CHECKOUT, "checkout", { "co", "get" }, cvs_checkout, 106 "", 107 "Checkout sources for editing", 108 }, 109 { 110 CVS_OP_COMMIT, "commit", { "ci", "com" }, cvs_commit, 111 "[-flR] [-F logfile | -m msg] [-r rev] ...", 112 "Check files into the repository", 113 }, 114 { 115 CVS_OP_DIFF, "diff", { "di", "dif" }, cvs_diff, 116 "[-cilu] [-D date] [-r rev] ...", 117 "Show differences between revisions", 118 }, 119 { 120 -1, "edit", { }, NULL, 121 "", 122 "Get ready to edit a watched file", 123 }, 124 { 125 -1, "editors", { }, NULL, 126 "", 127 "See who is editing a watched file", 128 }, 129 { 130 -1, "export", { "ex", "exp" }, NULL, 131 "", 132 "Export sources from CVS, similar to checkout", 133 }, 134 { 135 CVS_OP_HISTORY, "history", { "hi", "his" }, cvs_history, 136 "", 137 "Show repository access history", 138 }, 139 { 140 CVS_OP_IMPORT, "import", { "im", "imp" }, NULL, 141 "", 142 "Import sources into CVS, using vendor branches", 143 }, 144 { 145 CVS_OP_INIT, "init", { }, cvs_init, 146 "", 147 "Create a CVS repository if it doesn't exist", 148 }, 149#if defined(HAVE_KERBEROS) 150 { 151 "kserver", {}, NULL 152 "", 153 "Start a Kerberos authentication CVS server", 154 }, 155#endif 156 { 157 CVS_OP_LOG, "log", { "lo" }, cvs_getlog, 158 "", 159 "Print out history information for files", 160 }, 161 { 162 -1, "login", {}, NULL, 163 "", 164 "Prompt for password for authenticating server", 165 }, 166 { 167 -1, "logout", {}, NULL, 168 "", 169 "Removes entry in .cvspass for remote repository", 170 }, 171 { 172 -1, "rdiff", {}, NULL, 173 "", 174 "Create 'patch' format diffs between releases", 175 }, 176 { 177 -1, "release", {}, NULL, 178 "", 179 "Indicate that a Module is no longer in use", 180 }, 181 { 182 CVS_OP_REMOVE, "remove", {}, NULL, 183 "", 184 "Remove an entry from the repository", 185 }, 186 { 187 -1, "rlog", {}, NULL, 188 "", 189 "Print out history information for a module", 190 }, 191 { 192 -1, "rtag", {}, NULL, 193 "", 194 "Add a symbolic tag to a module", 195 }, 196 { 197 CVS_OP_SERVER, "server", {}, cvs_server, 198 "", 199 "Server mode", 200 }, 201 { 202 CVS_OP_STATUS, "status", {}, cvs_status, 203 "", 204 "Display status information on checked out files", 205 }, 206 { 207 CVS_OP_TAG, "tag", { "ta", }, NULL, 208 "", 209 "Add a symbolic tag to checked out version of files", 210 }, 211 { 212 -1, "unedit", {}, NULL, 213 "", 214 "Undo an edit command", 215 }, 216 { 217 CVS_OP_UPDATE, "update", {}, cvs_update, 218 "", 219 "Bring work tree in sync with repository", 220 }, 221 { 222 CVS_OP_VERSION, "version", {}, cvs_version, 223 "", 224 "Show current CVS version(s)", 225 }, 226 { 227 -1, "watch", {}, NULL, 228 "", 229 "Set watches", 230 }, 231 { 232 -1, "watchers", {}, NULL, 233 "", 234 "See who is watching a file", 235 }, 236}; 237 238#define CVS_NBCMD (sizeof(cvs_cdt)/sizeof(cvs_cdt[0])) 239 240 241 242void usage (void); 243void sigchld_hdlr (int); 244void cvs_readrc (void); 245struct cvs_cmd* cvs_findcmd (const char *); 246 247 248 249/* 250 * sigchld_hdlr() 251 * 252 * Handler for the SIGCHLD signal, which can be received in case we are 253 * running a remote server and it dies. 254 */ 255 256void 257sigchld_hdlr(int signo) 258{ 259 int status; 260 pid_t pid; 261 262 if ((pid = wait(&status)) == -1) { 263 } 264} 265 266 267/* 268 * usage() 269 * 270 * Display usage information. 271 */ 272 273void 274usage(void) 275{ 276 fprintf(stderr, 277 "Usage: %s [-lQqtv] [-d root] [-e editor] [-z level] " 278 "command [options] ...\n", 279 __progname); 280} 281 282 283int 284main(int argc, char **argv) 285{ 286 char *envstr, *ep; 287 int ret; 288 u_int i, readrc; 289 struct cvs_cmd *cmdp; 290 291 readrc = 1; 292 293 if (cvs_log_init(LD_STD, 0) < 0) 294 err(1, "failed to initialize logging"); 295 296 /* by default, be very verbose */ 297 (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO); 298 299#ifdef DEBUG 300 (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG); 301#endif 302 303 /* check environment so command-line options override it */ 304 if ((envstr = getenv("CVS_RSH")) != NULL) 305 cvs_rsh = envstr; 306 307 if (((envstr = getenv("CVSEDITOR")) != NULL) || 308 ((envstr = getenv("VISUAL")) != NULL) || 309 ((envstr = getenv("EDITOR")) != NULL)) 310 cvs_editor = envstr; 311 312 while ((ret = getopt(argc, argv, "d:e:fHlnQqrtvz:")) != -1) { 313 switch (ret) { 314 case 'd': 315 cvs_rootstr = optarg; 316 break; 317 case 'e': 318 cvs_editor = optarg; 319 break; 320 case 'f': 321 readrc = 0; 322 break; 323 case 'l': 324 cvs_nolog = 1; 325 break; 326 case 'n': 327 break; 328 case 'Q': 329 verbosity = 0; 330 break; 331 case 'q': 332 /* don't override -Q */ 333 if (verbosity > 1) 334 verbosity = 1; 335 break; 336 case 'r': 337 cvs_readonly = 1; 338 break; 339 case 't': 340 cvs_trace = 1; 341 break; 342 case 'v': 343 printf("%s\n", CVS_VERSION); 344 exit(0); 345 /* NOTREACHED */ 346 break; 347 case 'z': 348 cvs_compress = (int)strtol(optarg, &ep, 10); 349 if (*ep != '\0') 350 errx(1, "error parsing compression level"); 351 if (cvs_compress < 0 || cvs_compress > 9) 352 errx(1, "gzip compression level must be " 353 "between 0 and 9"); 354 break; 355 default: 356 usage(); 357 exit(EX_USAGE); 358 } 359 } 360 361 argc -= optind; 362 argv += optind; 363 364 /* reset getopt() for use by commands */ 365 optind = 1; 366 optreset = 1; 367 368 if (argc == 0) { 369 usage(); 370 exit(EX_USAGE); 371 } 372 373 /* setup signal handlers */ 374 signal(SIGCHLD, sigchld_hdlr); 375 376 cvs_file_init(); 377 378 if (readrc) 379 cvs_readrc(); 380 381 cvs_command = argv[0]; 382 ret = -1; 383 384 cmdp = cvs_findcmd(cvs_command); 385 if (cmdp == NULL) { 386 fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command); 387 fprintf(stderr, "CVS commands are:\n"); 388 for (i = 0; i < CVS_NBCMD; i++) 389 fprintf(stderr, "\t%-16s%s\n", 390 cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr); 391 exit(EX_USAGE); 392 } 393 394 if (cmdp->cmd_hdlr == NULL) { 395 cvs_log(LP_ERR, "command `%s' not implemented", cvs_command); 396 exit(1); 397 } 398 399 cvs_cmdop = cmdp->cmd_op; 400 401 ret = (*cmdp->cmd_hdlr)(argc, argv); 402 if (ret == EX_USAGE) { 403 fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command, 404 cmdp->cmd_synopsis); 405 } 406 407 return (ret); 408} 409 410 411/* 412 * cvs_findcmd() 413 * 414 * Find the entry in the command dispatch table whose name or one of its 415 * aliases matches <cmd>. 416 * Returns a pointer to the command entry on success, NULL on failure. 417 */ 418 419struct cvs_cmd* 420cvs_findcmd(const char *cmd) 421{ 422 u_int i, j; 423 struct cvs_cmd *cmdp; 424 425 cmdp = NULL; 426 427 for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) { 428 if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0) 429 cmdp = &cvs_cdt[i]; 430 else { 431 for (j = 0; j < CVS_CMD_MAXALIAS; j++) { 432 if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) { 433 cmdp = &cvs_cdt[i]; 434 break; 435 } 436 } 437 } 438 } 439 440 return (cmdp); 441} 442 443 444/* 445 * cvs_readrc() 446 * 447 * Read the CVS `.cvsrc' file in the user's home directory. If the file 448 * exists, it should contain a list of arguments that should always be given 449 * implicitly to the specified commands. 450 */ 451 452void 453cvs_readrc(void) 454{ 455 char rcpath[MAXPATHLEN], linebuf[128], *lp; 456 struct cvs_cmd *cmdp; 457 struct passwd *pw; 458 FILE *fp; 459 460 pw = getpwuid(getuid()); 461 if (pw == NULL) { 462 cvs_log(LP_NOTICE, "failed to get user's password entry"); 463 return; 464 } 465 466 snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC); 467 468 fp = fopen(rcpath, "r"); 469 if (fp == NULL) { 470 if (errno != ENOENT) 471 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 472 strerror(errno)); 473 return; 474 } 475 476 while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { 477 lp = strchr(linebuf, ' '); 478 479 /* ignore lines with no arguments */ 480 if (lp == NULL) 481 continue; 482 483 *(lp++) = '\0'; 484 if (strcmp(linebuf, "cvs") == 0) { 485 /* global options */ 486 } 487 else { 488 cmdp = cvs_findcmd(linebuf); 489 if (cmdp == NULL) { 490 cvs_log(LP_NOTICE, 491 "unknown command `%s' in cvsrc", 492 linebuf); 493 continue; 494 } 495 } 496 } 497 if (ferror(fp)) { 498 cvs_log(LP_NOTICE, "failed to read line from cvsrc"); 499 } 500 501 (void)fclose(fp); 502} 503