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
48static int
49collapsible(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
77static void
78splitNames(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
99static void
100joinNames(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
127static void
128collapse(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
189int
190canonicalize(char *original, char *resolved, int len)
191{
192    if (len < PATH_MAX) {
193        errno = EINVAL;
194        return -1;
195    }
196
197    if (strlen(original) > PATH_MAX) {
198        errno = ENAMETOOLONG;
199        return -1;
200    }
201
202    /* First try realpath() on the entire path */
203    if (realpath(original, resolved)) {
204        /* That worked, so return it */
205        collapse(resolved);
206        return 0;
207    }
208    else {
209        /* Something's bogus in the original path, so remove names from the end
210           until either some subpath works or we run out of names */
211        char *p, *end, *r = NULL;
212        char path[PATH_MAX + 1];
213
214        strncpy(path, original, sizeof(path));
215        if (path[PATH_MAX] != '\0') {
216            errno = ENAMETOOLONG;
217            return -1;
218        }
219        end = path + strlen(path);
220
221        for (p = end; p > path;) {
222
223            /* Skip last element */
224            while ((--p > path) && (*p != '/'));
225            if (p == path) break;
226
227            /* Try realpath() on this subpath */
228            *p = '\0';
229            r = realpath(path, resolved);
230            *p = (p == end) ? '\0' : '/';
231
232            if (r != NULL) {
233                /* The subpath has a canonical path */
234                break;
235            }
236            else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES || errno == ENOTCONN) {
237                /* If the lookup of a particular subpath fails because the file
238                   does not exist, because it is of the wrong type, or because
239                   access is denied, then remove its last name and try again.
240                   Other I/O problems cause an error return. */
241
242                /* NOTE: ENOTCONN seems like an odd errno to expect, but this is
243                   the behaviour on linux for fuse filesystems when the fuse device
244                   associated with the FS is closed but the filesystem is not
245                   unmounted. */
246                continue;
247            }
248            else {
249                return -1;
250            }
251        }
252
253        if (r != NULL) {
254            /* Append unresolved subpath to resolved subpath */
255            int rn = strlen(r);
256            if (rn + (int)strlen(p) >= len) {
257                /* Buffer overflow */
258                errno = ENAMETOOLONG;
259                return -1;
260            }
261            if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) {
262                /* Avoid duplicate slashes */
263                p++;
264            }
265            strcpy(r + rn, p);
266            collapse(r);
267            return 0;
268        }
269        else {
270            /* Nothing resolved, so just return the original path */
271            strcpy(resolved, path);
272            collapse(resolved);
273            return 0;
274        }
275    }
276
277}
278