]> git.saurik.com Git - apple/libc.git/blame_incremental - gen/FreeBSD/getcwd.c
Libc-1439.100.3.tar.gz
[apple/libc.git] / gen / FreeBSD / getcwd.c
... / ...
CommitLineData
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 * 4. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#pragma clang diagnostic push
31#pragma clang diagnostic ignored "-Wstrict-prototypes"
32
33#if defined(LIBC_SCCS) && !defined(lint)
34static char sccsid[] = "@(#)getcwd.c 8.5 (Berkeley) 2/7/95";
35#endif /* LIBC_SCCS and not lint */
36#include <sys/cdefs.h>
37__FBSDID("$FreeBSD: src/lib/libc/gen/getcwd.c,v 1.29 2007/01/09 00:27:53 imp Exp $");
38
39#include "namespace.h"
40#include <sys/param.h>
41#include <sys/stat.h>
42
43#include <dirent.h>
44#include <errno.h>
45#include <fcntl.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50#include "un-namespace.h"
51
52#if TARGET_OS_OSX && !TARGET_OS_SIMULATOR
53#include <sys/attr.h> /* for FSOPT_NOFOLLOW */
54#include <apfs/apfs_fsctl.h>
55#endif
56
57#define ISDOT(dp) \
58 (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
59 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
60
61/*
62 * Check if the given directory is a firmlink.
63 * Return 1 if it is firmlink otherwise return 0.
64 */
65static inline int
66__check_for_firmlink(char *dir_path)
67{
68#if TARGET_OS_OSX && !TARGET_OS_SIMULATOR
69 apfs_firmlink_control_t afc;
70 int is_firmlink;
71 int err;
72
73 afc.cmd = FIRMLINK_GET;
74 afc.val = 0;
75
76 err = fsctl(dir_path, APFSIOC_FIRMLINK_CTL, (void *)&afc, FSOPT_NOFOLLOW);
77 if (err == 0) {
78 is_firmlink = afc.val ? 1 : 0;
79 } else {
80 /*
81 * On error, we assume it is a firmlink. This could be a false positive
82 * and we will end up incurring a lstat() cost but it will give us some
83 * chance to build the path successfully.
84 */
85 is_firmlink = 1;
86 }
87
88 return is_firmlink;
89#else
90 return 0;
91#endif
92}
93
94/*
95 * If __getcwd() ever becomes a syscall, we can remove this workaround.
96 * The problem here is that fcntl() assumes a buffer of size MAXPATHLEN,
97 * if size is less than MAXPATHLEN, we need to use a temporary buffer
98 * and see if it fits. We also have to assume that open() or fcntl()
99 * don't fail with errno=ERANGE.
100 */
101static inline int
102__getcwd(char *buf, size_t size)
103{
104 int fd, err, save;
105 struct stat dot, pt;
106 char *b;
107
108 if ((fd = open(".", O_RDONLY)) < 0)
109 return -1;
110 if (fstat(fd, &dot) < 0) {
111 save = errno;
112 close(fd);
113 errno = save;
114 return -1;
115 }
116 /* check that the device and inode are non-zero, otherwise punt */
117 if (dot.st_dev == 0 || dot.st_ino == 0) {
118 close(fd);
119 errno = EINVAL;
120 return -1;
121 }
122 if (size < MAXPATHLEN) {
123 /* the hard case; allocate a buffer of size MAXPATHLEN to use */
124 b = (char *)alloca(MAXPATHLEN);
125 if (b == NULL) {
126 close(fd);
127 errno = ENOMEM; /* make sure it isn't ERANGE */
128 return -1;
129 }
130 } else
131 b = buf;
132
133 err = fcntl(fd, F_GETPATH, b);
134 if (err) {
135 save = errno;
136 close(fd);
137 errno = save;
138 return err;
139 }
140 close(fd);
141 /*
142 * now double-check that the path returned by fcntl() has the same
143 * device and inode number as '.'.
144 */
145 if (stat(b, &pt) < 0)
146 return -1;
147 /*
148 * Since dot.st_dev and dot.st_ino are non-zero, we don't need to
149 * separately test for pt.st_dev and pt.st_ino being non-zero, because
150 * they have to match
151 */
152 if (dot.st_dev != pt.st_dev || dot.st_ino != pt.st_ino) {
153 errno = EINVAL;
154 return -1;
155 }
156 /*
157 * For the case where we allocated a buffer, check that it can fit
158 * in the real buffer, and copy it over.
159 */
160 if (size < MAXPATHLEN) {
161 if (strlen(b) >= size) {
162 errno = ERANGE;
163 return -1;
164 }
165 strcpy(buf, b);
166 }
167 return 0;
168}
169
170__private_extern__ char *
171__private_getcwd(pt, size, usegetpath)
172 char *pt;
173 size_t size;
174 int usegetpath;
175{
176 struct dirent *dp;
177 DIR *dir = NULL;
178 dev_t dev;
179 ino_t ino;
180 int first;
181 char *bpt, *bup;
182 struct stat s;
183 dev_t root_dev;
184 ino_t root_ino;
185 size_t ptsize, upsize;
186 int save_errno;
187 char *ept, *eup, *up;
188
189 /*
190 * If no buffer specified by the user, allocate one as necessary.
191 * If a buffer is specified, the size has to be non-zero. The path
192 * is built from the end of the buffer backwards.
193 */
194 if (pt) {
195 ptsize = 0;
196 if (!size) {
197 errno = EINVAL;
198 return (NULL);
199 }
200 if (size == 1) {
201 errno = ERANGE;
202 return (NULL);
203 }
204 ept = pt + size;
205 } else {
206 if ((pt = malloc(ptsize = MAXPATHLEN)) == NULL)
207 return (NULL);
208 ept = pt + ptsize;
209 }
210 if (usegetpath) {
211 if (__getcwd(pt, ept - pt) == 0) {
212 return (pt);
213 } else if (errno == ERANGE) /* failed because buffer too small */
214 return NULL;
215 }
216 bpt = ept - 1;
217 *bpt = '\0';
218
219 /*
220 * Allocate MAXPATHLEN bytes for the string of "../"'s.
221 * Should always be enough. If it's not, allocate
222 * as necessary. Special case the first stat, it's ".", not "..".
223 */
224 if ((up = malloc(upsize = MAXPATHLEN)) == NULL)
225 goto err;
226 eup = up + MAXPATHLEN;
227 bup = up;
228 up[0] = '.';
229 up[1] = '\0';
230
231 /* Save root values, so know when to stop. */
232 if (stat("/", &s))
233 goto err;
234 root_dev = s.st_dev;
235 root_ino = s.st_ino;
236
237 errno = 0; /* XXX readdir has no error return. */
238
239 for (first = 1;; first = 0) {
240 /* Stat the current level. */
241 if (lstat(up, &s))
242 goto err;
243
244 /* Save current node values. */
245 ino = s.st_ino;
246 dev = s.st_dev;
247
248 /* Check for reaching root. */
249 if (root_dev == dev && root_ino == ino) {
250 *--bpt = '/';
251 /*
252 * It's unclear that it's a requirement to copy the
253 * path to the beginning of the buffer, but it's always
254 * been that way and stuff would probably break.
255 */
256 bcopy(bpt, pt, ept - bpt);
257 free(up);
258 return (pt);
259 }
260
261 /*
262 * Build pointer to the parent directory, allocating memory
263 * as necessary. Max length is 3 for "../", the largest
264 * possible component name, plus a trailing NUL.
265 */
266 while (bup + 3 + MAXNAMLEN + 1 >= eup) {
267 if ((up = reallocf(up, upsize *= 2)) == NULL)
268 goto err;
269 bup = up;
270 eup = up + upsize;
271 }
272 *bup++ = '.';
273 *bup++ = '.';
274 *bup = '\0';
275
276 /* Open and stat parent directory. */
277 if (!(dir = opendir(up)) || _fstat(dirfd(dir), &s))
278 goto err;
279
280 /* Add trailing slash for next directory. */
281 *bup++ = '/';
282 *bup = '\0';
283
284 /*
285 * If it's a mount point, have to stat each element because
286 * the inode number in the directory is for the entry in the
287 * parent directory, not the inode number of the mounted file.
288 */
289 save_errno = 0;
290 if (s.st_dev == dev) {
291 for (;;) {
292 if (!(dp = readdir(dir)))
293 goto notfound;
294 if (dp->d_fileno == ino) {
295 break;
296 } else if (!ISDOT(dp) && dp->d_type == DT_DIR) {
297 /*
298 * The 'd_fileno' for firmlink directory would be different
299 * than the 'st_ino' returned by stat(). We have to do an
300 * extra lstat() on firmlink directory to determine if the
301 * 'st_ino' matches with what we are looking for.
302 */
303 bcopy(dp->d_name, bup, dp->d_namlen + 1);
304
305 if (__check_for_firmlink(up) == 0)
306 continue;
307
308 if (lstat(up, &s)) {
309 if (!save_errno)
310 save_errno = errno;
311 errno = 0;
312 continue;
313 }
314 if (s.st_dev == dev && s.st_ino == ino)
315 break;
316 }
317 }
318 } else
319 for (;;) {
320 if (!(dp = readdir(dir)))
321 goto notfound;
322 if (ISDOT(dp))
323 continue;
324 bcopy(dp->d_name, bup, dp->d_namlen + 1);
325
326 /* Save the first error for later. */
327 if (lstat(up, &s)) {
328 if (!save_errno)
329 save_errno = errno;
330 errno = 0;
331 continue;
332 }
333 if (s.st_dev == dev && s.st_ino == ino)
334 break;
335 }
336
337 /*
338 * Check for length of the current name, preceding slash,
339 * leading slash.
340 */
341 while (bpt - pt < dp->d_namlen + (first ? 1 : 2)) {
342 size_t len, off;
343
344 if (!ptsize) {
345 errno = ERANGE;
346 goto err;
347 }
348 off = bpt - pt;
349 len = ept - bpt;
350 if ((pt = reallocf(pt, ptsize *= 2)) == NULL)
351 goto err;
352 bpt = pt + off;
353 ept = pt + ptsize;
354 bcopy(bpt, ept - len, len);
355 bpt = ept - len;
356 }
357 if (!first)
358 *--bpt = '/';
359 bpt -= dp->d_namlen;
360 bcopy(dp->d_name, bpt, dp->d_namlen);
361 (void) closedir(dir);
362 dir = NULL;
363
364 /* Truncate any file name. */
365 *bup = '\0';
366 }
367
368notfound:
369 /*
370 * If readdir set errno, use it, not any saved error; otherwise,
371 * didn't find the current directory in its parent directory, set
372 * errno to ENOENT.
373 */
374 if (!errno)
375 errno = save_errno ? save_errno : ENOENT;
376 /* FALLTHROUGH */
377err:
378 save_errno = errno;
379
380 if (ptsize)
381 free(pt);
382 if (dir)
383 (void) closedir(dir);
384 free(up);
385
386 errno = save_errno;
387 return (NULL);
388}
389
390char *
391getcwd(pt, size)
392 char *pt;
393 size_t size;
394{
395 return __private_getcwd(pt, size, 1);
396}
397#pragma clang diagnostic pop