xref: /openbsd-src/usr.bin/cvs/root.c (revision 0eea0d082377cb9c3ec583313dc4d52b7b6a4d6d)
1 /*	$OpenBSD: root.c,v 1.9 2004/08/02 22:45:57 jfb 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 <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]))
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 
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:
79  *	:method:[[user[:pass]@host]:path
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;
89 	void *tmp;
90 	struct cvsroot *root;
91 
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 
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 	}
104 	memset(root, 0, sizeof(*root));
105 	root->cr_ref = 2;
106 	root->cr_method = CVS_METHOD_NONE;
107 
108 	root->cr_str = strdup(str);
109 	if (root->cr_str == NULL) {
110 		free(root);
111 		return (NULL);
112 	}
113 	root->cr_buf = strdup(str);
114 	if (root->cr_buf == NULL) {
115 		cvs_log(LP_ERRNO, "failed to copy CVS root");
116 		cvsroot_free(root);
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");
128 			cvsroot_free(root);
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");
145 		cvsroot_free(root);
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");
159 		cvsroot_free(root);
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");
189 			cvsroot_free(root);
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 
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 
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 {
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 		if (root->cr_version != NULL)
237 			free(root->cr_version);
238 		free(root);
239 	}
240 }
241 
242 
243 /*
244  * cvsroot_get()
245  *
246  * Get the CVSROOT information for a specific directory <dir>.  The
247  * value is taken from one of 3 possible sources (in order of precedence):
248  *
249  * 1) the `-d' command-line option
250  * 2) the CVS/Root file found in checked-out trees
251  * 3) the CVSROOT environment variable
252  */
253 
254 struct cvsroot*
255 cvsroot_get(const char *dir)
256 {
257 	size_t len;
258 	char rootpath[MAXPATHLEN], *rootstr, line[128];
259 	FILE *fp;
260 
261 	if (cvs_rootstr != NULL)
262 		return cvsroot_parse(cvs_rootstr);
263 
264 	snprintf(rootpath, sizeof(rootpath), "%s/" CVS_PATH_ROOTSPEC, dir);
265 	fp = fopen(rootpath, "r");
266 	if (fp == NULL) {
267 		if (errno == ENOENT) {
268 			/* try env as a last resort */
269 			if ((rootstr = getenv("CVSROOT")) != NULL)
270 				return cvsroot_parse(rootstr);
271 			else
272 				return (NULL);
273 		}
274 		else {
275 			cvs_log(LP_ERRNO, "failed to open CVS/Root");
276 			return (NULL);
277 		}
278 	}
279 
280 	if (fgets(line, sizeof(line), fp) == NULL) {
281 		cvs_log(LP_ERR, "failed to read CVSROOT line from CVS/Root");
282 		(void)fclose(fp);
283 		return (NULL);
284 	}
285 	(void)fclose(fp);
286 
287 	len = strlen(line);
288 	if (len == 0) {
289 		cvs_log(LP_WARN, "empty CVS/Root file");
290 	}
291 	else if (line[len - 1] == '\n')
292 		line[--len] = '\0';
293 
294 	return cvsroot_parse(line);
295 }
296