1 /*
2 * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 /*
27 * Pathname canonicalization for Unix file systems
28 */
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <errno.h>
35 #include <limits.h>
36 #if !defined(_ALLBSD_SOURCE)
37 #include <alloca.h>
38 #endif
39
40
41 /* Note: The comments in this file use the terminology
42 defined in the java.io.File class */
43
44
45 /* Check the given name sequence to see if it can be further collapsed.
46 Return zero if not, otherwise return the number of names in the sequence. */
47
48 static int
collapsible(char * names)49 collapsible(char *names)
50 {
51 char *p = names;
52 int dots = 0, n = 0;
53
54 while (*p) {
55 if ((p[0] == '.') && ((p[1] == '\0')
56 || (p[1] == '/')
57 || ((p[1] == '.') && ((p[2] == '\0')
58 || (p[2] == '/'))))) {
59 dots = 1;
60 }
61 n++;
62 while (*p) {
63 if (*p == '/') {
64 p++;
65 break;
66 }
67 p++;
68 }
69 }
70 return (dots ? n : 0);
71 }
72
73
74 /* Split the names in the given name sequence,
75 replacing slashes with nulls and filling in the given index array */
76
77 static void
splitNames(char * names,char ** ix)78 splitNames(char *names, char **ix)
79 {
80 char *p = names;
81 int i = 0;
82
83 while (*p) {
84 ix[i++] = p++;
85 while (*p) {
86 if (*p == '/') {
87 *p++ = '\0';
88 break;
89 }
90 p++;
91 }
92 }
93 }
94
95
96 /* Join the names in the given name sequence, ignoring names whose index
97 entries have been cleared and replacing nulls with slashes as needed */
98
99 static void
joinNames(char * names,int nc,char ** ix)100 joinNames(char *names, int nc, char **ix)
101 {
102 int i;
103 char *p;
104
105 for (i = 0, p = names; i < nc; i++) {
106 if (!ix[i]) continue;
107 if (i > 0) {
108 p[-1] = '/';
109 }
110 if (p == ix[i]) {
111 p += strlen(p) + 1;
112 } else {
113 char *q = ix[i];
114 while ((*p++ = *q++));
115 }
116 }
117 *p = '\0';
118 }
119
120
121 /* Collapse "." and ".." names in the given path wherever possible.
122 A "." name may always be eliminated; a ".." name may be eliminated if it
123 follows a name that is neither "." nor "..". This is a syntactic operation
124 that performs no filesystem queries, so it should only be used to cleanup
125 after invoking the realpath() procedure. */
126
127 static void
collapse(char * path)128 collapse(char *path)
129 {
130 char *names = (path[0] == '/') ? path + 1 : path; /* Preserve first '/' */
131 int nc;
132 char **ix;
133 int i, j;
134 char *p, *q;
135
136 nc = collapsible(names);
137 if (nc < 2) return; /* Nothing to do */
138 ix = (char **)alloca(nc * sizeof(char *));
139 splitNames(names, ix);
140
141 for (i = 0; i < nc; i++) {
142 int dots = 0;
143
144 /* Find next occurrence of "." or ".." */
145 do {
146 char *p = ix[i];
147 if (p[0] == '.') {
148 if (p[1] == '\0') {
149 dots = 1;
150 break;
151 }
152 if ((p[1] == '.') && (p[2] == '\0')) {
153 dots = 2;
154 break;
155 }
156 }
157 i++;
158 } while (i < nc);
159 if (i >= nc) break;
160
161 /* At this point i is the index of either a "." or a "..", so take the
162 appropriate action and then continue the outer loop */
163 if (dots == 1) {
164 /* Remove this instance of "." */
165 ix[i] = 0;
166 }
167 else {
168 /* If there is a preceding name, remove both that name and this
169 instance of ".."; otherwise, leave the ".." as is */
170 for (j = i - 1; j >= 0; j--) {
171 if (ix[j]) break;
172 }
173 if (j < 0) continue;
174 ix[j] = 0;
175 ix[i] = 0;
176 }
177 /* i will be incremented at the top of the loop */
178 }
179
180 joinNames(names, nc, ix);
181 }
182
183
184 /* Convert a pathname to canonical form. The input path is assumed to contain
185 no duplicate slashes. On Solaris we can use realpath() to do most of the
186 work, though once that's done we still must collapse any remaining "." and
187 ".." names by hand. */
188
189 // Android-changed: hidden to avoid conflict with libm (b/135018555)
190 __attribute__((visibility("hidden")))
191 int
canonicalize(char * original,char * resolved,int len)192 canonicalize(char *original, char *resolved, int len)
193 {
194 if (len < PATH_MAX) {
195 errno = EINVAL;
196 return -1;
197 }
198
199 if (strlen(original) > PATH_MAX) {
200 errno = ENAMETOOLONG;
201 return -1;
202 }
203
204 /* First try realpath() on the entire path */
205 if (realpath(original, resolved)) {
206 /* That worked, so return it */
207 collapse(resolved);
208 return 0;
209 }
210 else {
211 /* Something's bogus in the original path, so remove names from the end
212 until either some subpath works or we run out of names */
213 char *p, *end, *r = NULL;
214 char path[PATH_MAX + 1];
215
216 strncpy(path, original, sizeof(path));
217 if (path[PATH_MAX] != '\0') {
218 errno = ENAMETOOLONG;
219 return -1;
220 }
221 end = path + strlen(path);
222
223 for (p = end; p > path;) {
224
225 /* Skip last element */
226 while ((--p > path) && (*p != '/'));
227 if (p == path) break;
228
229 /* Try realpath() on this subpath */
230 *p = '\0';
231 r = realpath(path, resolved);
232 *p = (p == end) ? '\0' : '/';
233
234 if (r != NULL) {
235 /* The subpath has a canonical path */
236 break;
237 }
238 // Android-changed: Added ENOTCONN case (b/26645585, b/26070583)
239 else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES || errno == ENOTCONN) {
240 /* If the lookup of a particular subpath fails because the file
241 does not exist, because it is of the wrong type, or because
242 access is denied, then remove its last name and try again.
243 Other I/O problems cause an error return. */
244
245 /* NOTE: ENOTCONN seems like an odd errno to expect, but this is
246 the behaviour on linux for fuse filesystems when the fuse device
247 associated with the FS is closed but the filesystem is not
248 unmounted. */
249 continue;
250 }
251 else {
252 return -1;
253 }
254 }
255
256 if (r != NULL) {
257 /* Append unresolved subpath to resolved subpath */
258 int rn = strlen(r);
259 if (rn + (int)strlen(p) >= len) {
260 /* Buffer overflow */
261 errno = ENAMETOOLONG;
262 return -1;
263 }
264 if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) {
265 /* Avoid duplicate slashes */
266 p++;
267 }
268 strcpy(r + rn, p);
269 collapse(r);
270 return 0;
271 }
272 else {
273 /* Nothing resolved, so just return the original path */
274 strcpy(resolved, path);
275 collapse(resolved);
276 return 0;
277 }
278 }
279
280 }
281