Annotation of src/usr.bin/cvs/cvs.c, Revision 1.76
1.76 ! xsa 1: /* $OpenBSD: cvs.c,v 1.75 2005/07/25 12:05:43 xsa Exp $ */
1.1 jfb 2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
1.18 tedu 4: * All rights reserved.
1.1 jfb 5: *
1.18 tedu 6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
1.1 jfb 9: *
1.18 tedu 10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
1.1 jfb 12: * 2. The name of the author may not be used to endorse or promote products
1.18 tedu 13: * derived from this software without specific prior written permission.
1.1 jfb 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
1.18 tedu 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 jfb 25: */
26:
27: #include <sys/types.h>
28: #include <sys/wait.h>
29:
1.68 xsa 30: #include <ctype.h>
1.1 jfb 31: #include <err.h>
1.68 xsa 32: #include <errno.h>
1.1 jfb 33: #include <pwd.h>
1.68 xsa 34: #include <signal.h>
1.1 jfb 35: #include <stdio.h>
36: #include <stdlib.h>
1.68 xsa 37: #include <string.h>
1.1 jfb 38: #include <unistd.h>
39:
40: #include "cvs.h"
41: #include "log.h"
1.8 jfb 42: #include "file.h"
1.44 jfb 43: #include "strtab.h"
1.1 jfb 44:
45:
46: extern char *__progname;
47:
48:
49: /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
50: int verbosity = 2;
51:
52: /* compression level used with zlib, 0 meaning no compression taking place */
53: int cvs_compress = 0;
1.17 jfb 54: int cvs_readrc = 1; /* read .cvsrc on startup */
1.1 jfb 55: int cvs_trace = 0;
56: int cvs_nolog = 0;
57: int cvs_readonly = 0;
1.17 jfb 58: int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */
1.65 xsa 59: int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */
1.73 joris 60: int cvs_error = -1; /* set to the correct error code on failure */
1.17 jfb 61: char *cvs_defargs; /* default global arguments from .cvsrc */
62: char *cvs_command; /* name of the command we are running */
1.6 jfb 63: int cvs_cmdop;
1.1 jfb 64: char *cvs_rootstr;
65: char *cvs_rsh = CVS_RSH_DEFAULT;
66: char *cvs_editor = CVS_EDITOR_DEFAULT;
1.69 joris 67: char *cvs_repo_base = NULL;
1.13 krapht 68: char *cvs_msg = NULL;
69:
1.10 jfb 70: /* hierarchy of all the files affected by the command */
71: CVSFILE *cvs_files;
72:
1.27 jfb 73: static TAILQ_HEAD(, cvs_var) cvs_variables;
74:
1.1 jfb 75:
1.75 xsa 76: void usage(void);
77: static void cvs_read_rcfile(void);
78: int cvs_getopt(int, char **);
1.1 jfb 79:
80: /*
81: * usage()
82: *
83: * Display usage information.
84: */
85: void
86: usage(void)
87: {
88: fprintf(stderr,
1.66 joris 89: "Usage: %s [-flnQqtv] [-d root] [-e editor] [-s var=val] [-z level] "
1.27 jfb 90: "command [...]\n", __progname);
1.1 jfb 91: }
92:
93:
94: int
95: main(int argc, char **argv)
96: {
1.17 jfb 97: char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
98: int i, ret, cmd_argc;
1.1 jfb 99: struct cvs_cmd *cmdp;
100:
1.27 jfb 101: TAILQ_INIT(&cvs_variables);
102:
1.1 jfb 103: if (cvs_log_init(LD_STD, 0) < 0)
104: err(1, "failed to initialize logging");
105:
106: /* by default, be very verbose */
107: (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
108:
109: #ifdef DEBUG
110: (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
111: #endif
112:
1.44 jfb 113: cvs_strtab_init();
114:
1.1 jfb 115: /* check environment so command-line options override it */
116: if ((envstr = getenv("CVS_RSH")) != NULL)
117: cvs_rsh = envstr;
118:
119: if (((envstr = getenv("CVSEDITOR")) != NULL) ||
120: ((envstr = getenv("VISUAL")) != NULL) ||
121: ((envstr = getenv("EDITOR")) != NULL))
122: cvs_editor = envstr;
1.76 ! xsa 123:
! 124: if ((envstr = getenv("CVSREAD")) != NULL)
! 125: cvs_readonly = 1;
1.1 jfb 126:
1.17 jfb 127: ret = cvs_getopt(argc, argv);
128:
129: argc -= ret;
130: argv += ret;
131: if (argc == 0) {
132: usage();
1.49 joris 133: exit(1);
1.17 jfb 134: }
135: cvs_command = argv[0];
136:
1.74 xsa 137: if (cvs_readrc == 1) {
1.17 jfb 138: cvs_read_rcfile();
139:
140: if (cvs_defargs != NULL) {
141: targv = cvs_makeargv(cvs_defargs, &i);
142: if (targv == NULL) {
143: cvs_log(LP_ERR,
144: "failed to load default arguments to %s",
145: __progname);
1.47 xsa 146: exit(1);
1.17 jfb 147: }
148:
149: cvs_getopt(i, targv);
150: cvs_freeargv(targv, i);
151: free(targv);
152: }
153: }
154:
155: /* setup signal handlers */
156: signal(SIGPIPE, SIG_IGN);
157:
1.40 jfb 158: if (cvs_file_init() < 0) {
159: cvs_log(LP_ERR, "failed to initialize file support");
160: exit(1);
161: }
1.17 jfb 162:
163: ret = -1;
164:
165: cmdp = cvs_findcmd(cvs_command);
166: if (cmdp == NULL) {
167: fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
168: fprintf(stderr, "CVS commands are:\n");
1.67 jfb 169: for (i = 0; cvs_cdt[i] != NULL; i++)
1.17 jfb 170: fprintf(stderr, "\t%-16s%s\n",
1.67 jfb 171: cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
172: exit(CVS_EX_USAGE);
1.17 jfb 173: }
174:
175: cvs_cmdop = cmdp->cmd_op;
176:
177: cmd_argc = 0;
178: memset(cmd_argv, 0, sizeof(cmd_argv));
179:
180: cmd_argv[cmd_argc++] = argv[0];
181: if (cmdp->cmd_defargs != NULL) {
182: /* transform into a new argument vector */
183: ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
184: CVS_CMD_MAXARG - 1);
185: if (ret < 0) {
186: cvs_log(LP_ERRNO, "failed to generate argument vector "
187: "from default arguments");
1.47 xsa 188: exit(1);
1.17 jfb 189: }
190: cmd_argc += ret;
191: }
192: for (ret = 1; ret < argc; ret++)
193: cmd_argv[cmd_argc++] = argv[ret];
194:
1.45 joris 195: ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
1.51 joris 196: switch (ret) {
197: case CVS_EX_USAGE:
1.56 jfb 198: fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
199: cmdp->cmd_synopsis);
1.51 joris 200: break;
201: case CVS_EX_DATA:
1.55 jfb 202: cvs_log(LP_ABORT, "internal data error");
1.51 joris 203: break;
204: case CVS_EX_PROTO:
1.55 jfb 205: cvs_log(LP_ABORT, "protocol error");
1.51 joris 206: break;
207: case CVS_EX_FILE:
1.55 jfb 208: cvs_log(LP_ABORT, "an operation on a file or directory failed");
1.60 jfb 209: break;
210: case CVS_EX_BADROOT:
1.71 xsa 211: /* match GNU CVS output, thus the LP_ERR and LP_ABORT codes. */
212: cvs_log(LP_ERR,
1.60 jfb 213: "No CVSROOT specified! Please use the `-d' option");
214: cvs_log(LP_ABORT,
215: "or set the CVSROOT enviroment variable.");
1.73 joris 216: break;
217: case CVS_EX_ERR:
218: cvs_log(LP_ABORT, "yeah, we failed, and we don't know why");
1.51 joris 219: break;
220: default:
221: break;
1.17 jfb 222: }
223:
224: if (cvs_files != NULL)
225: cvs_file_free(cvs_files);
1.33 jfb 226: if (cvs_msg != NULL)
227: free(cvs_msg);
1.44 jfb 228:
229: cvs_strtab_cleanup();
1.17 jfb 230:
231: return (ret);
232: }
233:
234:
235: int
236: cvs_getopt(int argc, char **argv)
237: {
238: int ret;
239: char *ep;
240:
1.27 jfb 241: while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) {
1.1 jfb 242: switch (ret) {
1.11 jfb 243: case 'b':
244: /*
245: * We do not care about the bin directory for RCS files
246: * as this program has no dependencies on RCS programs,
247: * so it is only here for backwards compatibility.
248: */
249: cvs_log(LP_NOTICE, "the -b argument is obsolete");
250: break;
1.1 jfb 251: case 'd':
252: cvs_rootstr = optarg;
253: break;
254: case 'e':
255: cvs_editor = optarg;
256: break;
257: case 'f':
1.17 jfb 258: cvs_readrc = 0;
1.1 jfb 259: break;
260: case 'l':
261: cvs_nolog = 1;
262: break;
263: case 'n':
1.65 xsa 264: cvs_noexec = 1;
1.1 jfb 265: break;
266: case 'Q':
267: verbosity = 0;
268: break;
269: case 'q':
270: /* don't override -Q */
271: if (verbosity > 1)
272: verbosity = 1;
273: break;
274: case 'r':
275: cvs_readonly = 1;
276: break;
1.27 jfb 277: case 's':
278: ep = strchr(optarg, '=');
279: if (ep == NULL) {
280: cvs_log(LP_ERR, "no = in variable assignment");
1.49 joris 281: exit(1);
1.27 jfb 282: }
283: *(ep++) = '\0';
284: if (cvs_var_set(optarg, ep) < 0)
1.49 joris 285: exit(1);
1.27 jfb 286: break;
1.1 jfb 287: case 't':
1.24 jfb 288: (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
1.1 jfb 289: cvs_trace = 1;
290: break;
291: case 'v':
292: printf("%s\n", CVS_VERSION);
293: exit(0);
294: /* NOTREACHED */
1.11 jfb 295: break;
296: case 'x':
297: /*
298: * Kerberos encryption support, kept for compatibility
299: */
1.1 jfb 300: break;
301: case 'z':
1.18 tedu 302: cvs_compress = (int)strtol(optarg, &ep, 10);
1.1 jfb 303: if (*ep != '\0')
304: errx(1, "error parsing compression level");
305: if (cvs_compress < 0 || cvs_compress > 9)
306: errx(1, "gzip compression level must be "
307: "between 0 and 9");
308: break;
309: default:
310: usage();
1.49 joris 311: exit(1);
1.1 jfb 312: }
313: }
314:
1.17 jfb 315: ret = optind;
1.1 jfb 316: optind = 1;
1.17 jfb 317: optreset = 1; /* for next call */
1.12 jfb 318:
1.1 jfb 319: return (ret);
320: }
321:
322:
323: /*
1.17 jfb 324: * cvs_read_rcfile()
1.1 jfb 325: *
326: * Read the CVS `.cvsrc' file in the user's home directory. If the file
327: * exists, it should contain a list of arguments that should always be given
328: * implicitly to the specified commands.
329: */
1.42 joris 330: static void
1.17 jfb 331: cvs_read_rcfile(void)
1.1 jfb 332: {
1.70 joris 333: char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
1.54 xsa 334: int l, linenum = 0;
1.17 jfb 335: size_t len;
1.1 jfb 336: struct cvs_cmd *cmdp;
337: struct passwd *pw;
338: FILE *fp;
339:
340: pw = getpwuid(getuid());
341: if (pw == NULL) {
342: cvs_log(LP_NOTICE, "failed to get user's password entry");
343: return;
344: }
345:
1.54 xsa 346: l = snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
347: if (l == -1 || l >= (int)sizeof(rcpath)) {
348: errno = ENAMETOOLONG;
349: cvs_log(LP_ERRNO, "%s", rcpath);
350: return;
351: }
1.1 jfb 352:
353: fp = fopen(rcpath, "r");
354: if (fp == NULL) {
355: if (errno != ENOENT)
356: cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
357: strerror(errno));
358: return;
359: }
360:
361: while (fgets(linebuf, sizeof(linebuf), fp) != NULL) {
1.23 xsa 362: linenum++;
1.17 jfb 363: if ((len = strlen(linebuf)) == 0)
364: continue;
365: if (linebuf[len - 1] != '\n') {
1.23 xsa 366: cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
367: linenum);
1.17 jfb 368: break;
369: }
370: linebuf[--len] = '\0';
371:
1.70 joris 372: /* skip any whitespaces */
373: p = linebuf;
374: while (*p == ' ')
375: *p++;
376:
377: /* allow comments */
378: if (*p == '#')
379: continue;
380:
381: lp = strchr(p, ' ');
1.1 jfb 382: if (lp == NULL)
1.17 jfb 383: continue; /* ignore lines with no arguments */
384: *lp = '\0';
1.70 joris 385: if (strcmp(p, "cvs") == 0) {
1.17 jfb 386: /*
387: * Global default options. In the case of cvs only,
388: * we keep the 'cvs' string as first argument because
389: * getopt() does not like starting at index 0 for
390: * argument processing.
391: */
392: *lp = ' ';
1.70 joris 393: cvs_defargs = strdup(p);
1.17 jfb 394: if (cvs_defargs == NULL)
395: cvs_log(LP_ERRNO,
396: "failed to copy global arguments");
1.15 deraadt 397: } else {
1.17 jfb 398: lp++;
1.70 joris 399: cmdp = cvs_findcmd(p);
1.1 jfb 400: if (cmdp == NULL) {
401: cvs_log(LP_NOTICE,
1.25 xsa 402: "unknown command `%s' in `%s:%d'",
1.70 joris 403: p, rcpath, linenum);
1.1 jfb 404: continue;
405: }
1.17 jfb 406:
407: cmdp->cmd_defargs = strdup(lp);
408: if (cmdp->cmd_defargs == NULL)
409: cvs_log(LP_ERRNO,
410: "failed to copy default arguments for %s",
411: cmdp->cmd_name);
1.1 jfb 412: }
413: }
414: if (ferror(fp)) {
1.23 xsa 415: cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
1.1 jfb 416: }
417:
418: (void)fclose(fp);
1.27 jfb 419: }
420:
421:
422: /*
423: * cvs_var_set()
424: *
425: * Set the value of the variable <var> to <val>. If there is no such variable,
426: * a new entry is created, otherwise the old value is overwritten.
427: * Returns 0 on success, or -1 on failure.
428: */
429: int
430: cvs_var_set(const char *var, const char *val)
431: {
432: char *valcp;
433: const char *cp;
434: struct cvs_var *vp;
435:
436: if ((var == NULL) || (*var == '\0')) {
437: cvs_log(LP_ERR, "no variable name");
438: return (-1);
439: }
440:
441: /* sanity check on the name */
442: for (cp = var; *cp != '\0'; cp++)
443: if (!isalnum(*cp) && (*cp != '_')) {
444: cvs_log(LP_ERR,
445: "variable name `%s' contains invalid characters",
446: var);
447: return (-1);
448: }
449:
450: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
451: if (strcmp(vp->cv_name, var) == 0)
452: break;
453:
454: valcp = strdup(val);
455: if (valcp == NULL) {
456: cvs_log(LP_ERRNO, "failed to allocate variable");
457: return (-1);
458: }
459:
460: if (vp == NULL) {
461: vp = (struct cvs_var *)malloc(sizeof(*vp));
462: if (vp == NULL) {
463: cvs_log(LP_ERRNO, "failed to allocate variable");
464: free(valcp);
465: return (-1);
466: }
467: memset(vp, 0, sizeof(*vp));
468:
469: vp->cv_name = strdup(var);
470: if (vp->cv_name == NULL) {
471: cvs_log(LP_ERRNO, "failed to allocate variable");
472: free(valcp);
473: free(vp);
474: return (-1);
475: }
476:
477: TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
478:
479: } else /* free the previous value */
480: free(vp->cv_val);
481:
482: vp->cv_val = valcp;
483:
484: return (0);
485: }
486:
487:
488: /*
489: * cvs_var_set()
490: *
491: * Remove any entry for the variable <var>.
492: * Returns 0 on success, or -1 on failure.
493: */
494: int
495: cvs_var_unset(const char *var)
496: {
497: struct cvs_var *vp;
498:
499: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
500: if (strcmp(vp->cv_name, var) == 0) {
501: TAILQ_REMOVE(&cvs_variables, vp, cv_link);
502: free(vp->cv_name);
503: free(vp->cv_val);
504: free(vp);
505: return (0);
506: }
507:
508: return (-1);
509:
510: }
511:
512:
513: /*
514: * cvs_var_get()
515: *
516: * Get the value associated with the variable <var>. Returns a pointer to the
517: * value string on success, or NULL if the variable does not exist.
518: */
519:
1.75 xsa 520: const char *
1.27 jfb 521: cvs_var_get(const char *var)
522: {
523: struct cvs_var *vp;
524:
525: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
526: if (strcmp(vp->cv_name, var) == 0)
527: return (vp->cv_val);
528:
529: return (NULL);
1.1 jfb 530: }