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