Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.vfs; |
| 15 | |
philwo | 3bcb9f6 | 2017-09-06 12:52:21 +0200 | [diff] [blame] | 16 | import com.google.devtools.build.lib.clock.Clock; |
| 17 | import com.google.devtools.build.lib.clock.JavaClock; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 18 | import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
| 19 | import com.google.devtools.build.lib.profiler.Profiler; |
| 20 | import com.google.devtools.build.lib.profiler.ProfilerTask; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 21 | import java.io.File; |
| 22 | import java.io.FileNotFoundException; |
| 23 | import java.io.IOException; |
Fabian Meumertzheim | 753dc97 | 2024-03-31 18:44:36 -0700 | [diff] [blame] | 24 | import java.nio.file.AccessDeniedException; |
| 25 | import java.nio.file.FileSystemException; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 26 | import java.nio.file.Files; |
mai93 | 2bfd69a | 2020-09-03 01:42:20 -0700 | [diff] [blame] | 27 | import java.nio.file.InvalidPathException; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 28 | import java.nio.file.LinkOption; |
Fabian Meumertzheim | 753dc97 | 2024-03-31 18:44:36 -0700 | [diff] [blame] | 29 | import java.nio.file.NoSuchFileException; |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 30 | import java.nio.file.Paths; |
Fabian Meumertzheim | 753dc97 | 2024-03-31 18:44:36 -0700 | [diff] [blame] | 31 | import java.nio.file.StandardCopyOption; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 32 | import java.nio.file.attribute.BasicFileAttributes; |
Fabian Meumertzheim | bf2eff8 | 2024-08-28 01:08:55 -0700 | [diff] [blame] | 33 | import java.util.Arrays; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 34 | import java.util.Collection; |
Googler | d0fedc4 | 2022-07-05 09:48:04 -0700 | [diff] [blame] | 35 | import javax.annotation.Nullable; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 36 | |
| 37 | /** |
| 38 | * A FileSystem that does not use any JNI and hence, does not require a shared library be present at |
| 39 | * execution. |
| 40 | * |
| 41 | * <p>Note: Blaze profiler tasks are defined on the system call level - thus we do not distinguish |
| 42 | * (from profiling perspective) between different methods on this class that end up doing stat() |
| 43 | * system call - they all are associated with the VFS_STAT task. |
| 44 | */ |
| 45 | @ThreadSafe |
Nathan Harmata | 13a74c0 | 2015-11-18 18:38:14 +0000 | [diff] [blame] | 46 | public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 47 | private static final LinkOption[] NO_LINK_OPTION = new LinkOption[0]; |
| 48 | // This isn't generally safe; we rely on the file system APIs not modifying the array. |
| 49 | private static final LinkOption[] NOFOLLOW_LINKS_OPTION = |
| 50 | new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; |
| 51 | |
Chris Parsons | cb5aa00 | 2016-07-26 17:52:37 +0000 | [diff] [blame] | 52 | private final Clock clock; |
| 53 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 54 | protected static final String ERR_IS_DIRECTORY = " (Is a directory)"; |
| 55 | protected static final String ERR_DIRECTORY_NOT_EMPTY = " (Directory not empty)"; |
| 56 | protected static final String ERR_FILE_EXISTS = " (File exists)"; |
| 57 | protected static final String ERR_NO_SUCH_FILE_OR_DIR = " (No such file or directory)"; |
| 58 | protected static final String ERR_NOT_A_DIRECTORY = " (Not a directory)"; |
| 59 | |
ccalvarin | bda12a1 | 2018-06-21 18:57:26 -0700 | [diff] [blame] | 60 | public JavaIoFileSystem(DigestHashFunction hashFunction) { |
buchgr | 559a07d | 2017-11-30 11:09:35 -0800 | [diff] [blame] | 61 | super(hashFunction); |
| 62 | this.clock = new JavaClock(); |
| 63 | } |
| 64 | |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 65 | protected File getIoFile(PathFragment path) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 66 | return new File(path.toString()); |
| 67 | } |
| 68 | |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 69 | /** |
| 70 | * Returns a {@link java.nio.file.Path} representing the same path as provided {@code path}. |
| 71 | * |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 72 | * <p>Note: while it's possible to use {@link #getIoFile(PathFragment)} in combination with {@link |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 73 | * File#toPath()} to achieve essentially the same, using this method is preferable because it |
| 74 | * avoids extra allocations and does not lose track of the underlying Java filesystem, which is |
| 75 | * useful for some in-memory filesystem implementations like JimFS. |
| 76 | */ |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 77 | protected java.nio.file.Path getNioPath(PathFragment path) { |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 78 | return Paths.get(path.toString()); |
| 79 | } |
| 80 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 81 | private LinkOption[] linkOpts(boolean followSymlinks) { |
| 82 | return followSymlinks ? NO_LINK_OPTION : NOFOLLOW_LINKS_OPTION; |
| 83 | } |
| 84 | |
| 85 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 86 | protected Collection<String> getDirectoryEntries(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 87 | File file = getIoFile(path); |
Fabian Meumertzheim | bf2eff8 | 2024-08-28 01:08:55 -0700 | [diff] [blame] | 88 | String[] entries; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 89 | long startTime = Profiler.nanoTimeMaybe(); |
| 90 | try { |
| 91 | entries = file.list(); |
| 92 | if (entries == null) { |
| 93 | if (file.exists()) { |
| 94 | throw new IOException(path + ERR_NOT_A_DIRECTORY); |
| 95 | } else { |
| 96 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 97 | } |
| 98 | } |
| 99 | } finally { |
| 100 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_DIR, file.getPath()); |
| 101 | } |
Fabian Meumertzheim | bf2eff8 | 2024-08-28 01:08:55 -0700 | [diff] [blame] | 102 | return Arrays.asList(entries); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 106 | protected boolean exists(PathFragment path, boolean followSymlinks) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 107 | long startTime = Profiler.nanoTimeMaybe(); |
| 108 | try { |
mai93 | 2bfd69a | 2020-09-03 01:42:20 -0700 | [diff] [blame] | 109 | java.nio.file.Path nioPath = getNioPath(path); |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 110 | return Files.exists(nioPath, linkOpts(followSymlinks)); |
mai93 | 2bfd69a | 2020-09-03 01:42:20 -0700 | [diff] [blame] | 111 | } catch (InvalidPathException e) { |
| 112 | return false; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 113 | } finally { |
| 114 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.toString()); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 119 | protected boolean isReadable(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 120 | File file = getIoFile(path); |
| 121 | long startTime = Profiler.nanoTimeMaybe(); |
| 122 | try { |
| 123 | if (!file.exists()) { |
| 124 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 125 | } |
| 126 | return file.canRead(); |
| 127 | } finally { |
| 128 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath()); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 133 | protected boolean isWritable(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 134 | File file = getIoFile(path); |
| 135 | long startTime = Profiler.nanoTimeMaybe(); |
| 136 | try { |
| 137 | if (!file.exists()) { |
| 138 | if (linkExists(file)) { |
| 139 | throw new IOException(path + ERR_PERMISSION_DENIED); |
| 140 | } else { |
| 141 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 142 | } |
| 143 | } |
| 144 | return file.canWrite(); |
| 145 | } finally { |
| 146 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath()); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 151 | protected boolean isExecutable(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 152 | File file = getIoFile(path); |
| 153 | long startTime = Profiler.nanoTimeMaybe(); |
| 154 | try { |
| 155 | if (!file.exists()) { |
| 156 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 157 | } |
| 158 | return file.canExecute(); |
| 159 | } finally { |
| 160 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath()); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 165 | protected void setReadable(PathFragment path, boolean readable) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 166 | File file = getIoFile(path); |
| 167 | if (!file.exists()) { |
| 168 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 169 | } |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 170 | if (!file.setReadable(readable) && readable) { |
| 171 | throw new IOException(String.format("Failed to make %s readable", path)); |
| 172 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 173 | } |
| 174 | |
| 175 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 176 | public void setWritable(PathFragment path, boolean writable) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 177 | File file = getIoFile(path); |
| 178 | if (!file.exists()) { |
| 179 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 180 | } |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 181 | if (!file.setWritable(writable) && writable) { |
| 182 | throw new IOException(String.format("Failed to make %s writable", path)); |
| 183 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 184 | } |
| 185 | |
| 186 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 187 | protected void setExecutable(PathFragment path, boolean executable) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 188 | File file = getIoFile(path); |
| 189 | if (!file.exists()) { |
| 190 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 191 | } |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 192 | if (!file.setExecutable(executable) && executable) { |
| 193 | throw new IOException(String.format("Failed to make %s executable", path)); |
| 194 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 195 | } |
| 196 | |
| 197 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 198 | public boolean supportsModifications(PathFragment path) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 199 | return true; |
| 200 | } |
| 201 | |
| 202 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 203 | public boolean supportsSymbolicLinksNatively(PathFragment path) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 204 | return true; |
| 205 | } |
| 206 | |
| 207 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 208 | public boolean supportsHardLinksNatively(PathFragment path) { |
Googler | e1cd950 | 2016-09-07 14:33:29 +0000 | [diff] [blame] | 209 | return true; |
| 210 | } |
| 211 | |
| 212 | @Override |
Yun Peng | 352f7e7 | 2016-05-09 11:08:25 +0000 | [diff] [blame] | 213 | public boolean isFilePathCaseSensitive() { |
| 214 | return true; |
| 215 | } |
| 216 | |
| 217 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 218 | public boolean createDirectory(PathFragment path) throws IOException { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 219 | File file = getIoFile(path); |
| 220 | if (file.mkdir()) { |
| 221 | return true; |
| 222 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 223 | |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 224 | if (fileIsSymbolicLink(file)) { |
| 225 | throw new IOException(path + ERR_FILE_EXISTS); |
| 226 | } |
| 227 | if (file.isDirectory()) { |
| 228 | return false; // directory already existed |
| 229 | } else if (file.exists()) { |
| 230 | throw new IOException(path + ERR_FILE_EXISTS); |
| 231 | } else if (!file.getParentFile().exists()) { |
| 232 | throw new FileNotFoundException(path.getParentDirectory() + ERR_NO_SUCH_FILE_OR_DIR); |
| 233 | } |
| 234 | // Parent directory apparently exists - try to create our directory again. |
| 235 | if (file.mkdir()) { |
| 236 | return true; // Everything is fine finally. |
| 237 | } else if (!file.getParentFile().canWrite()) { |
| 238 | throw new FileAccessException(path + ERR_PERMISSION_DENIED); |
| 239 | } else { |
| 240 | // Parent exists, is writable, yet we can't create our directory. |
| 241 | throw new FileNotFoundException(path.getParentDirectory() + ERR_NOT_A_DIRECTORY); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 242 | } |
| 243 | } |
| 244 | |
tomlu | decca2b | 2017-12-21 08:59:51 -0800 | [diff] [blame] | 245 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 246 | public void createDirectoryAndParents(PathFragment path) throws IOException { |
tomlu | decca2b | 2017-12-21 08:59:51 -0800 | [diff] [blame] | 247 | java.nio.file.Path nioPath = getNioPath(path); |
tomlu | 7f17d08 | 2018-01-09 13:46:29 -0800 | [diff] [blame] | 248 | try { |
| 249 | Files.createDirectories(nioPath); |
| 250 | } catch (java.nio.file.FileAlreadyExistsException e) { |
| 251 | // Files.createDirectories will handle this case normally, but if the existing |
| 252 | // file is a symlink to a directory then it still throws. Swallow this. |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 253 | if (!isDirectory(path, /*followSymlinks=*/ true)) { |
tomlu | 7f17d08 | 2018-01-09 13:46:29 -0800 | [diff] [blame] | 254 | throw e; |
| 255 | } |
| 256 | } |
tomlu | decca2b | 2017-12-21 08:59:51 -0800 | [diff] [blame] | 257 | } |
| 258 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 259 | private boolean linkExists(File file) { |
| 260 | String shortName = file.getName(); |
| 261 | File parentFile = file.getParentFile(); |
| 262 | if (parentFile == null) { |
| 263 | return false; |
| 264 | } |
| 265 | String[] filenames = parentFile.list(); |
| 266 | if (filenames == null) { |
| 267 | return false; |
| 268 | } |
| 269 | for (String name : filenames) { |
| 270 | if (name.equals(shortName)) { |
| 271 | return true; |
| 272 | } |
| 273 | } |
| 274 | return false; |
| 275 | } |
| 276 | |
| 277 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 278 | protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment) |
aehlig | c801c39 | 2017-12-19 07:12:25 -0800 | [diff] [blame] | 279 | throws IOException { |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 280 | java.nio.file.Path nioPath = getNioPath(linkPath); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 281 | try { |
tomlu | a729b9b | 2018-02-08 15:32:00 -0800 | [diff] [blame] | 282 | Files.createSymbolicLink(nioPath, Paths.get(targetFragment.getSafePathString())); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 283 | } catch (java.nio.file.FileAlreadyExistsException e) { |
Googler | 691fc5b | 2022-05-02 07:50:19 -0700 | [diff] [blame] | 284 | throw new IOException(linkPath + ERR_FILE_EXISTS, e); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 285 | } catch (java.nio.file.AccessDeniedException e) { |
Googler | 691fc5b | 2022-05-02 07:50:19 -0700 | [diff] [blame] | 286 | throw new IOException(linkPath + ERR_PERMISSION_DENIED, e); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 287 | } catch (java.nio.file.NoSuchFileException e) { |
| 288 | throw new FileNotFoundException(linkPath + ERR_NO_SUCH_FILE_OR_DIR); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 293 | protected PathFragment readSymbolicLink(PathFragment path) throws IOException { |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 294 | java.nio.file.Path nioPath = getNioPath(path); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 295 | long startTime = Profiler.nanoTimeMaybe(); |
| 296 | try { |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 297 | String link = Files.readSymbolicLink(nioPath).toString(); |
aehlig | c801c39 | 2017-12-19 07:12:25 -0800 | [diff] [blame] | 298 | return PathFragment.create(link); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 299 | } catch (java.nio.file.NotLinkException e) { |
Googler | 691fc5b | 2022-05-02 07:50:19 -0700 | [diff] [blame] | 300 | throw new NotASymlinkException(path, e); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 301 | } catch (java.nio.file.NoSuchFileException e) { |
| 302 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 303 | } finally { |
janakr | 52859b4 | 2018-04-01 19:12:26 -0700 | [diff] [blame] | 304 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_READLINK, path.getPathString()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 305 | } |
| 306 | } |
| 307 | |
| 308 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 309 | public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException { |
Fabian Meumertzheim | 753dc97 | 2024-03-31 18:44:36 -0700 | [diff] [blame] | 310 | java.nio.file.Path source = getNioPath(sourcePath); |
| 311 | java.nio.file.Path target = getNioPath(targetPath); |
| 312 | // Replace NIO exceptions with the types used by the native Unix filesystem implementation where |
| 313 | // necessary. |
| 314 | try { |
| 315 | Files.move( |
| 316 | source, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); |
| 317 | } catch (NoSuchFileException originalException) { |
| 318 | FileNotFoundException newException = |
| 319 | new FileNotFoundException(originalException.getMessage() + ERR_NO_SUCH_FILE_OR_DIR); |
| 320 | newException.initCause(originalException); |
| 321 | throw newException; |
| 322 | } catch (AccessDeniedException originalException) { |
| 323 | FileAccessException newException = |
| 324 | new FileAccessException(originalException.getMessage() + ERR_PERMISSION_DENIED); |
| 325 | newException.initCause(originalException); |
| 326 | throw newException; |
| 327 | } catch (FileSystemException e) { |
| 328 | // Rewrite exception messages to be identical to the ones produced by the native Unix |
| 329 | // filesystem implementation. Bazel forces the root locale for the JVM, so the error messages |
| 330 | // should be stable. |
| 331 | String filesPart = sourcePath + " -> " + targetPath; |
| 332 | throw switch (e.getReason()) { |
| 333 | case "Directory not empty" -> new IOException(filesPart + ERR_DIRECTORY_NOT_EMPTY, e); |
| 334 | case "Not a directory" -> new IOException(filesPart + ERR_NOT_A_DIRECTORY, e); |
| 335 | case "Is a directory" -> new IOException(filesPart + ERR_IS_DIRECTORY, e); |
| 336 | default -> e; |
| 337 | }; |
pcloudy | e28d3af | 2017-10-24 14:16:12 +0200 | [diff] [blame] | 338 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 339 | } |
| 340 | |
| 341 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 342 | protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 343 | long startTime = Profiler.nanoTimeMaybe(); |
| 344 | try { |
| 345 | return stat(path, followSymlinks).getSize(); |
| 346 | } finally { |
janakr | 52859b4 | 2018-04-01 19:12:26 -0700 | [diff] [blame] | 347 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.getPathString()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 348 | } |
| 349 | } |
| 350 | |
| 351 | @Override |
ajurkowski | ea26e0a | 2021-03-22 16:04:20 -0700 | [diff] [blame] | 352 | protected boolean delete(PathFragment path) throws IOException { |
Laszlo Csomor | 49d2031 | 2018-07-09 00:53:34 -0700 | [diff] [blame] | 353 | java.nio.file.Path nioPath = getNioPath(path); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 354 | long startTime = Profiler.nanoTimeMaybe(); |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 355 | try { |
Laszlo Csomor | 49d2031 | 2018-07-09 00:53:34 -0700 | [diff] [blame] | 356 | return Files.deleteIfExists(nioPath); |
| 357 | } catch (java.nio.file.DirectoryNotEmptyException e) { |
Googler | 691fc5b | 2022-05-02 07:50:19 -0700 | [diff] [blame] | 358 | throw new IOException(path.getPathString() + ERR_DIRECTORY_NOT_EMPTY, e); |
Laszlo Csomor | 49d2031 | 2018-07-09 00:53:34 -0700 | [diff] [blame] | 359 | } catch (java.nio.file.AccessDeniedException e) { |
Googler | 691fc5b | 2022-05-02 07:50:19 -0700 | [diff] [blame] | 360 | throw new IOException(path.getPathString() + ERR_PERMISSION_DENIED, e); |
Laszlo Csomor | 49d2031 | 2018-07-09 00:53:34 -0700 | [diff] [blame] | 361 | } catch (java.nio.file.AtomicMoveNotSupportedException |
| 362 | | java.nio.file.FileAlreadyExistsException |
| 363 | | java.nio.file.FileSystemLoopException |
| 364 | | java.nio.file.NoSuchFileException |
| 365 | | java.nio.file.NotDirectoryException |
| 366 | | java.nio.file.NotLinkException e) { |
| 367 | // All known but unexpected subclasses of FileSystemException. |
| 368 | throw new IOException(path.getPathString() + ": unexpected FileSystemException", e); |
| 369 | } catch (java.nio.file.FileSystemException e) { |
| 370 | // Files.deleteIfExists() throws FileSystemException on Linux if a path component is a file. |
| 371 | // We caught all known subclasses of FileSystemException so `e` is either an unknown |
| 372 | // subclass or it is indeed a "Not a directory" error. Non-English JDKs may use a different |
| 373 | // error message than "Not a directory", so we should not look for that text. Checking the |
| 374 | // parent directory if it's indeed a directory is unrealiable, because another process may |
| 375 | // modify it concurrently... but we have no better choice. |
| 376 | if (e.getClass().equals(java.nio.file.FileSystemException.class) |
| 377 | && !nioPath.getParent().toFile().isDirectory()) { |
| 378 | // Hopefully the try-block failed because a parent directory was in fact not a directory. |
| 379 | // Theoretically it's possible that the try-block failed for some other reason and all |
| 380 | // parent directories were indeed directories, but another process changed a parent |
| 381 | // directory into a file after the try-block failed but before this catch-block started, and |
| 382 | // we return false here losing the real exception in `e`, but we cannot know. |
| 383 | return false; |
| 384 | } else { |
| 385 | throw new IOException(path.getPathString() + ": unexpected FileSystemException", e); |
tomlu | d50cbbe | 2017-12-28 13:25:27 -0800 | [diff] [blame] | 386 | } |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 387 | } finally { |
Laszlo Csomor | 49d2031 | 2018-07-09 00:53:34 -0700 | [diff] [blame] | 388 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, path.getPathString()); |
pcloudy | e28d3af | 2017-10-24 14:16:12 +0200 | [diff] [blame] | 389 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 390 | } |
| 391 | |
| 392 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 393 | protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 394 | File file = getIoFile(path); |
| 395 | long startTime = Profiler.nanoTimeMaybe(); |
| 396 | try { |
| 397 | return stat(path, followSymlinks).getLastModifiedTime(); |
| 398 | } finally { |
| 399 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath()); |
| 400 | } |
| 401 | } |
| 402 | |
Lukacs Berki | 58dbb7f | 2016-01-29 11:56:29 +0000 | [diff] [blame] | 403 | protected boolean fileIsSymbolicLink(File file) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 404 | return Files.isSymbolicLink(file.toPath()); |
| 405 | } |
| 406 | |
| 407 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 408 | public void setLastModifiedTime(PathFragment path, long newTime) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 409 | File file = getIoFile(path); |
janakr | c873525 | 2021-12-13 12:35:06 -0800 | [diff] [blame] | 410 | if (!file.setLastModified( |
| 411 | newTime == Path.NOW_SENTINEL_TIME ? clock.currentTimeMillis() : newTime)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 412 | if (!file.exists()) { |
| 413 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 414 | } else if (!file.getParentFile().canWrite()) { |
| 415 | throw new FileAccessException(path.getParentDirectory() + ERR_PERMISSION_DENIED); |
| 416 | } else { |
| 417 | throw new FileAccessException(path + ERR_PERMISSION_DENIED); |
| 418 | } |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 423 | protected byte[] getDigest(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 424 | String name = path.toString(); |
| 425 | long startTime = Profiler.nanoTimeMaybe(); |
| 426 | try { |
ccalvarin | dd9f60e | 2018-07-23 18:16:18 -0700 | [diff] [blame] | 427 | return super.getDigest(path); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 428 | } finally { |
| 429 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | /** |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 434 | * Returns the status of a file. See {@link Path#stat(Symlinks)} for specification. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 435 | * |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 436 | * <p>The default implementation of this method is a "lazy" one, based on other accessor methods |
| 437 | * such as {@link #isFile}, etc. Subclasses may provide more efficient specializations. However, |
| 438 | * we still try to follow Unix-like semantics of failing fast in case of non-existent files (or in |
| 439 | * case of permission issues). |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 440 | */ |
| 441 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 442 | protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException { |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 443 | java.nio.file.Path nioPath = getNioPath(path); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 444 | final BasicFileAttributes attributes; |
| 445 | try { |
Taras Tsugrii | 7a3f104 | 2017-10-30 08:07:37 -0400 | [diff] [blame] | 446 | attributes = |
| 447 | Files.readAttributes(nioPath, BasicFileAttributes.class, linkOpts(followSymlinks)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 448 | } catch (java.nio.file.FileSystemException e) { |
| 449 | throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); |
| 450 | } |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 451 | FileStatus status = |
| 452 | new FileStatus() { |
| 453 | @Override |
| 454 | public boolean isFile() { |
| 455 | return attributes.isRegularFile() || isSpecialFile(); |
| 456 | } |
Nathan Harmata | d8b6ff2 | 2015-10-20 21:54:34 +0000 | [diff] [blame] | 457 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 458 | @Override |
| 459 | public boolean isSpecialFile() { |
| 460 | return attributes.isOther(); |
| 461 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 462 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 463 | @Override |
| 464 | public boolean isDirectory() { |
| 465 | return attributes.isDirectory(); |
| 466 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 467 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 468 | @Override |
| 469 | public boolean isSymbolicLink() { |
| 470 | return attributes.isSymbolicLink(); |
| 471 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 472 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 473 | @Override |
| 474 | public long getSize() { |
| 475 | return attributes.size(); |
| 476 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 477 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 478 | @Override |
| 479 | public long getLastModifiedTime() { |
| 480 | return attributes.lastModifiedTime().toMillis(); |
| 481 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 482 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 483 | @Override |
| 484 | public long getLastChangeTime() { |
| 485 | // This is the best we can do with Java NIO... |
| 486 | return attributes.lastModifiedTime().toMillis(); |
| 487 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 488 | |
Fabian Meumertzheim | 89a3d5e | 2023-04-11 11:15:50 -0700 | [diff] [blame] | 489 | @Override |
| 490 | public long getNodeId() { |
| 491 | // TODO(bazel-team): Consider making use of attributes.fileKey(). |
| 492 | return -1; |
| 493 | } |
| 494 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 495 | |
| 496 | return status; |
| 497 | } |
| 498 | |
| 499 | @Override |
Googler | d0fedc4 | 2022-07-05 09:48:04 -0700 | [diff] [blame] | 500 | @Nullable |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 501 | protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 502 | try { |
| 503 | return stat(path, followSymlinks); |
| 504 | } catch (FileNotFoundException e) { |
| 505 | // JavaIoFileSystem#stat (incorrectly) only throws FileNotFoundException (because it calls |
| 506 | // #getLastModifiedTime, which can only throw a FileNotFoundException), so we always hit this |
| 507 | // codepath. Thus, this method will incorrectly not throw an exception for some filesystem |
| 508 | // errors. |
| 509 | return null; |
| 510 | } catch (IOException e) { |
| 511 | // If this codepath is ever hit, then this method should be rewritten to properly distinguish |
| 512 | // between not-found exceptions and others. |
| 513 | throw new IllegalStateException(e); |
| 514 | } |
| 515 | } |
Googler | e1cd950 | 2016-09-07 14:33:29 +0000 | [diff] [blame] | 516 | |
| 517 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 518 | protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath) |
Googler | e1cd950 | 2016-09-07 14:33:29 +0000 | [diff] [blame] | 519 | throws IOException { |
| 520 | Files.createLink( |
| 521 | java.nio.file.Paths.get(linkPath.toString()), |
| 522 | java.nio.file.Paths.get(originalPath.toString())); |
| 523 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 524 | } |