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