realpath.c revision 330897
1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The names of the authors may not be used to endorse or promote
15 *    products derived from this software without specific prior written
16 *    permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#if defined(LIBC_SCCS) && !defined(lint)
32static char sccsid[] = "@(#)realpath.c	8.1 (Berkeley) 2/16/94";
33#endif /* LIBC_SCCS and not lint */
34#include <sys/cdefs.h>
35__FBSDID("$FreeBSD: stable/11/lib/libc/stdlib/realpath.c 330897 2018-03-14 03:19:51Z eadler $");
36
37#include "namespace.h"
38#include <sys/param.h>
39#include <sys/stat.h>
40
41#include <errno.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45#include "un-namespace.h"
46
47/*
48 * Find the real name of path, by removing all ".", ".." and symlink
49 * components.  Returns (resolved) on success, or (NULL) on failure,
50 * in which case the path which caused trouble is left in (resolved).
51 */
52static char *
53realpath1(const char *path, char *resolved)
54{
55	struct stat sb;
56	char *p, *q;
57	size_t left_len, resolved_len, next_token_len;
58	unsigned symlinks;
59	ssize_t slen;
60	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
61
62	symlinks = 0;
63	if (path[0] == '/') {
64		resolved[0] = '/';
65		resolved[1] = '\0';
66		if (path[1] == '\0')
67			return (resolved);
68		resolved_len = 1;
69		left_len = strlcpy(left, path + 1, sizeof(left));
70	} else {
71		if (getcwd(resolved, PATH_MAX) == NULL) {
72			resolved[0] = '.';
73			resolved[1] = '\0';
74			return (NULL);
75		}
76		resolved_len = strlen(resolved);
77		left_len = strlcpy(left, path, sizeof(left));
78	}
79	if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) {
80		errno = ENAMETOOLONG;
81		return (NULL);
82	}
83
84	/*
85	 * Iterate over path components in `left'.
86	 */
87	while (left_len != 0) {
88		/*
89		 * Extract the next path component and adjust `left'
90		 * and its length.
91		 */
92		p = strchr(left, '/');
93
94		next_token_len = p != NULL ? p - left : left_len;
95		memcpy(next_token, left, next_token_len);
96		next_token[next_token_len] = '\0';
97
98		if (p != NULL) {
99			left_len -= next_token_len + 1;
100			memmove(left, p + 1, left_len + 1);
101		} else {
102			left[0] = '\0';
103			left_len = 0;
104		}
105
106		if (resolved[resolved_len - 1] != '/') {
107			if (resolved_len + 1 >= PATH_MAX) {
108				errno = ENAMETOOLONG;
109				return (NULL);
110			}
111			resolved[resolved_len++] = '/';
112			resolved[resolved_len] = '\0';
113		}
114		if (next_token[0] == '\0') {
115			/* Handle consequential slashes. */
116			continue;
117		} else if (strcmp(next_token, ".") == 0) {
118			continue;
119		} else if (strcmp(next_token, "..") == 0) {
120			/*
121			 * Strip the last path component except when we have
122			 * single "/"
123			 */
124			if (resolved_len > 1) {
125				resolved[resolved_len - 1] = '\0';
126				q = strrchr(resolved, '/') + 1;
127				*q = '\0';
128				resolved_len = q - resolved;
129			}
130			continue;
131		}
132
133		/*
134		 * Append the next path component and lstat() it.
135		 */
136		resolved_len = strlcat(resolved, next_token, PATH_MAX);
137		if (resolved_len >= PATH_MAX) {
138			errno = ENAMETOOLONG;
139			return (NULL);
140		}
141		if (lstat(resolved, &sb) != 0)
142			return (NULL);
143		if (S_ISLNK(sb.st_mode)) {
144			if (symlinks++ > MAXSYMLINKS) {
145				errno = ELOOP;
146				return (NULL);
147			}
148			slen = readlink(resolved, symlink, sizeof(symlink));
149			if (slen <= 0 || slen >= sizeof(symlink)) {
150				if (slen < 0)
151					; /* keep errno from readlink(2) call */
152				else if (slen == 0)
153					errno = ENOENT;
154				else
155					errno = ENAMETOOLONG;
156				return (NULL);
157			}
158			symlink[slen] = '\0';
159			if (symlink[0] == '/') {
160				resolved[1] = 0;
161				resolved_len = 1;
162			} else {
163				/* Strip the last path component. */
164				q = strrchr(resolved, '/') + 1;
165				*q = '\0';
166				resolved_len = q - resolved;
167			}
168
169			/*
170			 * If there are any path components left, then
171			 * append them to symlink. The result is placed
172			 * in `left'.
173			 */
174			if (p != NULL) {
175				if (symlink[slen - 1] != '/') {
176					if (slen + 1 >= sizeof(symlink)) {
177						errno = ENAMETOOLONG;
178						return (NULL);
179					}
180					symlink[slen] = '/';
181					symlink[slen + 1] = 0;
182				}
183				left_len = strlcat(symlink, left,
184				    sizeof(symlink));
185				if (left_len >= sizeof(symlink)) {
186					errno = ENAMETOOLONG;
187					return (NULL);
188				}
189			}
190			left_len = strlcpy(left, symlink, sizeof(left));
191		} else if (!S_ISDIR(sb.st_mode) && p != NULL) {
192			errno = ENOTDIR;
193			return (NULL);
194		}
195	}
196
197	/*
198	 * Remove trailing slash except when the resolved pathname
199	 * is a single "/".
200	 */
201	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
202		resolved[resolved_len - 1] = '\0';
203	return (resolved);
204}
205
206char *
207realpath(const char * __restrict path, char * __restrict resolved)
208{
209	char *m, *res;
210
211	if (path == NULL) {
212		errno = EINVAL;
213		return (NULL);
214	}
215	if (path[0] == '\0') {
216		errno = ENOENT;
217		return (NULL);
218	}
219	if (resolved != NULL) {
220		m = NULL;
221	} else {
222		m = resolved = malloc(PATH_MAX);
223		if (resolved == NULL)
224			return (NULL);
225	}
226	res = realpath1(path, resolved);
227	if (res == NULL)
228		free(m);
229	return (res);
230}
231