Bring cvs-1.12.9 into the CVS repository
[dragonfly.git] / contrib / cvs-1.12.9 / 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     const char *s;
94     char *d;
95
96     char *mybuf = NULL;
97     size_t mybuf_size = 0;
98     char *buf = NULL;
99     size_t buf_size = 0;
100
101     size_t doff;
102     char inquotes = '\0';
103
104     char *result;
105
106     /* Sorry this routine is so ugly; it is a head-on collision
107        between the `traditional' unix *d++ style and the need to
108        dynamically allocate.  It would be much cleaner (and probably
109        faster, not that this is a bottleneck for CVS) with more use of
110        strcpy & friends, but I haven't taken the effort to rewrite it
111        thusly.  */
112
113     /* First copy from NAME to MYBUF, expanding $<foo> as we go.  */
114     s = name;
115     d = mybuf;
116     doff = d - mybuf;
117     expand_string (&mybuf, &mybuf_size, doff + 1);
118     d = mybuf + doff;
119     while ((*d++ = *s) != '\0')
120     {
121         if (*s == '\\')
122         {
123             /* The next character is a literal.  Leave the \ in the string since
124              * it will be needed again when the string is split into arguments.
125              */
126             /* if we have a \ as the last character of the string, just leave it there
127              * - this is where we would set the escape flag to tell our parent we want
128              * another line if we cared.
129              */
130             if (*++s)
131             {
132                 doff = d - mybuf;
133                 expand_string (&mybuf, &mybuf_size, doff + 1);
134                 d = mybuf + doff;
135                 *d++ = *s++;
136             }
137         }
138         /* skip $ variable processing for text inside single quotes */
139         else if (inquotes == '\'')
140         {
141             if (*s++ == '\'')
142             {
143                 inquotes = '\0';
144             }
145         }
146         else if (*s == '\'')
147         {
148             s++;
149             inquotes = '\'';
150         }
151         else if (*s == '"')
152         {
153             s++;
154             if (inquotes) inquotes = '\0';
155             else inquotes = '"';
156         }
157         else if (*s++ == '$')
158         {
159             char *p = d;
160             char *e;
161             int flag = (*s == '{');
162
163             doff = d - mybuf;
164             expand_string (&mybuf, &mybuf_size, doff + 1);
165             d = mybuf + doff;
166             for (; (*d++ = *s); s++)
167             {
168                 if (flag
169                     ? *s =='}'
170                     : isalnum ((unsigned char) *s) == 0 && *s != '_')
171                     break;
172                 doff = d - mybuf;
173                 expand_string (&mybuf, &mybuf_size, doff + 1);
174                 d = mybuf + doff;
175             }
176             *--d = '\0';
177             e = expand_variable (&p[flag], file, line);
178
179             if (e)
180             {
181                 doff = d - mybuf;
182                 expand_string (&mybuf, &mybuf_size, doff + 1);
183                 d = mybuf + doff;
184                 for (d = &p[-1]; (*d++ = *e++);)
185                 {
186                     doff = d - mybuf;
187                     expand_string (&mybuf, &mybuf_size, doff + 1);
188                     d = mybuf + doff;
189                     if (d[-1] == '"')
190                     {
191                         /* escape the double quotes if we're between a matched pair of
192                          * double quotes so that this sub will be passed inside as or as
193                          * part of a single argument during the argument split later.
194                          */
195                         if (inquotes)
196                         {
197                             d[-1] = '\\';
198                             doff = d - mybuf;
199                             expand_string (&mybuf, &mybuf_size, doff + 1);
200                             d = mybuf + doff;
201                             *d++ = '"';
202                         }
203                     }
204                     else if (formatsafe && d[-1] == '%')
205                     {
206                         /* escape '%' to get past printf style format strings later
207                          * (in make_cmdline).
208                          */
209                         doff = d - mybuf;
210                         expand_string (&mybuf, &mybuf_size, doff + 1);
211                         d = mybuf + doff;
212                         *d++ = d[-1];
213                     }
214                 }
215                 --d;
216                 if (flag && *s)
217                     s++;
218             }
219             else
220                 /* expand_variable has already printed an error message.  */
221                 goto error_exit;
222         }
223         doff = d - mybuf;
224         expand_string (&mybuf, &mybuf_size, doff + 1);
225         d = mybuf + doff;
226     }
227     doff = d - mybuf;
228     expand_string (&mybuf, &mybuf_size, doff + 1);
229     d = mybuf + doff;
230     *d = '\0';
231
232     /* Then copy from MYBUF to BUF, expanding ~.  */
233     s = mybuf;
234     d = buf;
235     /* If you don't want ~username ~/ to be expanded simply remove
236      * This entire if statement including the else portion
237      */
238     if (*s++ == '~')
239     {
240         char *t;
241         char *p, *pstart;
242         pstart = p = xstrdup (s);
243         if (*pstart=='/' || *pstart==0)
244             t = get_homedir ();
245         else
246         {
247 #ifdef GETPWNAM_MISSING
248             for (; *p!='/' && *p; p++)
249                 ;
250             *p = 0;
251             if (line != 0)
252                 error (0, 0,
253                        "%s:%d:tilde expansion not supported on this system",
254                        file, line);
255             else
256                 error (0, 0, "%s:tilde expansion not supported on this system",
257                        file);
258             return NULL;
259 #else
260             struct passwd *ps;
261             for (; *p!='/' && *p; p++)
262                 ;
263             *p = 0;
264             ps = getpwnam (pstart);
265             if (ps == 0)
266             {
267                 if (line != 0)
268                     error (0, 0, "%s:%d: no such user %s",
269                            file, line, pstart);
270                 else
271                     error (0, 0, "%s: no such user %s", file, pstart);
272                 return NULL;
273             }
274             t = ps->pw_dir;
275 #endif
276         }
277         if (t == NULL)
278             error (1, 0, "cannot find home directory");
279
280         doff = d - buf;
281         expand_string (&buf, &buf_size, doff + 1);
282         d = buf + doff;
283         while ((*d++ = *t++))
284         {
285             doff = d - buf;
286             expand_string (&buf, &buf_size, doff + 1);
287             d = buf + doff;
288         }
289         --d;
290         s+=p-pstart;
291         free (pstart);
292     }
293     else
294         --s;
295         /* Kill up to here */
296     doff = d - buf;
297     expand_string (&buf, &buf_size, doff + 1);
298     d = buf + doff;
299     while ((*d++ = *s++))
300     {
301         doff = d - buf;
302         expand_string (&buf, &buf_size, doff + 1);
303         d = buf + doff;
304     }
305     doff = d - buf;
306     expand_string (&buf, &buf_size, doff + 1);
307     d = buf + doff;
308     *d = '\0';
309
310     /* OK, buf contains the value we want to return.  Clean up and return
311        it.  */
312     free (mybuf);
313     /* Save a little memory with xstrdup; buf will tend to allocate
314        more than it needs to.  */
315     result = xstrdup (buf);
316     free (buf);
317     return result;
318
319  error_exit:
320     if (mybuf != NULL)
321         free (mybuf);
322     if (buf != NULL)
323         free (buf);
324     return NULL;
325 }
326
327
328
329 static char *
330 expand_variable (const char *name, const char *file, int line)
331 {
332     if (strcmp (name, CVSROOT_ENV) == 0)
333         return current_parsed_root->directory;
334     else if (strcmp (name, "RCSBIN") == 0)
335     {
336         error (0, 0, "RCSBIN internal variable is no longer supported");
337         return NULL;
338     }
339     else if (strcmp (name, EDITOR1_ENV) == 0)
340         return Editor;
341     else if (strcmp (name, EDITOR2_ENV) == 0)
342         return Editor;
343     else if (strcmp (name, EDITOR3_ENV) == 0)
344         return Editor;
345     else if (strcmp (name, "USER") == 0)
346         return getcaller ();
347     else if (isalpha ((unsigned char) name[0]))
348     {
349         /* These names are reserved for future versions of CVS,
350            so that is why it is an error.  */
351         if (line != 0)
352             error (0, 0, "%s:%d: no such internal variable $%s",
353                    file, line, name);
354         else
355             error (0, 0, "%s: no such internal variable $%s",
356                    file, name);
357         return NULL;
358     }
359     else if (name[0] == '=')
360     {
361         Node *node;
362         /* Crazy syntax for a user variable.  But we want
363            *something* that lets the user name a user variable
364            anything he wants, without interference from
365            (existing or future) internal variables.  */
366         node = findnode (variable_list, name + 1);
367         if (node == NULL)
368         {
369             if (line != 0)
370                 error (0, 0, "%s:%d: no such user variable ${%s}",
371                        file, line, name);
372             else
373                 error (0, 0, "%s: no such user variable ${%s}",
374                        file, name);
375             return NULL;
376         }
377         return node->data;
378     }
379     else
380     {
381         /* It is an unrecognized character.  We return an error to
382            reserve these for future versions of CVS; it is plausible
383            that various crazy syntaxes might be invented for inserting
384            information about revisions, branches, etc.  */
385         if (line != 0)
386             error (0, 0, "%s:%d: unrecognized variable syntax %s",
387                    file, line, name);
388         else
389             error (0, 0, "%s: unrecognized variable syntax %s",
390                    file, name);
391         return NULL;
392     }
393 }