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