]>
Commit | Line | Data |
---|---|---|
2e4498b3 | 1 | /* pathphys.c -- return pathname with all symlinks expanded. */ |
28ef6c31 JA |
2 | |
3 | /* Copyright (C) 2000 Free Software Foundation, Inc. | |
4 | ||
5 | This file is part of GNU Bash, the Bourne Again SHell. | |
6 | ||
2e4498b3 CR |
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. | |
28ef6c31 | 11 | |
2e4498b3 CR |
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. | |
28ef6c31 | 16 | |
2e4498b3 CR |
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 | */ | |
28ef6c31 | 20 | |
f73dda09 | 21 | #include <config.h> |
28ef6c31 | 22 | |
f73dda09 | 23 | #include <bashtypes.h> |
fd58d46e | 24 | #if defined (HAVE_SYS_PARAM_H) |
28ef6c31 JA |
25 | # include <sys/param.h> |
26 | #endif | |
f73dda09 | 27 | #include <posixstat.h> |
28ef6c31 JA |
28 | |
29 | #if defined (HAVE_UNISTD_H) | |
30 | # include <unistd.h> | |
31 | #endif | |
32 | ||
f73dda09 JA |
33 | #include <filecntl.h> |
34 | #include <bashansi.h> | |
28ef6c31 | 35 | #include <stdio.h> |
f73dda09 | 36 | #include <chartypes.h> |
28ef6c31 JA |
37 | #include <errno.h> |
38 | ||
39 | #include "shell.h" | |
40 | ||
28ef6c31 JA |
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. | |
d3a24ed2 | 71 | * This always gets an absolute pathname. |
28ef6c31 JA |
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 | ||
d3a24ed2 CR |
83 | linklen = strlen (path); |
84 | ||
85 | #if 0 | |
5e13499c | 86 | /* First sanity check -- punt immediately if the name is too long. */ |
d3a24ed2 CR |
87 | if (linklen >= PATH_MAX) |
88 | return (savestring (path)); | |
89 | #endif | |
90 | ||
28ef6c31 | 91 | nlink = 0; |
f73dda09 | 92 | q = result = (char *)xmalloc (PATH_MAX + 1); |
28ef6c31 | 93 | |
d3a24ed2 CR |
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 | } | |
28ef6c31 JA |
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__) | |
f73dda09 | 109 | qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; |
28ef6c31 JA |
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)) | |
d3a24ed2 CR |
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 | } | |
28ef6c31 JA |
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; | |
5e13499c CR |
181 | #else |
182 | errno = EINVAL; | |
28ef6c31 JA |
183 | #endif |
184 | error: | |
185 | free (result); | |
186 | free (workpath); | |
187 | return ((char *)NULL); | |
188 | } | |
189 | ||
190 | linkbuf[linklen] = '\0'; | |
191 | ||
d3a24ed2 CR |
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 | ||
28ef6c31 JA |
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__) | |
f73dda09 | 218 | qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; |
28ef6c31 JA |
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); | |
1442f67c | 272 | tdir = sh_makepath (wd, (char *)pathname, 0); |
28ef6c31 JA |
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'; | |
ff247e74 | 288 | free (wd); |
28ef6c31 JA |
289 | return resolved; |
290 | } | |
291 | else | |
292 | { | |
293 | resolved[0] = '\0'; | |
294 | return wd; | |
295 | } | |
296 | } |