Annotation of src/usr.bin/cvs/cvs.c, Revision 1.72
1.72 ! joris 1: /* $OpenBSD: cvs.c,v 1.71 2005/06/16 07:46:42 xsa Exp $ */
1.1 jfb 2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
1.18 tedu 4: * All rights reserved.
1.1 jfb 5: *
1.18 tedu 6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
1.1 jfb 9: *
1.18 tedu 10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
1.1 jfb 12: * 2. The name of the author may not be used to endorse or promote products
1.18 tedu 13: * derived from this software without specific prior written permission.
1.1 jfb 14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
1.18 tedu 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 jfb 25: */
26:
27: #include <sys/types.h>
28: #include <sys/wait.h>
29:
1.68 xsa 30: #include <ctype.h>
1.1 jfb 31: #include <err.h>
1.68 xsa 32: #include <errno.h>
1.1 jfb 33: #include <pwd.h>
1.68 xsa 34: #include <signal.h>
1.1 jfb 35: #include <stdio.h>
36: #include <stdlib.h>
1.68 xsa 37: #include <string.h>
1.1 jfb 38: #include <unistd.h>
39:
40: #include "cvs.h"
41: #include "log.h"
1.8 jfb 42: #include "file.h"
1.44 jfb 43: #include "strtab.h"
1.1 jfb 44:
45:
46: extern char *__progname;
47:
48:
49: /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
50: int verbosity = 2;
51:
52: /* compression level used with zlib, 0 meaning no compression taking place */
53: int cvs_compress = 0;
1.17 jfb 54: int cvs_readrc = 1; /* read .cvsrc on startup */
1.1 jfb 55: int cvs_trace = 0;
56: int cvs_nolog = 0;
57: int cvs_readonly = 0;
1.17 jfb 58: int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */
1.65 xsa 59: int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */
1.1 jfb 60:
1.17 jfb 61: char *cvs_defargs; /* default global arguments from .cvsrc */
62: char *cvs_command; /* name of the command we are running */
1.6 jfb 63: int cvs_cmdop;
1.1 jfb 64: char *cvs_rootstr;
65: char *cvs_rsh = CVS_RSH_DEFAULT;
66: char *cvs_editor = CVS_EDITOR_DEFAULT;
1.69 joris 67: char *cvs_repo_base = NULL;
1.13 krapht 68: char *cvs_msg = NULL;
69:
1.10 jfb 70: /* hierarchy of all the files affected by the command */
71: CVSFILE *cvs_files;
72:
73:
1.27 jfb 74: static TAILQ_HEAD(, cvs_var) cvs_variables;
75:
1.1 jfb 76:
1.48 jfb 77: void usage (void);
78: static void cvs_read_rcfile (void);
1.72 ! joris 79: int cvs_getopt(int, char **);
1.1 jfb 80:
81: /*
82: * usage()
83: *
84: * Display usage information.
85: */
86: void
87: usage(void)
88: {
89: fprintf(stderr,
1.66 joris 90: "Usage: %s [-flnQqtv] [-d root] [-e editor] [-s var=val] [-z level] "
1.27 jfb 91: "command [...]\n", __progname);
1.1 jfb 92: }
93:
94:
95: int
96: main(int argc, char **argv)
97: {
1.17 jfb 98: char *envstr, *cmd_argv[CVS_CMD_MAXARG], **targv;
99: int i, ret, cmd_argc;
1.1 jfb 100: struct cvs_cmd *cmdp;
101:
1.27 jfb 102: TAILQ_INIT(&cvs_variables);
103:
1.1 jfb 104: if (cvs_log_init(LD_STD, 0) < 0)
105: err(1, "failed to initialize logging");
106:
107: /* by default, be very verbose */
108: (void)cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
109:
110: #ifdef DEBUG
111: (void)cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
112: #endif
113:
1.44 jfb 114: cvs_strtab_init();
115:
1.1 jfb 116: /* check environment so command-line options override it */
117: if ((envstr = getenv("CVS_RSH")) != NULL)
118: cvs_rsh = envstr;
119:
120: if (((envstr = getenv("CVSEDITOR")) != NULL) ||
121: ((envstr = getenv("VISUAL")) != NULL) ||
122: ((envstr = getenv("EDITOR")) != NULL))
123: cvs_editor = envstr;
124:
1.17 jfb 125: ret = cvs_getopt(argc, argv);
126:
127: argc -= ret;
128: argv += ret;
129: if (argc == 0) {
130: usage();
1.49 joris 131: exit(1);
1.17 jfb 132: }
133: cvs_command = argv[0];
134:
135: if (cvs_readrc) {
136: cvs_read_rcfile();
137:
138: if (cvs_defargs != NULL) {
139: targv = cvs_makeargv(cvs_defargs, &i);
140: if (targv == NULL) {
141: cvs_log(LP_ERR,
142: "failed to load default arguments to %s",
143: __progname);
1.47 xsa 144: exit(1);
1.17 jfb 145: }
146:
147: cvs_getopt(i, targv);
148: cvs_freeargv(targv, i);
149: free(targv);
150: }
151: }
152:
153: /* setup signal handlers */
154: signal(SIGPIPE, SIG_IGN);
155:
1.40 jfb 156: if (cvs_file_init() < 0) {
157: cvs_log(LP_ERR, "failed to initialize file support");
158: exit(1);
159: }
1.17 jfb 160:
161: ret = -1;
162:
163: cmdp = cvs_findcmd(cvs_command);
164: if (cmdp == NULL) {
165: fprintf(stderr, "Unknown command: `%s'\n\n", cvs_command);
166: fprintf(stderr, "CVS commands are:\n");
1.67 jfb 167: for (i = 0; cvs_cdt[i] != NULL; i++)
1.17 jfb 168: fprintf(stderr, "\t%-16s%s\n",
1.67 jfb 169: cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
170: exit(CVS_EX_USAGE);
1.17 jfb 171: }
172:
173: cvs_cmdop = cmdp->cmd_op;
174:
175: cmd_argc = 0;
176: memset(cmd_argv, 0, sizeof(cmd_argv));
177:
178: cmd_argv[cmd_argc++] = argv[0];
179: if (cmdp->cmd_defargs != NULL) {
180: /* transform into a new argument vector */
181: ret = cvs_getargv(cmdp->cmd_defargs, cmd_argv + 1,
182: CVS_CMD_MAXARG - 1);
183: if (ret < 0) {
184: cvs_log(LP_ERRNO, "failed to generate argument vector "
185: "from default arguments");
1.47 xsa 186: exit(1);
1.17 jfb 187: }
188: cmd_argc += ret;
189: }
190: for (ret = 1; ret < argc; ret++)
191: cmd_argv[cmd_argc++] = argv[ret];
192:
1.45 joris 193: ret = cvs_startcmd(cmdp, cmd_argc, cmd_argv);
1.51 joris 194: switch (ret) {
195: case CVS_EX_USAGE:
1.56 jfb 196: fprintf(stderr, "Usage: %s %s %s\n", __progname, cvs_command,
197: cmdp->cmd_synopsis);
1.51 joris 198: break;
199: case CVS_EX_DATA:
1.55 jfb 200: cvs_log(LP_ABORT, "internal data error");
1.51 joris 201: break;
202: case CVS_EX_PROTO:
1.55 jfb 203: cvs_log(LP_ABORT, "protocol error");
1.51 joris 204: break;
205: case CVS_EX_FILE:
1.55 jfb 206: cvs_log(LP_ABORT, "an operation on a file or directory failed");
1.60 jfb 207: break;
208: case CVS_EX_BADROOT:
1.71 xsa 209: /* match GNU CVS output, thus the LP_ERR and LP_ABORT codes. */
210: cvs_log(LP_ERR,
1.60 jfb 211: "No CVSROOT specified! Please use the `-d' option");
212: cvs_log(LP_ABORT,
213: "or set the CVSROOT enviroment variable.");
1.51 joris 214: break;
215: default:
216: break;
1.17 jfb 217: }
218:
219: if (cvs_files != NULL)
220: cvs_file_free(cvs_files);
1.33 jfb 221: if (cvs_msg != NULL)
222: free(cvs_msg);
1.44 jfb 223:
224: cvs_strtab_cleanup();
1.17 jfb 225:
226: return (ret);
227: }
228:
229:
230: int
231: cvs_getopt(int argc, char **argv)
232: {
233: int ret;
234: char *ep;
235:
1.27 jfb 236: while ((ret = getopt(argc, argv, "b:d:e:fHlnQqrs:tvz:")) != -1) {
1.1 jfb 237: switch (ret) {
1.11 jfb 238: case 'b':
239: /*
240: * We do not care about the bin directory for RCS files
241: * as this program has no dependencies on RCS programs,
242: * so it is only here for backwards compatibility.
243: */
244: cvs_log(LP_NOTICE, "the -b argument is obsolete");
245: break;
1.1 jfb 246: case 'd':
247: cvs_rootstr = optarg;
248: break;
249: case 'e':
250: cvs_editor = optarg;
251: break;
252: case 'f':
1.17 jfb 253: cvs_readrc = 0;
1.1 jfb 254: break;
255: case 'l':
256: cvs_nolog = 1;
257: break;
258: case 'n':
1.65 xsa 259: cvs_noexec = 1;
1.1 jfb 260: break;
261: case 'Q':
262: verbosity = 0;
263: break;
264: case 'q':
265: /* don't override -Q */
266: if (verbosity > 1)
267: verbosity = 1;
268: break;
269: case 'r':
270: cvs_readonly = 1;
271: break;
1.27 jfb 272: case 's':
273: ep = strchr(optarg, '=');
274: if (ep == NULL) {
275: cvs_log(LP_ERR, "no = in variable assignment");
1.49 joris 276: exit(1);
1.27 jfb 277: }
278: *(ep++) = '\0';
279: if (cvs_var_set(optarg, ep) < 0)
1.49 joris 280: exit(1);
1.27 jfb 281: break;
1.1 jfb 282: case 't':
1.24 jfb 283: (void)cvs_log_filter(LP_FILTER_UNSET, LP_TRACE);
1.1 jfb 284: cvs_trace = 1;
285: break;
286: case 'v':
287: printf("%s\n", CVS_VERSION);
288: exit(0);
289: /* NOTREACHED */
1.11 jfb 290: break;
291: case 'x':
292: /*
293: * Kerberos encryption support, kept for compatibility
294: */
1.1 jfb 295: break;
296: case 'z':
1.18 tedu 297: cvs_compress = (int)strtol(optarg, &ep, 10);
1.1 jfb 298: if (*ep != '\0')
299: errx(1, "error parsing compression level");
300: if (cvs_compress < 0 || cvs_compress > 9)
301: errx(1, "gzip compression level must be "
302: "between 0 and 9");
303: break;
304: default:
305: usage();
1.49 joris 306: exit(1);
1.1 jfb 307: }
308: }
309:
1.17 jfb 310: ret = optind;
1.1 jfb 311: optind = 1;
1.17 jfb 312: optreset = 1; /* for next call */
1.12 jfb 313:
1.1 jfb 314: return (ret);
315: }
316:
317:
318: /*
1.17 jfb 319: * cvs_read_rcfile()
1.1 jfb 320: *
321: * Read the CVS `.cvsrc' file in the user's home directory. If the file
322: * exists, it should contain a list of arguments that should always be given
323: * implicitly to the specified commands.
324: */
1.42 joris 325: static void
1.17 jfb 326: cvs_read_rcfile(void)
1.1 jfb 327: {
1.70 joris 328: char rcpath[MAXPATHLEN], linebuf[128], *lp, *p;
1.54 xsa 329: int l, linenum = 0;
1.17 jfb 330: size_t len;
1.1 jfb 331: struct cvs_cmd *cmdp;
332: struct passwd *pw;
333: FILE *fp;
334:
335: pw = getpwuid(getuid());
336: if (pw == NULL) {
337: cvs_log(LP_NOTICE, "failed to get user's password entry");
338: return;
339: }
340:
1.54 xsa 341: l = snprintf(rcpath, sizeof(rcpath), "%s/%s", pw->pw_dir, CVS_PATH_RC);
342: if (l == -1 || l >= (int)sizeof(rcpath)) {
343: errno = ENAMETOOLONG;
344: cvs_log(LP_ERRNO, "%s", rcpath);
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:
356: while (fgets(linebuf, 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.23 xsa 361: cvs_log(LP_WARN, "line too long in `%s:%d'", rcpath,
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 == ' ')
370: *p++;
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.70 joris 388: cvs_defargs = strdup(p);
1.17 jfb 389: if (cvs_defargs == NULL)
390: cvs_log(LP_ERRNO,
391: "failed to copy global arguments");
1.15 deraadt 392: } else {
1.17 jfb 393: lp++;
1.70 joris 394: cmdp = cvs_findcmd(p);
1.1 jfb 395: if (cmdp == NULL) {
396: cvs_log(LP_NOTICE,
1.25 xsa 397: "unknown command `%s' in `%s:%d'",
1.70 joris 398: p, rcpath, linenum);
1.1 jfb 399: continue;
400: }
1.17 jfb 401:
402: cmdp->cmd_defargs = strdup(lp);
403: if (cmdp->cmd_defargs == NULL)
404: cvs_log(LP_ERRNO,
405: "failed to copy default arguments for %s",
406: cmdp->cmd_name);
1.1 jfb 407: }
408: }
409: if (ferror(fp)) {
1.23 xsa 410: cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
1.1 jfb 411: }
412:
413: (void)fclose(fp);
1.27 jfb 414: }
415:
416:
417: /*
418: * cvs_var_set()
419: *
420: * Set the value of the variable <var> to <val>. If there is no such variable,
421: * a new entry is created, otherwise the old value is overwritten.
422: * Returns 0 on success, or -1 on failure.
423: */
424: int
425: cvs_var_set(const char *var, const char *val)
426: {
427: char *valcp;
428: const char *cp;
429: struct cvs_var *vp;
430:
431: if ((var == NULL) || (*var == '\0')) {
432: cvs_log(LP_ERR, "no variable name");
433: return (-1);
434: }
435:
436: /* sanity check on the name */
437: for (cp = var; *cp != '\0'; cp++)
438: if (!isalnum(*cp) && (*cp != '_')) {
439: cvs_log(LP_ERR,
440: "variable name `%s' contains invalid characters",
441: var);
442: return (-1);
443: }
444:
445: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
446: if (strcmp(vp->cv_name, var) == 0)
447: break;
448:
449: valcp = strdup(val);
450: if (valcp == NULL) {
451: cvs_log(LP_ERRNO, "failed to allocate variable");
452: return (-1);
453: }
454:
455: if (vp == NULL) {
456: vp = (struct cvs_var *)malloc(sizeof(*vp));
457: if (vp == NULL) {
458: cvs_log(LP_ERRNO, "failed to allocate variable");
459: free(valcp);
460: return (-1);
461: }
462: memset(vp, 0, sizeof(*vp));
463:
464: vp->cv_name = strdup(var);
465: if (vp->cv_name == NULL) {
466: cvs_log(LP_ERRNO, "failed to allocate variable");
467: free(valcp);
468: free(vp);
469: return (-1);
470: }
471:
472: TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
473:
474: } else /* free the previous value */
475: free(vp->cv_val);
476:
477: vp->cv_val = valcp;
478:
479: return (0);
480: }
481:
482:
483: /*
484: * cvs_var_set()
485: *
486: * Remove any entry for the variable <var>.
487: * Returns 0 on success, or -1 on failure.
488: */
489: int
490: cvs_var_unset(const char *var)
491: {
492: struct cvs_var *vp;
493:
494: TAILQ_FOREACH(vp, &cvs_variables, cv_link)
495: if (strcmp(vp->cv_name, var) == 0) {
496: TAILQ_REMOVE(&cvs_variables, vp, cv_link);
497: free(vp->cv_name);
498: free(vp->cv_val);
499: free(vp);
500: return (0);
501: }
502:
503: return (-1);
504:
505: }
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:
515: const char*
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: }