xref: /openbsd-src/usr.bin/cvs/root.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: root.c,v 1.34 2007/01/18 22:52:23 niallo Exp $	*/
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 "includes.h"
28 
29 #include "cvs.h"
30 #include "log.h"
31 
32 extern char *cvs_rootstr;
33 
34 /* keep these ordered with the defines */
35 const char *cvs_methods[] = {
36 	"",
37 	"local",
38 	"ssh",
39 	"pserver",
40 	"kserver",
41 	"gserver",
42 	"ext",
43 	"fork",
44 };
45 
46 #define CVS_NBMETHODS	(sizeof(cvs_methods)/sizeof(cvs_methods[0]))
47 
48 /*
49  * CVSROOT cache
50  *
51  * Whenever cvsroot_parse() gets called for a specific string, it first
52  * checks in the cache to see if there is already a parsed version of the
53  * same string and returns a pointer to it in case one is found (it also
54  * increases the reference count).  Otherwise, it does the parsing and adds
55  * the result to the cache for future hits.
56  */
57 static TAILQ_HEAD(, cvsroot) cvs_rcache = TAILQ_HEAD_INITIALIZER(cvs_rcache);
58 static void cvsroot_free(struct cvsroot *);
59 
60 /*
61  * cvsroot_parse()
62  *
63  * Parse a CVS root string (as found in CVS/Root files or the CVSROOT
64  * environment variable) and store the fields in a dynamically
65  * allocated cvs_root structure.  The format of the string is as follows:
66  *	:method:[[user[:pass]@host]:path
67  * Returns a pointer to the allocated information on success, or NULL
68  * on failure.
69  */
70 struct cvsroot *
71 cvsroot_parse(const char *str)
72 {
73 	u_int i;
74 	char *cp, *sp, *pp;
75 	struct cvsroot *root;
76 
77 	/*
78 	 * Look if we have it in cache, if we found it add it to the cache
79 	 * at the first position again.
80 	 */
81 	TAILQ_FOREACH(root, &cvs_rcache, root_cache) {
82 		if (root->cr_str != NULL && strcmp(str, root->cr_str) == 0) {
83 			TAILQ_REMOVE(&cvs_rcache, root, root_cache);
84 			TAILQ_INSERT_HEAD(&cvs_rcache, root, root_cache);
85 			root->cr_ref++;
86 			return (root);
87 		}
88 	}
89 
90 	root = xcalloc(1, sizeof(*root));
91 	root->cr_ref = 1;
92 	root->cr_method = CVS_METHOD_NONE;
93 	CVS_RSTVR(root);
94 
95 	root->cr_str = xstrdup(str);
96 	root->cr_buf = xstrdup(str);
97 
98 	sp = root->cr_buf;
99 	cp = root->cr_buf;
100 	if (*sp == ':') {
101 		sp++;
102 		if ((cp = strchr(sp, ':')) == NULL)
103 			fatal("failed to parse CVSROOT: unterminated method");
104 
105 		*(cp++) = '\0';
106 
107 		for (i = 0; i < CVS_NBMETHODS; i++) {
108 			if (strcmp(sp, cvs_methods[i]) == 0) {
109 				root->cr_method = i;
110 				break;
111 			}
112 		}
113 		if (i == CVS_NBMETHODS)
114 			fatal("cvsroot_parse: unknown method `%s'", sp);
115 	}
116 
117 	/* find the start of the actual path */
118 	if ((sp = strchr(cp, '/')) == NULL)
119 		fatal("no path specification in CVSROOT");
120 
121 	root->cr_dir = sp;
122 	STRIP_SLASH(root->cr_dir);
123 	if (sp == cp) {
124 		if (root->cr_method == CVS_METHOD_NONE)
125 			root->cr_method = CVS_METHOD_LOCAL;
126 		/* stop here, it's just a path */
127 		TAILQ_INSERT_HEAD(&cvs_rcache, root, root_cache);
128 		return (root);
129 	}
130 
131 	if (*(sp - 1) != ':')
132 		fatal("missing host/path delimiter in CVSROOT");
133 
134 	*(sp - 1) = '\0';
135 
136 	/*
137 	 * looks like we have more than just a directory path, so
138 	 * attempt to split it into user and host parts
139 	 */
140 	sp = strchr(cp, '@');
141 	if (sp != NULL) {
142 		*(sp++) = '\0';
143 
144 		/* password ? */
145 		pp = strchr(cp, ':');
146 		if (pp != NULL) {
147 			*(pp++) = '\0';
148 			root->cr_pass = pp;
149 		}
150 
151 		root->cr_user = cp;
152 	} else
153 		sp = cp;
154 
155 	pp = strchr(sp, ':');
156 	if (pp != NULL) {
157 		*(pp++) = '\0';
158 		root->cr_port = (u_int)strtol(pp, &cp, 10);
159 		if ((*cp != '\0') || (root->cr_port > 65535))
160 			fatal("invalid port specification in CVSROOT");
161 
162 	}
163 
164 	root->cr_host = sp;
165 
166 	if (root->cr_method == CVS_METHOD_NONE) {
167 		/* no method found from start of CVSROOT, guess */
168 		if (root->cr_host != NULL)
169 			root->cr_method = CVS_METHOD_SERVER;
170 		else
171 			root->cr_method = CVS_METHOD_LOCAL;
172 	}
173 
174 	/* add to the cache */
175 	TAILQ_INSERT_HEAD(&cvs_rcache, root, root_cache);
176 	return (root);
177 }
178 
179 /*
180  * cvsroot_remove()
181  *
182  * Remove a CVSROOT structure from the cache, and free it.
183  */
184 void
185 cvsroot_remove(struct cvsroot *root)
186 {
187 	root->cr_ref--;
188 	if (root->cr_ref == 0) {
189 		TAILQ_REMOVE(&cvs_rcache, root, root_cache);
190 		cvsroot_free(root);
191 	}
192 }
193 
194 /*
195  * cvsroot_free()
196  *
197  * Free a CVSROOT structure previously allocated and returned by
198  * cvsroot_parse().
199  */
200 static void
201 cvsroot_free(struct cvsroot *root)
202 {
203 	if (root->cr_str != NULL)
204 		xfree(root->cr_str);
205 	if (root->cr_buf != NULL)
206 		xfree(root->cr_buf);
207 	if (root->cr_version != NULL)
208 		xfree(root->cr_version);
209 	xfree(root);
210 }
211 
212 /*
213  * cvsroot_get()
214  *
215  * Get the CVSROOT information for a specific directory <dir>.  The
216  * value is taken from one of 3 possible sources (in order of precedence):
217  *
218  * 1) the `-d' command-line option
219  * 2) the CVS/Root file found in checked-out trees
220  * 3) the CVSROOT environment variable
221  */
222 struct cvsroot *
223 cvsroot_get(const char *dir)
224 {
225 	size_t len;
226 	char rootpath[MAXPATHLEN], *rootstr, line[128];
227 	FILE *fp;
228 
229 	if (cvs_rootstr != NULL)
230 		return cvsroot_parse(cvs_rootstr);
231 
232 	if (strlcpy(rootpath, dir, sizeof(rootpath)) >= sizeof(rootpath) ||
233 	    strlcat(rootpath, "/", sizeof(rootpath)) >= sizeof(rootpath) ||
234 	    strlcat(rootpath, CVS_PATH_ROOTSPEC,
235 	    sizeof(rootpath)) >= sizeof(rootpath)) {
236 		errno = ENAMETOOLONG;
237 		fatal("cvsroot_get: %s: %s", rootpath, strerror(errno));
238 	}
239 
240 	if ((fp = fopen(rootpath, "r")) == NULL) {
241 		if (errno == ENOENT) {
242 			/* try env as a last resort */
243 			if ((rootstr = getenv("CVSROOT")) != NULL)
244 				return cvsroot_parse(rootstr);
245 			else
246 				return (NULL);
247 		} else {
248 			fatal("cvsroot_get: fopen: `%s': %s",
249 			    CVS_PATH_ROOTSPEC, strerror(errno));
250 		}
251 	}
252 
253 	if (fgets(line, (int)sizeof(line), fp) == NULL)
254 		fatal("cvsroot_get: fgets: `%s'", CVS_PATH_ROOTSPEC);
255 
256 	(void)fclose(fp);
257 
258 	len = strlen(line);
259 	if (len == 0)
260 		cvs_log(LP_ERR, "empty %s file", CVS_PATH_ROOTSPEC);
261 	else if (line[len - 1] == '\n')
262 		line[--len] = '\0';
263 
264 	return cvsroot_parse(line);
265 }
266