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