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