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