Annotation of src/usr.bin/cvs/cvs.c, Revision 1.118
1.118 ! otto 1: /* $OpenBSD: cvs.c,v 1.117 2007/02/22 06:42:09 otto Exp $ */
1.1 jfb 2: /*
1.113 joris 3: * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org>
1.1 jfb 4: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
1.18 tedu 5: * All rights reserved.
1.1 jfb 6: *
1.18 tedu 7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
1.1 jfb 10: *
1.18 tedu 11: * 1. Redistributions of source code must retain the above copyright
12: * notice, this list of conditions and the following disclaimer.
1.1 jfb 13: * 2. The name of the author may not be used to endorse or promote products
1.18 tedu 14: * derived from this software without specific prior written permission.
1.1 jfb 15: *
16: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
1.18 tedu 25: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 jfb 26: */
27:
1.117 otto 28: #include <sys/stat.h>
29:
30: #include <ctype.h>
31: #include <errno.h>
32: #include <pwd.h>
33: #include <stdlib.h>
34: #include <string.h>
35: #include <unistd.h>
1.1 jfb 36:
37: #include "cvs.h"
1.106 joris 38: #include "remote.h"
1.1 jfb 39:
40: extern char *__progname;
41:
42: /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
1.105 reyk 43: int verbosity = 1;
1.1 jfb 44:
45: /* compression level used with zlib, 0 meaning no compression taking place */
1.98 joris 46: int cvs_compress = 0;
47: int cvs_readrc = 1; /* read .cvsrc on startup */
48: int cvs_trace = 0;
49: int cvs_nolog = 0;
50: int cvs_readonly = 0;
1.111 xsa 51: int cvs_readonlyfs = 0;
1.98 joris 52: int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */
53: int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */
54: int cvs_error = -1; /* set to the correct error code on failure */
55: int cvs_cmdop;
1.99 joris 56: int cvs_umask = CVS_UMASK_DEFAULT;
1.106 joris 57: int cvs_server_active = 0;
1.98 joris 58:
1.99 joris 59: char *cvs_tagname = NULL;
1.98 joris 60: char *cvs_defargs; /* default global arguments from .cvsrc */
61: char *cvs_command; /* name of the command we are running */
62: char *cvs_rootstr;
63: char *cvs_rsh = CVS_RSH_DEFAULT;
64: char *cvs_editor = CVS_EDITOR_DEFAULT;
65: char *cvs_homedir = NULL;
66: char *cvs_msg = NULL;
67: char *cvs_tmpdir = CVS_TMPDIR_DEFAULT;
1.13 krapht 68:
1.98 joris 69: struct cvsroot *current_cvsroot = NULL;
1.27 jfb 70:
1.98 joris 71: int cvs_getopt(int, char **);
1.75 xsa 72: void usage(void);
73: static void cvs_read_rcfile(void);
1.1 jfb 74:
1.98 joris 75: struct cvs_wklhead temp_files;
76:
77: void sighandler(int);
78: volatile sig_atomic_t cvs_quit = 0;
79: volatile sig_atomic_t sig_received = 0;
80:
81: void
82: sighandler(int sig)
83: {
84: sig_received = sig;
85:
86: switch (sig) {
87: case SIGINT:
88: case SIGTERM:
1.107 joris 89: case SIGPIPE:
1.98 joris 90: cvs_quit = 1;
91: break;
92: default:
93: break;
94: }
95: }
96:
97: void
98: cvs_cleanup(void)
99: {
100: cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
101: cvs_worklist_run(&repo_locks, cvs_worklist_unlink);
102:
103: cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
104: cvs_worklist_run(&temp_files, cvs_worklist_unlink);
1.106 joris 105:
106: if (cvs_server_active) {
107: if (cvs_rmdir(cvs_server_path) == -1)
108: cvs_log(LP_ERR,
109: "warning: failed to remove server directory: %s",
110: cvs_server_path);
111: xfree(cvs_server_path);
112: }
1.98 joris 113: }
114:
1.1 jfb 115: void
116: usage(void)
117: {
118: fprintf(stderr,
1.114 jmc 119: "Usage: %s [-flnQqRrtVvw] [-d root] [-e editor] [-s var=val] "
1.81 xsa 120: "[-T tmpdir] [-z level] command [...]\n", __progname);
1.1 jfb 121: }
122:
123: int
124: main(int argc, char **argv)
125: {
1.17 jfb 126: char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
127: int i, ret, cmd_argc;
1.1 jfb 128: struct cvs_cmd *cmdp;
1.79 xsa 129: struct passwd *pw;
1.80 xsa 130: struct stat st;
1.100 joris 131: char fpath[MAXPATHLEN];
1.106 joris 132: char *root, *rootp;
1.87 joris 133:
134: tzset();
1.1 jfb 135:
1.27 jfb 136: TAILQ_INIT(&cvs_variables);
1.98 joris 137: SLIST_INIT(&repo_locks);
138: SLIST_INIT(&temp_files);
1.1 jfb 139:
140: /* check environment so command-line options override it */
141: if ((envstr = getenv("CVS_RSH")) != NULL)
142: cvs_rsh = envstr;
143:
144: if (((envstr = getenv("CVSEDITOR")) != NULL) ||
145: ((envstr = getenv("VISUAL")) != NULL) ||
146: ((envstr = getenv("EDITOR")) != NULL))
147: cvs_editor = envstr;
1.76 xsa 148:
149: if ((envstr = getenv("CVSREAD")) != NULL)
150: cvs_readonly = 1;
1.1 jfb 151:
1.111 xsa 152: if ((envstr = getenv("CVSREADONLYFS")) != NULL) {
153: cvs_readonlyfs = 1;
154: cvs_nolog = 1;
155: }
156:
1.79 xsa 157: if ((cvs_homedir = getenv("HOME")) == NULL) {
1.89 xsa 158: if ((pw = getpwuid(getuid())) == NULL)
159: fatal("getpwuid failed");
1.79 xsa 160: cvs_homedir = pw->pw_dir;
1.85 reyk 161: }
1.79 xsa 162:
1.80 xsa 163: if ((envstr = getenv("TMPDIR")) != NULL)
164: cvs_tmpdir = envstr;
165:
1.17 jfb 166: ret = cvs_getopt(argc, argv);
167:
168: argc -= ret;
169: argv += ret;
170: if (argc == 0) {
171: usage();
1.98 joris 172: exit(1);
1.17 jfb 173: }
1.80 xsa 174:
1.17 jfb 175: cvs_command = argv[0];
176:
1.80 xsa 177: /*
178: * check the tmp dir, either specified through
179: * the environment variable TMPDIR, or via
180: * the global option -T <dir>
181: */
1.94 xsa 182: if (stat(cvs_tmpdir, &st) == -1)
183: fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
184: else if (!S_ISDIR(st.st_mode))
185: fatal("`%s' is not valid temporary directory", cvs_tmpdir);
1.80 xsa 186:
1.74 xsa 187: if (cvs_readrc == 1) {
1.17 jfb 188: cvs_read_rcfile();
189:
190: if (cvs_defargs != NULL) {
1.94 xsa 191: if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
192: fatal("failed to load default arguments to %s",
1.17 jfb 193: __progname);
194:
195: cvs_getopt(i, targv);
196: cvs_freeargv(targv, i);
1.88 joris 197: xfree(targv);
1.17 jfb 198: }
199: }
200:
201: /* setup signal handlers */
1.98 joris 202: signal(SIGTERM, sighandler);
203: signal(SIGINT, sighandler);
204: signal(SIGHUP, sighandler);
205: signal(SIGABRT, sighandler);
206: signal(SIGALRM, sighandler);
207: signal(SIGPIPE, sighandler);
1.17 jfb 208:
209: cmdp = cvs_findcmd(cvs_command);
210: if (cmdp == NULL) {
211: fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
212: fprintf(stderr, "CVS commands are:\n");
1.67 jfb 213: for (i = 0; cvs_cdt[i] != NULL; i++)
1.17 jfb 214: fprintf(stderr, "\t%-16s%s\n",
1.67 jfb 215: cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
1.98 joris 216: exit(1);
1.17 jfb 217: }
218:
219: cvs_cmdop = cmdp->cmd_op;
220:
221: cmd_argc = 0;
222: memset(cmd_argv, 0, sizeof(cmd_argv));
223:
224: cmd_argv[cmd_argc++] = argv[0];
225: if (cmdp->cmd_defargs != NULL) {
226: /* transform into a new argument vector */
227: ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
228: CVS_CMD_MAXARG - 1);
1.94 xsa 229: if (ret < 0)
230: fatal("main: cvs_getargv failed");
231:
1.17 jfb 232: cmd_argc += ret;
233: }
1.98 joris 234:
1.17 jfb 235: for (ret = 1; ret < argc; ret++)
236: cmd_argv[cmd_argc++] = argv[ret];
237:
1.98 joris 238: cvs_file_init();
239:
1.106 joris 240: if (cvs_cmdop == CVS_OP_SERVER) {
241: setvbuf(stdin, NULL, _IOLBF, 0);
242: setvbuf(stdout, NULL, _IOLBF, 0);
243:
244: cvs_server_active = 1;
245: root = cvs_remote_input();
246: if ((rootp = strchr(root, ' ')) == NULL)
247: fatal("bad Root request");
248: cvs_rootstr = xstrdup(rootp + 1);
249: xfree(root);
250: }
251:
1.98 joris 252: if ((current_cvsroot = cvsroot_get(".")) == NULL) {
1.71 xsa 253: cvs_log(LP_ERR,
1.98 joris 254: "No CVSROOT specified! Please use the '-d' option");
1.102 david 255: fatal("or set the CVSROOT environment variable.");
1.17 jfb 256: }
257:
1.106 joris 258: if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
259: cmdp->cmd(cmd_argc, cmd_argv);
260: cvs_cleanup();
261: return (0);
262: }
1.100 joris 263:
1.116 xsa 264: (void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
265: current_cvsroot->cr_dir, CVS_PATH_ROOT);
1.110 xsa 266:
1.103 xsa 267: if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
1.100 joris 268: if (errno == ENOENT)
1.104 joris 269: fatal("repository '%s' does not exist",
1.100 joris 270: current_cvsroot->cr_dir);
271: else
272: fatal("%s: %s", current_cvsroot->cr_dir,
273: strerror(errno));
274: } else {
275: if (!S_ISDIR(st.st_mode))
276: fatal("'%s' is not a directory",
277: current_cvsroot->cr_dir);
278: }
1.99 joris 279:
1.103 xsa 280: if (cvs_cmdop != CVS_OP_INIT)
281: cvs_parse_configfile();
1.99 joris 282:
283: umask(cvs_umask);
1.98 joris 284:
285: cmdp->cmd(cmd_argc, cmd_argv);
286: cvs_cleanup();
1.17 jfb 287:
1.98 joris 288: return (0);
1.17 jfb 289: }
290:
291: int
292: cvs_getopt(int argc, char **argv)
293: {
294: int ret;
295: char *ep;
1.115 joris 296: const char *errstr;
1.17 jfb 297:
1.111 xsa 298: while ((ret = getopt(argc, argv, "b:d:e:fHlnQqRrs:T:tvVwz:")) != -1) {
1.1 jfb 299: switch (ret) {
1.11 jfb 300: case 'b':
301: /*
302: * We do not care about the bin directory for RCS files
303: * as this program has no dependencies on RCS programs,
304: * so it is only here for backwards compatibility.
305: */
306: cvs_log(LP_NOTICE, "the -b argument is obsolete");
307: break;
1.1 jfb 308: case 'd':
309: cvs_rootstr = optarg;
310: break;
311: case 'e':
312: cvs_editor = optarg;
313: break;
314: case 'f':
1.17 jfb 315: cvs_readrc = 0;
1.1 jfb 316: break;
317: case 'l':
318: cvs_nolog = 1;
319: break;
320: case 'n':
1.65 xsa 321: cvs_noexec = 1;
1.112 xsa 322: cvs_nolog = 1;
1.1 jfb 323: break;
324: case 'Q':
325: verbosity = 0;
326: break;
327: case 'q':
1.105 reyk 328: /*
329: * Be quiet. This is the default in OpenCVS.
330: */
1.111 xsa 331: break;
332: case 'R':
333: cvs_readonlyfs = 1;
334: cvs_nolog = 1;
1.1 jfb 335: break;
336: case 'r':
337: cvs_readonly = 1;
338: break;
1.27 jfb 339: case 's':
340: ep = strchr(optarg, '=');
341: if (ep == NULL) {
342: cvs_log(LP_ERR, "no = in variable assignment");
1.98 joris 343: exit(1);
1.27 jfb 344: }
345: *(ep++) = '\0';
346: if (cvs_var_set(optarg, ep) < 0)
1.98 joris 347: exit(1);
1.80 xsa 348: break;
349: case 'T':
350: cvs_tmpdir = optarg;
1.27 jfb 351: break;
1.1 jfb 352: case 't':
353: cvs_trace = 1;
1.105 reyk 354: break;
355: case 'V':
356: /* don't override -Q */
357: if (verbosity)
358: verbosity = 2;
1.1 jfb 359: break;
360: case 'v':
361: printf("%s\n", CVS_VERSION);
362: exit(0);
363: /* NOTREACHED */
1.83 xsa 364: break;
365: case 'w':
366: cvs_readonly = 0;
1.11 jfb 367: break;
368: case 'x':
369: /*
370: * Kerberos encryption support, kept for compatibility
371: */
1.1 jfb 372: break;
373: case 'z':
1.115 joris 374: cvs_compress = strtonum(optarg, 0, 9, &errstr);
375: if (errstr != NULL)
376: fatal("cvs_compress: %s", errstr);
1.1 jfb 377: break;
378: default:
379: usage();
1.98 joris 380: exit(1);
1.1 jfb 381: }
382: }
383:
1.17 jfb 384: ret = optind;
1.1 jfb 385: optind = 1;
1.17 jfb 386: optreset = 1; /* for next call */
1.12 jfb 387:
1.1 jfb 388: return (ret);
389: }
390:
391: /*
1.17 jfb 392: * cvs_read_rcfile()
1.1 jfb 393: *
394: * Read the CVS `.cvsrc' file in the user's home directory. If the file
395: * exists, it should contain a list of arguments that should always be given
396: * implicitly to the specified commands.
397: */
1.42 joris 398: static void
1.17 jfb 399: cvs_read_rcfile(void)
1.1 jfb 400: {
1.79 xsa 401: char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
1.116 xsa 402: int i, linenum;
1.17 jfb 403: size_t len;
1.1 jfb 404: struct cvs_cmd *cmdp;
405: FILE *fp;
406:
1.116 xsa 407: linenum = 0;
408:
409: i = snprintf(rcpath, MAXPATHLEN, "%s/%s", cvs_homedir, CVS_PATH_RC);
410: if (i < 0 || i >= MAXPATHLEN) {
1.108 xsa 411: cvs_log(LP_ERRNO, "%s", rcpath);
1.54 xsa 412: return;
413: }
1.1 jfb 414:
415: fp = fopen(rcpath, "r");
416: if (fp == NULL) {
417: if (errno != ENOENT)
418: cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
419: strerror(errno));
420: return;
421: }
422:
1.84 xsa 423: while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
1.23 xsa 424: linenum++;
1.17 jfb 425: if ((len = strlen(linebuf)) == 0)
426: continue;
427: if (linebuf[len - 1] != '\n') {
1.98 joris 428: cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
1.23 xsa 429: linenum);
1.17 jfb 430: break;
431: }
432: linebuf[--len] = '\0';
433:
1.70 joris 434: /* skip any whitespaces */
435: p = linebuf;
436: while (*p == ' ')
1.95 deraadt 437: p++;
1.70 joris 438:
439: /* allow comments */
440: if (*p == '#')
441: continue;
442:
443: lp = strchr(p, ' ');
1.1 jfb 444: if (lp == NULL)
1.17 jfb 445: continue; /* ignore lines with no arguments */
446: *lp = '\0';
1.70 joris 447: if (strcmp(p, "cvs") == 0) {
1.17 jfb 448: /*
449: * Global default options. In the case of cvs only,
450: * we keep the 'cvs' string as first argument because
451: * getopt() does not like starting at index 0 for
452: * argument processing.
453: */
454: *lp = ' ';
1.88 joris 455: cvs_defargs = xstrdup(p);
1.15 deraadt 456: } else {
1.17 jfb 457: lp++;
1.70 joris 458: cmdp = cvs_findcmd(p);
1.1 jfb 459: if (cmdp == NULL) {
460: cvs_log(LP_NOTICE,
1.25 xsa 461: "unknown command `%s' in `%s:%d'",
1.70 joris 462: p, rcpath, linenum);
1.1 jfb 463: continue;
464: }
1.17 jfb 465:
1.88 joris 466: cmdp->cmd_defargs = xstrdup(lp);
1.1 jfb 467: }
468: }
1.98 joris 469:
1.1 jfb 470: if (ferror(fp)) {
1.23 xsa 471: cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
1.1 jfb 472: }
473:
474: (void)fclose(fp);
1.27 jfb 475: }
476:
477: /*
478: * cvs_var_set()
479: *
480: * Set the value of the variable <var> to <val>. If there is no such variable,
481: * a new entry is created, otherwise the old value is overwritten.
482: * Returns 0 on success, or -1 on failure.
483: */
484: int
485: cvs_var_set(const char *var, const char *val)
486: {
487: char *valcp;
488: const char *cp;
489: struct cvs_var *vp;
490:
1.97 deraadt 491: if (var == NULL || *var == '\0') {
1.27 jfb 492: cvs_log(LP_ERR, "no variable name");
493: return (-1);
494: }
495:
496: /* sanity check on the name */
497: for (cp = var; *cp != '\0'; cp++)
498: if (!isalnum(*cp) && (*cp != '_')) {
499: cvs_log(LP_ERR,
500: "variable name `%s' contains invalid characters",
501: var);
502: return (-1);
503: }
504:
505: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
506: if (strcmp(vp->cv_name, var) == 0)
507: break;
508:
1.88 joris 509: valcp = xstrdup(val);
1.27 jfb 510: if (vp == NULL) {
1.96 ray 511: vp = xcalloc(1, sizeof(*vp));
1.27 jfb 512:
1.88 joris 513: vp->cv_name = xstrdup(var);
1.27 jfb 514: TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
515:
516: } else /* free the previous value */
1.88 joris 517: xfree(vp->cv_val);
1.27 jfb 518:
519: vp->cv_val = valcp;
520:
521: return (0);
522: }
523:
524: /*
1.118 ! otto 525: * cvs_var_unset()
1.27 jfb 526: *
527: * Remove any entry for the variable <var>.
528: * Returns 0 on success, or -1 on failure.
529: */
530: int
531: cvs_var_unset(const char *var)
532: {
533: struct cvs_var *vp;
534:
535: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
536: if (strcmp(vp->cv_name, var) == 0) {
537: TAILQ_REMOVE(&cvs_variables, vp, cv_link);
1.88 joris 538: xfree(vp->cv_name);
539: xfree(vp->cv_val);
540: xfree(vp);
1.27 jfb 541: return (0);
542: }
543:
544: return (-1);
545: }
546:
547: /*
548: * cvs_var_get()
549: *
550: * Get the value associated with the variable <var>. Returns a pointer to the
551: * value string on success, or NULL if the variable does not exist.
552: */
553:
1.75 xsa 554: const char *
1.27 jfb 555: cvs_var_get(const char *var)
556: {
557: struct cvs_var *vp;
558:
559: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
560: if (strcmp(vp->cv_name, var) == 0)
561: return (vp->cv_val);
562:
563: return (NULL);
1.1 jfb 564: }