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