Line data Source code
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 0 : _path_readlink (path, buf, bufsiz)
53 : char *path;
54 : char *buf;
55 : int bufsiz;
56 : {
57 : #ifdef HAVE_READLINK
58 0 : 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 0 : sh_physpath (path, flags)
76 : char *path;
77 : int flags;
78 : {
79 0 : char tbuf[PATH_MAX+1], linkbuf[PATH_MAX+1];
80 0 : char *result, *p, *q, *qsave, *qbase, *workpath;
81 0 : int double_slash_path, linklen, nlink;
82 :
83 0 : 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 0 : nlink = 0;
92 0 : 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 0 : if (linklen >= PATH_MAX)
97 0 : workpath = savestring (path);
98 : else
99 : {
100 0 : workpath = (char *)xmalloc (PATH_MAX + 1);
101 0 : 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 0 : qbase = workpath + 1;
112 : #endif
113 0 : double_slash_path = DOUBLE_SLASH (workpath);
114 0 : qbase += double_slash_path;
115 :
116 0 : for (p = workpath; p < qbase; )
117 0 : *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 0 : while (*p)
130 : {
131 0 : if (ISDIRSEP(p[0])) /* null element */
132 0 : p++;
133 0 : else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */
134 0 : p += 1; /* don't count the separator in case it is nul */
135 0 : else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */
136 : {
137 0 : p += 2; /* skip `..' */
138 0 : if (q > qbase)
139 : {
140 0 : while (--q > qbase && ISDIRSEP(*q) == 0)
141 0 : ;
142 : }
143 : }
144 : else /* real path element */
145 : {
146 : /* add separator if not at start of work portion of result */
147 0 : qsave = q;
148 0 : if (q != qbase)
149 0 : *q++ = DIRSEP;
150 0 : while (*p && (ISDIRSEP(*p) == 0))
151 : {
152 0 : if (q - result >= PATH_MAX)
153 : {
154 : #ifdef ENAMETOOLONG
155 0 : errno = ENAMETOOLONG;
156 : #else
157 : errno = EINVAL;
158 : #endif
159 0 : goto error;
160 : }
161 :
162 0 : *q++ = *p++;
163 : }
164 :
165 0 : *q = '\0';
166 :
167 0 : linklen = _path_readlink (result, linkbuf, PATH_MAX);
168 0 : if (linklen < 0) /* if errno == EINVAL, it's not a symlink */
169 : {
170 0 : if (errno != EINVAL)
171 : goto error;
172 : continue;
173 : }
174 :
175 : /* It's a symlink, and the value is in LINKBUF. */
176 0 : nlink++;
177 0 : if (nlink > MAXSYMLINKS)
178 : {
179 : #ifdef ELOOP
180 0 : errno = ELOOP;
181 : #else
182 : errno = EINVAL;
183 : #endif
184 0 : error:
185 0 : free (result);
186 0 : free (workpath);
187 0 : return ((char *)NULL);
188 : }
189 :
190 0 : linkbuf[linklen] = '\0';
191 :
192 : /* If the new path length would overrun PATH_MAX, punt now. */
193 0 : if ((strlen (p) + linklen + 2) >= PATH_MAX)
194 : {
195 : #ifdef ENAMETOOLONG
196 0 : errno = ENAMETOOLONG;
197 : #else
198 : errno = EINVAL;
199 : #endif
200 0 : 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 0 : strcpy (tbuf, linkbuf);
209 0 : tbuf[linklen] = '/';
210 0 : strcpy (tbuf + linklen, p);
211 0 : strcpy (workpath, tbuf);
212 :
213 0 : if (ABSPATH(linkbuf))
214 : {
215 0 : 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 0 : qbase = workpath + 1;
221 : #endif
222 0 : double_slash_path = DOUBLE_SLASH (workpath);
223 0 : qbase += double_slash_path;
224 :
225 0 : for (p = workpath; p < qbase; )
226 0 : *q++ = *p++;
227 : qbase = q;
228 : }
229 : else
230 : {
231 : p = workpath;
232 : q = qsave;
233 : }
234 : }
235 : }
236 :
237 0 : *q = '\0';
238 0 : 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 0 : if (DOUBLE_SLASH(result) && double_slash_path == 0)
244 : {
245 0 : if (result[2] == '\0') /* short-circuit for bare `//' */
246 0 : result[1] = '\0';
247 : else
248 0 : strcpy (result, result + 1);
249 : }
250 :
251 : return (result);
252 : }
253 :
254 : char *
255 0 : sh_realpath (pathname, resolved)
256 : const char *pathname;
257 : char *resolved;
258 : {
259 0 : char *tdir, *wd;
260 :
261 0 : if (pathname == 0 || *pathname == '\0')
262 : {
263 0 : errno = (pathname == 0) ? EINVAL : ENOENT;
264 0 : return ((char *)NULL);
265 : }
266 :
267 0 : if (ABSPATH (pathname) == 0)
268 : {
269 0 : wd = get_working_directory ("sh_realpath");
270 0 : if (wd == 0)
271 : return ((char *)NULL);
272 0 : tdir = sh_makepath (wd, (char *)pathname, 0);
273 0 : free (wd);
274 : }
275 : else
276 0 : tdir = savestring (pathname);
277 :
278 0 : wd = sh_physpath (tdir, 0);
279 0 : free (tdir);
280 :
281 0 : if (resolved == 0)
282 : return (wd);
283 :
284 0 : if (wd)
285 : {
286 0 : strncpy (resolved, wd, PATH_MAX - 1);
287 0 : resolved[PATH_MAX - 1] = '\0';
288 0 : free (wd);
289 0 : return resolved;
290 : }
291 : else
292 : {
293 0 : resolved[0] = '\0';
294 0 : return wd;
295 : }
296 : }
|