1/* expand_path.c -- expand environmental variables in passed in string
2 *
3 * The main routine is expand_path(), it is the routine that handles
4 * the '~' character in four forms:
5 *     ~name
6 *     ~name/
7 *     ~/
8 *     ~
9 * and handles environment variables contained within the pathname
10 * which are defined by:
11 *     ${var_name}   (var_name is the name of the environ variable)
12 *     $var_name     (var_name ends w/ non-alphanumeric char other than '_')
13 */
14
15#include "cvs.h"
16#include <sys/types.h>
17
18static char *expand_variable PROTO((char *env, char *file, int line));
19
20
21/* User variables.  */
22
23List *variable_list = NULL;
24
25static void variable_delproc PROTO ((Node *));
26
27static void
28variable_delproc (node)
29    Node *node;
30{
31    free (node->data);
32}
33
34/* Currently used by -s option; we might want a way to set user
35   variables in a file in the $CVSROOT/CVSROOT directory too.  */
36
37void
38variable_set (nameval)
39    char *nameval;
40{
41    char *p;
42    char *name;
43    Node *node;
44
45    p = nameval;
46    while (isalnum ((unsigned char) *p) || *p == '_')
47	++p;
48    if (*p != '=')
49	error (1, 0, "illegal character in user variable name in %s", nameval);
50    if (p == nameval)
51	error (1, 0, "empty user variable name in %s", nameval);
52    name = xmalloc (p - nameval + 1);
53    strncpy (name, nameval, p - nameval);
54    name[p - nameval] = '\0';
55    /* Make p point to the value.  */
56    ++p;
57    if (strchr (p, '\012') != NULL)
58	error (1, 0, "linefeed in user variable value in %s", nameval);
59
60    if (variable_list == NULL)
61	variable_list = getlist ();
62
63    node = findnode (variable_list, name);
64    if (node == NULL)
65    {
66	node = getnode ();
67	node->type = VARIABLE;
68	node->delproc = variable_delproc;
69	node->key = name;
70	node->data = xstrdup (p);
71	(void) addnode (variable_list, node);
72    }
73    else
74    {
75	/* Replace the old value.  For example, this means that -s
76	   options on the command line override ones from .cvsrc.  */
77	free (node->data);
78	node->data = xstrdup (p);
79	free (name);
80    }
81}
82
83/* This routine will expand the pathname to account for ~ and $
84   characters as described above.  Returns a pointer to a newly
85   malloc'd string.  If an error occurs, an error message is printed
86   via error() and NULL is returned.  FILE and LINE are the filename
87   and linenumber to include in the error message.  FILE must point
88   to something; LINE can be zero to indicate the line number is not
89   known.  */
90char *
91expand_path (name, file, line)
92    char *name;
93    char *file;
94    int line;
95{
96    char *s;
97    char *d;
98
99    char *mybuf = NULL;
100    size_t mybuf_size = 0;
101    char *buf = NULL;
102    size_t buf_size = 0;
103
104    size_t doff;
105
106    char *result;
107
108    /* Sorry this routine is so ugly; it is a head-on collision
109       between the `traditional' unix *d++ style and the need to
110       dynamically allocate.  It would be much cleaner (and probably
111       faster, not that this is a bottleneck for CVS) with more use of
112       strcpy & friends, but I haven't taken the effort to rewrite it
113       thusly.  */
114
115    /* First copy from NAME to MYBUF, expanding $<foo> as we go.  */
116    s = name;
117    d = mybuf;
118    doff = d - mybuf;
119    expand_string (&mybuf, &mybuf_size, doff + 1);
120    d = mybuf + doff;
121    while ((*d++ = *s))
122    {
123	if (*s++ == '$')
124	{
125	    char *p = d;
126	    char *e;
127	    int flag = (*s == '{');
128
129	    doff = d - mybuf;
130	    expand_string (&mybuf, &mybuf_size, doff + 1);
131	    d = mybuf + doff;
132	    for (; (*d++ = *s); s++)
133	    {
134		if (flag
135		    ? *s =='}'
136		    : isalnum ((unsigned char) *s) == 0 && *s != '_')
137		    break;
138		doff = d - mybuf;
139		expand_string (&mybuf, &mybuf_size, doff + 1);
140		d = mybuf + doff;
141	    }
142	    *--d = '\0';
143	    e = expand_variable (&p[flag], file, line);
144
145	    if (e)
146	    {
147		doff = d - mybuf;
148		expand_string (&mybuf, &mybuf_size, doff + 1);
149		d = mybuf + doff;
150		for (d = &p[-1]; (*d++ = *e++);)
151		{
152		    doff = d - mybuf;
153		    expand_string (&mybuf, &mybuf_size, doff + 1);
154		    d = mybuf + doff;
155		}
156		--d;
157		if (flag && *s)
158		    s++;
159	    }
160	    else
161		/* expand_variable has already printed an error message.  */
162		goto error_exit;
163	}
164	doff = d - mybuf;
165	expand_string (&mybuf, &mybuf_size, doff + 1);
166	d = mybuf + doff;
167    }
168    doff = d - mybuf;
169    expand_string (&mybuf, &mybuf_size, doff + 1);
170    d = mybuf + doff;
171    *d = '\0';
172
173    /* Then copy from MYBUF to BUF, expanding ~.  */
174    s = mybuf;
175    d = buf;
176    /* If you don't want ~username ~/ to be expanded simply remove
177     * This entire if statement including the else portion
178     */
179    if (*s++ == '~')
180    {
181	char *t;
182	char *p=s;
183	if (*s=='/' || *s==0)
184	    t = get_homedir ();
185	else
186	{
187#ifdef GETPWNAM_MISSING
188	    for (; *p!='/' && *p; p++)
189		;
190	    *p = 0;
191	    if (line != 0)
192		error (0, 0,
193		       "%s:%d:tilde expansion not supported on this system",
194		       file, line);
195	    else
196		error (0, 0, "%s:tilde expansion not supported on this system",
197		       file);
198	    return NULL;
199#else
200	    struct passwd *ps;
201	    for (; *p!='/' && *p; p++)
202		;
203	    *p = 0;
204	    ps = getpwnam (s);
205	    if (ps == 0)
206	    {
207		if (line != 0)
208		    error (0, 0, "%s:%d: no such user %s",
209			   file, line, s);
210		else
211		    error (0, 0, "%s: no such user %s", file, s);
212		return NULL;
213	    }
214	    t = ps->pw_dir;
215#endif
216	}
217	if (t == NULL)
218	    error (1, 0, "cannot find home directory");
219
220	doff = d - buf;
221	expand_string (&buf, &buf_size, doff + 1);
222	d = buf + doff;
223	while ((*d++ = *t++))
224	{
225	    doff = d - buf;
226	    expand_string (&buf, &buf_size, doff + 1);
227	    d = buf + doff;
228	}
229	--d;
230	if (*p == 0)
231	    *p = '/';	       /* always add / */
232	s=p;
233    }
234    else
235	--s;
236	/* Kill up to here */
237    doff = d - buf;
238    expand_string (&buf, &buf_size, doff + 1);
239    d = buf + doff;
240    while ((*d++ = *s++))
241    {
242	doff = d - buf;
243	expand_string (&buf, &buf_size, doff + 1);
244	d = buf + doff;
245    }
246    doff = d - buf;
247    expand_string (&buf, &buf_size, doff + 1);
248    d = buf + doff;
249    *d = '\0';
250
251    /* OK, buf contains the value we want to return.  Clean up and return
252       it.  */
253    free (mybuf);
254    /* Save a little memory with xstrdup; buf will tend to allocate
255       more than it needs to.  */
256    result = xstrdup (buf);
257    free (buf);
258    return result;
259
260 error_exit:
261    if (mybuf != NULL)
262	free (mybuf);
263    if (buf != NULL)
264	free (buf);
265    return NULL;
266}
267
268static char *
269expand_variable (name, file, line)
270    char *name;
271    char *file;
272    int line;
273{
274    if (strcmp (name, CVSROOT_ENV) == 0)
275	return current_parsed_root->original;
276    else if (strcmp (name, "RCSBIN") == 0)
277    {
278	error (0, 0, "RCSBIN internal variable is no longer supported");
279	return NULL;
280    }
281    else if (strcmp (name, EDITOR1_ENV) == 0)
282	return Editor;
283    else if (strcmp (name, EDITOR2_ENV) == 0)
284	return Editor;
285    else if (strcmp (name, EDITOR3_ENV) == 0)
286	return Editor;
287    else if (strcmp (name, "USER") == 0)
288	return getcaller ();
289    else if (strcmp (name, "SESSIONID") == 0 || strcmp (name, "COMMITID") == 0)
290	return global_session_id;
291    else if (isalpha ((unsigned char) name[0]))
292    {
293	/* These names are reserved for future versions of CVS,
294	   so that is why it is an error.  */
295	if (line != 0)
296	    error (0, 0, "%s:%d: no such internal variable $%s",
297		   file, line, name);
298	else
299	    error (0, 0, "%s: no such internal variable $%s",
300		   file, name);
301	return NULL;
302    }
303    else if (name[0] == '=')
304    {
305	Node *node;
306	/* Crazy syntax for a user variable.  But we want
307	   *something* that lets the user name a user variable
308	   anything he wants, without interference from
309	   (existing or future) internal variables.  */
310	node = findnode (variable_list, name + 1);
311	if (node == NULL)
312	{
313	    if (line != 0)
314		error (0, 0, "%s:%d: no such user variable ${%s}",
315		       file, line, name);
316	    else
317		error (0, 0, "%s: no such user variable ${%s}",
318		       file, name);
319	    return NULL;
320	}
321	return node->data;
322    }
323    else
324    {
325	/* It is an unrecognized character.  We return an error to
326	   reserve these for future versions of CVS; it is plausible
327	   that various crazy syntaxes might be invented for inserting
328	   information about revisions, branches, etc.  */
329	if (line != 0)
330	    error (0, 0, "%s:%d: unrecognized variable syntax %s",
331		   file, line, name);
332	else
333	    error (0, 0, "%s: unrecognized variable syntax %s",
334		   file, name);
335	return NULL;
336    }
337}
338