xref: /openbsd-src/gnu/usr.bin/cvs/src/expand_path.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
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 
18 static char *expand_variable PROTO((char *env, char *file, int line));
19 
20 
21 /* User variables.  */
22 
23 List *variable_list = NULL;
24 
25 static void variable_delproc PROTO ((Node *));
26 
27 static void
28 variable_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 
37 void
38 variable_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.  */
90 char *
91 expand_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 
268 static char *
269 expand_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 (isalpha ((unsigned char) name[0]))
290     {
291 	/* These names are reserved for future versions of CVS,
292 	   so that is why it is an error.  */
293 	if (line != 0)
294 	    error (0, 0, "%s:%d: no such internal variable $%s",
295 		   file, line, name);
296 	else
297 	    error (0, 0, "%s: no such internal variable $%s",
298 		   file, name);
299 	return NULL;
300     }
301     else if (name[0] == '=')
302     {
303 	Node *node;
304 	/* Crazy syntax for a user variable.  But we want
305 	   *something* that lets the user name a user variable
306 	   anything he wants, without interference from
307 	   (existing or future) internal variables.  */
308 	node = findnode (variable_list, name + 1);
309 	if (node == NULL)
310 	{
311 	    if (line != 0)
312 		error (0, 0, "%s:%d: no such user variable ${%s}",
313 		       file, line, name);
314 	    else
315 		error (0, 0, "%s: no such user variable ${%s}",
316 		       file, name);
317 	    return NULL;
318 	}
319 	return node->data;
320     }
321     else
322     {
323 	/* It is an unrecognized character.  We return an error to
324 	   reserve these for future versions of CVS; it is plausible
325 	   that various crazy syntaxes might be invented for inserting
326 	   information about revisions, branches, etc.  */
327 	if (line != 0)
328 	    error (0, 0, "%s:%d: unrecognized variable syntax %s",
329 		   file, line, name);
330 	else
331 	    error (0, 0, "%s: unrecognized variable syntax %s",
332 		   file, name);
333 	return NULL;
334     }
335 }
336