1/*
2 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 * 3. The names of the authors may not be used to endorse or promote
13 *    products derived from this software without specific prior written
14 *    permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#if defined(LIBC_SCCS) && !defined(lint)
30static char sccsid[] = "@(#)realpath.c	8.1 (Berkeley) 2/16/94";
31#endif /* LIBC_SCCS and not lint */
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD: src/lib/libc/stdlib/realpath.c,v 1.20 2003/05/28 08:23:01 fjoe Exp $");
34
35#include "namespace.h"
36#include <sys/param.h>
37#include <sys/stat.h>
38#include <sys/mount.h>
39
40#include <errno.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44#include <sys/attr.h>
45#include <sys/vnode.h>
46#include "un-namespace.h"
47
48struct attrs {
49	u_int32_t len;
50	attrreference_t name;
51	dev_t dev;
52	fsobj_type_t type;
53	fsobj_id_t id;
54	char buf[PATH_MAX];
55};
56
57#ifndef BUILDING_VARIANT
58__private_extern__ const struct attrlist _rp_alist = {
59	ATTR_BIT_MAP_COUNT,
60	0,
61	ATTR_CMN_NAME | ATTR_CMN_DEVID | ATTR_CMN_OBJTYPE | ATTR_CMN_OBJID,
62	0,
63	0,
64	0,
65	0,
66};
67#else /* BUILDING_VARIANT */
68__private_extern__ const struct attrlist _rp_alist;
69#endif /* BUILDING_VARIANT */
70
71extern char * __private_getcwd(char *, size_t, int);
72
73/*
74 * char *realpath(const char *path, char resolved[PATH_MAX]);
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 */
80char *
81realpath(const char *path, char inresolved[PATH_MAX])
82{
83	struct attrs attrs;
84	struct stat sb;
85	char *p, *q, *s;
86	size_t left_len, resolved_len, save_resolved_len;
87	unsigned symlinks;
88	int serrno, slen, useattrs, islink;
89	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
90	dev_t dev, lastdev;
91	struct statfs sfs;
92	static dev_t rootdev;
93	static int rootdev_inited = 0;
94	ino_t inode;
95	char *resolved;
96
97	if (path == NULL) {
98		errno = EINVAL;
99		return (NULL);
100	}
101#if __DARWIN_UNIX03
102	if (*path == 0) {
103		errno = ENOENT;
104		return (NULL);
105	}
106#endif /* __DARWIN_UNIX03 */
107	/*
108	 * Extension to the standard; if inresolved == NULL, allocate memory
109	 */
110	if (!inresolved) {
111	    if ((resolved = malloc(PATH_MAX)) == NULL) return (NULL);
112	} else {
113	    resolved = inresolved;
114	}
115	if (!rootdev_inited) {
116		rootdev_inited = 1;
117		if (stat("/", &sb) < 0) {
118error_return:
119			if (!inresolved) {
120				int e = errno;
121				free(resolved);
122				errno = e;
123			}
124			return (NULL);
125		}
126		rootdev = sb.st_dev;
127	}
128	serrno = errno;
129	symlinks = 0;
130	if (path[0] == '/') {
131		resolved[0] = '/';
132		resolved[1] = '\0';
133		if (path[1] == '\0') {
134			return (resolved);
135		}
136		resolved_len = 1;
137		left_len = strlcpy(left, path + 1, sizeof(left));
138	} else {
139#if !defined(VARIANT_DARWINEXTSN) && __DARWIN_UNIX03
140		/* 4447159: don't use GETPATH, so this will fail if */
141		/* if parent directories are not readable, as per POSIX */
142		if (__private_getcwd(resolved, PATH_MAX, 0) == NULL)
143#else /* VARIANT_DARWINEXTSN || !__DARWIN_UNIX03 */
144		if (__private_getcwd(resolved, PATH_MAX, 1) == NULL)
145#endif /* !VARIANT_DARWINEXTSN && __DARWIN_UNIX03 */
146		{
147			strlcpy(resolved, ".", PATH_MAX);
148			goto error_return;
149		}
150		resolved_len = strlen(resolved);
151		left_len = strlcpy(left, path, sizeof(left));
152	}
153	if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) {
154		errno = ENAMETOOLONG;
155		goto error_return;
156	}
157	if (resolved_len > 1) {
158		if (stat(resolved, &sb) < 0) {
159			goto error_return;
160		}
161		lastdev = sb.st_dev;
162	} else
163		lastdev = rootdev;
164
165	/*
166	 * Iterate over path components in `left'.
167	 */
168	while (left_len != 0) {
169		/*
170		 * Extract the next path component and adjust `left'
171		 * and its length.
172		 */
173		p = strchr(left, '/');
174		s = p ? p : left + left_len;
175		if (s - left >= sizeof(next_token)) {
176			errno = ENAMETOOLONG;
177			goto error_return;
178		}
179		memcpy(next_token, left, s - left);
180		next_token[s - left] = '\0';
181		left_len -= s - left;
182		if (p != NULL)
183			memmove(left, s + 1, left_len + 1);
184		if (resolved[resolved_len - 1] != '/') {
185			if (resolved_len + 1 >= PATH_MAX) {
186				errno = ENAMETOOLONG;
187				goto error_return;
188			}
189			resolved[resolved_len++] = '/';
190			resolved[resolved_len] = '\0';
191		}
192		if (next_token[0] == '\0')
193			continue;
194		else if (strcmp(next_token, ".") == 0)
195			continue;
196		else if (strcmp(next_token, "..") == 0) {
197			/*
198			 * Strip the last path component except when we have
199			 * single "/"
200			 */
201			if (resolved_len > 1) {
202				resolved[resolved_len - 1] = '\0';
203				q = strrchr(resolved, '/') + 1;
204				*q = '\0';
205				resolved_len = q - resolved;
206			}
207			continue;
208		}
209
210		/*
211		 * Save resolved_len, so that we can later null out
212		 * the the appended next_token, and replace with the
213		 * real name (matters on case-insensitive filesystems).
214		 */
215		save_resolved_len = resolved_len;
216
217		/*
218		 * Append the next path component and lstat() it. If
219		 * lstat() fails we still can return successfully if
220		 * there are no more path components left.
221		 */
222		resolved_len = strlcat(resolved, next_token, PATH_MAX);
223		if (resolved_len >= PATH_MAX) {
224			errno = ENAMETOOLONG;
225			goto error_return;
226		}
227		if (getattrlist(resolved, (void *)&_rp_alist, &attrs, sizeof(attrs), FSOPT_NOFOLLOW) == 0) {
228			useattrs = 1;
229			islink = (attrs.type == VLNK);
230			dev = attrs.dev;
231			inode = attrs.id.fid_objno;
232		} else if (errno == ENOTSUP || errno == EINVAL) {
233			if ((useattrs = lstat(resolved, &sb)) == 0) {
234				islink = S_ISLNK(sb.st_mode);
235				dev = sb.st_dev;
236				inode = sb.st_ino;
237			}
238		} else
239			useattrs = -1;
240		if (useattrs < 0) {
241#if !__DARWIN_UNIX03
242			if (errno == ENOENT && p == NULL) {
243				errno = serrno;
244				return (resolved);
245			}
246#endif /* !__DARWIN_UNIX03 */
247			goto error_return;
248		}
249		if (dev != lastdev) {
250			/*
251			 * We have crossed a mountpoint.  For volumes like UDF
252			 * the getattrlist name may not match the actual
253			 * mountpoint, so we just copy the mountpoint directly.
254			 * (3703138).  However, the mountpoint may not be
255			 * accessible, as when chroot-ed, so check first.
256			 * There may be a file on the chroot-ed volume with
257			 * the same name as the mountpoint, so compare device
258			 * and inode numbers.
259			 */
260			lastdev = dev;
261			if (statfs(resolved, &sfs) == 0 && lstat(sfs.f_mntonname, &sb) == 0 && dev == sb.st_dev && inode == sb.st_ino) {
262				/*
263				 * However, it's possible that the mountpoint
264				 * path matches, even though it isn't the real
265				 * path in the chroot-ed environment, so check
266				 * that each component of the mountpoint
267				 * is a directory (and not a symlink)
268				 */
269				char temp[sizeof(sfs.f_mntonname)];
270				char *cp;
271				int ok = 1;
272
273				strcpy(temp, sfs.f_mntonname);
274				for(;;) {
275					if ((cp = strrchr(temp, '/')) == NULL) {
276						ok = 0;
277						break;
278					}
279					if (cp <= temp)
280						break;
281					*cp = 0;
282					if (lstat(temp, &sb) < 0 || (sb.st_mode & S_IFMT) != S_IFDIR) {
283						ok = 0;
284						break;
285					}
286				}
287				if (ok) {
288					resolved_len = strlcpy(resolved, sfs.f_mntonname, PATH_MAX);
289					continue;
290				}
291			}
292			/* if we fail, use the other methods. */
293		}
294		if (islink) {
295			if (symlinks++ > MAXSYMLINKS) {
296				errno = ELOOP;
297				goto error_return;
298			}
299			slen = readlink(resolved, symlink, sizeof(symlink) - 1);
300			if (slen < 0) {
301				goto error_return;
302			}
303			symlink[slen] = '\0';
304			if (symlink[0] == '/') {
305				resolved[1] = 0;
306				resolved_len = 1;
307				lastdev = rootdev;
308			} else if (resolved_len > 1) {
309				/* Strip the last path component. */
310				resolved[resolved_len - 1] = '\0';
311				q = strrchr(resolved, '/') + 1;
312				*q = '\0';
313				resolved_len = q - resolved;
314			}
315
316			/*
317			 * If there are any path components left, then
318			 * append them to symlink. The result is placed
319			 * in `left'.
320			 */
321			if (p != NULL) {
322				if (symlink[slen - 1] != '/') {
323					if (slen + 1 >= sizeof(symlink)) {
324						errno = ENAMETOOLONG;
325						goto error_return;
326					}
327					symlink[slen] = '/';
328					symlink[slen + 1] = 0;
329				}
330				left_len = strlcat(symlink, left, sizeof(symlink));
331				if (left_len >= sizeof(left)) {
332					errno = ENAMETOOLONG;
333					goto error_return;
334				}
335			}
336			left_len = strlcpy(left, symlink, sizeof(left));
337		} else if (useattrs) {
338			/*
339			 * attrs already has the real name.
340			 */
341
342			resolved[save_resolved_len] = '\0';
343			resolved_len = strlcat(resolved, (const char *)&attrs.name + attrs.name.attr_dataoffset, PATH_MAX);
344			if (resolved_len >= PATH_MAX) {
345				errno = ENAMETOOLONG;
346				goto error_return;
347			}
348		}
349		/*
350		 * For the case of useattrs == 0, we could scan the directory
351		 * and try to match the inode.  There are many problems with
352		 * this: (1) the directory may not be readable, (2) for multiple
353		 * hard links, we would find the first, but not necessarily
354		 * the one specified in the path, (3) we can't try to do
355		 * a case-insensitive search to match the right one in (2),
356		 * because the underlying filesystem may do things like
357		 * decompose composed characters.  For most cases, doing
358		 * nothing is the right thing when useattrs == 0, so we punt
359		 * for now.
360		 */
361	}
362
363	/*
364	 * Remove trailing slash except when the resolved pathname
365	 * is a single "/".
366	 */
367	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
368		resolved[resolved_len - 1] = '\0';
369	return (resolved);
370}
371