eb5852413905c2745edc4f4205f2ade3a831c11c
[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 it under
8    the terms of the GNU General Public License as published by the Free
9    Software Foundation; either version 2, or (at your option) any later
10    version.
11
12    Bash is distributed in the hope that it will be useful, but WITHOUT ANY
13    WARRANTY; without even the implied warranty of MERCHANTABILITY or
14    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15    for more details.
16
17    You should have received a copy of the GNU General Public License along
18    with Bash; see the file COPYING.  If not, write to the Free Software
19    Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
20
21 #include "config.h"
22
23 #include "bashtypes.h"
24 #ifndef _MINIX
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 <errno.h>
37
38 #include "shell.h"
39
40 #include "maxpath.h"
41
42 #if !defined (MAXSYMLINKS)
43 #  define MAXSYMLINKS 32
44 #endif
45
46 #if !defined (errno)
47 extern int errno;
48 #endif /* !errno */
49
50 extern char *get_working_directory __P((char *));
51
52 static int
53 _path_readlink (path, buf, bufsiz)
54      char *path;
55      char *buf;
56      int bufsiz;
57 {
58 #ifdef HAVE_READLINK
59   return readlink (path, buf, bufsiz);
60 #else
61   errno = EINVAL;
62   return -1;
63 #endif
64 }
65
66 /* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */
67
68 #define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/')
69
70 /*
71  * Return PATH with all symlinks expanded in newly-allocated memory.
72  * This always gets a full pathname.
73  */
74
75 char *
76 sh_physpath (path, flags)
77      char *path;
78      int flags;
79 {
80   char tbuf[PATH_MAX+1], linkbuf[PATH_MAX+1];
81   char *result, *p, *q, *qsave, *qbase, *workpath;
82   int double_slash_path, linklen, nlink;
83
84   nlink = 0;
85   q = result = xmalloc (PATH_MAX + 1);
86
87   workpath = xmalloc (PATH_MAX + 1);
88   strcpy (workpath, path);
89
90   /* This always gets an absolute pathname. */
91
92   /* POSIX.2 says to leave a leading `//' alone.  On cygwin, we skip over any
93      leading `x:' (dos drive name). */
94 #if defined (__CYGWIN__)
95   qbase = (isalpha(workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1;
96 #else
97   qbase = workpath + 1;
98 #endif
99   double_slash_path = DOUBLE_SLASH (workpath);
100   qbase += double_slash_path;
101
102   for (p = workpath; p < qbase; )
103     *q++ = *p++;
104   qbase = q;
105
106   /*
107    * invariants:
108    *      qbase points to the portion of the result path we want to modify
109    *      p points at beginning of path element we're considering.
110    *      q points just past the last path element we wrote (no slash).
111    *
112    * XXX -- need to fix error checking for too-long pathnames
113    */
114
115   while (*p)
116     {
117       if (ISDIRSEP(p[0])) /* null element */
118         p++;
119       else if(p[0] == '.' && PATHSEP(p[1]))     /* . and ./ */
120         p += 1;         /* don't count the separator in case it is nul */
121       else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */
122         {
123           p += 2; /* skip `..' */
124           if (q > qbase)
125             {
126               while (--q > qbase && ISDIRSEP(*q) == 0)
127                 ;
128             }
129         }
130       else      /* real path element */
131         {
132           /* add separator if not at start of work portion of result */
133           qsave = q;
134           if (q != qbase)
135             *q++ = DIRSEP;
136           while (*p && (ISDIRSEP(*p) == 0))
137             *q++ = *p++;
138
139           *q = '\0';
140
141           linklen = _path_readlink (result, linkbuf, PATH_MAX);
142           if (linklen < 0)      /* if errno == EINVAL, it's not a symlink */
143             {
144               if (errno != EINVAL)
145                 goto error;
146               continue;
147             }
148
149           /* It's a symlink, and the value is in LINKBUF. */
150           nlink++;
151           if (nlink > MAXSYMLINKS)
152             {
153 #ifdef ELOOP
154               errno = ELOOP;
155 #endif
156 error:
157               free (result);
158               free (workpath);
159               return ((char *)NULL);
160             }
161
162           linkbuf[linklen] = '\0';
163
164           /* Form the new pathname by copying the link value to a temporary
165              buffer and appending the rest of `workpath'.  Reset p to point
166              to the start of the rest of the path.  If the link value is an
167              absolute pathname, reset p, q, and qbase.  If not, reset p
168              and q. */
169           strcpy (tbuf, linkbuf);
170           tbuf[linklen] = '/';
171           strcpy (tbuf + linklen, p);
172           strcpy (workpath, tbuf);
173
174           if (ABSPATH(linkbuf))
175             {
176               q = result;
177               /* Duplicating some code here... */
178 #if defined (__CYGWIN__)
179               qbase = (isalpha(workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1;
180 #else
181               qbase = workpath + 1;
182 #endif
183               double_slash_path = DOUBLE_SLASH (workpath);
184               qbase += double_slash_path;
185     
186               for (p = workpath; p < qbase; )
187                 *q++ = *p++;
188               qbase = q;
189             }
190           else
191             {
192               p = workpath;
193               q = qsave;
194             }
195         }
196     }
197
198   *q = '\0';
199   free (workpath);
200
201   /* If the result starts with `//', but the original path does not, we
202      can turn the // into /.  Because of how we set `qbase', this should never
203      be true, but it's a sanity check. */
204   if (DOUBLE_SLASH(result) && double_slash_path == 0)
205     {
206       if (result[2] == '\0')    /* short-circuit for bare `//' */
207         result[1] = '\0';
208       else
209         strcpy (result, result + 1);
210     }
211
212   return (result);
213 }
214
215 char *
216 sh_realpath (pathname, resolved)
217      const char *pathname;
218      char *resolved;
219 {
220   char *tdir, *wd;
221
222   if (pathname == 0 || *pathname == '\0')
223     {
224       errno = (pathname == 0) ? EINVAL : ENOENT;
225       return ((char *)NULL);
226     }
227
228   if (ABSPATH (pathname) == 0)
229     {
230       wd = get_working_directory ("sh_realpath");
231       if (wd == 0)
232         return ((char *)NULL);
233       tdir = sh_makepath ((char *)pathname, wd, 0);
234       free (wd);
235     }
236   else
237     tdir = savestring (pathname);
238
239   wd = sh_physpath (tdir, 0);
240   free (tdir);
241
242   if (resolved == 0)
243     return (wd);
244
245   if (wd)
246     {
247       strncpy (resolved, wd, PATH_MAX - 1);
248       resolved[PATH_MAX - 1] = '\0';
249       return resolved;
250     }
251   else
252     {
253       resolved[0] = '\0';
254       return wd;
255     }
256 }