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