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