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