blob: cb74d3f22d2682cb684d3cd23679cdcbe44ffcab [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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.
14package com.google.devtools.build.lib.vfs;
15
philwo3bcb9f62017-09-06 12:52:21 +020016import com.google.devtools.build.lib.clock.Clock;
17import com.google.devtools.build.lib.clock.JavaClock;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
19import com.google.devtools.build.lib.profiler.Profiler;
20import com.google.devtools.build.lib.profiler.ProfilerTask;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import java.io.File;
22import java.io.FileNotFoundException;
23import java.io.IOException;
Fabian Meumertzheim753dc972024-03-31 18:44:36 -070024import java.nio.file.AccessDeniedException;
25import java.nio.file.FileSystemException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import java.nio.file.Files;
mai932bfd69a2020-09-03 01:42:20 -070027import java.nio.file.InvalidPathException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import java.nio.file.LinkOption;
Fabian Meumertzheim753dc972024-03-31 18:44:36 -070029import java.nio.file.NoSuchFileException;
Taras Tsugrii7a3f1042017-10-30 08:07:37 -040030import java.nio.file.Paths;
Fabian Meumertzheim753dc972024-03-31 18:44:36 -070031import java.nio.file.StandardCopyOption;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032import java.nio.file.attribute.BasicFileAttributes;
Fabian Meumertzheimbf2eff82024-08-28 01:08:55 -070033import java.util.Arrays;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import java.util.Collection;
Googlerd0fedc42022-07-05 09:48:04 -070035import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036
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 Harmata13a74c02015-11-18 18:38:14 +000046public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047 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 Parsonscb5aa002016-07-26 17:52:37 +000052 private final Clock clock;
53
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054 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
ccalvarinbda12a12018-06-21 18:57:26 -070060 public JavaIoFileSystem(DigestHashFunction hashFunction) {
buchgr559a07d2017-11-30 11:09:35 -080061 super(hashFunction);
62 this.clock = new JavaClock();
63 }
64
ajurkowski8883c612021-03-08 08:12:37 -080065 protected File getIoFile(PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010066 return new File(path.toString());
67 }
68
Taras Tsugrii7a3f1042017-10-30 08:07:37 -040069 /**
70 * Returns a {@link java.nio.file.Path} representing the same path as provided {@code path}.
71 *
ajurkowski8883c612021-03-08 08:12:37 -080072 * <p>Note: while it's possible to use {@link #getIoFile(PathFragment)} in combination with {@link
Taras Tsugrii7a3f1042017-10-30 08:07:37 -040073 * 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 */
ajurkowski8883c612021-03-08 08:12:37 -080077 protected java.nio.file.Path getNioPath(PathFragment path) {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -040078 return Paths.get(path.toString());
79 }
80
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010081 private LinkOption[] linkOpts(boolean followSymlinks) {
82 return followSymlinks ? NO_LINK_OPTION : NOFOLLOW_LINKS_OPTION;
83 }
84
85 @Override
ajurkowski8883c612021-03-08 08:12:37 -080086 protected Collection<String> getDirectoryEntries(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010087 File file = getIoFile(path);
Fabian Meumertzheimbf2eff82024-08-28 01:08:55 -070088 String[] entries;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089 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 Meumertzheimbf2eff82024-08-28 01:08:55 -0700102 return Arrays.asList(entries);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100103 }
104
105 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800106 protected boolean exists(PathFragment path, boolean followSymlinks) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107 long startTime = Profiler.nanoTimeMaybe();
108 try {
mai932bfd69a2020-09-03 01:42:20 -0700109 java.nio.file.Path nioPath = getNioPath(path);
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400110 return Files.exists(nioPath, linkOpts(followSymlinks));
mai932bfd69a2020-09-03 01:42:20 -0700111 } catch (InvalidPathException e) {
112 return false;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100113 } finally {
114 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.toString());
115 }
116 }
117
118 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800119 protected boolean isReadable(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120 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
ajurkowski8883c612021-03-08 08:12:37 -0800133 protected boolean isWritable(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100134 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
ajurkowski8883c612021-03-08 08:12:37 -0800151 protected boolean isExecutable(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100152 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
ajurkowski8883c612021-03-08 08:12:37 -0800165 protected void setReadable(PathFragment path, boolean readable) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100166 File file = getIoFile(path);
167 if (!file.exists()) {
168 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
169 }
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700170 if (!file.setReadable(readable) && readable) {
171 throw new IOException(String.format("Failed to make %s readable", path));
172 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100173 }
174
175 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800176 public void setWritable(PathFragment path, boolean writable) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100177 File file = getIoFile(path);
178 if (!file.exists()) {
179 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
180 }
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700181 if (!file.setWritable(writable) && writable) {
182 throw new IOException(String.format("Failed to make %s writable", path));
183 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100184 }
185
186 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800187 protected void setExecutable(PathFragment path, boolean executable) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188 File file = getIoFile(path);
189 if (!file.exists()) {
190 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
191 }
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700192 if (!file.setExecutable(executable) && executable) {
193 throw new IOException(String.format("Failed to make %s executable", path));
194 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100195 }
196
197 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800198 public boolean supportsModifications(PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100199 return true;
200 }
201
202 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800203 public boolean supportsSymbolicLinksNatively(PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100204 return true;
205 }
206
207 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800208 public boolean supportsHardLinksNatively(PathFragment path) {
Googlere1cd9502016-09-07 14:33:29 +0000209 return true;
210 }
211
212 @Override
Yun Peng352f7e72016-05-09 11:08:25 +0000213 public boolean isFilePathCaseSensitive() {
214 return true;
215 }
216
217 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800218 public boolean createDirectory(PathFragment path) throws IOException {
tomlu22c2f9a2018-01-09 13:52:13 -0800219 File file = getIoFile(path);
220 if (file.mkdir()) {
221 return true;
222 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100223
tomlu22c2f9a2018-01-09 13:52:13 -0800224 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100242 }
243 }
244
tomludecca2b2017-12-21 08:59:51 -0800245 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800246 public void createDirectoryAndParents(PathFragment path) throws IOException {
tomludecca2b2017-12-21 08:59:51 -0800247 java.nio.file.Path nioPath = getNioPath(path);
tomlu7f17d082018-01-09 13:46:29 -0800248 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.
ajurkowski8883c612021-03-08 08:12:37 -0800253 if (!isDirectory(path, /*followSymlinks=*/ true)) {
tomlu7f17d082018-01-09 13:46:29 -0800254 throw e;
255 }
256 }
tomludecca2b2017-12-21 08:59:51 -0800257 }
258
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100259 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
ajurkowski8883c612021-03-08 08:12:37 -0800278 protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
aehligc801c392017-12-19 07:12:25 -0800279 throws IOException {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400280 java.nio.file.Path nioPath = getNioPath(linkPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100281 try {
tomlua729b9b2018-02-08 15:32:00 -0800282 Files.createSymbolicLink(nioPath, Paths.get(targetFragment.getSafePathString()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100283 } catch (java.nio.file.FileAlreadyExistsException e) {
Googler691fc5b2022-05-02 07:50:19 -0700284 throw new IOException(linkPath + ERR_FILE_EXISTS, e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 } catch (java.nio.file.AccessDeniedException e) {
Googler691fc5b2022-05-02 07:50:19 -0700286 throw new IOException(linkPath + ERR_PERMISSION_DENIED, e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 } catch (java.nio.file.NoSuchFileException e) {
288 throw new FileNotFoundException(linkPath + ERR_NO_SUCH_FILE_OR_DIR);
289 }
290 }
291
292 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800293 protected PathFragment readSymbolicLink(PathFragment path) throws IOException {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400294 java.nio.file.Path nioPath = getNioPath(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100295 long startTime = Profiler.nanoTimeMaybe();
296 try {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400297 String link = Files.readSymbolicLink(nioPath).toString();
aehligc801c392017-12-19 07:12:25 -0800298 return PathFragment.create(link);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100299 } catch (java.nio.file.NotLinkException e) {
Googler691fc5b2022-05-02 07:50:19 -0700300 throw new NotASymlinkException(path, e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301 } catch (java.nio.file.NoSuchFileException e) {
302 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
303 } finally {
janakr52859b42018-04-01 19:12:26 -0700304 profiler.logSimpleTask(startTime, ProfilerTask.VFS_READLINK, path.getPathString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100305 }
306 }
307
308 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800309 public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException {
Fabian Meumertzheim753dc972024-03-31 18:44:36 -0700310 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 };
pcloudye28d3af2017-10-24 14:16:12 +0200338 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100339 }
340
341 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800342 protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100343 long startTime = Profiler.nanoTimeMaybe();
344 try {
345 return stat(path, followSymlinks).getSize();
346 } finally {
janakr52859b42018-04-01 19:12:26 -0700347 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.getPathString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100348 }
349 }
350
351 @Override
ajurkowskiea26e0a2021-03-22 16:04:20 -0700352 protected boolean delete(PathFragment path) throws IOException {
Laszlo Csomor49d20312018-07-09 00:53:34 -0700353 java.nio.file.Path nioPath = getNioPath(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100354 long startTime = Profiler.nanoTimeMaybe();
tomlu22c2f9a2018-01-09 13:52:13 -0800355 try {
Laszlo Csomor49d20312018-07-09 00:53:34 -0700356 return Files.deleteIfExists(nioPath);
357 } catch (java.nio.file.DirectoryNotEmptyException e) {
Googler691fc5b2022-05-02 07:50:19 -0700358 throw new IOException(path.getPathString() + ERR_DIRECTORY_NOT_EMPTY, e);
Laszlo Csomor49d20312018-07-09 00:53:34 -0700359 } catch (java.nio.file.AccessDeniedException e) {
Googler691fc5b2022-05-02 07:50:19 -0700360 throw new IOException(path.getPathString() + ERR_PERMISSION_DENIED, e);
Laszlo Csomor49d20312018-07-09 00:53:34 -0700361 } 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);
tomlud50cbbe2017-12-28 13:25:27 -0800386 }
tomlu22c2f9a2018-01-09 13:52:13 -0800387 } finally {
Laszlo Csomor49d20312018-07-09 00:53:34 -0700388 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, path.getPathString());
pcloudye28d3af2017-10-24 14:16:12 +0200389 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390 }
391
392 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800393 protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100394 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 Berki58dbb7f2016-01-29 11:56:29 +0000403 protected boolean fileIsSymbolicLink(File file) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100404 return Files.isSymbolicLink(file.toPath());
405 }
406
407 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800408 public void setLastModifiedTime(PathFragment path, long newTime) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100409 File file = getIoFile(path);
janakrc8735252021-12-13 12:35:06 -0800410 if (!file.setLastModified(
411 newTime == Path.NOW_SENTINEL_TIME ? clock.currentTimeMillis() : newTime)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100412 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
ajurkowski8883c612021-03-08 08:12:37 -0800423 protected byte[] getDigest(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100424 String name = path.toString();
425 long startTime = Profiler.nanoTimeMaybe();
426 try {
ccalvarindd9f60e2018-07-23 18:16:18 -0700427 return super.getDigest(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100428 } finally {
429 profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name);
430 }
431 }
432
433 /**
ajurkowski8883c612021-03-08 08:12:37 -0800434 * Returns the status of a file. See {@link Path#stat(Symlinks)} for specification.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100435 *
ajurkowski8883c612021-03-08 08:12:37 -0800436 * <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 Nienhuysd08b27f2015-02-25 16:45:20 +0100440 */
441 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800442 protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400443 java.nio.file.Path nioPath = getNioPath(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100444 final BasicFileAttributes attributes;
445 try {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400446 attributes =
447 Files.readAttributes(nioPath, BasicFileAttributes.class, linkOpts(followSymlinks));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100448 } catch (java.nio.file.FileSystemException e) {
449 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
450 }
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700451 FileStatus status =
452 new FileStatus() {
453 @Override
454 public boolean isFile() {
455 return attributes.isRegularFile() || isSpecialFile();
456 }
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000457
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700458 @Override
459 public boolean isSpecialFile() {
460 return attributes.isOther();
461 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100462
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700463 @Override
464 public boolean isDirectory() {
465 return attributes.isDirectory();
466 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100467
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700468 @Override
469 public boolean isSymbolicLink() {
470 return attributes.isSymbolicLink();
471 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100472
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700473 @Override
474 public long getSize() {
475 return attributes.size();
476 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100477
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700478 @Override
479 public long getLastModifiedTime() {
480 return attributes.lastModifiedTime().toMillis();
481 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100482
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700483 @Override
484 public long getLastChangeTime() {
485 // This is the best we can do with Java NIO...
486 return attributes.lastModifiedTime().toMillis();
487 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100488
Fabian Meumertzheim89a3d5e2023-04-11 11:15:50 -0700489 @Override
490 public long getNodeId() {
491 // TODO(bazel-team): Consider making use of attributes.fileKey().
492 return -1;
493 }
494 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100495
496 return status;
497 }
498
499 @Override
Googlerd0fedc42022-07-05 09:48:04 -0700500 @Nullable
ajurkowski8883c612021-03-08 08:12:37 -0800501 protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100502 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 }
Googlere1cd9502016-09-07 14:33:29 +0000516
517 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800518 protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath)
Googlere1cd9502016-09-07 14:33:29 +0000519 throws IOException {
520 Files.createLink(
521 java.nio.file.Paths.get(linkPath.toString()),
522 java.nio.file.Paths.get(originalPath.toString()));
523 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100524}