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