1 /* expand_path.c -- expand environmental variables in passed in string 2 * 3 * Copyright (C) 1995-2005 The Free Software Foundation, Inc. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2, or (at your option) 8 * any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * The main routine is expand_path(), it is the routine that handles 16 * the '~' character in four forms: 17 * ~name 18 * ~name/ 19 * ~/ 20 * ~ 21 * and handles environment variables contained within the pathname 22 * which are defined by: 23 * ${var_name} (var_name is the name of the environ variable) 24 * $var_name (var_name ends w/ non-alphanumeric char other than '_') 25 */ 26 27 #include "cvs.h" 28 #include <sys/types.h> 29 30 /* User variables. */ 31 32 List *variable_list; 33 34 static void variable_delproc (Node *); 35 36 static void 37 variable_delproc (Node *node) 38 { 39 free (node->data); 40 } 41 42 /* Currently used by -s option; we might want a way to set user 43 variables in a file in the $CVSROOT/CVSROOT directory too. */ 44 45 void 46 variable_set (char *nameval) 47 { 48 char *p; 49 char *name; 50 Node *node; 51 52 p = nameval; 53 while (isalnum ((unsigned char) *p) || *p == '_') 54 ++p; 55 if (*p != '=') 56 error (1, 0, "invalid character in user variable name in %s", nameval); 57 if (p == nameval) 58 error (1, 0, "empty user variable name in %s", nameval); 59 name = xmalloc (p - nameval + 1); 60 strncpy (name, nameval, p - nameval); 61 name[p - nameval] = '\0'; 62 /* Make p point to the value. */ 63 ++p; 64 if (strchr (p, '\012')) 65 error (1, 0, "linefeed in user variable value in %s", nameval); 66 67 if (!variable_list) 68 variable_list = getlist (); 69 70 node = findnode (variable_list, name); 71 if (!node) 72 { 73 node = getnode (); 74 node->type = VARIABLE; 75 node->delproc = variable_delproc; 76 node->key = name; 77 node->data = xstrdup (p); 78 (void) addnode (variable_list, node); 79 } 80 else 81 { 82 /* Replace the old value. For example, this means that -s 83 options on the command line override ones from .cvsrc. */ 84 free (node->data); 85 node->data = xstrdup (p); 86 free (name); 87 } 88 } 89 90 91 92 /* Expand variable NAME into its contents, per the rules above. 93 * 94 * CVSROOT is used to expanding $CVSROOT. 95 * 96 * RETURNS 97 * A pointer to the requested variable contents or NULL when the requested 98 * variable is not found. 99 * 100 * ERRORS 101 * None, though this function may generate warning messages when NAME is not 102 * found. 103 */ 104 static const char * 105 expand_variable (const char *name, const char *cvsroot, 106 const char *file, int line) 107 { 108 if (!strcmp (name, CVSROOT_ENV)) 109 return cvsroot; 110 else if (!strcmp (name, "RCSBIN")) 111 { 112 error (0, 0, "RCSBIN internal variable is no longer supported"); 113 return NULL; 114 } 115 else if (!strcmp (name, EDITOR1_ENV)) 116 return Editor; 117 else if (!strcmp (name, EDITOR2_ENV)) 118 return Editor; 119 else if (!strcmp (name, EDITOR3_ENV)) 120 return Editor; 121 else if (!strcmp (name, "USER")) 122 return getcaller (); 123 else if (!strcmp (name, "SESSIONID") 124 || !strcmp (name, "COMMITID")) 125 return global_session_id; 126 else if (isalpha (name[0])) 127 { 128 /* These names are reserved for future versions of CVS, 129 so that is why it is an error. */ 130 if (line) 131 error (0, 0, "%s:%d: no such internal variable $%s", 132 file, line, name); 133 else 134 error (0, 0, "%s: no such internal variable $%s", 135 file, name); 136 return NULL; 137 } 138 else if (name[0] == '=') 139 { 140 Node *node; 141 /* Crazy syntax for a user variable. But we want 142 *something* that lets the user name a user variable 143 anything he wants, without interference from 144 (existing or future) internal variables. */ 145 node = findnode (variable_list, name + 1); 146 if (!node) 147 { 148 if (line) 149 error (0, 0, "%s:%d: no such user variable ${%s}", 150 file, line, name); 151 else 152 error (0, 0, "%s: no such user variable ${%s}", 153 file, name); 154 return NULL; 155 } 156 return node->data; 157 } 158 else 159 { 160 /* It is an unrecognized character. We return an error to 161 reserve these for future versions of CVS; it is plausible 162 that various crazy syntaxes might be invented for inserting 163 information about revisions, branches, etc. */ 164 if (line) 165 error (0, 0, "%s:%d: unrecognized variable syntax %s", 166 file, line, name); 167 else 168 error (0, 0, "%s: unrecognized variable syntax %s", 169 file, name); 170 return NULL; 171 } 172 } 173 174 175 176 /* This routine will expand the pathname to account for ~ and $ 177 * characters as described above. Returns a pointer to a newly 178 * malloc'd string. If an error occurs, an error message is printed 179 * via error() and NULL is returned. FILE and LINE are the filename 180 * and linenumber to include in the error message. FILE must point 181 * to something; LINE can be zero to indicate the line number is not 182 * known. 183 * 184 * When FORMATSAFE is set, percent signs (`%') in variable contents are doubled 185 * to prevent later expansion by format_cmdline. 186 * 187 * CVSROOT is used to expanding $CVSROOT. 188 */ 189 char * 190 expand_path (const char *name, const char *cvsroot, bool formatsafe, 191 const char *file, int line) 192 { 193 size_t s, d, p; 194 const char *e; 195 196 char *mybuf = NULL; 197 size_t mybuf_size = 0; 198 char *buf = NULL; 199 size_t buf_size = 0; 200 201 char inquotes = '\0'; 202 203 char *result; 204 205 /* Sorry this routine is so ugly; it is a head-on collision 206 between the `traditional' unix *d++ style and the need to 207 dynamically allocate. It would be much cleaner (and probably 208 faster, not that this is a bottleneck for CVS) with more use of 209 strcpy & friends, but I haven't taken the effort to rewrite it 210 thusly. */ 211 212 /* First copy from NAME to MYBUF, expanding $<foo> as we go. */ 213 s = d = 0; 214 expand_string (&mybuf, &mybuf_size, d + 1); 215 while ((mybuf[d++] = name[s]) != '\0') 216 { 217 if (name[s] == '\\') 218 { 219 /* The next character is a literal. Leave the \ in the string 220 * since it will be needed again when the string is split into 221 * arguments. 222 */ 223 /* if we have a \ as the last character of the string, just leave 224 * it there - this is where we would set the escape flag to tell 225 * our parent we want another line if we cared. 226 */ 227 if (name[++s]) 228 { 229 expand_string (&mybuf, &mybuf_size, d + 1); 230 mybuf[d++] = name[s++]; 231 } 232 } 233 /* skip $ variable processing for text inside single quotes */ 234 else if (inquotes == '\'') 235 { 236 if (name[s++] == '\'') 237 { 238 inquotes = '\0'; 239 } 240 } 241 else if (name[s] == '\'') 242 { 243 s++; 244 inquotes = '\''; 245 } 246 else if (name[s] == '"') 247 { 248 s++; 249 if (inquotes) inquotes = '\0'; 250 else inquotes = '"'; 251 } 252 else if (name[s++] == '$') 253 { 254 int flag = (name[s] == '{'); 255 p = d; 256 257 expand_string (&mybuf, &mybuf_size, d + 1); 258 for (; (mybuf[d++] = name[s]); s++) 259 { 260 if (flag 261 ? name[s] =='}' 262 : !isalnum (name[s]) && name[s] != '_') 263 break; 264 expand_string (&mybuf, &mybuf_size, d + 1); 265 } 266 mybuf[--d] = '\0'; 267 e = expand_variable (&mybuf[p+flag], cvsroot, file, line); 268 269 if (e) 270 { 271 expand_string (&mybuf, &mybuf_size, d + 1); 272 for (d = p - 1; (mybuf[d++] = *e++); ) 273 { 274 expand_string (&mybuf, &mybuf_size, d + 1); 275 if (mybuf[d-1] == '"') 276 { 277 /* escape the double quotes if we're between a matched 278 * pair of double quotes so that this sub will be 279 * passed inside as or as part of a single argument 280 * during the argument split later. 281 */ 282 if (inquotes) 283 { 284 mybuf[d-1] = '\\'; 285 expand_string (&mybuf, &mybuf_size, d + 1); 286 mybuf[d++] = '"'; 287 } 288 } 289 else if (formatsafe && mybuf[d-1] == '%') 290 { 291 /* escape '%' to get past printf style format strings 292 * later (in make_cmdline). 293 */ 294 expand_string (&mybuf, &mybuf_size, d + 1); 295 mybuf[d] = '%'; 296 d++; 297 } 298 } 299 --d; 300 if (flag && name[s]) 301 s++; 302 } 303 else 304 /* expand_variable has already printed an error message. */ 305 goto error_exit; 306 } 307 expand_string (&mybuf, &mybuf_size, d + 1); 308 } 309 expand_string (&mybuf, &mybuf_size, d + 1); 310 mybuf[d] = '\0'; 311 312 /* Then copy from MYBUF to BUF, expanding ~. */ 313 s = d = 0; 314 /* If you don't want ~username ~/ to be expanded simply remove 315 * This entire if statement including the else portion 316 */ 317 if (mybuf[s] == '~') 318 { 319 p = d; 320 while (mybuf[++s] != '/' && mybuf[s] != '\0') 321 { 322 expand_string (&buf, &buf_size, p + 1); 323 buf[p++] = name[s]; 324 } 325 expand_string (&buf, &buf_size, p + 1); 326 buf[p] = '\0'; 327 328 if (p == d) 329 e = get_homedir (); 330 else 331 { 332 #ifdef GETPWNAM_MISSING 333 if (line) 334 error (0, 0, 335 "%s:%d:tilde expansion not supported on this system", 336 file, line); 337 else 338 error (0, 0, "%s:tilde expansion not supported on this system", 339 file); 340 goto error_exit; 341 #else 342 struct passwd *ps; 343 ps = getpwnam (buf + d); 344 if (ps == NULL) 345 { 346 if (line) 347 error (0, 0, "%s:%d: no such user %s", 348 file, line, buf + d); 349 else 350 error (0, 0, "%s: no such user %s", file, buf + d); 351 goto error_exit; 352 } 353 e = ps->pw_dir; 354 #endif 355 } 356 if (!e) 357 error (1, 0, "cannot find home directory"); 358 359 p = strlen (e); 360 expand_string (&buf, &buf_size, d + p); 361 memcpy (buf + d, e, p); 362 d += p; 363 } 364 /* Kill up to here */ 365 p = strlen (mybuf + s) + 1; 366 expand_string (&buf, &buf_size, d + p); 367 memcpy (buf + d, mybuf + s, p); 368 369 /* OK, buf contains the value we want to return. Clean up and return 370 it. */ 371 free (mybuf); 372 /* Save a little memory with xstrdup; buf will tend to allocate 373 more than it needs to. */ 374 result = xstrdup (buf); 375 free (buf); 376 return result; 377 378 error_exit: 379 if (mybuf) free (mybuf); 380 if (buf) free (buf); 381 return NULL; 382 } 383