1*5b763bb3Sbluhm /* $OpenBSD: realpath3.c,v 1.4 2019/07/16 17:48:56 bluhm Exp $ */
2d1b74578Sbeck /*
3d1b74578Sbeck * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
4d1b74578Sbeck *
5d1b74578Sbeck * Redistribution and use in source and binary forms, with or without
6d1b74578Sbeck * modification, are permitted provided that the following conditions
7d1b74578Sbeck * are met:
8d1b74578Sbeck * 1. Redistributions of source code must retain the above copyright
9d1b74578Sbeck * notice, this list of conditions and the following disclaimer.
10d1b74578Sbeck * 2. Redistributions in binary form must reproduce the above copyright
11d1b74578Sbeck * notice, this list of conditions and the following disclaimer in the
12d1b74578Sbeck * documentation and/or other materials provided with the distribution.
13d1b74578Sbeck * 3. The names of the authors may not be used to endorse or promote
14d1b74578Sbeck * products derived from this software without specific prior written
15d1b74578Sbeck * permission.
16d1b74578Sbeck *
17d1b74578Sbeck * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18d1b74578Sbeck * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19d1b74578Sbeck * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20d1b74578Sbeck * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21d1b74578Sbeck * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22d1b74578Sbeck * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23d1b74578Sbeck * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24d1b74578Sbeck * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25d1b74578Sbeck * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26d1b74578Sbeck * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27d1b74578Sbeck * SUCH DAMAGE.
28d1b74578Sbeck */
29d1b74578Sbeck
30c7ce6e13Sbluhm #include <sys/stat.h>
31c7ce6e13Sbluhm
32d1b74578Sbeck #include <errno.h>
33d1b74578Sbeck #include <stdlib.h>
34d1b74578Sbeck #include <string.h>
35d1b74578Sbeck #include <unistd.h>
36d1b74578Sbeck #include <limits.h>
37d1b74578Sbeck
38d1b74578Sbeck /*
39d1b74578Sbeck * The OpenBSD 6.4 libc version of realpath(3), preserved to validate
40d1b74578Sbeck * an implementation of realpath(2).
41c7ce6e13Sbluhm * As our kernel realpath(2) is heading towards to POSIX compliance,
42c7ce6e13Sbluhm * some details in this version have changed.
43d1b74578Sbeck */
44d1b74578Sbeck
45d1b74578Sbeck /*
46d1b74578Sbeck * char *realpath(const char *path, char resolved[PATH_MAX]);
47d1b74578Sbeck *
48d1b74578Sbeck * Find the real name of path, by removing all ".", ".." and symlink
49d1b74578Sbeck * components. Returns (resolved) on success, or (NULL) on failure,
50d1b74578Sbeck * in which case the path which caused trouble is left in (resolved).
51d1b74578Sbeck */
52d1b74578Sbeck char *
realpath3(const char * path,char * resolved)53d1b74578Sbeck realpath3(const char *path, char *resolved)
54d1b74578Sbeck {
55d1b74578Sbeck const char *p;
56d1b74578Sbeck char *q;
57d1b74578Sbeck size_t left_len, resolved_len, next_token_len;
58d1b74578Sbeck unsigned symlinks;
59d1b74578Sbeck int serrno, mem_allocated;
60d1b74578Sbeck ssize_t slen;
61d1b74578Sbeck char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
628430be28Sbluhm struct stat sb;
63d1b74578Sbeck
64d1b74578Sbeck if (path == NULL) {
65d1b74578Sbeck errno = EINVAL;
66d1b74578Sbeck return (NULL);
67d1b74578Sbeck }
68d1b74578Sbeck
69d1b74578Sbeck if (path[0] == '\0') {
70d1b74578Sbeck errno = ENOENT;
71d1b74578Sbeck return (NULL);
72d1b74578Sbeck }
73d1b74578Sbeck
748430be28Sbluhm /*
758430be28Sbluhm * POSIX demands ENOENT for non existing file.
768430be28Sbluhm */
778430be28Sbluhm if (stat(path, &sb) == -1)
788430be28Sbluhm return (NULL);
798430be28Sbluhm
80*5b763bb3Sbluhm /*
81*5b763bb3Sbluhm * POSIX demands ENOTDIR for non directories ending in a "/".
82*5b763bb3Sbluhm */
83*5b763bb3Sbluhm if (!S_ISDIR(sb.st_mode) && strchr(path, '/') != NULL &&
84*5b763bb3Sbluhm path[strlen(path) - 1] == '/') {
85*5b763bb3Sbluhm errno = ENOTDIR;
86*5b763bb3Sbluhm return (NULL);
87*5b763bb3Sbluhm }
88*5b763bb3Sbluhm
89d1b74578Sbeck serrno = errno;
90d1b74578Sbeck
91d1b74578Sbeck if (resolved == NULL) {
92d1b74578Sbeck resolved = malloc(PATH_MAX);
93d1b74578Sbeck if (resolved == NULL)
94d1b74578Sbeck return (NULL);
95d1b74578Sbeck mem_allocated = 1;
96d1b74578Sbeck } else
97d1b74578Sbeck mem_allocated = 0;
98d1b74578Sbeck
99d1b74578Sbeck symlinks = 0;
100d1b74578Sbeck if (path[0] == '/') {
101d1b74578Sbeck resolved[0] = '/';
102d1b74578Sbeck resolved[1] = '\0';
103d1b74578Sbeck if (path[1] == '\0')
104d1b74578Sbeck return (resolved);
105d1b74578Sbeck resolved_len = 1;
106d1b74578Sbeck left_len = strlcpy(left, path + 1, sizeof(left));
107d1b74578Sbeck } else {
108d1b74578Sbeck if (getcwd(resolved, PATH_MAX) == NULL) {
109d1b74578Sbeck if (mem_allocated)
110d1b74578Sbeck free(resolved);
111d1b74578Sbeck else
112d1b74578Sbeck strlcpy(resolved, ".", PATH_MAX);
113d1b74578Sbeck return (NULL);
114d1b74578Sbeck }
115d1b74578Sbeck resolved_len = strlen(resolved);
116d1b74578Sbeck left_len = strlcpy(left, path, sizeof(left));
117d1b74578Sbeck }
118d1b74578Sbeck if (left_len >= sizeof(left)) {
119d1b74578Sbeck errno = ENAMETOOLONG;
120d1b74578Sbeck goto err;
121d1b74578Sbeck }
122d1b74578Sbeck
123d1b74578Sbeck /*
124d1b74578Sbeck * Iterate over path components in `left'.
125d1b74578Sbeck */
126d1b74578Sbeck while (left_len != 0) {
127d1b74578Sbeck /*
128d1b74578Sbeck * Extract the next path component and adjust `left'
129d1b74578Sbeck * and its length.
130d1b74578Sbeck */
131d1b74578Sbeck p = strchr(left, '/');
132d1b74578Sbeck
133d1b74578Sbeck next_token_len = p ? (size_t) (p - left) : left_len;
134d1b74578Sbeck memcpy(next_token, left, next_token_len);
135d1b74578Sbeck next_token[next_token_len] = '\0';
136d1b74578Sbeck
137d1b74578Sbeck if (p != NULL) {
138d1b74578Sbeck left_len -= next_token_len + 1;
139d1b74578Sbeck memmove(left, p + 1, left_len + 1);
140d1b74578Sbeck } else {
141d1b74578Sbeck left[0] = '\0';
142d1b74578Sbeck left_len = 0;
143d1b74578Sbeck }
144d1b74578Sbeck
145d1b74578Sbeck if (resolved[resolved_len - 1] != '/') {
146d1b74578Sbeck if (resolved_len + 1 >= PATH_MAX) {
147d1b74578Sbeck errno = ENAMETOOLONG;
148d1b74578Sbeck goto err;
149d1b74578Sbeck }
150d1b74578Sbeck resolved[resolved_len++] = '/';
151d1b74578Sbeck resolved[resolved_len] = '\0';
152d1b74578Sbeck }
153d1b74578Sbeck if (next_token[0] == '\0')
154d1b74578Sbeck continue;
155d1b74578Sbeck else if (strcmp(next_token, ".") == 0)
156d1b74578Sbeck continue;
157d1b74578Sbeck else if (strcmp(next_token, "..") == 0) {
158d1b74578Sbeck /*
159d1b74578Sbeck * Strip the last path component except when we have
160d1b74578Sbeck * single "/"
161d1b74578Sbeck */
162d1b74578Sbeck if (resolved_len > 1) {
163d1b74578Sbeck resolved[resolved_len - 1] = '\0';
164d1b74578Sbeck q = strrchr(resolved, '/') + 1;
165d1b74578Sbeck *q = '\0';
166d1b74578Sbeck resolved_len = q - resolved;
167d1b74578Sbeck }
168d1b74578Sbeck continue;
169d1b74578Sbeck }
170d1b74578Sbeck
171d1b74578Sbeck /*
172d1b74578Sbeck * Append the next path component and readlink() it. If
173d1b74578Sbeck * readlink() fails we still can return successfully if
174d1b74578Sbeck * it exists but isn't a symlink, or if there are no more
175d1b74578Sbeck * path components left.
176d1b74578Sbeck */
177d1b74578Sbeck resolved_len = strlcat(resolved, next_token, PATH_MAX);
178d1b74578Sbeck if (resolved_len >= PATH_MAX) {
179d1b74578Sbeck errno = ENAMETOOLONG;
180d1b74578Sbeck goto err;
181d1b74578Sbeck }
182d1b74578Sbeck slen = readlink(resolved, symlink, sizeof(symlink));
183d1b74578Sbeck if (slen < 0) {
184d1b74578Sbeck switch (errno) {
185d1b74578Sbeck case EINVAL:
186d1b74578Sbeck /* not a symlink, continue to next component */
187d1b74578Sbeck continue;
188d1b74578Sbeck case ENOENT:
189d1b74578Sbeck if (p == NULL) {
190d1b74578Sbeck errno = serrno;
191d1b74578Sbeck return (resolved);
192d1b74578Sbeck }
193d1b74578Sbeck /* FALLTHROUGH */
194d1b74578Sbeck default:
195d1b74578Sbeck goto err;
196d1b74578Sbeck }
197d1b74578Sbeck } else if (slen == 0) {
198d1b74578Sbeck errno = EINVAL;
199d1b74578Sbeck goto err;
200d1b74578Sbeck } else if (slen == sizeof(symlink)) {
201d1b74578Sbeck errno = ENAMETOOLONG;
202d1b74578Sbeck goto err;
203d1b74578Sbeck } else {
204d1b74578Sbeck if (symlinks++ > SYMLOOP_MAX) {
205d1b74578Sbeck errno = ELOOP;
206d1b74578Sbeck goto err;
207d1b74578Sbeck }
208d1b74578Sbeck
209d1b74578Sbeck symlink[slen] = '\0';
210d1b74578Sbeck if (symlink[0] == '/') {
211d1b74578Sbeck resolved[1] = 0;
212d1b74578Sbeck resolved_len = 1;
213d1b74578Sbeck } else {
214d1b74578Sbeck /* Strip the last path component. */
215d1b74578Sbeck q = strrchr(resolved, '/') + 1;
216d1b74578Sbeck *q = '\0';
217d1b74578Sbeck resolved_len = q - resolved;
218d1b74578Sbeck }
219d1b74578Sbeck
220d1b74578Sbeck /*
221d1b74578Sbeck * If there are any path components left, then
222d1b74578Sbeck * append them to symlink. The result is placed
223d1b74578Sbeck * in `left'.
224d1b74578Sbeck */
225d1b74578Sbeck if (p != NULL) {
226d1b74578Sbeck if (symlink[slen - 1] != '/') {
227d1b74578Sbeck if (slen + 1 >= sizeof(symlink)) {
228d1b74578Sbeck errno = ENAMETOOLONG;
229d1b74578Sbeck goto err;
230d1b74578Sbeck }
231d1b74578Sbeck symlink[slen] = '/';
232d1b74578Sbeck symlink[slen + 1] = 0;
233d1b74578Sbeck }
234d1b74578Sbeck left_len = strlcat(symlink, left, sizeof(symlink));
235d1b74578Sbeck if (left_len >= sizeof(symlink)) {
236d1b74578Sbeck errno = ENAMETOOLONG;
237d1b74578Sbeck goto err;
238d1b74578Sbeck }
239d1b74578Sbeck }
240d1b74578Sbeck left_len = strlcpy(left, symlink, sizeof(left));
241d1b74578Sbeck }
242d1b74578Sbeck }
243d1b74578Sbeck
244d1b74578Sbeck /*
245d1b74578Sbeck * Remove trailing slash except when the resolved pathname
246d1b74578Sbeck * is a single "/".
247d1b74578Sbeck */
248d1b74578Sbeck if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
249d1b74578Sbeck resolved[resolved_len - 1] = '\0';
250d1b74578Sbeck return (resolved);
251d1b74578Sbeck
252d1b74578Sbeck err:
253d1b74578Sbeck if (mem_allocated)
254d1b74578Sbeck free(resolved);
255d1b74578Sbeck return (NULL);
256d1b74578Sbeck }
257