Annotation of src/usr.bin/cvs/root.c, Revision 1.7
1.1 jfb 1: /* $OpenBSD$ */
2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
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
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
27: #include <sys/types.h>
28:
29: #include <stdlib.h>
30: #include <stdio.h>
31: #include <unistd.h>
32: #include <err.h>
33: #include <errno.h>
34: #include <string.h>
35: #include <paths.h>
36:
37: #include "cvs.h"
38: #include "log.h"
39:
40:
41: extern char *cvs_rootstr;
42:
43:
44: /* keep these ordered with the defines */
45: const char *cvs_methods[] = {
46: "",
47: "local",
48: "ssh",
49: "pserver",
50: "kserver",
51: "gserver",
52: "ext",
53: "fork",
54: };
55:
56: #define CVS_NBMETHODS (sizeof(cvs_methods)/sizeof(cvs_methods[0]))
1.6 jfb 57:
58: /*
59: * CVSROOT cache
60: *
61: * Whenever cvsroot_parse() gets called for a specific string, it first
62: * checks in the cache to see if there is already a parsed version of the
63: * same string and returns a pointer to it in case one is found (it also
64: * increases the reference count). Otherwise, it does the parsing and adds
65: * the result to the cache for future hits.
66: */
67:
68: static struct cvsroot **cvs_rcache = NULL;
69: static u_int cvs_rcsz = 0;
70:
1.1 jfb 71:
72:
73: /*
74: * cvsroot_parse()
75: *
76: * Parse a CVS root string (as found in CVS/Root files or the CVSROOT
77: * environment variable) and store the fields in a dynamically
78: * allocated cvs_root structure. The format of the string is as follows:
1.6 jfb 79: * :method:[[user[:pass]@host]:path
1.1 jfb 80: * Returns a pointer to the allocated information on success, or NULL
81: * on failure.
82: */
83:
84: struct cvsroot*
85: cvsroot_parse(const char *str)
86: {
87: u_int i;
88: char *cp, *sp, *pp;
1.6 jfb 89: void *tmp;
1.1 jfb 90: struct cvsroot *root;
91:
1.6 jfb 92: for (i = 0; i < cvs_rcsz; i++) {
93: if (strcmp(str, cvs_rcache[i]->cr_str) == 0) {
94: cvs_rcache[i]->cr_ref++;
95: return (cvs_rcache[i]);
96: }
97: }
98:
1.1 jfb 99: root = (struct cvsroot *)malloc(sizeof(*root));
100: if (root == NULL) {
101: cvs_log(LP_ERRNO, "failed to allocate CVS root data");
102: return (NULL);
103: }
1.4 jfb 104: memset(root, 0, sizeof(*root));
1.6 jfb 105: root->cr_ref = 2;
1.1 jfb 106: root->cr_method = CVS_METHOD_NONE;
107:
1.6 jfb 108: root->cr_str = strdup(str);
109: if (root->cr_str == NULL) {
110: free(root);
111: return (NULL);
112: }
1.1 jfb 113: root->cr_buf = strdup(str);
114: if (root->cr_buf == NULL) {
115: cvs_log(LP_ERRNO, "failed to copy CVS root");
1.6 jfb 116: cvsroot_free(root);
1.1 jfb 117: return (NULL);
118: }
119:
120: sp = root->cr_buf;
121: cp = root->cr_buf;
122: if (*sp == ':') {
123: sp++;
124: cp = strchr(sp, ':');
125: if (cp == NULL) {
126: cvs_log(LP_ERR, "failed to parse CVSROOT: "
127: "unterminated method");
1.6 jfb 128: cvsroot_free(root);
1.1 jfb 129: return (NULL);
130: }
131: *(cp++) = '\0';
132:
133: for (i = 0; i < CVS_NBMETHODS; i++) {
134: if (strcmp(sp, cvs_methods[i]) == 0) {
135: root->cr_method = i;
136: break;
137: }
138: }
139: }
140:
141: /* find the start of the actual path */
142: sp = strchr(cp, '/');
143: if (sp == NULL) {
144: cvs_log(LP_ERR, "no path specification in CVSROOT");
1.6 jfb 145: cvsroot_free(root);
1.1 jfb 146: return (NULL);
147: }
148:
149: root->cr_dir = sp;
150: if (sp == cp) {
151: if (root->cr_method == CVS_METHOD_NONE)
152: root->cr_method = CVS_METHOD_LOCAL;
153: /* stop here, it's just a path */
154: return (root);
155: }
156:
157: if (*(sp - 1) != ':') {
158: cvs_log(LP_ERR, "missing host/path delimiter in CVS root");
1.6 jfb 159: cvsroot_free(root);
1.1 jfb 160: return (NULL);
161: }
162: *(sp - 1) = '\0';
163:
164: /*
165: * looks like we have more than just a directory path, so
166: * attempt to split it into user and host parts
167: */
168: sp = strchr(cp, '@');
169: if (sp != NULL) {
170: *(sp++) = '\0';
171:
172: /* password ? */
173: pp = strchr(cp, ':');
174: if (pp != NULL) {
175: *(pp++) = '\0';
176: root->cr_pass = pp;
177: }
178:
179: root->cr_user = cp;
180: }
181:
182: pp = strchr(sp, ':');
183: if (pp != NULL) {
184: *(pp++) = '\0';
185: root->cr_port = (u_int)strtol(pp, &cp, 10);
186: if (*cp != '\0' || root->cr_port > 65535) {
187: cvs_log(LP_ERR,
188: "invalid port specification in CVSROOT");
1.6 jfb 189: cvsroot_free(root);
1.1 jfb 190: return (NULL);
191: }
192:
193: }
194:
195: root->cr_host = sp;
196:
197: if (root->cr_method == CVS_METHOD_NONE) {
198: /* no method found from start of CVSROOT, guess */
199: if (root->cr_host != NULL)
200: root->cr_method = CVS_METHOD_SERVER;
201: else
202: root->cr_method = CVS_METHOD_LOCAL;
203: }
204:
1.6 jfb 205: /* add to the cache */
206: tmp = realloc(cvs_rcache, (cvs_rcsz + 1) * sizeof(struct cvsroot *));
207: if (tmp == NULL) {
208: /* just forget about the cache and return anyways */
209: root->cr_ref--;
210: }
211: else {
212: cvs_rcache = (struct cvsroot **)tmp;
213: cvs_rcache[cvs_rcsz++] = root;
214: }
215:
1.1 jfb 216: return (root);
217: }
218:
219:
220: /*
221: * cvsroot_free()
222: *
223: * Free a CVSROOT structure previously allocated and returned by
224: * cvsroot_parse().
225: */
226:
227: void
228: cvsroot_free(struct cvsroot *root)
229: {
1.6 jfb 230: root->cr_ref--;
231: if (root->cr_ref == 0) {
232: if (root->cr_str != NULL)
233: free(root->cr_str);
234: if (root->cr_buf != NULL)
235: free(root->cr_buf);
236: free(root);
237: }
1.1 jfb 238: }
239:
240:
241: /*
242: * cvsroot_get()
243: *
244: * Get the CVSROOT information for a specific directory <dir>. The
245: * value is taken from one of 3 possible sources (in order of precedence):
246: *
247: * 1) the `-d' command-line option
248: * 2) the CVS/Root file found in checked-out trees
249: * 3) the CVSROOT environment variable
250: */
251:
252: struct cvsroot*
253: cvsroot_get(const char *dir)
254: {
255: size_t len;
1.5 jfb 256: char rootpath[MAXPATHLEN], *rootstr, line[128];
1.1 jfb 257: FILE *fp;
258: struct cvsroot *rp;
259:
260: if (cvs_rootstr != NULL)
261: return cvsroot_parse(cvs_rootstr);
262:
263: snprintf(rootpath, sizeof(rootpath), "%s/" CVS_PATH_ROOTSPEC, dir);
264: fp = fopen(rootpath, "r");
265: if (fp == NULL) {
266: if (errno == ENOENT) {
267: /* try env as a last resort */
268: if ((rootstr = getenv("CVSROOT")) != NULL)
269: return cvsroot_parse(rootstr);
270: else
271: return (NULL);
272: }
273: else {
274: cvs_log(LP_ERRNO, "failed to open CVS/Root");
275: return (NULL);
276: }
277: }
278:
1.5 jfb 279: if (fgets(line, sizeof(line), fp) == NULL) {
1.1 jfb 280: cvs_log(LP_ERR, "failed to read CVSROOT line from CVS/Root");
281: (void)fclose(fp);
1.5 jfb 282: return (NULL);
1.1 jfb 283: }
1.5 jfb 284: (void)fclose(fp);
1.1 jfb 285:
1.5 jfb 286: len = strlen(line);
287: if (len == 0) {
288: cvs_log(LP_WARN, "empty CVS/Root file");
1.1 jfb 289: }
1.5 jfb 290: else if (line[len - 1] == '\n')
291: line[--len] = '\0';
1.1 jfb 292:
1.5 jfb 293: return cvsroot_parse(line);
1.1 jfb 294: }