Commit | Line | Data |
---|---|---|
bb0cfa24 | 1 | /* |
57e59a7c | 2 | * Copyright (c) 1989, 1991, 1993, 1995 |
74155b62 | 3 | * The Regents of the University of California. All rights reserved. |
8a4b7e23 | 4 | * |
3422576a JSP |
5 | * This code is derived from software contributed to Berkeley by |
6 | * Jan-Simon Pendry. | |
7 | * | |
269a7923 | 8 | * %sccs.include.redist.c% |
bb0cfa24 DF |
9 | */ |
10 | ||
2ce81398 | 11 | #if defined(LIBC_SCCS) && !defined(lint) |
f34802fd | 12 | static char sccsid[] = "@(#)getcwd.c 8.5 (Berkeley) %G%"; |
8a4b7e23 | 13 | #endif /* LIBC_SCCS and not lint */ |
9b6e6036 | 14 | |
87dccba9 SL |
15 | #include <sys/param.h> |
16 | #include <sys/stat.h> | |
d2d2ef06 | 17 | |
8a4b7e23 | 18 | #include <dirent.h> |
3422576a JSP |
19 | #include <errno.h> |
20 | #include <fcntl.h> | |
e2462278 KB |
21 | #include <stdio.h> |
22 | #include <stdlib.h> | |
36226ba7 | 23 | #include <string.h> |
c5980113 | 24 | #include <unistd.h> |
36226ba7 | 25 | |
3422576a JSP |
26 | static char *getcwd_physical __P((char *, size_t)); |
27 | ||
36226ba7 KB |
28 | #define ISDOT(dp) \ |
29 | (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \ | |
30 | dp->d_name[1] == '.' && dp->d_name[2] == '\0')) | |
9b6e6036 SL |
31 | |
32 | char * | |
e2462278 KB |
33 | getcwd(pt, size) |
34 | char *pt; | |
35 | size_t size; | |
9b6e6036 | 36 | { |
3422576a JSP |
37 | char *pwd; |
38 | size_t pwdlen; | |
39 | dev_t dev; | |
40 | ino_t ino; | |
36226ba7 | 41 | struct stat s; |
e2462278 | 42 | |
57e59a7c | 43 | /* Check $PWD -- if it's right, it's fast. */ |
f3291ecc | 44 | if ((pwd = getenv("PWD")) != NULL && pwd[0] == '/' && !stat(pwd, &s)) { |
57e59a7c KB |
45 | dev = s.st_dev; |
46 | ino = s.st_ino; | |
47 | if (!stat(".", &s) && dev == s.st_dev && ino == s.st_ino) { | |
48 | pwdlen = strlen(pwd); | |
49 | if (size != 0) { | |
50 | if (pwdlen + 1 > size) { | |
51 | errno = ERANGE; | |
52 | return (NULL); | |
53 | } | |
54 | } else if ((pt = malloc(pwdlen + 1)) == NULL) | |
55 | return (NULL); | |
56 | memmove(pt, pwd, pwdlen); | |
f34802fd | 57 | pt[pwdlen] = '\0'; |
57e59a7c KB |
58 | return (pt); |
59 | } | |
60 | } | |
61 | ||
3422576a JSP |
62 | return (getcwd_physical(pt, size)); |
63 | } | |
64 | ||
65 | /* | |
66 | * char *realpath(const char *path, char resolved_path[MAXPATHLEN]); | |
67 | * | |
68 | * Find the real name of path, by removing all ".", ".." and symlink | |
69 | * components. Returns (resolved) on success, or (NULL) on failure, | |
70 | * in which case the path which caused trouble is left in (resolved). | |
71 | */ | |
72 | char * | |
73 | realpath(path, resolved) | |
74 | const char *path; | |
75 | char *resolved; | |
76 | { | |
77 | struct stat sb; | |
78 | int fd, n, rootd, serrno; | |
79 | char *p, *q, wbuf[MAXPATHLEN]; | |
80 | ||
81 | /* Save the starting point. */ | |
82 | if ((fd = open(".", O_RDONLY)) < 0) { | |
83 | (void)strcpy(resolved, "."); | |
84 | return (NULL); | |
85 | } | |
86 | ||
87 | /* | |
88 | * Find the dirname and basename from the path to be resolved. | |
89 | * Change directory to the dirname component. | |
90 | * lstat the basename part. | |
91 | * if it is a symlink, read in the value and loop. | |
92 | * if it is a directory, then change to that directory. | |
93 | * get the current directory name and append the basename. | |
94 | */ | |
95 | (void)strncpy(resolved, path, MAXPATHLEN - 1); | |
96 | resolved[MAXPATHLEN - 1] = '\0'; | |
97 | loop: | |
98 | q = strrchr(resolved, '/'); | |
99 | if (q != NULL) { | |
100 | p = q + 1; | |
101 | if (q == resolved) | |
102 | q = "/"; | |
103 | else { | |
104 | do { | |
105 | --q; | |
106 | } while (q > resolved && *q == '/'); | |
107 | q[1] = '\0'; | |
108 | q = resolved; | |
109 | } | |
110 | if (chdir(q) < 0) | |
111 | goto err1; | |
112 | } else | |
113 | p = resolved; | |
114 | ||
115 | /* Deal with the last component. */ | |
116 | if (lstat(p, &sb) == 0) { | |
117 | if (S_ISLNK(sb.st_mode)) { | |
118 | n = readlink(p, resolved, MAXPATHLEN); | |
119 | if (n < 0) | |
120 | goto err1; | |
121 | resolved[n] = '\0'; | |
122 | goto loop; | |
123 | } | |
124 | if (S_ISDIR(sb.st_mode)) { | |
125 | if (chdir(p) < 0) | |
126 | goto err1; | |
127 | p = ""; | |
128 | } | |
129 | } | |
130 | ||
131 | /* | |
132 | * Save the last component name and get the full pathname of | |
133 | * the current directory. | |
134 | */ | |
135 | (void)strcpy(wbuf, p); | |
136 | ||
137 | /* | |
138 | * Call the inernal internal version of getcwd which | |
139 | * does a physical search rather than using the $PWD short-cut | |
140 | */ | |
141 | if (getcwd_physical(resolved, MAXPATHLEN) == 0) | |
142 | goto err1; | |
143 | ||
144 | /* | |
145 | * Join the two strings together, ensuring that the right thing | |
146 | * happens if the last component is empty, or the dirname is root. | |
147 | */ | |
148 | if (resolved[0] == '/' && resolved[1] == '\0') | |
149 | rootd = 1; | |
150 | else | |
151 | rootd = 0; | |
152 | ||
153 | if (*wbuf) { | |
154 | if (strlen(resolved) + strlen(wbuf) + rootd + 1 > MAXPATHLEN) { | |
155 | errno = ENAMETOOLONG; | |
156 | goto err1; | |
157 | } | |
158 | if (rootd == 0) | |
159 | (void)strcat(resolved, "/"); | |
160 | (void)strcat(resolved, wbuf); | |
161 | } | |
162 | ||
163 | /* Go back to where we came from. */ | |
164 | if (fchdir(fd) < 0) { | |
165 | serrno = errno; | |
166 | goto err2; | |
167 | } | |
168 | ||
169 | /* It's okay if the close fails, what's an fd more or less? */ | |
170 | (void)close(fd); | |
171 | return (resolved); | |
172 | ||
173 | err1: serrno = errno; | |
174 | (void)fchdir(fd); | |
175 | err2: (void)close(fd); | |
176 | errno = serrno; | |
177 | return (NULL); | |
178 | } | |
179 | ||
180 | static char * | |
181 | getcwd_physical(pt, size) | |
182 | char *pt; | |
183 | size_t size; | |
184 | { | |
185 | register struct dirent *dp; | |
186 | register DIR *dir; | |
187 | register dev_t dev; | |
188 | register ino_t ino; | |
189 | register int first; | |
190 | register char *bpt, *bup; | |
191 | struct stat s; | |
192 | dev_t root_dev; | |
193 | ino_t root_ino; | |
194 | size_t ptsize, upsize; | |
195 | int save_errno; | |
196 | char *ept, *eup, *up; | |
197 | ||
e2462278 KB |
198 | /* |
199 | * If no buffer specified by the user, allocate one as necessary. | |
200 | * If a buffer is specified, the size has to be non-zero. The path | |
201 | * is built from the end of the buffer backwards. | |
202 | */ | |
203 | if (pt) { | |
204 | ptsize = 0; | |
205 | if (!size) { | |
206 | errno = EINVAL; | |
d2d2ef06 | 207 | return (NULL); |
e2462278 KB |
208 | } |
209 | ept = pt + size; | |
210 | } else { | |
d2d2ef06 KB |
211 | if ((pt = malloc(ptsize = 1024 - 4)) == NULL) |
212 | return (NULL); | |
e2462278 KB |
213 | ept = pt + ptsize; |
214 | } | |
215 | bpt = ept - 1; | |
dee07d30 | 216 | *bpt = '\0'; |
e2462278 KB |
217 | |
218 | /* | |
219 | * Allocate bytes (1024 - malloc space) for the string of "../"'s. | |
220 | * Should always be enough (it's 340 levels). If it's not, allocate | |
57e59a7c | 221 | * as necessary. Special case the first stat, it's ".", not "..". |
e2462278 | 222 | */ |
d2d2ef06 | 223 | if ((up = malloc(upsize = 1024 - 4)) == NULL) |
e2462278 KB |
224 | goto err; |
225 | eup = up + MAXPATHLEN; | |
226 | bup = up; | |
227 | up[0] = '.'; | |
228 | up[1] = '\0'; | |
9b6e6036 | 229 | |
e2462278 KB |
230 | /* Save root values, so know when to stop. */ |
231 | if (stat("/", &s)) | |
8a4b7e23 KB |
232 | goto err; |
233 | root_dev = s.st_dev; | |
234 | root_ino = s.st_ino; | |
36226ba7 | 235 | |
e2462278 | 236 | errno = 0; /* XXX readdir has no error return. */ |
36226ba7 | 237 | |
e2462278 KB |
238 | for (first = 1;; first = 0) { |
239 | /* Stat the current level. */ | |
240 | if (lstat(up, &s)) | |
36226ba7 | 241 | goto err; |
36226ba7 | 242 | |
e2462278 | 243 | /* Save current node values. */ |
36226ba7 KB |
244 | ino = s.st_ino; |
245 | dev = s.st_dev; | |
246 | ||
e2462278 | 247 | /* Check for reaching root. */ |
36226ba7 | 248 | if (root_dev == dev && root_ino == ino) { |
e2462278 KB |
249 | *--bpt = '/'; |
250 | /* | |
251 | * It's unclear that it's a requirement to copy the | |
252 | * path to the beginning of the buffer, but it's always | |
253 | * been that way and stuff would probably break. | |
254 | */ | |
255 | (void)bcopy(bpt, pt, ept - bpt); | |
256 | free(up); | |
d2d2ef06 | 257 | return (pt); |
90432f68 | 258 | } |
36226ba7 | 259 | |
e2462278 KB |
260 | /* |
261 | * Build pointer to the parent directory, allocating memory | |
262 | * as necessary. Max length is 3 for "../", the largest | |
263 | * possible component name, plus a trailing NULL. | |
264 | */ | |
265 | if (bup + 3 + MAXNAMLEN + 1 >= eup) { | |
d2d2ef06 | 266 | if ((up = realloc(up, upsize *= 2)) == NULL) |
e2462278 | 267 | goto err; |
d2d2ef06 | 268 | bup = up; |
e2462278 KB |
269 | eup = up + upsize; |
270 | } | |
271 | *bup++ = '.'; | |
272 | *bup++ = '.'; | |
273 | *bup = '\0'; | |
36226ba7 | 274 | |
e2462278 KB |
275 | /* Open and stat parent directory. */ |
276 | if (!(dir = opendir(up)) || fstat(dirfd(dir), &s)) | |
1f8e47f9 | 277 | goto err; |
36226ba7 | 278 | |
e2462278 KB |
279 | /* Add trailing slash for next directory. */ |
280 | *bup++ = '/'; | |
36226ba7 KB |
281 | |
282 | /* | |
e2462278 | 283 | * If it's a mount point, have to stat each element because |
36226ba7 KB |
284 | * the inode number in the directory is for the entry in the |
285 | * parent directory, not the inode number of the mounted file. | |
286 | */ | |
e2462278 | 287 | save_errno = 0; |
36226ba7 | 288 | if (s.st_dev == dev) { |
e2462278 KB |
289 | for (;;) { |
290 | if (!(dp = readdir(dir))) | |
291 | goto notfound; | |
36226ba7 | 292 | if (dp->d_fileno == ino) |
e2462278 KB |
293 | break; |
294 | } | |
295 | } else | |
296 | for (;;) { | |
297 | if (!(dp = readdir(dir))) | |
298 | goto notfound; | |
36226ba7 KB |
299 | if (ISDOT(dp)) |
300 | continue; | |
e2462278 KB |
301 | bcopy(dp->d_name, bup, dp->d_namlen + 1); |
302 | ||
303 | /* Save the first error for later. */ | |
36226ba7 | 304 | if (lstat(up, &s)) { |
e2462278 KB |
305 | if (!save_errno) |
306 | save_errno = errno; | |
36226ba7 KB |
307 | errno = 0; |
308 | continue; | |
309 | } | |
e2462278 | 310 | if (s.st_dev == dev && s.st_ino == ino) |
36226ba7 | 311 | break; |
90432f68 | 312 | } |
36226ba7 | 313 | |
e2462278 KB |
314 | /* |
315 | * Check for length of the current name, preceding slash, | |
316 | * leading slash. | |
317 | */ | |
318 | if (bpt - pt <= dp->d_namlen + (first ? 1 : 2)) { | |
319 | size_t len, off; | |
36226ba7 | 320 | |
e2462278 KB |
321 | if (!ptsize) { |
322 | errno = ERANGE; | |
1f8e47f9 MK |
323 | goto err; |
324 | } | |
e2462278 KB |
325 | off = bpt - pt; |
326 | len = ept - bpt; | |
d2d2ef06 | 327 | if ((pt = realloc(pt, ptsize *= 2)) == NULL) |
e2462278 KB |
328 | goto err; |
329 | bpt = pt + off; | |
330 | ept = pt + ptsize; | |
331 | (void)bcopy(bpt, ept - len, len); | |
332 | bpt = ept - len; | |
1f8e47f9 | 333 | } |
e2462278 KB |
334 | if (!first) |
335 | *--bpt = '/'; | |
336 | bpt -= dp->d_namlen; | |
337 | bcopy(dp->d_name, bpt, dp->d_namlen); | |
338 | (void)closedir(dir); | |
339 | ||
340 | /* Truncate any file name. */ | |
341 | *bup = '\0'; | |
90432f68 | 342 | } |
e2462278 KB |
343 | |
344 | notfound: | |
345 | /* | |
346 | * If readdir set errno, use it, not any saved error; otherwise, | |
347 | * didn't find the current directory in its parent directory, set | |
348 | * errno to ENOENT. | |
349 | */ | |
350 | if (!errno) | |
351 | errno = save_errno ? save_errno : ENOENT; | |
352 | /* FALLTHROUGH */ | |
1f8e47f9 | 353 | err: |
e2462278 KB |
354 | if (ptsize) |
355 | free(pt); | |
356 | free(up); | |
d2d2ef06 | 357 | return (NULL); |
9b6e6036 | 358 | } |