Annotation of src/usr.bin/cvs/cvs.c, Revision 1.7
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"
42:
43:
44: extern char *__progname;
45:
46:
47: /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
48: int verbosity = 2;
49:
50:
51:
52: /* compression level used with zlib, 0 meaning no compression taking place */
53: int cvs_compress = 0;
54: int cvs_trace = 0;
55: int cvs_nolog = 0;
56: int cvs_readonly = 0;
57:
58: /* name of the command we are running */
59: char *cvs_command;
1.6 jfb 60: int cvs_cmdop;
1.1 jfb 61: char *cvs_rootstr;
62: char *cvs_rsh = CVS_RSH_DEFAULT;
63: char *cvs_editor = CVS_EDITOR_DEFAULT;
64:
65: struct 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:
81: static struct cvs_cmd {
1.6 jfb 82: int cmd_op;
1.1 jfb 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: {
1.6 jfb 90: CVS_OP_ADD, "add", { "ad", "new" }, cvs_add,
1.1 jfb 91: "[-m msg] file ...",
92: "Add a new file/directory to the repository",
93: },
94: {
1.6 jfb 95: -1, "admin", { "adm", "rcs" }, NULL,
1.1 jfb 96: "",
97: "Administration front end for rcs",
98: },
99: {
1.6 jfb 100: CVS_OP_ANNOTATE, "annotate", { "ann" }, NULL,
1.1 jfb 101: "",
102: "Show last revision where each line was modified",
103: },
104: {
1.6 jfb 105: CVS_OP_CHECKOUT, "checkout", { "co", "get" }, cvs_checkout,
1.1 jfb 106: "",
107: "Checkout sources for editing",
108: },
109: {
1.6 jfb 110: CVS_OP_COMMIT, "commit", { "ci", "com" }, cvs_commit,
1.4 jfb 111: "[-flR] [-F logfile | -m msg] [-r rev] ...",
1.1 jfb 112: "Check files into the repository",
113: },
114: {
1.6 jfb 115: CVS_OP_DIFF, "diff", { "di", "dif" }, cvs_diff,
1.3 jfb 116: "[-cilu] [-D date] [-r rev] ...",
1.1 jfb 117: "Show differences between revisions",
118: },
119: {
1.6 jfb 120: -1, "edit", { }, NULL,
1.1 jfb 121: "",
122: "Get ready to edit a watched file",
123: },
124: {
1.6 jfb 125: -1, "editors", { }, NULL,
1.1 jfb 126: "",
127: "See who is editing a watched file",
128: },
129: {
1.6 jfb 130: -1, "export", { "ex", "exp" }, NULL,
1.1 jfb 131: "",
132: "Export sources from CVS, similar to checkout",
133: },
134: {
1.6 jfb 135: CVS_OP_HISTORY, "history", { "hi", "his" }, cvs_history,
1.1 jfb 136: "",
137: "Show repository access history",
138: },
139: {
1.6 jfb 140: CVS_OP_IMPORT, "import", { "im", "imp" }, NULL,
1.1 jfb 141: "",
142: "Import sources into CVS, using vendor branches",
143: },
144: {
1.6 jfb 145: CVS_OP_INIT, "init", { }, cvs_init,
1.1 jfb 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: {
1.6 jfb 157: CVS_OP_LOG, "log", { "lo" }, cvs_getlog,
1.1 jfb 158: "",
159: "Print out history information for files",
160: },
161: {
1.6 jfb 162: -1, "login", {}, NULL,
1.1 jfb 163: "",
164: "Prompt for password for authenticating server",
165: },
166: {
1.6 jfb 167: -1, "logout", {}, NULL,
1.1 jfb 168: "",
169: "Removes entry in .cvspass for remote repository",
170: },
171: {
1.6 jfb 172: -1, "rdiff", {}, NULL,
1.1 jfb 173: "",
174: "Create 'patch' format diffs between releases",
175: },
176: {
1.6 jfb 177: -1, "release", {}, NULL,
1.1 jfb 178: "",
179: "Indicate that a Module is no longer in use",
180: },
181: {
1.6 jfb 182: CVS_OP_REMOVE, "remove", {}, NULL,
1.1 jfb 183: "",
184: "Remove an entry from the repository",
185: },
186: {
1.6 jfb 187: -1, "rlog", {}, NULL,
1.1 jfb 188: "",
189: "Print out history information for a module",
190: },
191: {
1.6 jfb 192: -1, "rtag", {}, NULL,
1.1 jfb 193: "",
194: "Add a symbolic tag to a module",
195: },
196: {
1.6 jfb 197: CVS_OP_SERVER, "server", {}, cvs_server,
1.1 jfb 198: "",
199: "Server mode",
200: },
201: {
1.7 ! jfb 202: CVS_OP_STATUS, "status", {}, cvs_status,
1.1 jfb 203: "",
204: "Display status information on checked out files",
205: },
206: {
1.6 jfb 207: CVS_OP_TAG, "tag", { "ta", }, NULL,
1.1 jfb 208: "",
209: "Add a symbolic tag to checked out version of files",
210: },
211: {
1.6 jfb 212: -1, "unedit", {}, NULL,
1.1 jfb 213: "",
214: "Undo an edit command",
215: },
216: {
1.6 jfb 217: CVS_OP_UPDATE, "update", {}, cvs_update,
1.1 jfb 218: "",
219: "Bring work tree in sync with repository",
220: },
221: {
1.6 jfb 222: CVS_OP_VERSION, "version", {}, cvs_version,
1.1 jfb 223: "",
224: "Show current CVS version(s)",
225: },
226: {
1.6 jfb 227: -1, "watch", {}, NULL,
1.1 jfb 228: "",
229: "Set watches",
230: },
231: {
1.6 jfb 232: -1, "watchers", {}, NULL,
1.1 jfb 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:
242: void usage (void);
243: void sigchld_hdlr (int);
244: void cvs_readrc (void);
245: struct 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:
256: void
257: sigchld_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:
273: void
274: usage(void)
275: {
276: fprintf(stderr,
277: "Usage: %s [-lQqtv] [-d root] [-e editor] [-z level] "
278: "command [options] ...\n",
279: __progname);
280: }
281:
282:
283: int
284: main(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:
1.2 jfb 376: cvs_file_init();
377:
1.1 jfb 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: }
1.6 jfb 398:
399: cvs_cmdop = cmdp->cmd_op;
1.1 jfb 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:
419: struct cvs_cmd*
420: cvs_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:
452: void
453: cvs_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: }