getcwd.c revision 1.8 1 /* $NetBSD: getcwd.c,v 1.8 1998/02/02 02:41:23 perry Exp $ */
2
3 /*
4 * Copyright (c) 1989, 1991, 1993, 1995
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Jan-Simon Pendry.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38
39 #include <sys/cdefs.h>
40 #if defined(LIBC_SCCS) && !defined(lint)
41 #if 0
42 static char sccsid[] = "@(#)getcwd.c 8.5 (Berkeley) 2/7/95";
43 #else
44 __RCSID("$NetBSD: getcwd.c,v 1.8 1998/02/02 02:41:23 perry Exp $");
45 #endif
46 #endif /* LIBC_SCCS and not lint */
47
48 #include "namespace.h"
49 #include <sys/param.h>
50 #include <sys/stat.h>
51
52 #include <dirent.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <unistd.h>
59
60 #ifdef __weak_alias
61 __weak_alias(getcwd,_getcwd);
62 #endif
63
64 static char *getcwd_physical __P((char *, size_t));
65
66 #define ISDOT(dp) \
67 (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
68 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
69
70 char *
71 getcwd(pt, size)
72 char *pt;
73 size_t size;
74 {
75 char *pwd;
76 size_t pwdlen;
77 dev_t dev;
78 ino_t ino;
79 struct stat s;
80
81 /* Check $PWD -- if it's right, it's fast. */
82 if ((pwd = getenv("PWD")) != NULL && pwd[0] == '/' && !stat(pwd, &s)) {
83 dev = s.st_dev;
84 ino = s.st_ino;
85 if (!stat(".", &s) && dev == s.st_dev && ino == s.st_ino) {
86 pwdlen = strlen(pwd);
87 if (size != 0) {
88 if (pwdlen + 1 > size) {
89 errno = ERANGE;
90 return (NULL);
91 }
92 } else if ((pt = malloc(pwdlen + 1)) == NULL)
93 return (NULL);
94 memmove(pt, pwd, pwdlen);
95 pt[pwdlen] = '\0';
96 return (pt);
97 }
98 }
99
100 return (getcwd_physical(pt, size));
101 }
102
103 /*
104 * char *realpath(const char *path, char resolved_path[MAXPATHLEN]);
105 *
106 * Find the real name of path, by removing all ".", ".." and symlink
107 * components. Returns (resolved) on success, or (NULL) on failure,
108 * in which case the path which caused trouble is left in (resolved).
109 */
110 char *
111 realpath(path, resolved)
112 const char *path;
113 char *resolved;
114 {
115 struct stat sb;
116 int fd, n, rootd, serrno;
117 char *p, *q, wbuf[MAXPATHLEN];
118
119 /* Save the starting point. */
120 if ((fd = open(".", O_RDONLY)) < 0) {
121 (void)strcpy(resolved, ".");
122 return (NULL);
123 }
124
125 /*
126 * Find the dirname and basename from the path to be resolved.
127 * Change directory to the dirname component.
128 * lstat the basename part.
129 * if it is a symlink, read in the value and loop.
130 * if it is a directory, then change to that directory.
131 * get the current directory name and append the basename.
132 */
133 (void)strncpy(resolved, path, MAXPATHLEN - 1);
134 resolved[MAXPATHLEN - 1] = '\0';
135 loop:
136 q = strrchr(resolved, '/');
137 if (q != NULL) {
138 p = q + 1;
139 if (q == resolved)
140 q = "/";
141 else {
142 do {
143 --q;
144 } while (q > resolved && *q == '/');
145 q[1] = '\0';
146 q = resolved;
147 }
148 if (chdir(q) < 0)
149 goto err1;
150 } else
151 p = resolved;
152
153 /* Deal with the last component. */
154 if (lstat(p, &sb) == 0) {
155 if (S_ISLNK(sb.st_mode)) {
156 n = readlink(p, resolved, MAXPATHLEN);
157 if (n < 0)
158 goto err1;
159 resolved[n] = '\0';
160 goto loop;
161 }
162 if (S_ISDIR(sb.st_mode)) {
163 if (chdir(p) < 0)
164 goto err1;
165 p = "";
166 }
167 }
168
169 /*
170 * Save the last component name and get the full pathname of
171 * the current directory.
172 */
173 (void)strcpy(wbuf, p);
174
175 /*
176 * Call the inernal internal version of getcwd which
177 * does a physical search rather than using the $PWD short-cut
178 */
179 if (getcwd_physical(resolved, MAXPATHLEN) == 0)
180 goto err1;
181
182 /*
183 * Join the two strings together, ensuring that the right thing
184 * happens if the last component is empty, or the dirname is root.
185 */
186 if (resolved[0] == '/' && resolved[1] == '\0')
187 rootd = 1;
188 else
189 rootd = 0;
190
191 if (*wbuf) {
192 if (strlen(resolved) + strlen(wbuf) + rootd + 1 > MAXPATHLEN) {
193 errno = ENAMETOOLONG;
194 goto err1;
195 }
196 if (rootd == 0)
197 (void)strcat(resolved, "/");
198 (void)strcat(resolved, wbuf);
199 }
200
201 /* Go back to where we came from. */
202 if (fchdir(fd) < 0) {
203 serrno = errno;
204 goto err2;
205 }
206
207 /* It's okay if the close fails, what's an fd more or less? */
208 (void)close(fd);
209 return (resolved);
210
211 err1: serrno = errno;
212 (void)fchdir(fd);
213 err2: (void)close(fd);
214 errno = serrno;
215 return (NULL);
216 }
217
218 static char *
219 getcwd_physical(pt, size)
220 char *pt;
221 size_t size;
222 {
223 register struct dirent *dp;
224 register DIR *dir;
225 register dev_t dev;
226 register ino_t ino;
227 register int first;
228 register char *bpt, *bup;
229 struct stat s;
230 dev_t root_dev;
231 ino_t root_ino;
232 size_t ptsize, upsize;
233 int save_errno;
234 char *ept, *eup, *up;
235
236 /*
237 * If no buffer specified by the user, allocate one as necessary.
238 * If a buffer is specified, the size has to be non-zero. The path
239 * is built from the end of the buffer backwards.
240 */
241 if (pt) {
242 ptsize = 0;
243 if (!size) {
244 errno = EINVAL;
245 return (NULL);
246 }
247 ept = pt + size;
248 } else {
249 if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
250 return (NULL);
251 ept = pt + ptsize;
252 }
253 bpt = ept - 1;
254 *bpt = '\0';
255
256 /*
257 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
258 * Should always be enough (it's 340 levels). If it's not, allocate
259 * as necessary. Special case the first stat, it's ".", not "..".
260 */
261 if ((up = malloc(upsize = 1024 - 4)) == NULL)
262 goto err;
263 eup = up + MAXPATHLEN;
264 bup = up;
265 up[0] = '.';
266 up[1] = '\0';
267
268 /* Save root values, so know when to stop. */
269 if (stat("/", &s))
270 goto err;
271 root_dev = s.st_dev;
272 root_ino = s.st_ino;
273
274 errno = 0; /* XXX readdir has no error return. */
275
276 for (first = 1;; first = 0) {
277 /* Stat the current level. */
278 if (lstat(up, &s))
279 goto err;
280
281 /* Save current node values. */
282 ino = s.st_ino;
283 dev = s.st_dev;
284
285 /* Check for reaching root. */
286 if (root_dev == dev && root_ino == ino) {
287 *--bpt = '/';
288 /*
289 * It's unclear that it's a requirement to copy the
290 * path to the beginning of the buffer, but it's always
291 * been that way and stuff would probably break.
292 */
293 bcopy(bpt, pt, ept - bpt);
294 free(up);
295 return (pt);
296 }
297
298 /*
299 * Build pointer to the parent directory, allocating memory
300 * as necessary. Max length is 3 for "../", the largest
301 * possible component name, plus a trailing NULL.
302 */
303 if (bup + 3 + MAXNAMLEN + 1 >= eup) {
304 if ((up = realloc(up, upsize *= 2)) == NULL)
305 goto err;
306 bup = up;
307 eup = up + upsize;
308 }
309 *bup++ = '.';
310 *bup++ = '.';
311 *bup = '\0';
312
313 /* Open and stat parent directory. */
314 if (!(dir = opendir(up)) || fstat(dirfd(dir), &s))
315 goto err;
316
317 /* Add trailing slash for next directory. */
318 *bup++ = '/';
319
320 /*
321 * If it's a mount point, have to stat each element because
322 * the inode number in the directory is for the entry in the
323 * parent directory, not the inode number of the mounted file.
324 */
325 save_errno = 0;
326 if (s.st_dev == dev) {
327 for (;;) {
328 if (!(dp = readdir(dir)))
329 goto notfound;
330 if (dp->d_fileno == ino)
331 break;
332 }
333 } else
334 for (;;) {
335 if (!(dp = readdir(dir)))
336 goto notfound;
337 if (ISDOT(dp))
338 continue;
339 bcopy(dp->d_name, bup, dp->d_namlen + 1);
340
341 /* Save the first error for later. */
342 if (lstat(up, &s)) {
343 if (!save_errno)
344 save_errno = errno;
345 errno = 0;
346 continue;
347 }
348 if (s.st_dev == dev && s.st_ino == ino)
349 break;
350 }
351
352 /*
353 * Check for length of the current name, preceding slash,
354 * leading slash.
355 */
356 if (bpt - pt <= dp->d_namlen + (first ? 1 : 2)) {
357 size_t len, off;
358
359 if (!ptsize) {
360 errno = ERANGE;
361 goto err;
362 }
363 off = bpt - pt;
364 len = ept - bpt;
365 if ((pt = realloc(pt, ptsize *= 2)) == NULL)
366 goto err;
367 bpt = pt + off;
368 ept = pt + ptsize;
369 bcopy(bpt, ept - len, len);
370 bpt = ept - len;
371 }
372 if (!first)
373 *--bpt = '/';
374 bpt -= dp->d_namlen;
375 bcopy(dp->d_name, bpt, dp->d_namlen);
376 (void)closedir(dir);
377
378 /* Truncate any file name. */
379 *bup = '\0';
380 }
381
382 notfound:
383 /*
384 * If readdir set errno, use it, not any saved error; otherwise,
385 * didn't find the current directory in its parent directory, set
386 * errno to ENOENT.
387 */
388 if (!errno)
389 errno = save_errno ? save_errno : ENOENT;
390 /* FALLTHROUGH */
391 err:
392 if (ptsize)
393 free(pt);
394 free(up);
395 return (NULL);
396 }
397