1 /* @(#)resolvepath.c	1.10 19/09/27 Copyright 2011-2019 J. Schilling */
2 /*
3  *	resolvepath() removes "./" and non-leading "/.." path components.
4  *	It tries to do the same as the Solaris syscall with the same name.
5  *	We however also implement the resolvenpath() variant that does
6  *	not require the file to exist.
7  *
8  *	Copyright (c) 2011-2019 J. Schilling
9  */
10 /*
11  * The contents of this file are subject to the terms of the
12  * Common Development and Distribution License, Version 1.0 only
13  * (the "License").  You may not use this file except in compliance
14  * with the License.
15  *
16  * See the file CDDL.Schily.txt in this distribution for details.
17  * A copy of the CDDL is also available via the Internet at
18  * http://www.opensource.org/licenses/cddl1.txt
19  *
20  * When distributing Covered Code, include this CDDL HEADER in each
21  * file and include the License file CDDL.Schily.txt from this distribution.
22  */
23 
24 /*
25  * Since we need to call stat()/lstat() and since this is not a
26  * predictable call, we always compile this module in largefile mode.
27  */
28 #define	USE_LARGEFILES
29 
30 #include <schily/types.h>
31 #include <schily/stat.h>
32 #include <schily/errno.h>
33 #include <schily/maxpath.h>
34 #include <schily/string.h>
35 #include <schily/standard.h>
36 #include <schily/schily.h>
37 
38 #ifndef	HAVE_LSTAT
39 #define	lstat	stat
40 #endif
41 
42 #ifndef	HAVE_RESOLVEPATH
43 EXPORT	int	resolvepath	__PR((const char *path, char *buf,
44 					size_t bufsiz));
45 #endif
46 EXPORT	int	resolvenpath	__PR((const char *path, char *buf,
47 					size_t bufsiz));
48 LOCAL	int	pathresolve	__PR((const char *path, const char *p,
49 					char *buf, char *b,
50 					size_t bufsiz, int flags));
51 LOCAL	int	shorten		__PR((char *path));
52 
53 #ifndef	HAVE_RESOLVEPATH
54 /*
55  * Warning: The Solaris system call "resolvepath()" does not null-terminate
56  * the result in "buf". Code that used resolvepath() should manually
57  * null-terminate the buffer.
58  *
59  * Warning: The Solaris system call "resolvepath()" works when "path" and "buf"
60  * are the same buffer but our implementation does not.
61  *
62  * Note: Path needs to exist.
63  */
64 EXPORT int
resolvepath(path,buf,bufsiz)65 resolvepath(path, buf, bufsiz)
66 	register const char	*path;
67 			char	*buf;
68 			size_t	bufsiz;
69 {
70 	return (pathresolve(path, path, buf, buf, bufsiz, RSPF_EXIST));
71 }
72 #endif
73 
74 /*
75  * Path may not exist.
76  */
77 EXPORT int
resolvenpath(path,buf,bufsiz)78 resolvenpath(path, buf, bufsiz)
79 	register const char	*path;
80 			char	*buf;
81 			size_t	bufsiz;
82 {
83 	return (pathresolve(path, path, buf, buf, bufsiz, 0));
84 }
85 
86 /*
87  * The behavior may be controlled via flags:
88  *
89  * RSPF_EXIST		All path components must exist
90  * RSPF_NOFOLLOW_LAST	Do not follow link in last path component
91  */
92 EXPORT int
resolvefpath(path,buf,bufsiz,flags)93 resolvefpath(path, buf, bufsiz, flags)
94 	register const char	*path;
95 			char	*buf;
96 			size_t	bufsiz;
97 			int	flags;
98 {
99 	return (pathresolve(path, path, buf, buf, bufsiz, flags));
100 }
101 
102 LOCAL int
pathresolve(path,p,buf,b,bufsiz,flags)103 pathresolve(path, p, buf, b, bufsiz, flags)
104 	register const char	*path;
105 	register const char	*p;
106 			char	*buf;
107 	register	char	*b;
108 			size_t	bufsiz;
109 			int	flags;
110 {
111 register 	char	*e;
112 		int	len = 0;
113 		struct stat rsb;
114 		struct stat sb;
115 
116 	if (path == NULL || buf == NULL) {
117 		seterrno(EFAULT);
118 		return (-1);
119 	}
120 	if (*path == '\0') {
121 		seterrno(EINVAL);
122 		return (-1);
123 	}
124 
125 	if (bufsiz <= 0)
126 		return (0);
127 	if (p == path && p[0] == '/')
128 		*b++ = *p++;
129 
130 	rsb.st_ino = 0;
131 	rsb.st_nlink = 0;
132 
133 	while (*p) {
134 		if ((b - buf) >= bufsiz) {
135 			buf[bufsiz-1] = '\0';	/* bufziz > 0 tested before */
136 			return (bufsiz);
137 		}
138 		*b = '\0';
139 
140 		if (p[0] == '/') {
141 			p += 1;
142 			continue;
143 		}
144 		if (p[0] == '.' &&
145 		    (p[1] == '/' || p[1] == '\0')) {
146 			if (p == path && p[1] == '\0')
147 				return (strlcpy(buf, ".", bufsiz));
148 			if (p[1] == '\0')
149 				p += 1;
150 			else
151 				p += 2;
152 			continue;
153 		}
154 
155 		if (p[0] == '.' && p[1] == '.' &&
156 		    (p[2] == '/' || p[2] == '\0')) {
157 			if (p[2] == '\0')
158 				p += 2;
159 			else
160 				p += 3;
161 			if (!shorten(buf)) {
162 				if (strlcat(buf,
163 					buf[0]?"/..":"..", bufsiz) >= bufsiz) {
164 					return (bufsiz);
165 				}
166 			}
167 			b = buf + strlen(buf);
168 			if (stat(buf, &sb) < 0) {
169 				/*
170 				 * If it does not exist, it cannot be identical
171 				 * to "/", so let us continue.
172 				 */
173 				if (flags & RSPF_EXIST)
174 					return (-1);
175 				else
176 					sb.st_ino = 0;
177 			}
178 			if (rsb.st_ino == 0 || rsb.st_nlink == 0) {
179 				if (stat("/", &rsb) < 0)
180 					return (-1);
181 			}
182 			if (sb.st_dev == rsb.st_dev &&
183 			    sb.st_ino == rsb.st_ino) {
184 				if (strlcpy(buf, "/", bufsiz) >= bufsiz) {
185 					return (bufsiz);
186 				}
187 			}
188 			continue;
189 		}
190 
191 		if (b > &buf[1] || (b == &buf[1] && buf[0] != '/'))
192 			*b++ = '/';
193 		if ((b - buf) >= bufsiz) {
194 			buf[bufsiz-1] = '\0';	/* bufziz > 0 tested before */
195 			return (bufsiz);
196 		}
197 		e = strchr(p, '/');
198 		if (e)
199 			len = e - p;
200 		else
201 			len = strlen(p);
202 		if (++len > (bufsiz - (b - buf))) { /* Add one for strlcpy() */
203 			len = bufsiz - (b - buf);
204 			strlcpy(b, p, len);
205 			return (bufsiz);
206 		}
207 
208 		strlcpy(b, p, len);
209 		p += len - 1;
210 		if (lstat(buf, &sb) < 0) {
211 			if (flags & RSPF_EXIST)
212 				return (-1);
213 			sb.st_mode = S_IFREG;
214 		}
215 		if (e == NULL && (flags & RSPF_NOFOLLOW_LAST) &&
216 		    S_ISLNK(sb.st_mode)) {
217 			b += len - 1;
218 			break;
219 		} else if (S_ISLNK(sb.st_mode)) {
220 			char	lname[PATH_MAX+1];
221 			char	c = *b;
222 			int	depth = (flags >> 8) + 1;
223 
224 			len = readlink(buf, lname, sizeof (lname));
225 			if (len < 0) {
226 				return (-1);
227 			}
228 			if (len < sizeof (lname))
229 				lname[len] = '\0';
230 			else
231 				lname[sizeof (lname)-1] = '\0';
232 			*b = '\0';
233 			len += strlen(buf) + 1;
234 			{
235 #ifdef	HAVE_DYN_ARRAYS
236 			char bx[len];
237 #else
238 			char *bx = malloc(len);
239 			if (bx == NULL)
240 				return (-1);
241 #endif
242 			strcatl(bx, buf, lname, (char *)0);
243 			/*
244 			 * Check whether we are in a symlink loop.
245 			 * This is when the path does not change or when
246 			 * we have too many symlinks in the path.
247 			 */
248 #define	MAX_NEST	1024
249 			if (depth > MAX_NEST || strcmp(path, bx) == 0) {
250 				*b = c;
251 #ifndef	HAVE_DYN_ARRAYS
252 				free(bx);
253 #endif
254 				/*
255 				 * The Solaris syscall returns ELOOP in this
256 				 * case, so do we...
257 				 */
258 				if (depth > MAX_NEST || (flags & RSPF_EXIST)) {
259 #ifdef	ELOOP
260 					seterrno(ELOOP);
261 #else
262 					seterrno(EINVAL);
263 #endif
264 					return (-1);
265 				}
266 				return (strlen(buf));
267 			}
268 			if (b > &buf[1] && b[-1] == '/')
269 				--b;
270 			flags &= 0xFF;		/* Mask off depth counter */
271 			flags |= depth << 8;	/* Add new depth counter  */
272 			len = pathresolve(bx, bx + (b - buf), buf, b,
273 								bufsiz, flags);
274 #ifndef	HAVE_DYN_ARRAYS
275 			free(bx);
276 #endif
277 			}
278 			if (len < 0) {
279 				return (-1);
280 			}
281 			b = buf + len;
282 		} else {
283 			b += len - 1;
284 		}
285 		if (e == NULL)
286 			break;
287 	}
288 
289 	if ((b - buf) >= bufsiz) {
290 		buf[bufsiz-1] = '\0';		/* bufziz > 0 tested before */
291 		return (bufsiz);
292 	}
293 	buf[(b - buf)] = '\0';
294 	return (b - buf);
295 }
296 
297 /*
298  * Removes last path name component.
299  * Returns FALSE if path could not be shortened.
300  * Does not remove path components if already at root direcory.
301  */
302 LOCAL int
shorten(path)303 shorten(path)
304 	register	char	*path;
305 {
306 	register	char	*p = path;
307 
308 
309 	if (p[0] == '\0')			/* Cannot shorten empty path */
310 		return (FALSE);			/* Leave empty to add ".."   */
311 	if (p[0] == '.' && p[1] == '\0') {	/* Path is just "."	    */
312 		*p = '\0';			/* Prepare to add ".."	    */
313 		return (FALSE);
314 	}
315 
316 	for (p = path; *p++ != '\0'; );
317 	while (p > path) {
318 		if (*--p == '/')
319 			break;
320 	}
321 
322 	if (p[0] == '.' && p[1] == '.' && p[2] == '\0') {
323 		return (FALSE);
324 	}
325 	if (p[0] == '/' && p[1] == '.' && p[2] == '.' && p[3] == '\0') {
326 		return (FALSE);
327 	}
328 
329 	if (p == path && p[0] == '/')
330 		p++;
331 	else if (p == path)
332 		*p++ = '.';
333 	*p = '\0';
334 	return (TRUE);
335 }
336