Imported from ../bash-2.05a.tar.gz.
[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 <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 a full 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   nlink = 0;
84   q = result = (char *)xmalloc (PATH_MAX + 1);
85
86   workpath = (char *)xmalloc (PATH_MAX + 1);
87   strcpy (workpath, path);
88
89   /* This always gets an absolute pathname. */
90
91   /* POSIX.2 says to leave a leading `//' alone.  On cygwin, we skip over any
92      leading `x:' (dos drive name). */
93 #if defined (__CYGWIN__)
94   qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1;
95 #else
96   qbase = workpath + 1;
97 #endif
98   double_slash_path = DOUBLE_SLASH (workpath);
99   qbase += double_slash_path;
100
101   for (p = workpath; p < qbase; )
102     *q++ = *p++;
103   qbase = q;
104
105   /*
106    * invariants:
107    *      qbase points to the portion of the result path we want to modify
108    *      p points at beginning of path element we're considering.
109    *      q points just past the last path element we wrote (no slash).
110    *
111    * XXX -- need to fix error checking for too-long pathnames
112    */
113
114   while (*p)
115     {
116       if (ISDIRSEP(p[0])) /* null element */
117         p++;
118       else if(p[0] == '.' && PATHSEP(p[1]))     /* . and ./ */
119         p += 1;         /* don't count the separator in case it is nul */
120       else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */
121         {
122           p += 2; /* skip `..' */
123           if (q > qbase)
124             {
125               while (--q > qbase && ISDIRSEP(*q) == 0)
126                 ;
127             }
128         }
129       else      /* real path element */
130         {
131           /* add separator if not at start of work portion of result */
132           qsave = q;
133           if (q != qbase)
134             *q++ = DIRSEP;
135           while (*p && (ISDIRSEP(*p) == 0))
136             *q++ = *p++;
137
138           *q = '\0';
139
140           linklen = _path_readlink (result, linkbuf, PATH_MAX);
141           if (linklen < 0)      /* if errno == EINVAL, it's not a symlink */
142             {
143               if (errno != EINVAL)
144                 goto error;
145               continue;
146             }
147
148           /* It's a symlink, and the value is in LINKBUF. */
149           nlink++;
150           if (nlink > MAXSYMLINKS)
151             {
152 #ifdef ELOOP
153               errno = ELOOP;
154 #endif
155 error:
156               free (result);
157               free (workpath);
158               return ((char *)NULL);
159             }
160
161           linkbuf[linklen] = '\0';
162
163           /* Form the new pathname by copying the link value to a temporary
164              buffer and appending the rest of `workpath'.  Reset p to point
165              to the start of the rest of the path.  If the link value is an
166              absolute pathname, reset p, q, and qbase.  If not, reset p
167              and q. */
168           strcpy (tbuf, linkbuf);
169           tbuf[linklen] = '/';
170           strcpy (tbuf + linklen, p);
171           strcpy (workpath, tbuf);
172
173           if (ABSPATH(linkbuf))
174             {
175               q = result;
176               /* Duplicating some code here... */
177 #if defined (__CYGWIN__)
178               qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1;
179 #else
180               qbase = workpath + 1;
181 #endif
182               double_slash_path = DOUBLE_SLASH (workpath);
183               qbase += double_slash_path;
184     
185               for (p = workpath; p < qbase; )
186                 *q++ = *p++;
187               qbase = q;
188             }
189           else
190             {
191               p = workpath;
192               q = qsave;
193             }
194         }
195     }
196
197   *q = '\0';
198   free (workpath);
199
200   /* If the result starts with `//', but the original path does not, we
201      can turn the // into /.  Because of how we set `qbase', this should never
202      be true, but it's a sanity check. */
203   if (DOUBLE_SLASH(result) && double_slash_path == 0)
204     {
205       if (result[2] == '\0')    /* short-circuit for bare `//' */
206         result[1] = '\0';
207       else
208         strcpy (result, result + 1);
209     }
210
211   return (result);
212 }
213
214 char *
215 sh_realpath (pathname, resolved)
216      const char *pathname;
217      char *resolved;
218 {
219   char *tdir, *wd;
220
221   if (pathname == 0 || *pathname == '\0')
222     {
223       errno = (pathname == 0) ? EINVAL : ENOENT;
224       return ((char *)NULL);
225     }
226
227   if (ABSPATH (pathname) == 0)
228     {
229       wd = get_working_directory ("sh_realpath");
230       if (wd == 0)
231         return ((char *)NULL);
232       tdir = sh_makepath ((char *)pathname, wd, 0);
233       free (wd);
234     }
235   else
236     tdir = savestring (pathname);
237
238   wd = sh_physpath (tdir, 0);
239   free (tdir);
240
241   if (resolved == 0)
242     return (wd);
243
244   if (wd)
245     {
246       strncpy (resolved, wd, PATH_MAX - 1);
247       resolved[PATH_MAX - 1] = '\0';
248       return resolved;
249     }
250   else
251     {
252       resolved[0] = '\0';
253       return wd;
254     }
255 }