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