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