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