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
variable_delproc(Node * node)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
variable_set(char * nameval)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 *
expand_variable(const char * name,const char * cvsroot,const char * file,int line)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 *
expand_path(const char * name,const char * cvsroot,bool formatsafe,const char * file,int line)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