xref: /openbsd/gnu/usr.bin/cvs/src/expand_path.c (revision c133e2ca)
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
variable_delproc(node)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
variable_set(nameval)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 *
expand_path(name,file,line)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 *
expand_variable(name,file,line)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 (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