1 /* 2 * Copyright (c) 2007, 2011, 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 package java.nio.file; 27 28 import java.nio.file.attribute.*; 29 import java.io.IOException; 30 import java.util.*; 31 import sun.nio.fs.BasicFileAttributesHolder; 32 33 /** 34 * Simple file tree walker that works in a similar manner to nftw(3C). 35 * 36 * @see Files#walkFileTree 37 */ 38 39 class FileTreeWalker { 40 private final boolean followLinks; 41 private final LinkOption[] linkOptions; 42 private final FileVisitor<? super Path> visitor; 43 private final int maxDepth; 44 45 FileTreeWalker(Set<FileVisitOption> options, 46 FileVisitor<? super Path> visitor, 47 int maxDepth) 48 { 49 boolean fl = false; 50 for (FileVisitOption option: options) { 51 // will throw NPE if options contains null 52 switch (option) { 53 case FOLLOW_LINKS : fl = true; break; 54 default: 55 throw new AssertionError("Should not get here"); 56 } 57 } 58 this.followLinks = fl; 59 this.linkOptions = (fl) ? new LinkOption[0] : 60 new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; 61 this.visitor = visitor; 62 this.maxDepth = maxDepth; 63 } 64 65 /** 66 * Walk file tree starting at the given file 67 */ 68 void walk(Path start) throws IOException { 69 FileVisitResult result = walk(start, 70 0, 71 new ArrayList<AncestorDirectory>()); 72 Objects.requireNonNull(result, "FileVisitor returned null"); 73 } 74 75 /** 76 * @param file 77 * the directory to visit 78 * @param depth 79 * depth remaining 80 * @param ancestors 81 * use when cycle detection is enabled 82 */ 83 private FileVisitResult walk(Path file, 84 int depth, 85 List<AncestorDirectory> ancestors) 86 throws IOException 87 { 88 // if attributes are cached then use them if possible 89 BasicFileAttributes attrs = null; 90 if ((depth > 0) && 91 (file instanceof BasicFileAttributesHolder) && 92 (System.getSecurityManager() == null)) 93 { 94 BasicFileAttributes cached = ((BasicFileAttributesHolder)file).get(); 95 if (!followLinks || !cached.isSymbolicLink()) 96 attrs = cached; 97 } 98 IOException exc = null; 99 100 // attempt to get attributes of file. If fails and we are following 101 // links then a link target might not exist so get attributes of link 102 if (attrs == null) { 103 try { 104 try { 105 attrs = Files.readAttributes(file, BasicFileAttributes.class, linkOptions); 106 } catch (IOException x1) { 107 if (followLinks) { 108 try { 109 attrs = Files.readAttributes(file, 110 BasicFileAttributes.class, 111 LinkOption.NOFOLLOW_LINKS); 112 } catch (IOException x2) { 113 exc = x2; 114 } 115 } else { 116 exc = x1; 117 } 118 } 119 } catch (SecurityException x) { 120 // If access to starting file is denied then SecurityException 121 // is thrown, otherwise the file is ignored. 122 if (depth == 0) 123 throw x; 124 return FileVisitResult.CONTINUE; 125 } 126 } 127 128 // unable to get attributes of file 129 if (exc != null) { 130 return visitor.visitFileFailed(file, exc); 131 } 132 133 // at maximum depth or file is not a directory 134 if (depth >= maxDepth || !attrs.isDirectory()) { 135 return visitor.visitFile(file, attrs); 136 } 137 138 // check for cycles when following links 139 if (followLinks) { 140 Object key = attrs.fileKey(); 141 142 // if this directory and ancestor has a file key then we compare 143 // them; otherwise we use less efficient isSameFile test. 144 for (AncestorDirectory ancestor: ancestors) { 145 Object ancestorKey = ancestor.fileKey(); 146 if (key != null && ancestorKey != null) { 147 if (key.equals(ancestorKey)) { 148 // cycle detected 149 return visitor.visitFileFailed(file, 150 new FileSystemLoopException(file.toString())); 151 } 152 } else { 153 boolean isSameFile = false; 154 try { 155 isSameFile = Files.isSameFile(file, ancestor.file()); 156 } catch (IOException x) { 157 // ignore 158 } catch (SecurityException x) { 159 // ignore 160 } 161 if (isSameFile) { 162 // cycle detected 163 return visitor.visitFileFailed(file, 164 new FileSystemLoopException(file.toString())); 165 } 166 } 167 } 168 169 ancestors.add(new AncestorDirectory(file, key)); 170 } 171 172 // visit directory 173 try { 174 DirectoryStream<Path> stream = null; 175 FileVisitResult result; 176 177 // open the directory 178 try { 179 stream = Files.newDirectoryStream(file); 180 } catch (IOException x) { 181 return visitor.visitFileFailed(file, x); 182 } catch (SecurityException x) { 183 // ignore, as per spec 184 return FileVisitResult.CONTINUE; 185 } 186 187 // the exception notified to the postVisitDirectory method 188 IOException ioe = null; 189 190 // invoke preVisitDirectory and then visit each entry 191 try { 192 result = visitor.preVisitDirectory(file, attrs); 193 if (result != FileVisitResult.CONTINUE) { 194 return result; 195 } 196 197 try { 198 for (Path entry: stream) { 199 result = walk(entry, depth+1, ancestors); 200 201 // returning null will cause NPE to be thrown 202 if (result == null || result == FileVisitResult.TERMINATE) 203 return result; 204 205 // skip remaining siblings in this directory 206 if (result == FileVisitResult.SKIP_SIBLINGS) 207 break; 208 } 209 } catch (DirectoryIteratorException e) { 210 // IOException will be notified to postVisitDirectory 211 ioe = e.getCause(); 212 } 213 } finally { 214 try { 215 stream.close(); 216 } catch (IOException e) { 217 // IOException will be notified to postVisitDirectory 218 if (ioe == null) 219 ioe = e; 220 } 221 } 222 223 // invoke postVisitDirectory last 224 return visitor.postVisitDirectory(file, ioe); 225 226 } finally { 227 // remove key from trail if doing cycle detection 228 if (followLinks) { 229 ancestors.remove(ancestors.size()-1); 230 } 231 } 232 } 233 234 private static class AncestorDirectory { 235 private final Path dir; 236 private final Object key; 237 AncestorDirectory(Path dir, Object key) { 238 this.dir = dir; 239 this.key = key; 240 } 241 Path file() { 242 return dir; 243 } 244 Object fileKey() { 245 return key; 246 } 247 } 248 }