Sync with FreeBSD. This adds read-only support for zip and ISO9660.
[dragonfly.git] / contrib / cvs-1.12.12 / src / expand_path.c
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 (const char *env, const char *file, int line);
19
20 /* User variables.  */
21
22 List *variable_list = NULL;
23
24 static void variable_delproc (Node *);
25
26 static void
27 variable_delproc (Node *node)
28 {
29     free (node->data);
30 }
31
32 /* Currently used by -s option; we might want a way to set user
33    variables in a file in the $CVSROOT/CVSROOT directory too.  */
34
35 void
36 variable_set (char *nameval)
37 {
38     char *p;
39     char *name;
40     Node *node;
41
42     p = nameval;
43     while (isalnum ((unsigned char) *p) || *p == '_')
44         ++p;
45     if (*p != '=')
46         error ( 1, 0, "invalid character in user variable name in %s",
47                 nameval );
48     if (p == nameval)
49         error (1, 0, "empty user variable name in %s", nameval);
50     name = xmalloc (p - nameval + 1);
51     strncpy (name, nameval, p - nameval);
52     name[p - nameval] = '\0';
53     /* Make p point to the value.  */
54     ++p;
55     if (strchr (p, '\012') != NULL)
56         error (1, 0, "linefeed in user variable value in %s", nameval);
57
58     if (variable_list == NULL)
59         variable_list = getlist ();
60
61     node = findnode (variable_list, name);
62     if (node == NULL)
63     {
64         node = getnode ();
65         node->type = VARIABLE;
66         node->delproc = variable_delproc;
67         node->key = name;
68         node->data = xstrdup (p);
69         (void) addnode (variable_list, node);
70     }
71     else
72     {
73         /* Replace the old value.  For example, this means that -s
74            options on the command line override ones from .cvsrc.  */
75         free (node->data);
76         node->data = xstrdup (p);
77         free (name);
78     }
79 }
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 (const char *name, const char *file, int line, int formatsafe)
92 {
93     size_t s, d, p;
94     char *e;
95
96     char *mybuf = NULL;
97     size_t mybuf_size = 0;
98     char *buf = NULL;
99     size_t buf_size = 0;
100
101     char inquotes = '\0';
102
103     char *result;
104
105     /* Sorry this routine is so ugly; it is a head-on collision
106        between the `traditional' unix *d++ style and the need to
107        dynamically allocate.  It would be much cleaner (and probably
108        faster, not that this is a bottleneck for CVS) with more use of
109        strcpy & friends, but I haven't taken the effort to rewrite it
110        thusly.  */
111
112     /* First copy from NAME to MYBUF, expanding $<foo> as we go.  */
113     s = d = 0;
114     expand_string (&mybuf, &mybuf_size, d + 1);
115     while ((mybuf[d++] = name[s]) != '\0')
116     {
117         if (name[s] == '\\')
118         {
119             /* The next character is a literal.  Leave the \ in the string
120              * since it will be needed again when the string is split into
121              * arguments.
122              */
123             /* if we have a \ as the last character of the string, just leave
124              * it there - this is where we would set the escape flag to tell
125              * our parent we want another line if we cared.
126              */
127             if (name[++s])
128             {
129                 expand_string (&mybuf, &mybuf_size, d + 1);
130                 mybuf[d++] = name[s++];
131             }
132         }
133         /* skip $ variable processing for text inside single quotes */
134         else if (inquotes == '\'')
135         {
136             if (name[s++] == '\'')
137             {
138                 inquotes = '\0';
139             }
140         }
141         else if (name[s] == '\'')
142         {
143             s++;
144             inquotes = '\'';
145         }
146         else if (name[s] == '"')
147         {
148             s++;
149             if (inquotes) inquotes = '\0';
150             else inquotes = '"';
151         }
152         else if (name[s++] == '$')
153         {
154             int flag = (name[s] == '{');
155             p = d;
156
157             expand_string (&mybuf, &mybuf_size, d + 1);
158             for (; (mybuf[d++] = name[s]); s++)
159             {
160                 if (flag
161                     ? name[s] =='}'
162                     : isalnum ((unsigned char) name[s]) == 0 && name[s] != '_')
163                     break;
164                 expand_string (&mybuf, &mybuf_size, d + 1);
165             }
166             mybuf[--d] = '\0';
167             e = expand_variable (&mybuf[p+flag], file, line);
168
169             if (e)
170             {
171                 expand_string (&mybuf, &mybuf_size, d + 1);
172                 for (d = p - 1; (mybuf[d++] = *e++); )
173                 {
174                     expand_string (&mybuf, &mybuf_size, d + 1);
175                     if (mybuf[d-1] == '"')
176                     {
177                         /* escape the double quotes if we're between a matched
178                          * pair of double quotes so that this sub will be
179                          * passed inside as or as part of a single argument
180                          * during the argument split later.
181                          */
182                         if (inquotes)
183                         {
184                             mybuf[d-1] = '\\';
185                             expand_string (&mybuf, &mybuf_size, d + 1);
186                             mybuf[d++] = '"';
187                         }
188                     }
189                     else if (formatsafe && mybuf[d-1] == '%')
190                     {
191                         /* escape '%' to get past printf style format strings
192                          * later (in make_cmdline).
193                          */
194                         expand_string (&mybuf, &mybuf_size, d + 1);
195                         mybuf[d] = mybuf[d-1];
196                         d++;
197                     }
198                 }
199                 --d;
200                 if (flag && name[s])
201                     s++;
202             }
203             else
204                 /* expand_variable has already printed an error message.  */
205                 goto error_exit;
206         }
207         expand_string (&mybuf, &mybuf_size, d + 1);
208     }
209     expand_string (&mybuf, &mybuf_size, d + 1);
210     mybuf[d] = '\0';
211
212     /* Then copy from MYBUF to BUF, expanding ~.  */
213     s = d = 0;
214     /* If you don't want ~username ~/ to be expanded simply remove
215      * This entire if statement including the else portion
216      */
217     if (mybuf[s] == '~')
218     {
219         p = d;
220         while (mybuf[++s] != '/' && mybuf[s] != '\0')
221         {
222             expand_string (&buf, &buf_size, p + 1);
223             buf[p++] = name[s];
224         }
225         expand_string (&buf, &buf_size, p + 1);
226         buf[p] = '\0';
227
228         if (p == d)
229             e = get_homedir ();
230         else
231         {
232 #ifdef GETPWNAM_MISSING
233             if (line != 0)
234                 error (0, 0,
235                        "%s:%d:tilde expansion not supported on this system",
236                        file, line);
237             else
238                 error (0, 0, "%s:tilde expansion not supported on this system",
239                        file);
240             return NULL;
241 #else
242             struct passwd *ps;
243             ps = getpwnam (buf + d);
244             if (ps == 0)
245             {
246                 if (line != 0)
247                     error (0, 0, "%s:%d: no such user %s",
248                            file, line, buf + d);
249                 else
250                     error (0, 0, "%s: no such user %s", file, buf + d);
251                 return NULL;
252             }
253             e = ps->pw_dir;
254 #endif
255         }
256         if (e == NULL)
257             error (1, 0, "cannot find home directory");
258
259         p = strlen(e);
260         expand_string (&buf, &buf_size, d + p);
261         memcpy(buf + d, e, p);
262         d += p;
263     }
264     /* Kill up to here */
265     p = strlen(mybuf + s) + 1;
266     expand_string (&buf, &buf_size, d + p);
267     memcpy(buf + d, mybuf + s, p);
268
269     /* OK, buf contains the value we want to return.  Clean up and return
270        it.  */
271     free (mybuf);
272     /* Save a little memory with xstrdup; buf will tend to allocate
273        more than it needs to.  */
274     result = xstrdup (buf);
275     free (buf);
276     return result;
277
278  error_exit:
279     if (mybuf != NULL)
280         free (mybuf);
281     if (buf != NULL)
282         free (buf);
283     return NULL;
284 }
285
286
287
288 static char *
289 expand_variable (const char *name, const char *file, int line)
290 {
291     if (strcmp (name, CVSROOT_ENV) == 0)
292         return current_parsed_root->directory;
293     else if (strcmp (name, "RCSBIN") == 0)
294     {
295         error (0, 0, "RCSBIN internal variable is no longer supported");
296         return NULL;
297     }
298     else if (strcmp (name, EDITOR1_ENV) == 0)
299         return Editor;
300     else if (strcmp (name, EDITOR2_ENV) == 0)
301         return Editor;
302     else if (strcmp (name, EDITOR3_ENV) == 0)
303         return Editor;
304     else if (strcmp (name, "USER") == 0)
305         return getcaller ();
306     else if (isalpha ((unsigned char) name[0]))
307     {
308         /* These names are reserved for future versions of CVS,
309            so that is why it is an error.  */
310         if (line != 0)
311             error (0, 0, "%s:%d: no such internal variable $%s",
312                    file, line, name);
313         else
314             error (0, 0, "%s: no such internal variable $%s",
315                    file, name);
316         return NULL;
317     }
318     else if (name[0] == '=')
319     {
320         Node *node;
321         /* Crazy syntax for a user variable.  But we want
322            *something* that lets the user name a user variable
323            anything he wants, without interference from
324            (existing or future) internal variables.  */
325         node = findnode (variable_list, name + 1);
326         if (node == NULL)
327         {
328             if (line != 0)
329                 error (0, 0, "%s:%d: no such user variable ${%s}",
330                        file, line, name);
331             else
332                 error (0, 0, "%s: no such user variable ${%s}",
333                        file, name);
334             return NULL;
335         }
336         return node->data;
337     }
338     else
339     {
340         /* It is an unrecognized character.  We return an error to
341            reserve these for future versions of CVS; it is plausible
342            that various crazy syntaxes might be invented for inserting
343            information about revisions, branches, etc.  */
344         if (line != 0)
345             error (0, 0, "%s:%d: unrecognized variable syntax %s",
346                    file, line, name);
347         else
348             error (0, 0, "%s: unrecognized variable syntax %s",
349                    file, name);
350         return NULL;
351     }
352 }