No specific user configuration
[platform/upstream/bash.git] / lib / sh / pathphys.c
1 /* pathphys.c -- return pathname with all symlinks expanded. */
2
3 /* Copyright (C) 2000 Free Software Foundation, Inc.
4
5    This file is part of GNU Bash, the Bourne Again SHell.
6
7    Bash is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    Bash is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Bash.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #include <bashtypes.h>
24 #if defined (HAVE_SYS_PARAM_H)
25 #  include <sys/param.h>
26 #endif
27 #include <posixstat.h>
28
29 #if defined (HAVE_UNISTD_H)
30 #  include <unistd.h>
31 #endif
32
33 #include <filecntl.h>
34 #include <bashansi.h>
35 #include <stdio.h>
36 #include <chartypes.h>
37 #include <errno.h>
38
39 #include "shell.h"
40
41 #if !defined (MAXSYMLINKS)
42 #  define MAXSYMLINKS 32
43 #endif
44
45 #if !defined (errno)
46 extern int errno;
47 #endif /* !errno */
48
49 extern char *get_working_directory __P((char *));
50
51 static int
52 _path_readlink (path, buf, bufsiz)
53      char *path;
54      char *buf;
55      int bufsiz;
56 {
57 #ifdef HAVE_READLINK
58   return readlink (path, buf, bufsiz);
59 #else
60   errno = EINVAL;
61   return -1;
62 #endif
63 }
64
65 /* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */
66
67 #define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/')
68
69 /*
70  * Return PATH with all symlinks expanded in newly-allocated memory.
71  * This always gets an absolute pathname.
72  */
73
74 char *
75 sh_physpath (path, flags)
76      char *path;
77      int flags;
78 {
79   char tbuf[PATH_MAX+1], linkbuf[PATH_MAX+1];
80   char *result, *p, *q, *qsave, *qbase, *workpath;
81   int double_slash_path, linklen, nlink;
82
83   linklen = strlen (path);
84
85 #if 0
86   /* First sanity check -- punt immediately if the name is too long. */
87   if (linklen >= PATH_MAX)
88     return (savestring (path));
89 #endif
90
91   nlink = 0;
92   q = result = (char *)xmalloc (PATH_MAX + 1);
93
94   /* Even if we get something longer than PATH_MAX, we might be able to
95      shorten it, so we try. */
96   if (linklen >= PATH_MAX)
97     workpath = savestring (path);
98   else
99     {
100       workpath = (char *)xmalloc (PATH_MAX + 1);
101       strcpy (workpath, path);
102     }
103
104   /* This always gets an absolute pathname. */
105
106   /* POSIX.2 says to leave a leading `//' alone.  On cygwin, we skip over any
107      leading `x:' (dos drive name). */
108 #if defined (__CYGWIN__)
109   qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1;
110 #else
111   qbase = workpath + 1;
112 #endif
113   double_slash_path = DOUBLE_SLASH (workpath);
114   qbase += double_slash_path;
115
116   for (p = workpath; p < qbase; )
117     *q++ = *p++;
118   qbase = q;
119
120   /*
121    * invariants:
122    *      qbase points to the portion of the result path we want to modify
123    *      p points at beginning of path element we're considering.
124    *      q points just past the last path element we wrote (no slash).
125    *
126    * XXX -- need to fix error checking for too-long pathnames
127    */
128
129   while (*p)
130     {
131       if (ISDIRSEP(p[0])) /* null element */
132         p++;
133       else if(p[0] == '.' && PATHSEP(p[1]))     /* . and ./ */
134         p += 1;         /* don't count the separator in case it is nul */
135       else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */
136         {
137           p += 2; /* skip `..' */
138           if (q > qbase)
139             {
140               while (--q > qbase && ISDIRSEP(*q) == 0)
141                 ;
142             }
143         }
144       else      /* real path element */
145         {
146           /* add separator if not at start of work portion of result */
147           qsave = q;
148           if (q != qbase)
149             *q++ = DIRSEP;
150           while (*p && (ISDIRSEP(*p) == 0))
151             {
152               if (q - result >= PATH_MAX)
153                 {
154 #ifdef ENAMETOOLONG
155                   errno = ENAMETOOLONG;
156 #else
157                   errno = EINVAL;
158 #endif
159                   goto error;
160                 }
161                 
162               *q++ = *p++;
163             }
164
165           *q = '\0';
166
167           linklen = _path_readlink (result, linkbuf, PATH_MAX);
168           if (linklen < 0)      /* if errno == EINVAL, it's not a symlink */
169             {
170               if (errno != EINVAL)
171                 goto error;
172               continue;
173             }
174
175           /* It's a symlink, and the value is in LINKBUF. */
176           nlink++;
177           if (nlink > MAXSYMLINKS)
178             {
179 #ifdef ELOOP
180               errno = ELOOP;
181 #else
182               errno = EINVAL;
183 #endif
184 error:
185               free (result);
186               free (workpath);
187               return ((char *)NULL);
188             }
189
190           linkbuf[linklen] = '\0';
191
192           /* If the new path length would overrun PATH_MAX, punt now. */
193           if ((strlen (p) + linklen + 2) >= PATH_MAX)
194             {
195 #ifdef ENAMETOOLONG
196               errno = ENAMETOOLONG;
197 #else
198               errno = EINVAL;
199 #endif
200               goto error;
201             }
202
203           /* Form the new pathname by copying the link value to a temporary
204              buffer and appending the rest of `workpath'.  Reset p to point
205              to the start of the rest of the path.  If the link value is an
206              absolute pathname, reset p, q, and qbase.  If not, reset p
207              and q. */
208           strcpy (tbuf, linkbuf);
209           tbuf[linklen] = '/';
210           strcpy (tbuf + linklen, p);
211           strcpy (workpath, tbuf);
212
213           if (ABSPATH(linkbuf))
214             {
215               q = result;
216               /* Duplicating some code here... */
217 #if defined (__CYGWIN__)
218               qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1;
219 #else
220               qbase = workpath + 1;
221 #endif
222               double_slash_path = DOUBLE_SLASH (workpath);
223               qbase += double_slash_path;
224     
225               for (p = workpath; p < qbase; )
226                 *q++ = *p++;
227               qbase = q;
228             }
229           else
230             {
231               p = workpath;
232               q = qsave;
233             }
234         }
235     }
236
237   *q = '\0';
238   free (workpath);
239
240   /* If the result starts with `//', but the original path does not, we
241      can turn the // into /.  Because of how we set `qbase', this should never
242      be true, but it's a sanity check. */
243   if (DOUBLE_SLASH(result) && double_slash_path == 0)
244     {
245       if (result[2] == '\0')    /* short-circuit for bare `//' */
246         result[1] = '\0';
247       else
248         strcpy (result, result + 1);
249     }
250
251   return (result);
252 }
253
254 char *
255 sh_realpath (pathname, resolved)
256      const char *pathname;
257      char *resolved;
258 {
259   char *tdir, *wd;
260
261   if (pathname == 0 || *pathname == '\0')
262     {
263       errno = (pathname == 0) ? EINVAL : ENOENT;
264       return ((char *)NULL);
265     }
266
267   if (ABSPATH (pathname) == 0)
268     {
269       wd = get_working_directory ("sh_realpath");
270       if (wd == 0)
271         return ((char *)NULL);
272       tdir = sh_makepath (wd, (char *)pathname, 0);
273       free (wd);
274     }
275   else
276     tdir = savestring (pathname);
277
278   wd = sh_physpath (tdir, 0);
279   free (tdir);
280
281   if (resolved == 0)
282     return (wd);
283
284   if (wd)
285     {
286       strncpy (resolved, wd, PATH_MAX - 1);
287       resolved[PATH_MAX - 1] = '\0';
288       free (wd);
289       return resolved;
290     }
291   else
292     {
293       resolved[0] = '\0';
294       return wd;
295     }
296 }