Annotation of src/usr.bin/cvs/cvs.c, Revision 1.125
1.125 ! ray 1: /* $OpenBSD: cvs.c,v 1.124 2007/05/26 20:45:26 ray 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_command; /* name of the command we are running */
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_msg = NULL;
68: char *cvs_tmpdir = CVS_TMPDIR_DEFAULT;
1.13 krapht 69:
1.98 joris 70: struct cvsroot *current_cvsroot = NULL;
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.123 xsa 121: "usage: %s [-flnQqRrtVvw] [-d root] [-e editor] [-s var=val]\n"
1.119 sobrado 122: " [-T tmpdir] [-z level] command [...]\n", __progname);
1.121 xsa 123: exit(1);
1.1 jfb 124: }
125:
126: int
127: main(int argc, char **argv)
128: {
1.17 jfb 129: char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
130: int i, ret, cmd_argc;
1.1 jfb 131: struct cvs_cmd *cmdp;
1.79 xsa 132: struct passwd *pw;
1.80 xsa 133: struct stat st;
1.100 joris 134: char fpath[MAXPATHLEN];
1.106 joris 135: char *root, *rootp;
1.87 joris 136:
137: tzset();
1.1 jfb 138:
1.27 jfb 139: TAILQ_INIT(&cvs_variables);
1.98 joris 140: SLIST_INIT(&repo_locks);
141: SLIST_INIT(&temp_files);
1.1 jfb 142:
143: /* check environment so command-line options override it */
144: if ((envstr = getenv("CVS_RSH")) != NULL)
145: cvs_rsh = envstr;
146:
147: if (((envstr = getenv("CVSEDITOR")) != NULL) ||
148: ((envstr = getenv("VISUAL")) != NULL) ||
149: ((envstr = getenv("EDITOR")) != NULL))
150: cvs_editor = envstr;
1.76 xsa 151:
152: if ((envstr = getenv("CVSREAD")) != NULL)
153: cvs_readonly = 1;
1.1 jfb 154:
1.111 xsa 155: if ((envstr = getenv("CVSREADONLYFS")) != NULL) {
156: cvs_readonlyfs = 1;
157: cvs_nolog = 1;
158: }
159:
1.79 xsa 160: if ((cvs_homedir = getenv("HOME")) == NULL) {
1.89 xsa 161: if ((pw = getpwuid(getuid())) == NULL)
162: fatal("getpwuid failed");
1.79 xsa 163: cvs_homedir = pw->pw_dir;
1.85 reyk 164: }
1.79 xsa 165:
1.80 xsa 166: if ((envstr = getenv("TMPDIR")) != NULL)
167: cvs_tmpdir = envstr;
168:
1.17 jfb 169: ret = cvs_getopt(argc, argv);
170:
171: argc -= ret;
172: argv += ret;
1.121 xsa 173: if (argc == 0)
1.17 jfb 174: usage();
1.80 xsa 175:
1.17 jfb 176: cvs_command = argv[0];
177:
1.80 xsa 178: /*
179: * check the tmp dir, either specified through
180: * the environment variable TMPDIR, or via
181: * the global option -T <dir>
182: */
1.94 xsa 183: if (stat(cvs_tmpdir, &st) == -1)
184: fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
185: else if (!S_ISDIR(st.st_mode))
186: fatal("`%s' is not valid temporary directory", cvs_tmpdir);
1.80 xsa 187:
1.74 xsa 188: if (cvs_readrc == 1) {
1.17 jfb 189: cvs_read_rcfile();
190:
191: if (cvs_defargs != NULL) {
1.94 xsa 192: if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
193: fatal("failed to load default arguments to %s",
1.17 jfb 194: __progname);
195:
196: cvs_getopt(i, targv);
197: cvs_freeargv(targv, i);
1.88 joris 198: xfree(targv);
1.17 jfb 199: }
200: }
201:
202: /* setup signal handlers */
1.98 joris 203: signal(SIGTERM, sighandler);
204: signal(SIGINT, sighandler);
205: signal(SIGHUP, sighandler);
206: signal(SIGABRT, sighandler);
207: signal(SIGALRM, sighandler);
208: signal(SIGPIPE, sighandler);
1.17 jfb 209:
210: cmdp = cvs_findcmd(cvs_command);
211: if (cmdp == NULL) {
212: fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
213: fprintf(stderr, "CVS commands are:\n");
1.67 jfb 214: for (i = 0; cvs_cdt[i] != NULL; i++)
1.17 jfb 215: fprintf(stderr, "\t%-16s%s\n",
1.67 jfb 216: cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
1.98 joris 217: exit(1);
1.17 jfb 218: }
219:
220: cvs_cmdop = cmdp->cmd_op;
221:
222: cmd_argc = 0;
223: memset(cmd_argv, 0, sizeof(cmd_argv));
224:
225: cmd_argv[cmd_argc++] = argv[0];
226: if (cmdp->cmd_defargs != NULL) {
227: /* transform into a new argument vector */
228: ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
229: CVS_CMD_MAXARG - 1);
1.94 xsa 230: if (ret < 0)
231: fatal("main: cvs_getargv failed");
232:
1.17 jfb 233: cmd_argc += ret;
234: }
1.98 joris 235:
1.17 jfb 236: for (ret = 1; ret < argc; ret++)
237: cmd_argv[cmd_argc++] = argv[ret];
238:
1.98 joris 239: cvs_file_init();
240:
1.106 joris 241: if (cvs_cmdop == CVS_OP_SERVER) {
1.125 ! ray 242: if (cmd_argc > 1)
! 243: fatal("server does not take any extra arguments");
! 244:
1.106 joris 245: setvbuf(stdin, NULL, _IOLBF, 0);
246: setvbuf(stdout, NULL, _IOLBF, 0);
247:
248: cvs_server_active = 1;
249: root = cvs_remote_input();
250: if ((rootp = strchr(root, ' ')) == NULL)
251: fatal("bad Root request");
252: cvs_rootstr = xstrdup(rootp + 1);
253: xfree(root);
254: }
255:
1.98 joris 256: if ((current_cvsroot = cvsroot_get(".")) == NULL) {
1.71 xsa 257: cvs_log(LP_ERR,
1.98 joris 258: "No CVSROOT specified! Please use the '-d' option");
1.102 david 259: fatal("or set the CVSROOT environment variable.");
1.17 jfb 260: }
261:
1.106 joris 262: if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
263: cmdp->cmd(cmd_argc, cmd_argv);
264: cvs_cleanup();
265: return (0);
266: }
1.100 joris 267:
1.116 xsa 268: (void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
269: current_cvsroot->cr_dir, CVS_PATH_ROOT);
1.110 xsa 270:
1.103 xsa 271: if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
1.100 joris 272: if (errno == ENOENT)
1.104 joris 273: fatal("repository '%s' does not exist",
1.100 joris 274: current_cvsroot->cr_dir);
275: else
276: fatal("%s: %s", current_cvsroot->cr_dir,
277: strerror(errno));
278: } else {
279: if (!S_ISDIR(st.st_mode))
280: fatal("'%s' is not a directory",
281: current_cvsroot->cr_dir);
282: }
1.99 joris 283:
1.103 xsa 284: if (cvs_cmdop != CVS_OP_INIT)
285: cvs_parse_configfile();
1.99 joris 286:
287: umask(cvs_umask);
1.98 joris 288:
289: cmdp->cmd(cmd_argc, cmd_argv);
290: cvs_cleanup();
1.17 jfb 291:
1.98 joris 292: return (0);
1.17 jfb 293: }
294:
295: int
296: cvs_getopt(int argc, char **argv)
297: {
298: int ret;
299: char *ep;
1.115 joris 300: const char *errstr;
1.17 jfb 301:
1.122 xsa 302: while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tVvwxz:")) != -1) {
1.1 jfb 303: switch (ret) {
1.11 jfb 304: case 'b':
305: /*
306: * We do not care about the bin directory for RCS files
307: * as this program has no dependencies on RCS programs,
308: * so it is only here for backwards compatibility.
309: */
310: cvs_log(LP_NOTICE, "the -b argument is obsolete");
311: break;
1.1 jfb 312: case 'd':
313: cvs_rootstr = optarg;
314: break;
315: case 'e':
316: cvs_editor = optarg;
317: break;
318: case 'f':
1.17 jfb 319: cvs_readrc = 0;
1.1 jfb 320: break;
321: case 'l':
322: cvs_nolog = 1;
323: break;
324: case 'n':
1.65 xsa 325: cvs_noexec = 1;
1.112 xsa 326: cvs_nolog = 1;
1.1 jfb 327: break;
328: case 'Q':
329: verbosity = 0;
330: break;
331: case 'q':
1.105 reyk 332: /*
333: * Be quiet. This is the default in OpenCVS.
334: */
1.111 xsa 335: break;
336: case 'R':
337: cvs_readonlyfs = 1;
338: cvs_nolog = 1;
1.1 jfb 339: break;
340: case 'r':
341: cvs_readonly = 1;
342: break;
1.27 jfb 343: case 's':
344: ep = strchr(optarg, '=');
345: if (ep == NULL) {
346: cvs_log(LP_ERR, "no = in variable assignment");
1.98 joris 347: exit(1);
1.27 jfb 348: }
349: *(ep++) = '\0';
350: if (cvs_var_set(optarg, ep) < 0)
1.98 joris 351: exit(1);
1.80 xsa 352: break;
353: case 'T':
354: cvs_tmpdir = optarg;
1.27 jfb 355: break;
1.1 jfb 356: case 't':
357: cvs_trace = 1;
1.105 reyk 358: break;
359: case 'V':
360: /* don't override -Q */
361: if (verbosity)
362: verbosity = 2;
1.1 jfb 363: break;
364: case 'v':
365: printf("%s\n", CVS_VERSION);
366: exit(0);
367: /* NOTREACHED */
1.83 xsa 368: break;
369: case 'w':
370: cvs_readonly = 0;
1.11 jfb 371: break;
372: case 'x':
373: /*
374: * Kerberos encryption support, kept for compatibility
375: */
1.1 jfb 376: break;
377: case 'z':
1.115 joris 378: cvs_compress = strtonum(optarg, 0, 9, &errstr);
379: if (errstr != NULL)
380: fatal("cvs_compress: %s", errstr);
1.1 jfb 381: break;
382: default:
383: usage();
1.121 xsa 384: /* NOTREACHED */
1.1 jfb 385: }
386: }
387:
1.17 jfb 388: ret = optind;
1.1 jfb 389: optind = 1;
1.17 jfb 390: optreset = 1; /* for next call */
1.12 jfb 391:
1.1 jfb 392: return (ret);
393: }
394:
395: /*
1.17 jfb 396: * cvs_read_rcfile()
1.1 jfb 397: *
398: * Read the CVS `.cvsrc' file in the user's home directory. If the file
399: * exists, it should contain a list of arguments that should always be given
400: * implicitly to the specified commands.
401: */
1.42 joris 402: static void
1.17 jfb 403: cvs_read_rcfile(void)
1.1 jfb 404: {
1.79 xsa 405: char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
1.116 xsa 406: int i, linenum;
1.17 jfb 407: size_t len;
1.1 jfb 408: struct cvs_cmd *cmdp;
409: FILE *fp;
410:
1.116 xsa 411: linenum = 0;
412:
413: i = snprintf(rcpath, MAXPATHLEN, "%s/%s", cvs_homedir, CVS_PATH_RC);
414: if (i < 0 || i >= MAXPATHLEN) {
1.108 xsa 415: cvs_log(LP_ERRNO, "%s", rcpath);
1.54 xsa 416: return;
417: }
1.1 jfb 418:
419: fp = fopen(rcpath, "r");
420: if (fp == NULL) {
421: if (errno != ENOENT)
422: cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
423: strerror(errno));
424: return;
425: }
426:
1.84 xsa 427: while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
1.23 xsa 428: linenum++;
1.17 jfb 429: if ((len = strlen(linebuf)) == 0)
430: continue;
431: if (linebuf[len - 1] != '\n') {
1.98 joris 432: cvs_log(LP_ERR, "line too long in `%s:%d'", rcpath,
1.23 xsa 433: linenum);
1.17 jfb 434: break;
435: }
436: linebuf[--len] = '\0';
437:
1.70 joris 438: /* skip any whitespaces */
439: p = linebuf;
440: while (*p == ' ')
1.95 deraadt 441: p++;
1.70 joris 442:
443: /* allow comments */
444: if (*p == '#')
445: continue;
446:
447: lp = strchr(p, ' ');
1.1 jfb 448: if (lp == NULL)
1.17 jfb 449: continue; /* ignore lines with no arguments */
450: *lp = '\0';
1.70 joris 451: if (strcmp(p, "cvs") == 0) {
1.17 jfb 452: /*
453: * Global default options. In the case of cvs only,
454: * we keep the 'cvs' string as first argument because
455: * getopt() does not like starting at index 0 for
456: * argument processing.
457: */
458: *lp = ' ';
1.88 joris 459: cvs_defargs = xstrdup(p);
1.15 deraadt 460: } else {
1.17 jfb 461: lp++;
1.70 joris 462: cmdp = cvs_findcmd(p);
1.1 jfb 463: if (cmdp == NULL) {
464: cvs_log(LP_NOTICE,
1.25 xsa 465: "unknown command `%s' in `%s:%d'",
1.70 joris 466: p, rcpath, linenum);
1.1 jfb 467: continue;
468: }
1.17 jfb 469:
1.88 joris 470: cmdp->cmd_defargs = xstrdup(lp);
1.1 jfb 471: }
472: }
1.98 joris 473:
1.1 jfb 474: if (ferror(fp)) {
1.23 xsa 475: cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
1.1 jfb 476: }
477:
478: (void)fclose(fp);
1.27 jfb 479: }
480:
481: /*
482: * cvs_var_set()
483: *
484: * Set the value of the variable <var> to <val>. If there is no such variable,
485: * a new entry is created, otherwise the old value is overwritten.
486: * Returns 0 on success, or -1 on failure.
487: */
488: int
489: cvs_var_set(const char *var, const char *val)
490: {
491: char *valcp;
492: const char *cp;
493: struct cvs_var *vp;
494:
1.97 deraadt 495: if (var == NULL || *var == '\0') {
1.27 jfb 496: cvs_log(LP_ERR, "no variable name");
497: return (-1);
498: }
499:
500: /* sanity check on the name */
501: for (cp = var; *cp != '\0'; cp++)
502: if (!isalnum(*cp) && (*cp != '_')) {
503: cvs_log(LP_ERR,
504: "variable name `%s' contains invalid characters",
505: var);
506: return (-1);
507: }
508:
509: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
510: if (strcmp(vp->cv_name, var) == 0)
511: break;
512:
1.88 joris 513: valcp = xstrdup(val);
1.27 jfb 514: if (vp == NULL) {
1.96 ray 515: vp = xcalloc(1, sizeof(*vp));
1.27 jfb 516:
1.88 joris 517: vp->cv_name = xstrdup(var);
1.27 jfb 518: TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
519:
520: } else /* free the previous value */
1.88 joris 521: xfree(vp->cv_val);
1.27 jfb 522:
523: vp->cv_val = valcp;
524:
525: return (0);
526: }
527:
528: /*
1.118 otto 529: * cvs_var_unset()
1.27 jfb 530: *
531: * Remove any entry for the variable <var>.
532: * Returns 0 on success, or -1 on failure.
533: */
534: int
535: cvs_var_unset(const char *var)
536: {
537: struct cvs_var *vp;
538:
539: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
540: if (strcmp(vp->cv_name, var) == 0) {
541: TAILQ_REMOVE(&cvs_variables, vp, cv_link);
1.88 joris 542: xfree(vp->cv_name);
543: xfree(vp->cv_val);
544: xfree(vp);
1.27 jfb 545: return (0);
546: }
547:
548: return (-1);
549: }
550:
551: /*
552: * cvs_var_get()
553: *
554: * Get the value associated with the variable <var>. Returns a pointer to the
555: * value string on success, or NULL if the variable does not exist.
556: */
557:
1.75 xsa 558: const char *
1.27 jfb 559: cvs_var_get(const char *var)
560: {
561: struct cvs_var *vp;
562:
563: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
564: if (strcmp(vp->cv_name, var) == 0)
565: return (vp->cv_val);
566:
567: return (NULL);
1.1 jfb 568: }