blob: e55877dd66adaa591a7b01d0b5bb82e5109863ef [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
Chris Parsonscb5aa002016-07-26 17:52:37 +000016import com.google.common.annotations.VisibleForTesting;
philwo3bcb9f62017-09-06 12:52:21 +020017import com.google.devtools.build.lib.clock.Clock;
18import com.google.devtools.build.lib.clock.JavaClock;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
20import com.google.devtools.build.lib.profiler.Profiler;
21import com.google.devtools.build.lib.profiler.ProfilerTask;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010022import java.io.File;
23import java.io.FileNotFoundException;
24import java.io.IOException;
25import java.nio.file.Files;
26import java.nio.file.LinkOption;
Taras Tsugrii7a3f1042017-10-30 08:07:37 -040027import java.nio.file.Paths;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import java.nio.file.attribute.BasicFileAttributes;
29import java.util.ArrayList;
30import java.util.Collection;
31
32/**
33 * A FileSystem that does not use any JNI and hence, does not require a shared library be present at
34 * execution.
35 *
36 * <p>Note: Blaze profiler tasks are defined on the system call level - thus we do not distinguish
37 * (from profiling perspective) between different methods on this class that end up doing stat()
38 * system call - they all are associated with the VFS_STAT task.
39 */
40@ThreadSafe
Nathan Harmata13a74c02015-11-18 18:38:14 +000041public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042 private static final LinkOption[] NO_LINK_OPTION = new LinkOption[0];
43 // This isn't generally safe; we rely on the file system APIs not modifying the array.
44 private static final LinkOption[] NOFOLLOW_LINKS_OPTION =
45 new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
46
Chris Parsonscb5aa002016-07-26 17:52:37 +000047 private final Clock clock;
48
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010049 protected static final String ERR_IS_DIRECTORY = " (Is a directory)";
50 protected static final String ERR_DIRECTORY_NOT_EMPTY = " (Directory not empty)";
51 protected static final String ERR_FILE_EXISTS = " (File exists)";
52 protected static final String ERR_NO_SUCH_FILE_OR_DIR = " (No such file or directory)";
53 protected static final String ERR_NOT_A_DIRECTORY = " (Not a directory)";
54
Chris Parsonscb5aa002016-07-26 17:52:37 +000055 public JavaIoFileSystem() {
56 this(new JavaClock());
57 }
58
buchgr559a07d2017-11-30 11:09:35 -080059 public JavaIoFileSystem(HashFunction hashFunction) {
60 super(hashFunction);
61 this.clock = new JavaClock();
62 }
63
Chris Parsonscb5aa002016-07-26 17:52:37 +000064 @VisibleForTesting
65 JavaIoFileSystem(Clock clock) {
66 this.clock = clock;
67 }
68
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010069 protected File getIoFile(Path path) {
70 return new File(path.toString());
71 }
72
Taras Tsugrii7a3f1042017-10-30 08:07:37 -040073 /**
74 * Returns a {@link java.nio.file.Path} representing the same path as provided {@code path}.
75 *
76 * <p>Note: while it's possible to use {@link #getIoFile(Path)} in combination with {@link
77 * File#toPath()} to achieve essentially the same, using this method is preferable because it
78 * avoids extra allocations and does not lose track of the underlying Java filesystem, which is
79 * useful for some in-memory filesystem implementations like JimFS.
80 */
81 protected java.nio.file.Path getNioPath(Path path) {
82 return Paths.get(path.toString());
83 }
84
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010085 private LinkOption[] linkOpts(boolean followSymlinks) {
86 return followSymlinks ? NO_LINK_OPTION : NOFOLLOW_LINKS_OPTION;
87 }
88
89 @Override
tomlu0a82e702017-10-23 18:16:44 +020090 protected Collection<String> getDirectoryEntries(Path path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091 File file = getIoFile(path);
92 String[] entries = null;
93 long startTime = Profiler.nanoTimeMaybe();
94 try {
95 entries = file.list();
96 if (entries == null) {
97 if (file.exists()) {
98 throw new IOException(path + ERR_NOT_A_DIRECTORY);
99 } else {
100 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
101 }
102 }
103 } finally {
104 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DIR, file.getPath());
105 }
tomlu0a82e702017-10-23 18:16:44 +0200106 Collection<String> result = new ArrayList<>(entries.length);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107 for (String entry : entries) {
108 if (!entry.equals(".") && !entry.equals("..")) {
tomlu0a82e702017-10-23 18:16:44 +0200109 result.add(entry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100110 }
111 }
112 return result;
113 }
114
115 @Override
116 protected boolean exists(Path path, boolean followSymlinks) {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400117 java.nio.file.Path nioPath = getNioPath(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100118 long startTime = Profiler.nanoTimeMaybe();
119 try {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400120 return Files.exists(nioPath, linkOpts(followSymlinks));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100121 } finally {
122 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path.toString());
123 }
124 }
125
126 @Override
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100127 protected boolean isReadable(Path path) throws IOException {
128 File file = getIoFile(path);
129 long startTime = Profiler.nanoTimeMaybe();
130 try {
131 if (!file.exists()) {
132 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
133 }
134 return file.canRead();
135 } finally {
136 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath());
137 }
138 }
139
140 @Override
141 protected boolean isWritable(Path path) throws IOException {
142 File file = getIoFile(path);
143 long startTime = Profiler.nanoTimeMaybe();
144 try {
145 if (!file.exists()) {
146 if (linkExists(file)) {
147 throw new IOException(path + ERR_PERMISSION_DENIED);
148 } else {
149 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
150 }
151 }
152 return file.canWrite();
153 } finally {
154 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath());
155 }
156 }
157
158 @Override
159 protected boolean isExecutable(Path path) throws IOException {
160 File file = getIoFile(path);
161 long startTime = Profiler.nanoTimeMaybe();
162 try {
163 if (!file.exists()) {
164 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
165 }
166 return file.canExecute();
167 } finally {
168 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath());
169 }
170 }
171
172 @Override
173 protected void setReadable(Path path, boolean readable) throws IOException {
174 File file = getIoFile(path);
175 if (!file.exists()) {
176 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
177 }
178 file.setReadable(readable);
179 }
180
181 @Override
182 protected void setWritable(Path path, boolean writable) throws IOException {
183 File file = getIoFile(path);
184 if (!file.exists()) {
185 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
186 }
187 file.setWritable(writable);
188 }
189
190 @Override
191 protected void setExecutable(Path path, boolean executable) throws IOException {
192 File file = getIoFile(path);
193 if (!file.exists()) {
194 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
195 }
196 file.setExecutable(executable);
197 }
198
199 @Override
tomlu39fab102017-10-19 21:35:06 +0200200 public boolean supportsModifications(Path path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100201 return true;
202 }
203
204 @Override
tomlu39fab102017-10-19 21:35:06 +0200205 public boolean supportsSymbolicLinksNatively(Path path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206 return true;
207 }
208
209 @Override
tomlu39fab102017-10-19 21:35:06 +0200210 public boolean supportsHardLinksNatively(Path path) {
Googlere1cd9502016-09-07 14:33:29 +0000211 return true;
212 }
213
214 @Override
Yun Peng352f7e72016-05-09 11:08:25 +0000215 public boolean isFilePathCaseSensitive() {
216 return true;
217 }
218
219 @Override
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100220 protected boolean createDirectory(Path path) throws IOException {
221
pcloudye28d3af2017-10-24 14:16:12 +0200222 // We always synchronize on the current path before doing it on the parent path and file system
223 // path structure ensures that this locking order will never be reversed.
224 // When refactoring, check that subclasses still work as expected and there can be no
225 // deadlocks.
226 synchronized (path) {
227 File file = getIoFile(path);
228 if (file.mkdir()) {
229 return true;
230 }
231
232 // We will be checking the state of the parent path as well. Synchronize on it before
233 // attempting anything.
234 Path parentDirectory = path.getParentDirectory();
235 synchronized (parentDirectory) {
236 if (fileIsSymbolicLink(file)) {
237 throw new IOException(path + ERR_FILE_EXISTS);
238 }
239 if (file.isDirectory()) {
240 return false; // directory already existed
241 } else if (file.exists()) {
242 throw new IOException(path + ERR_FILE_EXISTS);
243 } else if (!file.getParentFile().exists()) {
244 throw new FileNotFoundException(path.getParentDirectory() + ERR_NO_SUCH_FILE_OR_DIR);
245 }
246 // Parent directory apparently exists - try to create our directory again - protecting
247 // against the case where parent directory would be created right before us obtaining
248 // synchronization lock.
249 if (file.mkdir()) {
250 return true; // Everything is fine finally.
251 } else if (!file.getParentFile().canWrite()) {
252 throw new FileAccessException(path + ERR_PERMISSION_DENIED);
253 } else {
254 // Parent exists, is writable, yet we can't create our directory.
255 throw new FileNotFoundException(path.getParentDirectory() + ERR_NOT_A_DIRECTORY);
256 }
257 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100258 }
259 }
260
261 private boolean linkExists(File file) {
262 String shortName = file.getName();
263 File parentFile = file.getParentFile();
264 if (parentFile == null) {
265 return false;
266 }
267 String[] filenames = parentFile.list();
268 if (filenames == null) {
269 return false;
270 }
271 for (String name : filenames) {
272 if (name.equals(shortName)) {
273 return true;
274 }
275 }
276 return false;
277 }
278
279 @Override
280 protected void createSymbolicLink(Path linkPath, PathFragment targetFragment)
281 throws IOException {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400282 java.nio.file.Path nioPath = getNioPath(linkPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100283 try {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400284 Files.createSymbolicLink(nioPath, Paths.get(targetFragment.getPathString()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 } catch (java.nio.file.FileAlreadyExistsException e) {
286 throw new IOException(linkPath + ERR_FILE_EXISTS);
287 } catch (java.nio.file.AccessDeniedException e) {
288 throw new IOException(linkPath + ERR_PERMISSION_DENIED);
289 } catch (java.nio.file.NoSuchFileException e) {
290 throw new FileNotFoundException(linkPath + ERR_NO_SUCH_FILE_OR_DIR);
291 }
292 }
293
294 @Override
295 protected PathFragment readSymbolicLink(Path path) throws IOException {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400296 java.nio.file.Path nioPath = getNioPath(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100297 long startTime = Profiler.nanoTimeMaybe();
298 try {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400299 String link = Files.readSymbolicLink(nioPath).toString();
nharmatab4060b62017-04-04 17:11:39 +0000300 return PathFragment.create(link);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301 } catch (java.nio.file.NotLinkException e) {
302 throw new NotASymlinkException(path);
303 } catch (java.nio.file.NoSuchFileException e) {
304 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
305 } finally {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400306 profiler.logSimpleTask(startTime, ProfilerTask.VFS_READLINK, nioPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 }
308 }
309
310 @Override
tomluc2f6ae82017-12-08 14:19:42 -0800311 public void renameTo(Path sourcePath, Path targetPath) throws IOException {
pcloudye28d3af2017-10-24 14:16:12 +0200312 synchronized (sourcePath) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100313 File sourceFile = getIoFile(sourcePath);
314 File targetFile = getIoFile(targetPath);
315 if (!sourceFile.renameTo(targetFile)) {
316 if (!sourceFile.exists()) {
317 throw new FileNotFoundException(sourcePath + ERR_NO_SUCH_FILE_OR_DIR);
318 }
319 if (targetFile.exists()) {
320 if (targetFile.isDirectory() && targetFile.list().length > 0) {
321 throw new IOException(targetPath + ERR_DIRECTORY_NOT_EMPTY);
322 } else if (sourceFile.isDirectory() && targetFile.isFile()) {
323 throw new IOException(sourcePath + " -> " + targetPath + ERR_NOT_A_DIRECTORY);
324 } else if (sourceFile.isFile() && targetFile.isDirectory()) {
325 throw new IOException(sourcePath + " -> " + targetPath + ERR_IS_DIRECTORY);
326 } else {
327 throw new IOException(sourcePath + " -> " + targetPath + ERR_PERMISSION_DENIED);
328 }
329 } else {
330 throw new FileAccessException(sourcePath + " -> " + targetPath + ERR_PERMISSION_DENIED);
331 }
332 }
pcloudye28d3af2017-10-24 14:16:12 +0200333 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100334 }
335
336 @Override
337 protected long getFileSize(Path path, boolean followSymlinks) throws IOException {
338 long startTime = Profiler.nanoTimeMaybe();
339 try {
340 return stat(path, followSymlinks).getSize();
341 } finally {
342 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, path);
343 }
344 }
345
346 @Override
347 protected boolean delete(Path path) throws IOException {
348 File file = getIoFile(path);
349 long startTime = Profiler.nanoTimeMaybe();
pcloudye28d3af2017-10-24 14:16:12 +0200350 synchronized (path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100351 try {
352 if (file.delete()) {
353 return true;
354 }
355 if (file.exists()) {
356 if (file.isDirectory() && file.list().length > 0) {
357 throw new IOException(path + ERR_DIRECTORY_NOT_EMPTY);
358 } else {
359 throw new IOException(path + ERR_PERMISSION_DENIED);
360 }
361 }
362 return false;
363 } finally {
364 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, file.getPath());
365 }
pcloudye28d3af2017-10-24 14:16:12 +0200366 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367 }
368
369 @Override
370 protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException {
371 File file = getIoFile(path);
372 long startTime = Profiler.nanoTimeMaybe();
373 try {
374 return stat(path, followSymlinks).getLastModifiedTime();
375 } finally {
376 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, file.getPath());
377 }
378 }
379
Lukacs Berki58dbb7f2016-01-29 11:56:29 +0000380 protected boolean fileIsSymbolicLink(File file) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100381 return Files.isSymbolicLink(file.toPath());
382 }
383
384 @Override
tomluc2f6ae82017-12-08 14:19:42 -0800385 public void setLastModifiedTime(Path path, long newTime) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100386 File file = getIoFile(path);
Chris Parsonscb5aa002016-07-26 17:52:37 +0000387 if (!file.setLastModified(newTime == -1L ? clock.currentTimeMillis() : newTime)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 if (!file.exists()) {
389 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
390 } else if (!file.getParentFile().canWrite()) {
391 throw new FileAccessException(path.getParentDirectory() + ERR_PERMISSION_DENIED);
392 } else {
393 throw new FileAccessException(path + ERR_PERMISSION_DENIED);
394 }
395 }
396 }
397
398 @Override
olaolabfd1d332017-06-19 16:55:24 -0400399 protected byte[] getDigest(Path path, HashFunction hashFunction) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100400 String name = path.toString();
401 long startTime = Profiler.nanoTimeMaybe();
402 try {
olaolabfd1d332017-06-19 16:55:24 -0400403 return super.getDigest(path, hashFunction);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100404 } finally {
405 profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name);
406 }
407 }
408
409 /**
410 * Returns the status of a file. See {@link Path#stat(Symlinks)} for
411 * specification.
412 *
413 * <p>The default implementation of this method is a "lazy" one, based on
414 * other accessor methods such as {@link #isFile}, etc. Subclasses may provide
415 * more efficient specializations. However, we still try to follow Unix-like
416 * semantics of failing fast in case of non-existent files (or in case of
417 * permission issues).
418 */
419 @Override
420 protected FileStatus stat(final Path path, final boolean followSymlinks) throws IOException {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400421 java.nio.file.Path nioPath = getNioPath(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100422 final BasicFileAttributes attributes;
423 try {
Taras Tsugrii7a3f1042017-10-30 08:07:37 -0400424 attributes =
425 Files.readAttributes(nioPath, BasicFileAttributes.class, linkOpts(followSymlinks));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100426 } catch (java.nio.file.FileSystemException e) {
427 throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
428 }
429 FileStatus status = new FileStatus() {
430 @Override
431 public boolean isFile() {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000432 return attributes.isRegularFile() || isSpecialFile();
433 }
434
435 @Override
436 public boolean isSpecialFile() {
437 return attributes.isOther();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100438 }
439
440 @Override
441 public boolean isDirectory() {
442 return attributes.isDirectory();
443 }
444
445 @Override
446 public boolean isSymbolicLink() {
447 return attributes.isSymbolicLink();
448 }
449
450 @Override
451 public long getSize() throws IOException {
452 return attributes.size();
453 }
454
455 @Override
456 public long getLastModifiedTime() throws IOException {
457 return attributes.lastModifiedTime().toMillis();
458 }
459
460 @Override
461 public long getLastChangeTime() {
462 // This is the best we can do with Java NIO...
463 return attributes.lastModifiedTime().toMillis();
464 }
465
466 @Override
467 public long getNodeId() {
468 // TODO(bazel-team): Consider making use of attributes.fileKey().
469 return -1;
470 }
471 };
472
473 return status;
474 }
475
476 @Override
477 protected FileStatus statIfFound(Path path, boolean followSymlinks) {
478 try {
479 return stat(path, followSymlinks);
480 } catch (FileNotFoundException e) {
481 // JavaIoFileSystem#stat (incorrectly) only throws FileNotFoundException (because it calls
482 // #getLastModifiedTime, which can only throw a FileNotFoundException), so we always hit this
483 // codepath. Thus, this method will incorrectly not throw an exception for some filesystem
484 // errors.
485 return null;
486 } catch (IOException e) {
487 // If this codepath is ever hit, then this method should be rewritten to properly distinguish
488 // between not-found exceptions and others.
489 throw new IllegalStateException(e);
490 }
491 }
Googlere1cd9502016-09-07 14:33:29 +0000492
493 @Override
494 protected void createFSDependentHardLink(Path linkPath, Path originalPath)
495 throws IOException {
496 Files.createLink(
497 java.nio.file.Paths.get(linkPath.toString()),
498 java.nio.file.Paths.get(originalPath.toString()));
499 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100500}