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