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