Annotation of src/usr.bin/cvs/root.c, Revision 1.6
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: printf("hitting cache for `%s'\n", str);
! 95: cvs_rcache[i]->cr_ref++;
! 96: return (cvs_rcache[i]);
! 97: }
! 98: }
! 99:
1.1 jfb 100: root = (struct cvsroot *)malloc(sizeof(*root));
101: if (root == NULL) {
102: cvs_log(LP_ERRNO, "failed to allocate CVS root data");
103: return (NULL);
104: }
1.4 jfb 105: memset(root, 0, sizeof(*root));
1.6 ! jfb 106: root->cr_ref = 2;
1.1 jfb 107: root->cr_method = CVS_METHOD_NONE;
108:
1.6 ! jfb 109: root->cr_str = strdup(str);
! 110: if (root->cr_str == NULL) {
! 111: free(root);
! 112: return (NULL);
! 113: }
1.1 jfb 114: root->cr_buf = strdup(str);
115: if (root->cr_buf == NULL) {
116: cvs_log(LP_ERRNO, "failed to copy CVS root");
1.6 ! jfb 117: cvsroot_free(root);
1.1 jfb 118: return (NULL);
119: }
120:
121: sp = root->cr_buf;
122: cp = root->cr_buf;
123: if (*sp == ':') {
124: sp++;
125: cp = strchr(sp, ':');
126: if (cp == NULL) {
127: cvs_log(LP_ERR, "failed to parse CVSROOT: "
128: "unterminated method");
1.6 ! jfb 129: cvsroot_free(root);
1.1 jfb 130: return (NULL);
131: }
132: *(cp++) = '\0';
133:
134: for (i = 0; i < CVS_NBMETHODS; i++) {
135: if (strcmp(sp, cvs_methods[i]) == 0) {
136: root->cr_method = i;
137: break;
138: }
139: }
140: }
141:
142: /* find the start of the actual path */
143: sp = strchr(cp, '/');
144: if (sp == NULL) {
145: cvs_log(LP_ERR, "no path specification in CVSROOT");
1.6 ! jfb 146: cvsroot_free(root);
1.1 jfb 147: return (NULL);
148: }
149:
150: root->cr_dir = sp;
151: if (sp == cp) {
152: if (root->cr_method == CVS_METHOD_NONE)
153: root->cr_method = CVS_METHOD_LOCAL;
154: /* stop here, it's just a path */
155: return (root);
156: }
157:
158: if (*(sp - 1) != ':') {
159: cvs_log(LP_ERR, "missing host/path delimiter in CVS root");
1.6 ! jfb 160: cvsroot_free(root);
1.1 jfb 161: return (NULL);
162: }
163: *(sp - 1) = '\0';
164:
165: /*
166: * looks like we have more than just a directory path, so
167: * attempt to split it into user and host parts
168: */
169: sp = strchr(cp, '@');
170: if (sp != NULL) {
171: *(sp++) = '\0';
172:
173: /* password ? */
174: pp = strchr(cp, ':');
175: if (pp != NULL) {
176: *(pp++) = '\0';
177: root->cr_pass = pp;
178: }
179:
180: root->cr_user = cp;
181: }
182:
183: pp = strchr(sp, ':');
184: if (pp != NULL) {
185: *(pp++) = '\0';
186: root->cr_port = (u_int)strtol(pp, &cp, 10);
187: if (*cp != '\0' || root->cr_port > 65535) {
188: cvs_log(LP_ERR,
189: "invalid port specification in CVSROOT");
1.6 ! jfb 190: cvsroot_free(root);
1.1 jfb 191: return (NULL);
192: }
193:
194: }
195:
196: root->cr_host = sp;
197:
198: if (root->cr_method == CVS_METHOD_NONE) {
199: /* no method found from start of CVSROOT, guess */
200: if (root->cr_host != NULL)
201: root->cr_method = CVS_METHOD_SERVER;
202: else
203: root->cr_method = CVS_METHOD_LOCAL;
204: }
205:
1.6 ! jfb 206: /* add to the cache */
! 207: tmp = realloc(cvs_rcache, (cvs_rcsz + 1) * sizeof(struct cvsroot *));
! 208: if (tmp == NULL) {
! 209: /* just forget about the cache and return anyways */
! 210: root->cr_ref--;
! 211: }
! 212: else {
! 213: cvs_rcache = (struct cvsroot **)tmp;
! 214: cvs_rcache[cvs_rcsz++] = root;
! 215: }
! 216:
1.1 jfb 217: return (root);
218: }
219:
220:
221: /*
222: * cvsroot_free()
223: *
224: * Free a CVSROOT structure previously allocated and returned by
225: * cvsroot_parse().
226: */
227:
228: void
229: cvsroot_free(struct cvsroot *root)
230: {
1.6 ! jfb 231: root->cr_ref--;
! 232: if (root->cr_ref == 0) {
! 233: if (root->cr_str != NULL)
! 234: free(root->cr_str);
! 235: if (root->cr_buf != NULL)
! 236: free(root->cr_buf);
! 237: free(root);
! 238: }
1.1 jfb 239: }
240:
241:
242: /*
243: * cvsroot_get()
244: *
245: * Get the CVSROOT information for a specific directory <dir>. The
246: * value is taken from one of 3 possible sources (in order of precedence):
247: *
248: * 1) the `-d' command-line option
249: * 2) the CVS/Root file found in checked-out trees
250: * 3) the CVSROOT environment variable
251: */
252:
253: struct cvsroot*
254: cvsroot_get(const char *dir)
255: {
256: size_t len;
1.5 jfb 257: char rootpath[MAXPATHLEN], *rootstr, line[128];
1.1 jfb 258: FILE *fp;
259: struct cvsroot *rp;
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:
1.5 jfb 280: if (fgets(line, sizeof(line), fp) == NULL) {
1.1 jfb 281: cvs_log(LP_ERR, "failed to read CVSROOT line from CVS/Root");
282: (void)fclose(fp);
1.5 jfb 283: return (NULL);
1.1 jfb 284: }
1.5 jfb 285: (void)fclose(fp);
1.1 jfb 286:
1.5 jfb 287: len = strlen(line);
288: if (len == 0) {
289: cvs_log(LP_WARN, "empty CVS/Root file");
1.1 jfb 290: }
1.5 jfb 291: else if (line[len - 1] == '\n')
292: line[--len] = '\0';
1.1 jfb 293:
1.5 jfb 294: return cvsroot_parse(line);
1.1 jfb 295: }