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