1 /* 2 * Copyright (c) 2007, 2009, 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.nonNull(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 = Attributes.readBasicFileAttributes(file, linkOptions); 106 } catch (IOException x1) { 107 if (followLinks) { 108 try { 109 attrs = Attributes 110 .readBasicFileAttributes(file, LinkOption.NOFOLLOW_LINKS); 111 } catch (IOException x2) { 112 exc = x2; 113 } 114 } else { 115 exc = x1; 116 } 117 } 118 } catch (SecurityException x) { 119 // If access to starting file is denied then SecurityException 120 // is thrown, otherwise the file is ignored. 121 if (depth == 0) 122 throw x; 123 return FileVisitResult.CONTINUE; 124 } 125 } 126 127 // unable to get attributes of file 128 if (exc != null) { 129 return visitor.visitFileFailed(file, exc); 130 } 131 132 // at maximum depth or file is not a directory 133 if (depth >= maxDepth || !attrs.isDirectory()) { 134 return visitor.visitFile(file, attrs); 135 } 136 137 // check for cycles when following links 138 if (followLinks) { 139 Object key = attrs.fileKey(); 140 141 // if this directory and ancestor has a file key then we compare 142 // them; otherwise we use less efficient isSameFile test. 143 for (AncestorDirectory ancestor: ancestors) { 144 Object ancestorKey = ancestor.fileKey(); 145 if (key != null && ancestorKey != null) { 146 if (key.equals(ancestorKey)) { 147 // cycle detected 148 return visitor.visitFileFailed(file, 149 new FileSystemLoopException(file.toString())); 150 } 151 } else { 152 boolean isSameFile = false; 153 try { 154 isSameFile = file.isSameFile(ancestor.file()); 155 } catch (IOException x) { 156 // ignore 157 } catch (SecurityException x) { 158 // ignore 159 } 160 if (isSameFile) { 161 // cycle detected 162 return visitor.visitFileFailed(file, 163 new FileSystemLoopException(file.toString())); 164 } 165 } 166 } 167 168 ancestors.add(new AncestorDirectory(file, key)); 169 } 170 171 // visit directory 172 try { 173 DirectoryStream<Path> stream = null; 174 FileVisitResult result; 175 176 // open the directory 177 try { 178 stream = file.newDirectoryStream(); 179 } catch (IOException x) { 180 return visitor.visitFileFailed(file, x); 181 } catch (SecurityException x) { 182 // ignore, as per spec 183 return FileVisitResult.CONTINUE; 184 } 185 186 // the exception notified to the postVisitDirectory method 187 IOException ioe = null; 188 189 // invoke preVisitDirectory and then visit each entry 190 try { 191 result = visitor.preVisitDirectory(file, attrs); 192 if (result != FileVisitResult.CONTINUE) { 193 return result; 194 } 195 196 try { 197 for (Path entry: stream) { 198 result = walk(entry, depth+1, ancestors); 199 200 // returning null will cause NPE to be thrown 201 if (result == null || result == FileVisitResult.TERMINATE) 202 return result; 203 204 // skip remaining siblings in this directory 205 if (result == FileVisitResult.SKIP_SIBLINGS) 206 break; 207 } 208 } catch (DirectoryIteratorException e) { 209 // IOException will be notified to postVisitDirectory 210 ioe = e.getCause(); 211 } 212 } finally { 213 try { 214 stream.close(); 215 } catch (IOException x) { } 216 } 217 218 // invoke postVisitDirectory last 219 return visitor.postVisitDirectory(file, ioe); 220 221 } finally { 222 // remove key from trail if doing cycle detection 223 if (followLinks) { 224 ancestors.remove(ancestors.size()-1); 225 } 226 } 227 } 228 229 private static class AncestorDirectory { 230 private final Path dir; 231 private final Object key; 232 AncestorDirectory(Path dir, Object key) { 233 this.dir = dir; 234 this.key = key; 235 } 236 Path file() { 237 return dir; 238 } 239 Object fileKey() { 240 return key; 241 } 242 } 243 }