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