xref: /netbsd-src/external/gpl2/xcvs/dist/src/expand_path.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
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