Annotation of src/usr.bin/cvs/cvs.c, Revision 1.92
1.92 ! xsa 1: /* $OpenBSD: cvs.c,v 1.91 2006/01/02 17:06:10 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: */
143: if (stat(cvs_tmpdir, &st) == -1) {
144: cvs_log(LP_ERR, "failed to stat `%s'", cvs_tmpdir);
1.82 xsa 145: exit(CVS_EX_FILE);
1.80 xsa 146: } else if (!S_ISDIR(st.st_mode)) {
147: cvs_log(LP_ERR, "`%s' is not valid temporary directory",
148: cvs_tmpdir);
1.82 xsa 149: exit(CVS_EX_FILE);
1.80 xsa 150: }
151:
1.74 xsa 152: if (cvs_readrc == 1) {
1.17 jfb 153: cvs_read_rcfile();
154:
155: if (cvs_defargs != NULL) {
156: targv = cvs_makeargv(cvs_defargs, &i);
157: if (targv == NULL) {
158: cvs_log(LP_ERR,
159: "failed to load default arguments to %s",
160: __progname);
1.82 xsa 161: exit(CVS_EX_DATA);
1.17 jfb 162: }
163:
164: cvs_getopt(i, targv);
165: cvs_freeargv(targv, i);
1.88 joris 166: xfree(targv);
1.17 jfb 167: }
168: }
169:
170: /* setup signal handlers */
171: signal(SIGPIPE, SIG_IGN);
172:
1.40 jfb 173: if (cvs_file_init() < 0) {
174: cvs_log(LP_ERR, "failed to initialize file support");
1.82 xsa 175: exit(CVS_EX_FILE);
1.40 jfb 176: }
1.17 jfb 177:
178: ret = -1;
179:
180: cmdp = cvs_findcmd(cvs_command);
181: if (cmdp == NULL) {
182: fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
183: fprintf(stderr, "CVS commands are:\n");
1.67 jfb 184: for (i = 0; cvs_cdt[i] != NULL; i++)
1.17 jfb 185: fprintf(stderr, "\t%-16s%s\n",
1.67 jfb 186: cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
187: exit(CVS_EX_USAGE);
1.17 jfb 188: }
189:
190: cvs_cmdop = cmdp->cmd_op;
191:
192: cmd_argc = 0;
193: memset(cmd_argv, 0, sizeof(cmd_argv));
194:
195: cmd_argv[cmd_argc++] = argv[0];
196: if (cmdp->cmd_defargs != NULL) {
197: /* transform into a new argument vector */
198: ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
199: CVS_CMD_MAXARG - 1);
200: if (ret < 0) {
201: cvs_log(LP_ERRNO, "failed to generate argument vector "
202: "from default arguments");
1.82 xsa 203: exit(CVS_EX_DATA);
1.17 jfb 204: }
205: cmd_argc += ret;
206: }
207: for (ret = 1; ret < argc; ret++)
208: cmd_argv[cmd_argc++] = argv[ret];
209:
1.45 joris 210: ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
1.51 joris 211: switch (ret) {
212: case CVS_EX_USAGE:
1.92 ! xsa 213: fprintf(stderr, "Usage: %s %s %s\n", __progname,
! 214: cmdp->cmd_name, cmdp->cmd_synopsis);
1.51 joris 215: break;
216: case CVS_EX_DATA:
1.55 jfb 217: cvs_log(LP_ABORT, "internal data error");
1.51 joris 218: break;
219: case CVS_EX_PROTO:
1.55 jfb 220: cvs_log(LP_ABORT, "protocol error");
1.51 joris 221: break;
222: case CVS_EX_FILE:
1.55 jfb 223: cvs_log(LP_ABORT, "an operation on a file or directory failed");
1.60 jfb 224: break;
225: case CVS_EX_BADROOT:
1.71 xsa 226: /* match GNU CVS output, thus the LP_ERR and LP_ABORT codes. */
227: cvs_log(LP_ERR,
1.60 jfb 228: "No CVSROOT specified! Please use the `-d' option");
229: cvs_log(LP_ABORT,
230: "or set the CVSROOT enviroment variable.");
1.73 joris 231: break;
232: case CVS_EX_ERR:
233: cvs_log(LP_ABORT, "yeah, we failed, and we don't know why");
1.51 joris 234: break;
235: default:
236: break;
1.17 jfb 237: }
238:
239: if (cvs_files != NULL)
240: cvs_file_free(cvs_files);
1.33 jfb 241: if (cvs_msg != NULL)
1.88 joris 242: xfree(cvs_msg);
1.17 jfb 243:
244: return (ret);
245: }
246:
247:
248: int
249: cvs_getopt(int argc, char **argv)
250: {
251: int ret;
252: char *ep;
253:
1.83 xsa 254: while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:T:tvwz:")) != -1) {
1.1 jfb 255: switch (ret) {
1.11 jfb 256: case 'b':
257: /*
258: * We do not care about the bin directory for RCS files
259: * as this program has no dependencies on RCS programs,
260: * so it is only here for backwards compatibility.
261: */
262: cvs_log(LP_NOTICE, "the -b argument is obsolete");
263: break;
1.1 jfb 264: case 'd':
265: cvs_rootstr = optarg;
266: break;
267: case 'e':
268: cvs_editor = optarg;
269: break;
270: case 'f':
1.17 jfb 271: cvs_readrc = 0;
1.1 jfb 272: break;
273: case 'l':
274: cvs_nolog = 1;
275: break;
276: case 'n':
1.65 xsa 277: cvs_noexec = 1;
1.1 jfb 278: break;
279: case 'Q':
280: verbosity = 0;
281: break;
282: case 'q':
283: /* don't override -Q */
284: if (verbosity > 1)
285: verbosity = 1;
286: break;
287: case 'r':
288: cvs_readonly = 1;
289: break;
1.27 jfb 290: case 's':
291: ep = strchr(optarg, '=');
292: if (ep == NULL) {
293: cvs_log(LP_ERR, "no = in variable assignment");
1.82 xsa 294: exit(CVS_EX_USAGE);
1.27 jfb 295: }
296: *(ep++) = '\0';
297: if (cvs_var_set(optarg, ep) < 0)
1.82 xsa 298: exit(CVS_EX_USAGE);
1.80 xsa 299: break;
300: case 'T':
301: cvs_tmpdir = optarg;
1.27 jfb 302: break;
1.1 jfb 303: case 't':
1.24 jfb 304: (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
1.1 jfb 305: cvs_trace = 1;
306: break;
307: case 'v':
308: printf("%s\n", CVS_VERSION);
309: exit(0);
310: /* NOTREACHED */
1.83 xsa 311: break;
312: case 'w':
313: cvs_readonly = 0;
1.11 jfb 314: break;
315: case 'x':
316: /*
317: * Kerberos encryption support, kept for compatibility
318: */
1.1 jfb 319: break;
320: case 'z':
1.18 tedu 321: cvs_compress = (int)strtol(optarg, &ep, 10);
1.1 jfb 322: if (*ep != '\0')
1.91 xsa 323: fatal("error parsing compression level");
1.1 jfb 324: if (cvs_compress < 0 || cvs_compress > 9)
1.91 xsa 325: fatal("gzip compression level must be "
1.1 jfb 326: "between 0 and 9");
327: break;
328: default:
329: usage();
1.82 xsa 330: exit(CVS_EX_USAGE);
1.1 jfb 331: }
332: }
333:
1.17 jfb 334: ret = optind;
1.1 jfb 335: optind = 1;
1.17 jfb 336: optreset = 1; /* for next call */
1.12 jfb 337:
1.1 jfb 338: return (ret);
339: }
340:
341:
342: /*
1.17 jfb 343: * cvs_read_rcfile()
1.1 jfb 344: *
345: * Read the CVS `.cvsrc' file in the user's home directory. If the file
346: * exists, it should contain a list of arguments that should always be given
347: * implicitly to the specified commands.
348: */
1.42 joris 349: static void
1.17 jfb 350: cvs_read_rcfile(void)
1.1 jfb 351: {
1.79 xsa 352: char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
1.54 xsa 353: int l, linenum = 0;
1.17 jfb 354: size_t len;
1.1 jfb 355: struct cvs_cmd *cmdp;
356: FILE *fp;
357:
1.79 xsa 358: l = snprintf(rcpath, sizeof(rcpath), "%s/%s", cvs_homedir, CVS_PATH_RC);
1.54 xsa 359: if (l == -1 || l >= (int)sizeof(rcpath)) {
360: errno = ENAMETOOLONG;
361: cvs_log(LP_ERRNO, "%s", rcpath);
362: return;
363: }
1.1 jfb 364:
365: fp = fopen(rcpath, "r");
366: if (fp == NULL) {
367: if (errno != ENOENT)
368: cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
369: strerror(errno));
370: return;
371: }
372:
1.84 xsa 373: while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
1.23 xsa 374: linenum++;
1.17 jfb 375: if ((len = strlen(linebuf)) == 0)
376: continue;
377: if (linebuf[len - 1] != '\n') {
1.23 xsa 378: cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
379: linenum);
1.17 jfb 380: break;
381: }
382: linebuf[--len] = '\0';
383:
1.70 joris 384: /* skip any whitespaces */
385: p = linebuf;
386: while (*p == ' ')
387: *p++;
388:
389: /* allow comments */
390: if (*p == '#')
391: continue;
392:
393: lp = strchr(p, ' ');
1.1 jfb 394: if (lp == NULL)
1.17 jfb 395: continue; /* ignore lines with no arguments */
396: *lp = '\0';
1.70 joris 397: if (strcmp(p, "cvs") == 0) {
1.17 jfb 398: /*
399: * Global default options. In the case of cvs only,
400: * we keep the 'cvs' string as first argument because
401: * getopt() does not like starting at index 0 for
402: * argument processing.
403: */
404: *lp = ' ';
1.88 joris 405: cvs_defargs = xstrdup(p);
1.15 deraadt 406: } else {
1.17 jfb 407: lp++;
1.70 joris 408: cmdp = cvs_findcmd(p);
1.1 jfb 409: if (cmdp == NULL) {
410: cvs_log(LP_NOTICE,
1.25 xsa 411: "unknown command `%s' in `%s:%d'",
1.70 joris 412: p, rcpath, linenum);
1.1 jfb 413: continue;
414: }
1.17 jfb 415:
1.88 joris 416: cmdp->cmd_defargs = xstrdup(lp);
1.1 jfb 417: }
418: }
419: if (ferror(fp)) {
1.23 xsa 420: cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
1.1 jfb 421: }
422:
423: (void)fclose(fp);
1.27 jfb 424: }
425:
426:
427: /*
428: * cvs_var_set()
429: *
430: * Set the value of the variable <var> to <val>. If there is no such variable,
431: * a new entry is created, otherwise the old value is overwritten.
432: * Returns 0 on success, or -1 on failure.
433: */
434: int
435: cvs_var_set(const char *var, const char *val)
436: {
437: char *valcp;
438: const char *cp;
439: struct cvs_var *vp;
440:
441: if ((var == NULL) || (*var == '\0')) {
442: cvs_log(LP_ERR, "no variable name");
443: return (-1);
444: }
445:
446: /* sanity check on the name */
447: for (cp = var; *cp != '\0'; cp++)
448: if (!isalnum(*cp) && (*cp != '_')) {
449: cvs_log(LP_ERR,
450: "variable name `%s' contains invalid characters",
451: var);
452: return (-1);
453: }
454:
455: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
456: if (strcmp(vp->cv_name, var) == 0)
457: break;
458:
1.88 joris 459: valcp = xstrdup(val);
1.27 jfb 460: if (vp == NULL) {
1.88 joris 461: vp = (struct cvs_var *)xmalloc(sizeof(*vp));
1.27 jfb 462: memset(vp, 0, sizeof(*vp));
463:
1.88 joris 464: vp->cv_name = xstrdup(var);
1.27 jfb 465: TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
466:
467: } else /* free the previous value */
1.88 joris 468: xfree(vp->cv_val);
1.27 jfb 469:
470: vp->cv_val = valcp;
471:
472: return (0);
473: }
474:
475:
476: /*
477: * cvs_var_set()
478: *
479: * Remove any entry for the variable <var>.
480: * Returns 0 on success, or -1 on failure.
481: */
482: int
483: cvs_var_unset(const char *var)
484: {
485: struct cvs_var *vp;
486:
487: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
488: if (strcmp(vp->cv_name, var) == 0) {
489: TAILQ_REMOVE(&cvs_variables, vp, cv_link);
1.88 joris 490: xfree(vp->cv_name);
491: xfree(vp->cv_val);
492: xfree(vp);
1.27 jfb 493: return (0);
494: }
495:
496: return (-1);
497:
498: }
499:
500:
501: /*
502: * cvs_var_get()
503: *
504: * Get the value associated with the variable <var>. Returns a pointer to the
505: * value string on success, or NULL if the variable does not exist.
506: */
507:
1.75 xsa 508: const char *
1.27 jfb 509: cvs_var_get(const char *var)
510: {
511: struct cvs_var *vp;
512:
513: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
514: if (strcmp(vp->cv_name, var) == 0)
515: return (vp->cv_val);
516:
517: return (NULL);
1.1 jfb 518: }