Annotation of src/usr.bin/cvs/cvs.c, Revision 1.154
1.154 ! deraadt 1: /* $OpenBSD: cvs.c,v 1.153 2014/01/08 13:23:55 okan 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>
1.120 xsa 35: #include <time.h>
1.117 otto 36: #include <unistd.h>
1.1 jfb 37:
38: #include "cvs.h"
1.106 joris 39: #include "remote.h"
1.150 joris 40: #include "hash.h"
1.1 jfb 41:
42: extern char *__progname;
43:
44: /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
1.147 joris 45: int verbosity = 2;
1.1 jfb 46:
47: /* compression level used with zlib, 0 meaning no compression taking place */
1.98 joris 48: int cvs_compress = 0;
49: int cvs_readrc = 1; /* read .cvsrc on startup */
50: int cvs_trace = 0;
51: int cvs_nolog = 0;
52: int cvs_readonly = 0;
1.111 xsa 53: int cvs_readonlyfs = 0;
1.98 joris 54: int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */
55: int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */
56: int cvs_cmdop;
1.99 joris 57: int cvs_umask = CVS_UMASK_DEFAULT;
1.106 joris 58: int cvs_server_active = 0;
1.98 joris 59:
1.99 joris 60: char *cvs_tagname = NULL;
1.98 joris 61: char *cvs_defargs; /* default global arguments from .cvsrc */
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_tmpdir = CVS_TMPDIR_DEFAULT;
1.13 krapht 67:
1.98 joris 68: struct cvsroot *current_cvsroot = NULL;
1.140 tobias 69: struct cvs_cmd *cmdp; /* struct of command we are running */
1.27 jfb 70:
1.98 joris 71: int cvs_getopt(int, char **);
1.121 xsa 72: __dead void usage(void);
1.75 xsa 73: static void cvs_read_rcfile(void);
1.1 jfb 74:
1.151 ray 75: struct wklhead temp_files;
1.98 joris 76:
77: void sighandler(int);
78: volatile sig_atomic_t cvs_quit = 0;
79: volatile sig_atomic_t sig_received = 0;
80:
1.148 joris 81: extern CVSENTRIES *current_list;
82:
1.150 joris 83: struct hash_table created_directories;
84: struct hash_table created_cvs_directories;
85:
1.98 joris 86: void
87: sighandler(int sig)
88: {
89: sig_received = sig;
90:
91: switch (sig) {
92: case SIGINT:
93: case SIGTERM:
1.107 joris 94: case SIGPIPE:
1.98 joris 95: cvs_quit = 1;
96: break;
97: default:
98: break;
99: }
100: }
101:
102: void
103: cvs_cleanup(void)
104: {
105: cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
1.151 ray 106: worklist_run(&repo_locks, worklist_unlink);
1.98 joris 107:
108: cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
1.151 ray 109: worklist_run(&temp_files, worklist_unlink);
1.106 joris 110:
1.124 ray 111: if (cvs_server_path != NULL) {
1.106 joris 112: if (cvs_rmdir(cvs_server_path) == -1)
113: cvs_log(LP_ERR,
114: "warning: failed to remove server directory: %s",
115: cvs_server_path);
116: xfree(cvs_server_path);
1.124 ray 117: cvs_server_path = NULL;
1.106 joris 118: }
1.148 joris 119:
120: if (current_list != NULL)
121: cvs_ent_close(current_list, ENT_SYNC);
1.98 joris 122: }
123:
1.121 xsa 124: __dead void
1.1 jfb 125: usage(void)
126: {
1.121 xsa 127: (void)fprintf(stderr,
1.147 joris 128: "usage: %s [-flnQqRrtvw] [-d root] [-e editor] [-s var=val]\n"
1.129 sobrado 129: " [-T tmpdir] [-z level] command ...\n", __progname);
1.121 xsa 130: exit(1);
1.1 jfb 131: }
132:
133: int
1.145 tobias 134: cvs_build_cmd(char ***cmd_argv, char **argv, int argc)
135: {
136: int cmd_argc, i, cur;
137: char *cp, *linebuf, *lp;
138:
139: if (cmdp->cmd_defargs == NULL) {
140: *cmd_argv = argv;
141: return argc;
142: }
143:
144: cur = argc + 2;
145: cmd_argc = 0;
146: *cmd_argv = xcalloc(cur, sizeof(char *));
147: (*cmd_argv)[cmd_argc++] = argv[0];
148:
149: linebuf = xstrdup(cmdp->cmd_defargs);
150: for (lp = linebuf; lp != NULL;) {
151: cp = strsep(&lp, " \t\b\f\n\r\t\v");
152: if (cp == NULL)
153: break;
154: if (*cp == '\0')
155: continue;
156:
157: if (cmd_argc == cur) {
158: cur += 8;
1.154 ! deraadt 159: *cmd_argv = xreallocarray(*cmd_argv, cur,
1.145 tobias 160: sizeof(char *));
161: }
162:
163: (*cmd_argv)[cmd_argc++] = cp;
164: }
165:
166: if (cmd_argc + argc > cur) {
167: cur = cmd_argc + argc + 1;
1.154 ! deraadt 168: *cmd_argv = xreallocarray(*cmd_argv, cur,
1.145 tobias 169: sizeof(char *));
170: }
171:
172: for (i = 1; i < argc; i++)
173: (*cmd_argv)[cmd_argc++] = argv[i];
174:
175: (*cmd_argv)[cmd_argc] = NULL;
176:
177: return cmd_argc;
178: }
179:
180: int
1.1 jfb 181: main(int argc, char **argv)
182: {
1.145 tobias 183: char *envstr, **cmd_argv, **targv;
1.17 jfb 184: int i, ret, cmd_argc;
1.79 xsa 185: struct passwd *pw;
1.80 xsa 186: struct stat st;
1.100 joris 187: char fpath[MAXPATHLEN];
1.87 joris 188:
189: tzset();
1.1 jfb 190:
1.27 jfb 191: TAILQ_INIT(&cvs_variables);
1.98 joris 192: SLIST_INIT(&repo_locks);
193: SLIST_INIT(&temp_files);
1.150 joris 194:
195: hash_table_init(&created_directories, 100);
196: hash_table_init(&created_cvs_directories, 100);
1.1 jfb 197:
198: /* check environment so command-line options override it */
199: if ((envstr = getenv("CVS_RSH")) != NULL)
200: cvs_rsh = envstr;
201:
202: if (((envstr = getenv("CVSEDITOR")) != NULL) ||
203: ((envstr = getenv("VISUAL")) != NULL) ||
204: ((envstr = getenv("EDITOR")) != NULL))
205: cvs_editor = envstr;
1.76 xsa 206:
207: if ((envstr = getenv("CVSREAD")) != NULL)
208: cvs_readonly = 1;
1.1 jfb 209:
1.111 xsa 210: if ((envstr = getenv("CVSREADONLYFS")) != NULL) {
211: cvs_readonlyfs = 1;
212: cvs_nolog = 1;
213: }
214:
1.79 xsa 215: if ((cvs_homedir = getenv("HOME")) == NULL) {
1.131 tobias 216: if ((pw = getpwuid(getuid())) != NULL)
217: cvs_homedir = pw->pw_dir;
1.85 reyk 218: }
1.79 xsa 219:
1.80 xsa 220: if ((envstr = getenv("TMPDIR")) != NULL)
221: cvs_tmpdir = envstr;
222:
1.17 jfb 223: ret = cvs_getopt(argc, argv);
224:
225: argc -= ret;
226: argv += ret;
1.121 xsa 227: if (argc == 0)
1.17 jfb 228: usage();
1.80 xsa 229:
1.146 tobias 230: cmdp = cvs_findcmd(argv[0]);
231: if (cmdp == NULL) {
232: fprintf(stderr, "Unknown command: `%s'\n\n", argv[0]);
233: fprintf(stderr, "CVS commands are:\n");
234: for (i = 0; cvs_cdt[i] != NULL; i++)
235: fprintf(stderr, "\t%-16s%s\n",
236: cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
237: exit(1);
238: }
239:
1.80 xsa 240: /*
241: * check the tmp dir, either specified through
242: * the environment variable TMPDIR, or via
243: * the global option -T <dir>
244: */
1.94 xsa 245: if (stat(cvs_tmpdir, &st) == -1)
246: fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
247: else if (!S_ISDIR(st.st_mode))
248: fatal("`%s' is not valid temporary directory", cvs_tmpdir);
1.132 tobias 249:
1.131 tobias 250: if (cvs_readrc == 1 && cvs_homedir != NULL) {
1.17 jfb 251: cvs_read_rcfile();
252:
253: if (cvs_defargs != NULL) {
1.94 xsa 254: if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
255: fatal("failed to load default arguments to %s",
1.17 jfb 256: __progname);
257:
258: cvs_getopt(i, targv);
259: cvs_freeargv(targv, i);
1.88 joris 260: xfree(targv);
1.17 jfb 261: }
262: }
263:
264: /* setup signal handlers */
1.98 joris 265: signal(SIGTERM, sighandler);
266: signal(SIGINT, sighandler);
267: signal(SIGHUP, sighandler);
268: signal(SIGABRT, sighandler);
269: signal(SIGALRM, sighandler);
270: signal(SIGPIPE, sighandler);
1.17 jfb 271:
272: cvs_cmdop = cmdp->cmd_op;
273:
1.145 tobias 274: cmd_argc = cvs_build_cmd(&cmd_argv, argv, argc);
1.17 jfb 275:
1.98 joris 276: cvs_file_init();
277:
1.106 joris 278: if (cvs_cmdop == CVS_OP_SERVER) {
1.130 tobias 279: cmdp->cmd(cmd_argc, cmd_argv);
280: cvs_cleanup();
281: return (0);
1.106 joris 282: }
283:
1.144 tobias 284: cvs_umask = umask(0);
285: umask(cvs_umask);
286:
1.98 joris 287: if ((current_cvsroot = cvsroot_get(".")) == NULL) {
1.71 xsa 288: cvs_log(LP_ERR,
1.98 joris 289: "No CVSROOT specified! Please use the '-d' option");
1.102 david 290: fatal("or set the CVSROOT environment variable.");
1.17 jfb 291: }
292:
1.106 joris 293: if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
294: cmdp->cmd(cmd_argc, cmd_argv);
295: cvs_cleanup();
296: return (0);
297: }
1.100 joris 298:
1.116 xsa 299: (void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
300: current_cvsroot->cr_dir, CVS_PATH_ROOT);
1.110 xsa 301:
1.103 xsa 302: if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
1.100 joris 303: if (errno == ENOENT)
1.104 joris 304: fatal("repository '%s' does not exist",
1.100 joris 305: current_cvsroot->cr_dir);
306: else
307: fatal("%s: %s", current_cvsroot->cr_dir,
308: strerror(errno));
309: } else {
310: if (!S_ISDIR(st.st_mode))
311: fatal("'%s' is not a directory",
312: current_cvsroot->cr_dir);
313: }
1.99 joris 314:
1.142 joris 315: if (cvs_cmdop != CVS_OP_INIT) {
1.103 xsa 316: cvs_parse_configfile();
1.142 joris 317: cvs_parse_modules();
318: }
1.98 joris 319:
320: cmdp->cmd(cmd_argc, cmd_argv);
321: cvs_cleanup();
1.17 jfb 322:
1.98 joris 323: return (0);
1.17 jfb 324: }
325:
326: int
327: cvs_getopt(int argc, char **argv)
328: {
329: int ret;
330: char *ep;
1.115 joris 331: const char *errstr;
1.17 jfb 332:
1.147 joris 333: while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tvwxz:")) != -1) {
1.1 jfb 334: switch (ret) {
1.11 jfb 335: case 'b':
336: /*
337: * We do not care about the bin directory for RCS files
338: * as this program has no dependencies on RCS programs,
339: * so it is only here for backwards compatibility.
340: */
341: cvs_log(LP_NOTICE, "the -b argument is obsolete");
342: break;
1.1 jfb 343: case 'd':
344: cvs_rootstr = optarg;
345: break;
346: case 'e':
347: cvs_editor = optarg;
348: break;
349: case 'f':
1.17 jfb 350: cvs_readrc = 0;
1.1 jfb 351: break;
352: case 'l':
353: cvs_nolog = 1;
354: break;
355: case 'n':
1.65 xsa 356: cvs_noexec = 1;
1.112 xsa 357: cvs_nolog = 1;
1.1 jfb 358: break;
359: case 'Q':
360: verbosity = 0;
361: break;
362: case 'q':
1.147 joris 363: if (verbosity > 1)
364: verbosity = 1;
1.111 xsa 365: break;
366: case 'R':
367: cvs_readonlyfs = 1;
368: cvs_nolog = 1;
1.1 jfb 369: break;
370: case 'r':
371: cvs_readonly = 1;
372: break;
1.27 jfb 373: case 's':
374: ep = strchr(optarg, '=');
375: if (ep == NULL) {
376: cvs_log(LP_ERR, "no = in variable assignment");
1.98 joris 377: exit(1);
1.27 jfb 378: }
379: *(ep++) = '\0';
380: if (cvs_var_set(optarg, ep) < 0)
1.98 joris 381: exit(1);
1.80 xsa 382: break;
383: case 'T':
384: cvs_tmpdir = optarg;
1.27 jfb 385: break;
1.1 jfb 386: case 't':
387: cvs_trace = 1;
388: break;
389: case 'v':
390: printf("%s\n", CVS_VERSION);
391: exit(0);
392: /* NOTREACHED */
1.83 xsa 393: case 'w':
394: cvs_readonly = 0;
1.11 jfb 395: break;
396: case 'x':
397: /*
398: * Kerberos encryption support, kept for compatibility
399: */
1.1 jfb 400: break;
401: case 'z':
1.115 joris 402: cvs_compress = strtonum(optarg, 0, 9, &errstr);
403: if (errstr != NULL)
404: fatal("cvs_compress: %s", errstr);
1.1 jfb 405: break;
406: default:
407: usage();
1.121 xsa 408: /* NOTREACHED */
1.1 jfb 409: }
410: }
411:
1.17 jfb 412: ret = optind;
1.1 jfb 413: optind = 1;
1.17 jfb 414: optreset = 1; /* for next call */
1.12 jfb 415:
1.1 jfb 416: return (ret);
417: }
418:
419: /*
1.17 jfb 420: * cvs_read_rcfile()
1.1 jfb 421: *
422: * Read the CVS `.cvsrc' file in the user's home directory. If the file
423: * exists, it should contain a list of arguments that should always be given
424: * implicitly to the specified commands.
425: */
1.42 joris 426: static void
1.17 jfb 427: cvs_read_rcfile(void)
1.1 jfb 428: {
1.133 tobias 429: char rcpath[MAXPATHLEN], *buf, *lbuf, *lp, *p;
1.137 tobias 430: int cmd_parsed, cvs_parsed, i, linenum;
1.135 tobias 431: size_t len, pos;
1.140 tobias 432: struct cvs_cmd *tcmdp;
1.1 jfb 433: FILE *fp;
434:
1.116 xsa 435: linenum = 0;
436:
437: i = snprintf(rcpath, MAXPATHLEN, "%s/%s", cvs_homedir, CVS_PATH_RC);
438: if (i < 0 || i >= MAXPATHLEN) {
1.108 xsa 439: cvs_log(LP_ERRNO, "%s", rcpath);
1.54 xsa 440: return;
441: }
1.1 jfb 442:
443: fp = fopen(rcpath, "r");
444: if (fp == NULL) {
445: if (errno != ENOENT)
446: cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
447: strerror(errno));
448: return;
449: }
1.137 tobias 450:
451: cmd_parsed = cvs_parsed = 0;
1.133 tobias 452: lbuf = NULL;
453: while ((buf = fgetln(fp, &len)) != NULL) {
454: if (buf[len - 1] == '\n') {
455: buf[len - 1] = '\0';
456: } else {
457: lbuf = xmalloc(len + 1);
458: memcpy(lbuf, buf, len);
459: lbuf[len] = '\0';
460: buf = lbuf;
461: }
462:
1.23 xsa 463: linenum++;
1.17 jfb 464:
1.70 joris 465: /* skip any whitespaces */
1.133 tobias 466: p = buf;
1.70 joris 467: while (*p == ' ')
1.95 deraadt 468: p++;
1.70 joris 469:
1.134 tobias 470: /*
471: * Allow comments.
472: * GNU cvs stops parsing a line if it encounters a \t
473: * in front of a command, stick at this behaviour for
474: * compatibility.
475: */
476: if (*p == '#' || *p == '\t')
1.70 joris 477: continue;
478:
1.135 tobias 479: pos = strcspn(p, " \t");
480: if (pos == strlen(p)) {
1.138 tobias 481: lp = NULL;
1.135 tobias 482: } else {
483: lp = p + pos;
484: *lp = '\0';
485: }
486:
1.137 tobias 487: if (strcmp(p, "cvs") == 0 && !cvs_parsed) {
1.17 jfb 488: /*
489: * Global default options. In the case of cvs only,
490: * we keep the 'cvs' string as first argument because
491: * getopt() does not like starting at index 0 for
492: * argument processing.
493: */
1.138 tobias 494: if (lp != NULL) {
495: *lp = ' ';
496: cvs_defargs = xstrdup(p);
497: }
1.137 tobias 498: cvs_parsed = 1;
1.15 deraadt 499: } else {
1.137 tobias 500: tcmdp = cvs_findcmd(p);
501: if (tcmdp == NULL && verbosity == 2)
502: cvs_log(LP_NOTICE,
503: "unknown command `%s' in `%s:%d'",
504: p, rcpath, linenum);
505:
506: if (tcmdp != cmdp || cmd_parsed)
1.1 jfb 507: continue;
1.17 jfb 508:
1.138 tobias 509: if (lp != NULL) {
510: lp++;
511: cmdp->cmd_defargs = xstrdup(lp);
512: }
1.137 tobias 513: cmd_parsed = 1;
1.1 jfb 514: }
515: }
1.133 tobias 516: if (lbuf != NULL)
517: xfree(lbuf);
1.98 joris 518:
1.1 jfb 519: if (ferror(fp)) {
1.23 xsa 520: cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
1.1 jfb 521: }
522:
523: (void)fclose(fp);
1.27 jfb 524: }
525:
526: /*
527: * cvs_var_set()
528: *
529: * Set the value of the variable <var> to <val>. If there is no such variable,
530: * a new entry is created, otherwise the old value is overwritten.
531: * Returns 0 on success, or -1 on failure.
532: */
533: int
534: cvs_var_set(const char *var, const char *val)
535: {
536: const char *cp;
537: struct cvs_var *vp;
538:
1.97 deraadt 539: if (var == NULL || *var == '\0') {
1.27 jfb 540: cvs_log(LP_ERR, "no variable name");
541: return (-1);
542: }
543:
544: /* sanity check on the name */
545: for (cp = var; *cp != '\0'; cp++)
1.153 okan 546: if (!isalnum((unsigned char)*cp) && (*cp != '_')) {
1.27 jfb 547: cvs_log(LP_ERR,
548: "variable name `%s' contains invalid characters",
549: var);
550: return (-1);
551: }
552:
553: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
554: if (strcmp(vp->cv_name, var) == 0)
555: break;
556:
557: if (vp == NULL) {
1.96 ray 558: vp = xcalloc(1, sizeof(*vp));
1.27 jfb 559:
1.88 joris 560: vp->cv_name = xstrdup(var);
1.27 jfb 561: TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
562:
563: } else /* free the previous value */
1.88 joris 564: xfree(vp->cv_val);
1.27 jfb 565:
1.141 tobias 566: vp->cv_val = xstrdup(val);
1.27 jfb 567:
568: return (0);
569: }
570:
571: /*
1.118 otto 572: * cvs_var_unset()
1.27 jfb 573: *
574: * Remove any entry for the variable <var>.
575: * Returns 0 on success, or -1 on failure.
576: */
577: int
578: cvs_var_unset(const char *var)
579: {
580: struct cvs_var *vp;
581:
582: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
583: if (strcmp(vp->cv_name, var) == 0) {
584: TAILQ_REMOVE(&cvs_variables, vp, cv_link);
1.88 joris 585: xfree(vp->cv_name);
586: xfree(vp->cv_val);
587: xfree(vp);
1.27 jfb 588: return (0);
589: }
590:
591: return (-1);
592: }
593:
594: /*
595: * cvs_var_get()
596: *
597: * Get the value associated with the variable <var>. Returns a pointer to the
598: * value string on success, or NULL if the variable does not exist.
599: */
600:
1.75 xsa 601: const char *
1.27 jfb 602: cvs_var_get(const char *var)
603: {
604: struct cvs_var *vp;
605:
606: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
607: if (strcmp(vp->cv_name, var) == 0)
608: return (vp->cv_val);
609:
610: return (NULL);
1.1 jfb 611: }