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$");
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 <fcntl.h>
46#include "un-namespace.h"
47#include "libc_private.h"
48
49extern int __realpathat(int fd, const char *path, char *buf, size_t size,
50    int flags);
51
52/*
53 * Find the real name of path, by removing all ".", ".." and symlink
54 * components.  Returns (resolved) on success, or (NULL) on failure,
55 * in which case the path which caused trouble is left in (resolved).
56 */
57static char * __noinline
58realpath1(const char *path, char *resolved)
59{
60	struct stat sb;
61	char *p, *q;
62	size_t left_len, resolved_len, next_token_len;
63	unsigned symlinks;
64	ssize_t slen;
65	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
66
67	symlinks = 0;
68	if (path[0] == '/') {
69		resolved[0] = '/';
70		resolved[1] = '\0';
71		if (path[1] == '\0')
72			return (resolved);
73		resolved_len = 1;
74		left_len = strlcpy(left, path + 1, sizeof(left));
75	} else {
76		if (getcwd(resolved, PATH_MAX) == NULL) {
77			resolved[0] = '.';
78			resolved[1] = '\0';
79			return (NULL);
80		}
81		resolved_len = strlen(resolved);
82		left_len = strlcpy(left, path, sizeof(left));
83	}
84	if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) {
85		errno = ENAMETOOLONG;
86		return (NULL);
87	}
88
89	/*
90	 * Iterate over path components in `left'.
91	 */
92	while (left_len != 0) {
93		/*
94		 * Extract the next path component and adjust `left'
95		 * and its length.
96		 */
97		p = strchr(left, '/');
98
99		next_token_len = p != NULL ? (size_t)(p - left) : left_len;
100		memcpy(next_token, left, next_token_len);
101		next_token[next_token_len] = '\0';
102
103		if (p != NULL) {
104			left_len -= next_token_len + 1;
105			memmove(left, p + 1, left_len + 1);
106		} else {
107			left[0] = '\0';
108			left_len = 0;
109		}
110
111		if (resolved[resolved_len - 1] != '/') {
112			if (resolved_len + 1 >= PATH_MAX) {
113				errno = ENAMETOOLONG;
114				return (NULL);
115			}
116			resolved[resolved_len++] = '/';
117			resolved[resolved_len] = '\0';
118		}
119		if (next_token[0] == '\0') {
120			/* Handle consequential slashes. */
121			continue;
122		} else if (strcmp(next_token, ".") == 0) {
123			continue;
124		} else if (strcmp(next_token, "..") == 0) {
125			/*
126			 * Strip the last path component except when we have
127			 * single "/"
128			 */
129			if (resolved_len > 1) {
130				resolved[resolved_len - 1] = '\0';
131				q = strrchr(resolved, '/') + 1;
132				*q = '\0';
133				resolved_len = q - resolved;
134			}
135			continue;
136		}
137
138		/*
139		 * Append the next path component and lstat() it.
140		 */
141		resolved_len = strlcat(resolved, next_token, PATH_MAX);
142		if (resolved_len >= PATH_MAX) {
143			errno = ENAMETOOLONG;
144			return (NULL);
145		}
146		if (lstat(resolved, &sb) != 0)
147			return (NULL);
148		if (S_ISLNK(sb.st_mode)) {
149			if (symlinks++ > MAXSYMLINKS) {
150				errno = ELOOP;
151				return (NULL);
152			}
153			slen = readlink(resolved, symlink, sizeof(symlink));
154			if (slen <= 0 || slen >= (ssize_t)sizeof(symlink)) {
155				if (slen < 0)
156					; /* keep errno from readlink(2) call */
157				else if (slen == 0)
158					errno = ENOENT;
159				else
160					errno = ENAMETOOLONG;
161				return (NULL);
162			}
163			symlink[slen] = '\0';
164			if (symlink[0] == '/') {
165				resolved[1] = 0;
166				resolved_len = 1;
167			} else {
168				/* Strip the last path component. */
169				q = strrchr(resolved, '/') + 1;
170				*q = '\0';
171				resolved_len = q - resolved;
172			}
173
174			/*
175			 * If there are any path components left, then
176			 * append them to symlink. The result is placed
177			 * in `left'.
178			 */
179			if (p != NULL) {
180				if (symlink[slen - 1] != '/') {
181					if (slen + 1 >= (ssize_t)sizeof(symlink)) {
182						errno = ENAMETOOLONG;
183						return (NULL);
184					}
185					symlink[slen] = '/';
186					symlink[slen + 1] = 0;
187				}
188				left_len = strlcat(symlink, left,
189				    sizeof(symlink));
190				if (left_len >= sizeof(symlink)) {
191					errno = ENAMETOOLONG;
192					return (NULL);
193				}
194			}
195			left_len = strlcpy(left, symlink, sizeof(left));
196		} else if (!S_ISDIR(sb.st_mode) && p != NULL) {
197			errno = ENOTDIR;
198			return (NULL);
199		}
200	}
201
202	/*
203	 * Remove trailing slash except when the resolved pathname
204	 * is a single "/".
205	 */
206	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
207		resolved[resolved_len - 1] = '\0';
208	return (resolved);
209}
210
211char *
212realpath(const char * __restrict path, char * __restrict resolved)
213{
214	char *m, *res;
215
216	if (path == NULL) {
217		errno = EINVAL;
218		return (NULL);
219	}
220	if (path[0] == '\0') {
221		errno = ENOENT;
222		return (NULL);
223	}
224	if (resolved != NULL) {
225		m = NULL;
226	} else {
227		m = resolved = malloc(PATH_MAX);
228		if (resolved == NULL)
229			return (NULL);
230	}
231	if (__getosreldate() >= 1300080) {
232		if (__realpathat(AT_FDCWD, path, resolved, PATH_MAX, 0) == 0)
233			return (resolved);
234	}
235	res = realpath1(path, resolved);
236	if (res == NULL)
237		free(m);
238	return (res);
239}
240