Annotation of src/usr.bin/cvs/cvs.c, Revision 1.17
1.17 ! jfb 1: /* $OpenBSD: cvs.c,v 1.16 2004/12/06 21:03:12 deraadt Exp $ */
1.1 jfb 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"
1.8 jfb 42: #include "file.h"
1.1 jfb 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;
1.17 ! jfb 55: int cvs_readrc = 1; /* read .cvsrc on startup */
1.1 jfb 56: int cvs_trace = 0;
57: int cvs_nolog = 0;
58: int cvs_readonly = 0;
1.17 ! jfb 59: int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */
1.1 jfb 60:
1.17 ! jfb 61: char *cvs_defargs; /* default global arguments from .cvsrc */
! 62: char *cvs_command; /* name of the command we are running */
1.6 jfb 63: int cvs_cmdop;
1.1 jfb 64: char *cvs_rootstr;
65: char *cvs_rsh = CVS_RSH_DEFAULT;
66: char *cvs_editor = CVS_EDITOR_DEFAULT;
67:
1.13 krapht 68: char *cvs_msg = NULL;
69:
1.1 jfb 70:
1.10 jfb 71: /* hierarchy of all the files affected by the command */
72: CVSFILE *cvs_files;
73:
74:
75:
1.1 jfb 76: /*
77: * Command dispatch table
78: * ----------------------
79: *
80: * The synopsis field should only contain the list of arguments that the
81: * command supports, without the actual command's name.
82: *
83: * Command handlers are expected to return 0 if no error occured, or one of
84: * the values known in sysexits.h in case of an error. In case the error
85: * returned is EX_USAGE, the command's usage string is printed to standard
86: * error before returning.
87: */
88:
89: static struct cvs_cmd {
1.6 jfb 90: int cmd_op;
1.1 jfb 91: char cmd_name[CVS_CMD_MAXNAMELEN];
92: char cmd_alias[CVS_CMD_MAXALIAS][CVS_CMD_MAXNAMELEN];
93: int (*cmd_hdlr)(int, char **);
94: char *cmd_synopsis;
1.13 krapht 95: char *cmd_opts;
1.1 jfb 96: char cmd_descr[CVS_CMD_MAXDESCRLEN];
1.17 ! jfb 97: char *cmd_defargs;
1.1 jfb 98: } cvs_cdt[] = {
99: {
1.6 jfb 100: CVS_OP_ADD, "add", { "ad", "new" }, cvs_add,
1.1 jfb 101: "[-m msg] file ...",
1.13 krapht 102: "",
1.1 jfb 103: "Add a new file/directory to the repository",
1.17 ! jfb 104: NULL,
1.1 jfb 105: },
106: {
1.6 jfb 107: -1, "admin", { "adm", "rcs" }, NULL,
1.1 jfb 108: "",
1.13 krapht 109: "",
1.1 jfb 110: "Administration front end for rcs",
1.17 ! jfb 111: NULL,
1.1 jfb 112: },
113: {
1.6 jfb 114: CVS_OP_ANNOTATE, "annotate", { "ann" }, NULL,
1.1 jfb 115: "",
1.13 krapht 116: "",
1.1 jfb 117: "Show last revision where each line was modified",
1.17 ! jfb 118: NULL,
1.1 jfb 119: },
120: {
1.6 jfb 121: CVS_OP_CHECKOUT, "checkout", { "co", "get" }, cvs_checkout,
1.1 jfb 122: "",
1.13 krapht 123: "",
1.1 jfb 124: "Checkout sources for editing",
1.17 ! jfb 125: NULL,
1.1 jfb 126: },
127: {
1.6 jfb 128: CVS_OP_COMMIT, "commit", { "ci", "com" }, cvs_commit,
1.4 jfb 129: "[-flR] [-F logfile | -m msg] [-r rev] ...",
1.13 krapht 130: "F:flm:Rr:",
1.1 jfb 131: "Check files into the repository",
1.17 ! jfb 132: NULL,
1.1 jfb 133: },
134: {
1.6 jfb 135: CVS_OP_DIFF, "diff", { "di", "dif" }, cvs_diff,
1.3 jfb 136: "[-cilu] [-D date] [-r rev] ...",
1.13 krapht 137: "cD:ilur:",
1.1 jfb 138: "Show differences between revisions",
1.17 ! jfb 139: NULL,
1.1 jfb 140: },
141: {
1.6 jfb 142: -1, "edit", { }, NULL,
1.1 jfb 143: "",
1.13 krapht 144: "",
1.1 jfb 145: "Get ready to edit a watched file",
1.17 ! jfb 146: NULL,
1.1 jfb 147: },
148: {
1.6 jfb 149: -1, "editors", { }, NULL,
1.1 jfb 150: "",
1.13 krapht 151: "",
1.1 jfb 152: "See who is editing a watched file",
1.17 ! jfb 153: NULL,
1.1 jfb 154: },
155: {
1.6 jfb 156: -1, "export", { "ex", "exp" }, NULL,
1.1 jfb 157: "",
1.13 krapht 158: "",
1.1 jfb 159: "Export sources from CVS, similar to checkout",
1.17 ! jfb 160: NULL,
1.1 jfb 161: },
162: {
1.6 jfb 163: CVS_OP_HISTORY, "history", { "hi", "his" }, cvs_history,
1.1 jfb 164: "",
1.13 krapht 165: "",
1.1 jfb 166: "Show repository access history",
1.17 ! jfb 167: NULL,
1.1 jfb 168: },
169: {
1.14 krapht 170: CVS_OP_IMPORT, "import", { "im", "imp" }, NULL,
1.13 krapht 171: "[-d] [-b branch] [-I ign] [-k subst] [-m msg] "
172: "repository vendor-tag release-tags ...",
173: "b:dI:k:m:",
1.1 jfb 174: "Import sources into CVS, using vendor branches",
1.17 ! jfb 175: NULL,
1.1 jfb 176: },
177: {
1.6 jfb 178: CVS_OP_INIT, "init", { }, cvs_init,
1.1 jfb 179: "",
1.13 krapht 180: "",
1.1 jfb 181: "Create a CVS repository if it doesn't exist",
1.17 ! jfb 182: NULL,
1.1 jfb 183: },
184: #if defined(HAVE_KERBEROS)
185: {
186: "kserver", {}, NULL
187: "",
1.13 krapht 188: "",
1.1 jfb 189: "Start a Kerberos authentication CVS server",
1.17 ! jfb 190: NULL,
1.1 jfb 191: },
192: #endif
193: {
1.6 jfb 194: CVS_OP_LOG, "log", { "lo" }, cvs_getlog,
1.1 jfb 195: "",
1.13 krapht 196: "",
1.1 jfb 197: "Print out history information for files",
1.17 ! jfb 198: NULL,
1.1 jfb 199: },
200: {
1.6 jfb 201: -1, "login", {}, NULL,
1.1 jfb 202: "",
1.13 krapht 203: "",
1.1 jfb 204: "Prompt for password for authenticating server",
1.17 ! jfb 205: NULL,
1.1 jfb 206: },
207: {
1.6 jfb 208: -1, "logout", {}, NULL,
1.1 jfb 209: "",
1.13 krapht 210: "",
1.1 jfb 211: "Removes entry in .cvspass for remote repository",
1.17 ! jfb 212: NULL,
1.1 jfb 213: },
214: {
1.6 jfb 215: -1, "rdiff", {}, NULL,
1.1 jfb 216: "",
1.13 krapht 217: "",
1.1 jfb 218: "Create 'patch' format diffs between releases",
1.17 ! jfb 219: NULL,
1.1 jfb 220: },
221: {
1.6 jfb 222: -1, "release", {}, NULL,
1.1 jfb 223: "",
1.13 krapht 224: "",
1.1 jfb 225: "Indicate that a Module is no longer in use",
1.17 ! jfb 226: NULL,
1.1 jfb 227: },
228: {
1.6 jfb 229: CVS_OP_REMOVE, "remove", {}, NULL,
1.1 jfb 230: "",
1.13 krapht 231: "",
1.1 jfb 232: "Remove an entry from the repository",
1.17 ! jfb 233: NULL,
1.1 jfb 234: },
235: {
1.6 jfb 236: -1, "rlog", {}, NULL,
1.1 jfb 237: "",
1.13 krapht 238: "",
1.1 jfb 239: "Print out history information for a module",
1.17 ! jfb 240: NULL,
1.1 jfb 241: },
242: {
1.6 jfb 243: -1, "rtag", {}, NULL,
1.1 jfb 244: "",
1.13 krapht 245: "",
1.1 jfb 246: "Add a symbolic tag to a module",
1.17 ! jfb 247: NULL,
1.1 jfb 248: },
249: {
1.6 jfb 250: CVS_OP_SERVER, "server", {}, cvs_server,
1.1 jfb 251: "",
1.13 krapht 252: "",
1.1 jfb 253: "Server mode",
1.17 ! jfb 254: NULL,
1.1 jfb 255: },
256: {
1.16 jfb 257: CVS_OP_STATUS, "status", { "st", "stat" }, cvs_status,
1.1 jfb 258: "",
1.13 krapht 259: "",
1.1 jfb 260: "Display status information on checked out files",
1.17 ! jfb 261: NULL,
1.1 jfb 262: },
263: {
1.16 jfb 264: CVS_OP_TAG, "tag", { "ta", "freeze" }, NULL,
1.1 jfb 265: "",
1.13 krapht 266: "",
1.1 jfb 267: "Add a symbolic tag to checked out version of files",
1.17 ! jfb 268: NULL,
1.1 jfb 269: },
270: {
1.6 jfb 271: -1, "unedit", {}, NULL,
1.1 jfb 272: "",
1.13 krapht 273: "",
1.1 jfb 274: "Undo an edit command",
1.17 ! jfb 275: NULL,
1.1 jfb 276: },
277: {
1.16 jfb 278: CVS_OP_UPDATE, "update", { "up", "upd" }, cvs_update,
1.1 jfb 279: "",
1.13 krapht 280: "",
1.1 jfb 281: "Bring work tree in sync with repository",
1.17 ! jfb 282: NULL,
1.1 jfb 283: },
284: {
1.16 jfb 285: CVS_OP_VERSION, "version", { "ve", "ver" }, cvs_version,
1.13 krapht 286: "", "",
1.1 jfb 287: "Show current CVS version(s)",
1.17 ! jfb 288: NULL,
1.1 jfb 289: },
290: {
1.6 jfb 291: -1, "watch", {}, NULL,
1.1 jfb 292: "",
1.13 krapht 293: "",
1.1 jfb 294: "Set watches",
1.17 ! jfb 295: NULL,
1.1 jfb 296: },
297: {
1.6 jfb 298: -1, "watchers", {}, NULL,
1.1 jfb 299: "",
1.13 krapht 300: "",
1.1 jfb 301: "See who is watching a file",
1.17 ! jfb 302: NULL,
1.1 jfb 303: },
304: };
305:
306: #define CVS_NBCMD (sizeof(cvs_cdt)/sizeof(cvs_cdt[0]))
307:
308:
309:
310: void usage (void);
311: void sigchld_hdlr (int);
1.17 ! jfb 312: void cvs_read_rcfile (void);
1.1 jfb 313: struct cvs_cmd* cvs_findcmd (const char *);
1.17 ! jfb 314: int cvs_getopt (int, char **);
1.1 jfb 315:
316:
317: /*
318: * usage()
319: *
320: * Display usage information.
321: */
322:
323: void
324: usage(void)
325: {
326: fprintf(stderr,
1.11 jfb 327: "Usage: %s [-lQqtvx] [-b bindir] [-d root] [-e editor] [-z level] "
1.1 jfb 328: "command [options] ...\n",
329: __progname);
330: }
331:
332:
333: int
334: main(int argc, char **argv)
335: {
1.17 ! jfb 336: char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
! 337: int i, ret, cmd_argc;
1.1 jfb 338: struct cvs_cmd *cmdp;
339:
340: if (cvs_log_init(LD_STD, 0) < 0)
341: err(1, "failed to initialize logging");
342:
343: /* by default, be very verbose */
344: (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
345:
346: #ifdef DEBUG
347: (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
348: #endif
349:
350: /* check environment so command-line options override it */
351: if ((envstr = getenv("CVS_RSH")) != NULL)
352: cvs_rsh = envstr;
353:
354: if (((envstr = getenv("CVSEDITOR")) != NULL) ||
355: ((envstr = getenv("VISUAL")) != NULL) ||
356: ((envstr = getenv("EDITOR")) != NULL))
357: cvs_editor = envstr;
358:
1.17 ! jfb 359: ret = cvs_getopt(argc, argv);
! 360:
! 361: argc -= ret;
! 362: argv += ret;
! 363: if (argc == 0) {
! 364: usage();
! 365: exit(EX_USAGE);
! 366: }
! 367: cvs_command = argv[0];
! 368:
! 369: if (cvs_readrc) {
! 370: cvs_read_rcfile();
! 371:
! 372: if (cvs_defargs != NULL) {
! 373: targv = cvs_makeargv(cvs_defargs, &i);
! 374: if (targv == NULL) {
! 375: cvs_log(LP_ERR,
! 376: "failed to load default arguments to %s",
! 377: __progname);
! 378: exit(EX_OSERR);
! 379: }
! 380:
! 381: cvs_getopt(i, targv);
! 382: cvs_freeargv(targv, i);
! 383: free(targv);
! 384: }
! 385: }
! 386:
! 387: /* setup signal handlers */
! 388: signal(SIGPIPE, SIG_IGN);
! 389:
! 390: cvs_file_init();
! 391:
! 392: ret = -1;
! 393:
! 394: cmdp = cvs_findcmd(cvs_command);
! 395: if (cmdp == NULL) {
! 396: fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
! 397: fprintf(stderr, "CVS commands are:\n");
! 398: for (i = 0; i < (int)CVS_NBCMD; i++)
! 399: fprintf(stderr, "\t%-16s%s\n",
! 400: cvs_cdt[i].cmd_name, cvs_cdt[i].cmd_descr);
! 401: exit(EX_USAGE);
! 402: }
! 403:
! 404: if (cmdp->cmd_hdlr == NULL) {
! 405: cvs_log(LP_ERR, "command `%s' not implemented", cvs_command);
! 406: exit(1);
! 407: }
! 408:
! 409: cvs_cmdop = cmdp->cmd_op;
! 410:
! 411: cmd_argc = 0;
! 412: memset(cmd_argv, 0, sizeof(cmd_argv));
! 413:
! 414: cmd_argv[cmd_argc++] = argv[0];
! 415: if (cmdp->cmd_defargs != NULL) {
! 416: /* transform into a new argument vector */
! 417: ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
! 418: CVS_CMD_MAXARG - 1);
! 419: if (ret < 0) {
! 420: cvs_log(LP_ERRNO, "failed to generate argument vector "
! 421: "from default arguments");
! 422: exit(EX_DATAERR);
! 423: }
! 424: cmd_argc += ret;
! 425: }
! 426: for (ret = 1; ret < argc; ret++)
! 427: cmd_argv[cmd_argc++] = argv[ret];
! 428:
! 429: ret = (*cmdp->cmd_hdlr)(cmd_argc, cmd_argv);
! 430: if (ret == EX_USAGE) {
! 431: fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
! 432: cmdp->cmd_synopsis);
! 433: }
! 434:
! 435: if (cvs_files != NULL)
! 436: cvs_file_free(cvs_files);
! 437:
! 438: return (ret);
! 439: }
! 440:
! 441:
! 442: int
! 443: cvs_getopt(int argc, char **argv)
! 444: {
! 445: int ret;
! 446: char *ep;
! 447:
1.11 jfb 448: while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrtvz:")) != -1) {
1.1 jfb 449: switch (ret) {
1.11 jfb 450: case 'b':
451: /*
452: * We do not care about the bin directory for RCS files
453: * as this program has no dependencies on RCS programs,
454: * so it is only here for backwards compatibility.
455: */
456: cvs_log(LP_NOTICE, "the -b argument is obsolete");
457: break;
1.1 jfb 458: case 'd':
459: cvs_rootstr = optarg;
460: break;
461: case 'e':
462: cvs_editor = optarg;
463: break;
464: case 'f':
1.17 ! jfb 465: cvs_readrc = 0;
1.1 jfb 466: break;
467: case 'l':
468: cvs_nolog = 1;
469: break;
470: case 'n':
471: break;
472: case 'Q':
473: verbosity = 0;
474: break;
475: case 'q':
476: /* don't override -Q */
477: if (verbosity > 1)
478: verbosity = 1;
479: break;
480: case 'r':
481: cvs_readonly = 1;
482: break;
483: case 't':
484: cvs_trace = 1;
485: break;
486: case 'v':
487: printf("%s\n", CVS_VERSION);
488: exit(0);
489: /* NOTREACHED */
1.11 jfb 490: break;
491: case 'x':
492: /*
493: * Kerberos encryption support, kept for compatibility
494: */
1.1 jfb 495: break;
496: case 'z':
497: cvs_compress = (int)strtol(optarg, &ep, 10);
498: if (*ep != '\0')
499: errx(1, "error parsing compression level");
500: if (cvs_compress < 0 || cvs_compress > 9)
501: errx(1, "gzip compression level must be "
502: "between 0 and 9");
503: break;
504: default:
505: usage();
506: exit(EX_USAGE);
507: }
508: }
509:
1.17 ! jfb 510: ret = optind;
1.1 jfb 511: optind = 1;
1.17 ! jfb 512: optreset = 1; /* for next call */
1.12 jfb 513:
1.1 jfb 514: return (ret);
515: }
516:
517:
518: /*
519: * cvs_findcmd()
520: *
521: * Find the entry in the command dispatch table whose name or one of its
522: * aliases matches <cmd>.
523: * Returns a pointer to the command entry on success, NULL on failure.
524: */
525:
526: struct cvs_cmd*
527: cvs_findcmd(const char *cmd)
528: {
529: u_int i, j;
530: struct cvs_cmd *cmdp;
531:
532: cmdp = NULL;
533:
534: for (i = 0; (i < CVS_NBCMD) && (cmdp == NULL); i++) {
535: if (strcmp(cmd, cvs_cdt[i].cmd_name) == 0)
536: cmdp = &cvs_cdt[i];
537: else {
538: for (j = 0; j < CVS_CMD_MAXALIAS; j++) {
539: if (strcmp(cmd, cvs_cdt[i].cmd_alias[j]) == 0) {
540: cmdp = &cvs_cdt[i];
541: break;
542: }
543: }
544: }
545: }
546:
547: return (cmdp);
548: }
549:
550:
551: /*
1.17 ! jfb 552: * cvs_read_rcfile()
1.1 jfb 553: *
554: * Read the CVS `.cvsrc' file in the user's home directory. If the file
555: * exists, it should contain a list of arguments that should always be given
556: * implicitly to the specified commands.
557: */
558:
559: void
1.17 ! jfb 560: cvs_read_rcfile(void)
1.1 jfb 561: {
562: char rcpath[MAXPATHLEN], linebuf[128], *lp;
1.17 ! jfb 563: size_t len;
1.1 jfb 564: struct cvs_cmd *cmdp;
565: struct passwd *pw;
566: FILE *fp;
567:
568: pw = getpwuid(getuid());
569: if (pw == NULL) {
570: cvs_log(LP_NOTICE, "failed to get user's password entry");
571: return;
572: }
573:
574: snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
575:
576: fp = fopen(rcpath, "r");
577: if (fp == NULL) {
578: if (errno != ENOENT)
579: cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
580: strerror(errno));
581: return;
582: }
583:
584: while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
1.17 ! jfb 585: if ((len = strlen(linebuf)) == 0)
! 586: continue;
! 587: if (linebuf[len - 1] != '\n') {
! 588: cvs_log(LP_WARN, ".cvsrc line too long");
! 589: break;
! 590: }
! 591: linebuf[--len] = '\0';
! 592:
1.1 jfb 593: lp = strchr(linebuf, ' ');
594: if (lp == NULL)
1.17 ! jfb 595: continue; /* ignore lines with no arguments */
! 596: *lp = '\0';
1.1 jfb 597: if (strcmp(linebuf, "cvs") == 0) {
1.17 ! jfb 598: /*
! 599: * Global default options. In the case of cvs only,
! 600: * we keep the 'cvs' string as first argument because
! 601: * getopt() does not like starting at index 0 for
! 602: * argument processing.
! 603: */
! 604: *lp = ' ';
! 605: cvs_defargs = strdup(linebuf);
! 606: if (cvs_defargs == NULL)
! 607: cvs_log(LP_ERRNO,
! 608: "failed to copy global arguments");
1.15 deraadt 609: } else {
1.17 ! jfb 610: lp++;
1.1 jfb 611: cmdp = cvs_findcmd(linebuf);
612: if (cmdp == NULL) {
613: cvs_log(LP_NOTICE,
614: "unknown command `%s' in cvsrc",
615: linebuf);
616: continue;
617: }
1.17 ! jfb 618:
! 619: cmdp->cmd_defargs = strdup(lp);
! 620: if (cmdp->cmd_defargs == NULL)
! 621: cvs_log(LP_ERRNO,
! 622: "failed to copy default arguments for %s",
! 623: cmdp->cmd_name);
1.1 jfb 624: }
625: }
626: if (ferror(fp)) {
627: cvs_log(LP_NOTICE, "failed to read line from cvsrc");
628: }
629:
630: (void)fclose(fp);
631: }