xref: /openbsd/regress/sys/kern/realpath/realpath3.c (revision 5b763bb3)
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