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