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