getcwd.c revision 98937
1/*
2 * Copyright (c) 1989, 1991, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include "config.h"
28
29#if !defined(HAVE_GETCWD)
30
31#if defined(LIBC_SCCS) && !defined(lint)
32static char rcsid[] = "$OpenBSD: getcwd.c,v 1.6 2000/07/19 15:25:13 deraadt Exp $";
33#endif /* LIBC_SCCS and not lint */
34
35#include <sys/param.h>
36#include <sys/stat.h>
37#include <errno.h>
38#include <dirent.h>
39#include <sys/dir.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include "includes.h"
44
45#define	ISDOT(dp) \
46	(dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
47	    (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
48
49char *
50getcwd(char *pt,size_t size)
51{
52	register struct dirent *dp;
53	register DIR *dir = NULL;
54	register dev_t dev;
55	register ino_t ino;
56	register int first;
57	register char *bpt, *bup;
58	struct stat s;
59	dev_t root_dev;
60	ino_t root_ino;
61	size_t ptsize, upsize;
62	int save_errno;
63	char *ept, *eup, *up;
64
65	/*
66	 * If no buffer specified by the user, allocate one as necessary.
67	 * If a buffer is specified, the size has to be non-zero.  The path
68	 * is built from the end of the buffer backwards.
69	 */
70	if (pt) {
71		ptsize = 0;
72		if (!size) {
73			errno = EINVAL;
74			return (NULL);
75		}
76		ept = pt + size;
77	} else {
78		if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
79			return (NULL);
80		ept = pt + ptsize;
81	}
82	bpt = ept - 1;
83	*bpt = '\0';
84
85	/*
86	 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
87	 * Should always be enough (it's 340 levels).  If it's not, allocate
88	 * as necessary.  Special * case the first stat, it's ".", not "..".
89	 */
90	if ((up = malloc(upsize = 1024 - 4)) == NULL)
91		goto err;
92	eup = up + MAXPATHLEN;
93	bup = up;
94	up[0] = '.';
95	up[1] = '\0';
96
97	/* Save root values, so know when to stop. */
98	if (stat("/", &s))
99		goto err;
100	root_dev = s.st_dev;
101	root_ino = s.st_ino;
102
103	errno = 0;			/* XXX readdir has no error return. */
104
105	for (first = 1;; first = 0) {
106		/* Stat the current level. */
107		if (lstat(up, &s))
108			goto err;
109
110		/* Save current node values. */
111		ino = s.st_ino;
112		dev = s.st_dev;
113
114		/* Check for reaching root. */
115		if (root_dev == dev && root_ino == ino) {
116			*--bpt = '/';
117			/*
118			 * It's unclear that it's a requirement to copy the
119			 * path to the beginning of the buffer, but it's always
120			 * been that way and stuff would probably break.
121			 */
122			memmove(pt, bpt, ept - bpt);
123			free(up);
124			return (pt);
125		}
126
127		/*
128		 * Build pointer to the parent directory, allocating memory
129		 * as necessary.  Max length is 3 for "../", the largest
130		 * possible component name, plus a trailing NULL.
131		 */
132		if (bup + 3  + MAXNAMLEN + 1 >= eup) {
133			char *nup;
134
135			if ((nup = realloc(up, upsize *= 2)) == NULL)
136				goto err;
137			up = nup;
138			bup = up;
139			eup = up + upsize;
140		}
141		*bup++ = '.';
142		*bup++ = '.';
143		*bup = '\0';
144
145		/* Open and stat parent directory.
146		 * RACE?? - replaced fstat(dirfd(dir), &s) w/ lstat(up,&s)
147                 */
148		if (!(dir = opendir(up)) || lstat(up,&s))
149			goto err;
150
151		/* Add trailing slash for next directory. */
152		*bup++ = '/';
153
154		/*
155		 * If it's a mount point, have to stat each element because
156		 * the inode number in the directory is for the entry in the
157		 * parent directory, not the inode number of the mounted file.
158		 */
159		save_errno = 0;
160		if (s.st_dev == dev) {
161			for (;;) {
162				if (!(dp = readdir(dir)))
163					goto notfound;
164				if (dp->d_fileno == ino)
165					break;
166			}
167		} else
168			for (;;) {
169				if (!(dp = readdir(dir)))
170					goto notfound;
171				if (ISDOT(dp))
172					continue;
173				memmove(bup, dp->d_name, dp->d_namlen + 1);
174
175				/* Save the first error for later. */
176				if (lstat(up, &s)) {
177					if (!save_errno)
178						save_errno = errno;
179					errno = 0;
180					continue;
181				}
182				if (s.st_dev == dev && s.st_ino == ino)
183					break;
184			}
185
186		/*
187		 * Check for length of the current name, preceding slash,
188		 * leading slash.
189		 */
190		if (bpt - pt < dp->d_namlen + (first ? 1 : 2)) {
191			size_t len, off;
192			char *npt;
193
194			if (!ptsize) {
195				errno = ERANGE;
196				goto err;
197			}
198			off = bpt - pt;
199			len = ept - bpt;
200			if ((npt = realloc(pt, ptsize *= 2)) == NULL)
201				goto err;
202			pt = npt;
203			bpt = pt + off;
204			ept = pt + ptsize;
205			memmove(ept - len, bpt, len);
206			bpt = ept - len;
207		}
208		if (!first)
209			*--bpt = '/';
210		bpt -= dp->d_namlen;
211		memmove(bpt, dp->d_name, dp->d_namlen);
212		(void)closedir(dir);
213
214		/* Truncate any file name. */
215		*bup = '\0';
216	}
217
218notfound:
219	/*
220	 * If readdir set errno, use it, not any saved error; otherwise,
221	 * didn't find the current directory in its parent directory, set
222	 * errno to ENOENT.
223	 */
224	if (!errno)
225		errno = save_errno ? save_errno : ENOENT;
226	/* FALLTHROUGH */
227err:
228	if (ptsize)
229		free(pt);
230	if (up)
231		free(up);
232	if (dir)
233		(void)closedir(dir);
234	return (NULL);
235}
236
237#endif /* !defined(HAVE_GETCWD) */
238