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