xref: /freebsd/lib/libc/gen/getcwd.c (revision e8420087b0ae4a2d0611cd2f6413d150cfc83554)
1 /*
2  * Copyright (c) 1989, 1991, 1993, 1995
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  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #if defined(LIBC_SCCS) && !defined(lint)
35 static char sccsid[] = "@(#)getcwd.c	8.5 (Berkeley) 2/7/95";
36 #endif /* LIBC_SCCS and not lint */
37 
38 #include <sys/param.h>
39 #include <sys/stat.h>
40 
41 #include <dirent.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <signal.h>
49 
50 #define	ISDOT(dp) \
51 	(dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
52 	    (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
53 
54 static int have__getcwd = 1;	/* 0 = no, 1 = perhaps, 2 = yes */
55 
56 char *
57 getcwd(pt, size)
58 	char *pt;
59 	size_t size;
60 {
61 	register struct dirent *dp;
62 	register DIR *dir = NULL;
63 	register dev_t dev;
64 	register ino_t ino;
65 	register int first;
66 	register char *bpt, *bup;
67 	struct stat s;
68 	dev_t root_dev;
69 	ino_t root_ino;
70 	size_t ptsize, upsize;
71 	int save_errno;
72 	char *ept, *eup, *up, c;
73 
74 	/*
75 	 * If no buffer specified by the user, allocate one as necessary.
76 	 * If a buffer is specified, the size has to be non-zero.  The path
77 	 * is built from the end of the buffer backwards.
78 	 */
79 	if (pt) {
80 		ptsize = 0;
81 		if (!size) {
82 			errno = EINVAL;
83 			return (NULL);
84 		}
85 		if (size == 1) {
86 			errno = ERANGE;
87 			return (NULL);
88 		}
89 		ept = pt + size;
90 	} else {
91 		if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
92 			return (NULL);
93 		ept = pt + ptsize;
94 	}
95 #if	defined(__NETBSD_SYSCALLS)
96 	have__getcwd = 0;
97 #else
98 	if (have__getcwd) {
99 		struct sigaction sa, osa;
100 		int sigsys_installed = 0;
101 		int ret;
102 
103 		if (have__getcwd == 1) {	/* unsure? */
104 			bzero(&sa, sizeof(sa));
105 			sa.sa_handler = SIG_IGN;
106 			if (sigaction(SIGSYS, &sa, &osa) >= 0)
107 				sigsys_installed = 1;
108 		}
109 		ret = __getcwd(pt, ept - pt);
110 		if (sigsys_installed == 1) {
111 			int oerrno = errno;
112 			sigaction(SIGSYS, &osa, NULL);
113 			errno = oerrno;
114 		}
115 		/* XXX a bogus syscall seems to return EINVAL(!) */
116 		if (ret < 0 && (errno == ENOSYS || errno == EINVAL))
117 			have__getcwd = 0;
118 		else if (have__getcwd == 1)
119 			have__getcwd = 2;	/* yep, remember we have it */
120 		if (ret == 0) {
121 			if (*pt != '/') {
122 				bpt = pt;
123 				ept = pt + strlen(pt) - 1;
124 				while (bpt < ept) {
125 					c = *bpt;
126 					*bpt++ = *ept;
127 					*ept-- = c;
128 				}
129 			}
130 			return (pt);
131 		}
132 	}
133 #endif
134 	bpt = ept - 1;
135 	*bpt = '\0';
136 
137 	/*
138 	 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
139 	 * Should always be enough (it's 340 levels).  If it's not, allocate
140 	 * as necessary.  Special case the first stat, it's ".", not "..".
141 	 */
142 	if ((up = malloc(upsize = 1024 - 4)) == NULL)
143 		goto err;
144 	eup = up + MAXPATHLEN;
145 	bup = up;
146 	up[0] = '.';
147 	up[1] = '\0';
148 
149 	/* Save root values, so know when to stop. */
150 	if (stat("/", &s))
151 		goto err;
152 	root_dev = s.st_dev;
153 	root_ino = s.st_ino;
154 
155 	errno = 0;			/* XXX readdir has no error return. */
156 
157 	for (first = 1;; first = 0) {
158 		/* Stat the current level. */
159 		if (lstat(up, &s))
160 			goto err;
161 
162 		/* Save current node values. */
163 		ino = s.st_ino;
164 		dev = s.st_dev;
165 
166 		/* Check for reaching root. */
167 		if (root_dev == dev && root_ino == ino) {
168 			*--bpt = '/';
169 			/*
170 			 * It's unclear that it's a requirement to copy the
171 			 * path to the beginning of the buffer, but it's always
172 			 * been that way and stuff would probably break.
173 			 */
174 			bcopy(bpt, pt, ept - bpt);
175 			free(up);
176 			return (pt);
177 		}
178 
179 		/*
180 		 * Build pointer to the parent directory, allocating memory
181 		 * as necessary.  Max length is 3 for "../", the largest
182 		 * possible component name, plus a trailing NULL.
183 		 */
184 		if (bup + 3  + MAXNAMLEN + 1 >= eup) {
185 			if ((up = reallocf(up, upsize *= 2)) == NULL)
186 				goto err;
187 			bup = up;
188 			eup = up + upsize;
189 		}
190 		*bup++ = '.';
191 		*bup++ = '.';
192 		*bup = '\0';
193 
194 		/* Open and stat parent directory. */
195 		if (!(dir = opendir(up)) || fstat(dirfd(dir), &s))
196 			goto err;
197 
198 		/* Add trailing slash for next directory. */
199 		*bup++ = '/';
200 		*bup = '\0';
201 
202 		/*
203 		 * If it's a mount point, have to stat each element because
204 		 * the inode number in the directory is for the entry in the
205 		 * parent directory, not the inode number of the mounted file.
206 		 */
207 		save_errno = 0;
208 		if (s.st_dev == dev) {
209 			for (;;) {
210 				if (!(dp = readdir(dir)))
211 					goto notfound;
212 				if (dp->d_fileno == ino)
213 					break;
214 			}
215 		} else
216 			for (;;) {
217 				if (!(dp = readdir(dir)))
218 					goto notfound;
219 				if (ISDOT(dp))
220 					continue;
221 				bcopy(dp->d_name, bup, dp->d_namlen + 1);
222 
223 				/* Save the first error for later. */
224 				if (lstat(up, &s)) {
225 					if (!save_errno)
226 						save_errno = errno;
227 					errno = 0;
228 					continue;
229 				}
230 				if (s.st_dev == dev && s.st_ino == ino)
231 					break;
232 			}
233 
234 		/*
235 		 * Check for length of the current name, preceding slash,
236 		 * leading slash.
237 		 */
238 		if (bpt - pt < dp->d_namlen + (first ? 1 : 2)) {
239 			size_t len, off;
240 
241 			if (!ptsize) {
242 				errno = ERANGE;
243 				goto err;
244 			}
245 			off = bpt - pt;
246 			len = ept - bpt;
247 			if ((pt = realloc(pt, ptsize *= 2)) == NULL)
248 				goto err;
249 			bpt = pt + off;
250 			ept = pt + ptsize;
251 			bcopy(bpt, ept - len, len);
252 			bpt = ept - len;
253 		}
254 		if (!first)
255 			*--bpt = '/';
256 		bpt -= dp->d_namlen;
257 		bcopy(dp->d_name, bpt, dp->d_namlen);
258 		(void) closedir(dir);
259 		dir = NULL;
260 
261 		/* Truncate any file name. */
262 		*bup = '\0';
263 	}
264 
265 notfound:
266 	/*
267 	 * If readdir set errno, use it, not any saved error; otherwise,
268 	 * didn't find the current directory in its parent directory, set
269 	 * errno to ENOENT.
270 	 */
271 	if (!errno)
272 		errno = save_errno ? save_errno : ENOENT;
273 	/* FALLTHROUGH */
274 err:
275 	save_errno = errno;
276 
277 	if (ptsize)
278 		free(pt);
279 	if (dir)
280 		(void) closedir(dir);
281 	free(up);
282 
283 	errno = save_errno;
284 	return (NULL);
285 }
286