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