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