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