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