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 #include <alloca.h> 37 38 39 /* Note: The comments in this file use the terminology 40 defined in the java.io.File class */ 41 42 43 /* Check the given name sequence to see if it can be further collapsed. 44 Return zero if not, otherwise return the number of names in the sequence. */ 45 46 static int 47 collapsible(char *names) 48 { 49 char *p = names; 50 int dots = 0, n = 0; 51 52 while (*p) { 53 if ((p[0] == '.') && ((p[1] == '\0') 54 || (p[1] == '/') 55 || ((p[1] == '.') && ((p[2] == '\0') 56 || (p[2] == '/'))))) { 57 dots = 1; 58 } 59 n++; 60 while (*p) { 61 if (*p == '/') { 62 p++; 63 break; 64 } 65 p++; 66 } 67 } 68 return (dots ? n : 0); 69 } 70 71 72 /* Split the names in the given name sequence, 73 replacing slashes with nulls and filling in the given index array */ 74 75 static void 76 splitNames(char *names, char **ix) 77 { 78 char *p = names; 79 int i = 0; 80 81 while (*p) { 82 ix[i++] = p++; 83 while (*p) { 84 if (*p == '/') { 85 *p++ = '\0'; 86 break; 87 } 88 p++; 89 } 90 } 91 } 92 93 94 /* Join the names in the given name sequence, ignoring names whose index 95 entries have been cleared and replacing nulls with slashes as needed */ 96 97 static void 98 joinNames(char *names, int nc, char **ix) 99 { 100 int i; 101 char *p; 102 103 for (i = 0, p = names; i < nc; i++) { 104 if (!ix[i]) continue; 105 if (i > 0) { 106 p[-1] = '/'; 107 } 108 if (p == ix[i]) { 109 p += strlen(p) + 1; 110 } else { 111 char *q = ix[i]; 112 while ((*p++ = *q++)); 113 } 114 } 115 *p = '\0'; 116 } 117 118 119 /* Collapse "." and ".." names in the given path wherever possible. 120 A "." name may always be eliminated; a ".." name may be eliminated if it 121 follows a name that is neither "." nor "..". This is a syntactic operation 122 that performs no filesystem queries, so it should only be used to cleanup 123 after invoking the realpath() procedure. */ 124 125 static void 126 collapse(char *path) 127 { 128 char *names = (path[0] == '/') ? path + 1 : path; /* Preserve first '/' */ 129 int nc; 130 char **ix; 131 int i, j; 132 char *p, *q; 133 134 nc = collapsible(names); 135 if (nc < 2) return; /* Nothing to do */ 136 ix = (char **)alloca(nc * sizeof(char *)); 137 splitNames(names, ix); 138 139 for (i = 0; i < nc; i++) { 140 int dots = 0; 141 142 /* Find next occurrence of "." or ".." */ 143 do { 144 char *p = ix[i]; 145 if (p[0] == '.') { 146 if (p[1] == '\0') { 147 dots = 1; 148 break; 149 } 150 if ((p[1] == '.') && (p[2] == '\0')) { 151 dots = 2; 152 break; 153 } 154 } 155 i++; 156 } while (i < nc); 157 if (i >= nc) break; 158 159 /* At this point i is the index of either a "." or a "..", so take the 160 appropriate action and then continue the outer loop */ 161 if (dots == 1) { 162 /* Remove this instance of "." */ 163 ix[i] = 0; 164 } 165 else { 166 /* If there is a preceding name, remove both that name and this 167 instance of ".."; otherwise, leave the ".." as is */ 168 for (j = i - 1; j >= 0; j--) { 169 if (ix[j]) break; 170 } 171 if (j < 0) continue; 172 ix[j] = 0; 173 ix[i] = 0; 174 } 175 /* i will be incremented at the top of the loop */ 176 } 177 178 joinNames(names, nc, ix); 179 } 180 181 182 /* Convert a pathname to canonical form. The input path is assumed to contain 183 no duplicate slashes. On Solaris we can use realpath() to do most of the 184 work, though once that's done we still must collapse any remaining "." and 185 ".." names by hand. */ 186 187 int 188 canonicalize(char *original, char *resolved, int len) 189 { 190 if (len < PATH_MAX) { 191 errno = EINVAL; 192 return -1; 193 } 194 195 if (strlen(original) > PATH_MAX) { 196 errno = ENAMETOOLONG; 197 return -1; 198 } 199 200 /* First try realpath() on the entire path */ 201 if (realpath(original, resolved)) { 202 /* That worked, so return it */ 203 collapse(resolved); 204 return 0; 205 } 206 else { 207 /* Something's bogus in the original path, so remove names from the end 208 until either some subpath works or we run out of names */ 209 char *p, *end, *r = NULL; 210 char path[PATH_MAX + 1]; 211 212 strncpy(path, original, sizeof(path)); 213 if (path[PATH_MAX] != '\0') { 214 errno = ENAMETOOLONG; 215 return -1; 216 } 217 end = path + strlen(path); 218 219 for (p = end; p > path;) { 220 221 /* Skip last element */ 222 while ((--p > path) && (*p != '/')); 223 if (p == path) break; 224 225 /* Try realpath() on this subpath */ 226 *p = '\0'; 227 r = realpath(path, resolved); 228 *p = (p == end) ? '\0' : '/'; 229 230 if (r != NULL) { 231 /* The subpath has a canonical path */ 232 break; 233 } 234 else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES) { 235 /* If the lookup of a particular subpath fails because the file 236 does not exist, because it is of the wrong type, or because 237 access is denied, then remove its last name and try again. 238 Other I/O problems cause an error return. */ 239 continue; 240 } 241 else { 242 return -1; 243 } 244 } 245 246 if (r != NULL) { 247 /* Append unresolved subpath to resolved subpath */ 248 int rn = strlen(r); 249 if (rn + (int)strlen(p) >= len) { 250 /* Buffer overflow */ 251 errno = ENAMETOOLONG; 252 return -1; 253 } 254 if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) { 255 /* Avoid duplicate slashes */ 256 p++; 257 } 258 strcpy(r + rn, p); 259 collapse(r); 260 return 0; 261 } 262 else { 263 /* Nothing resolved, so just return the original path */ 264 strcpy(resolved, path); 265 collapse(resolved); 266 return 0; 267 } 268 } 269 270 }