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