blob: 5b3669b8ed5a8cd253941877ac765cd03e2f4a8b [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.windows;
import com.google.common.annotations.VisibleForTesting;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.DigestHashFunction.DefaultHashFunctionNotSetException;
import com.google.devtools.build.lib.vfs.FileStatus;
import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.attribute.DosFileAttributes;
/** File system implementation for Windows. */
@ThreadSafe
public class WindowsFileSystem extends JavaIoFileSystem {
public static final LinkOption[] NO_OPTIONS = new LinkOption[0];
public static final LinkOption[] NO_FOLLOW = new LinkOption[] {LinkOption.NOFOLLOW_LINKS};
public WindowsFileSystem() throws DefaultHashFunctionNotSetException {}
public WindowsFileSystem(DigestHashFunction hashFunction) {
super(hashFunction);
}
@Override
public String getFileSystemType(Path path) {
// TODO(laszlocsomor): implement this properly, i.e. actually query this information from
// somewhere (java.nio.Filesystem? System.getProperty? implement JNI method and use WinAPI?).
return "ntfs";
}
@Override
public boolean delete(Path path) throws IOException {
long startTime = Profiler.nanoTimeMaybe();
try {
return WindowsFileOperations.deletePath(path.getPathString());
} catch (java.nio.file.DirectoryNotEmptyException e) {
throw new IOException(path.getPathString() + ERR_DIRECTORY_NOT_EMPTY);
} catch (java.nio.file.AccessDeniedException e) {
throw new IOException(path.getPathString() + ERR_PERMISSION_DENIED);
} finally {
profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, path.getPathString());
}
}
@Override
protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException {
Path targetPath =
targetFragment.isAbsolute()
? getPath(targetFragment)
: linkPath.getParentDirectory().getRelative(targetFragment);
try {
java.nio.file.Path link = getIoFile(linkPath).toPath();
java.nio.file.Path target = getIoFile(targetPath).toPath();
// Still Create a dangling junction if the target doesn't exist.
if (!target.toFile().exists() || target.toFile().isDirectory()) {
WindowsFileOperations.createJunction(link.toString(), target.toString());
} else {
Files.copy(target, link);
}
} catch (java.nio.file.FileAlreadyExistsException e) {
throw new IOException(linkPath + ERR_FILE_EXISTS);
} catch (java.nio.file.AccessDeniedException e) {
throw new IOException(linkPath + ERR_PERMISSION_DENIED);
} catch (java.nio.file.NoSuchFileException e) {
throw new FileNotFoundException(linkPath + ERR_NO_SUCH_FILE_OR_DIR);
}
}
@Override
protected PathFragment readSymbolicLink(Path path) throws IOException {
java.nio.file.Path nioPath = getNioPath(path);
return PathFragment.create(WindowsFileOperations.readSymlinkOrJunction(nioPath.toString()));
}
@Override
public boolean supportsSymbolicLinksNatively(Path path) {
return false;
}
@Override
public boolean isFilePathCaseSensitive() {
return false;
}
@Override
protected boolean fileIsSymbolicLink(File file) {
try {
if (isSymlinkOrJunction(file)) {
return true;
}
} catch (IOException e) {
// Did not work, try in another way
}
return super.fileIsSymbolicLink(file);
}
public static LinkOption[] symlinkOpts(boolean followSymlinks) {
return followSymlinks ? NO_OPTIONS : NO_FOLLOW;
}
@Override
protected FileStatus stat(Path path, boolean followSymlinks) throws IOException {
File file = getIoFile(path);
final DosFileAttributes attributes;
try {
attributes = getAttribs(file, followSymlinks);
} catch (IOException e) {
throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR);
}
final boolean isSymbolicLink = !followSymlinks && fileIsSymbolicLink(file);
FileStatus status =
new FileStatus() {
@Override
public boolean isFile() {
return attributes.isRegularFile() || (isSpecialFile() && !isDirectory());
}
@Override
public boolean isSpecialFile() {
return attributes.isOther();
}
@Override
public boolean isDirectory() {
return attributes.isDirectory();
}
@Override
public boolean isSymbolicLink() {
return isSymbolicLink;
}
@Override
public long getSize() throws IOException {
return attributes.size();
}
@Override
public long getLastModifiedTime() throws IOException {
return attributes.lastModifiedTime().toMillis();
}
@Override
public long getLastChangeTime() {
// This is the best we can do with Java NIO...
return attributes.lastModifiedTime().toMillis();
}
@Override
public long getNodeId() {
// TODO(bazel-team): Consider making use of attributes.fileKey().
return -1;
}
};
return status;
}
@Override
protected boolean isDirectory(Path path, boolean followSymlinks) {
if (!followSymlinks) {
try {
if (isSymlinkOrJunction(getIoFile(path))) {
return false;
}
} catch (IOException e) {
return false;
}
}
return super.isDirectory(path, followSymlinks);
}
/**
* Returns true if the path refers to a directory junction, directory symlink, or regular symlink.
*
* <p>Directory junctions are symbolic links created with "mklink /J" where the target is a
* directory or another directory junction. Directory junctions can be created without any user
* privileges.
*
* <p>Directory symlinks are symbolic links created with "mklink /D" where the target is a
* directory or another directory symlink. Note that directory symlinks can only be created by
* Administrators.
*
* <p>Normal symlinks are symbolic links created with "mklink". Normal symlinks should not point
* at directories, because even though "mklink" can create the link, it will not be a functional
* one (the linked directory's contents cannot be listed). Only Administrators may create regular
* symlinks.
*
* <p>This method returns true for all three types as long as their target is a directory (even if
* they are dangling), though only directory junctions and directory symlinks are useful.
*/
@VisibleForTesting
static boolean isSymlinkOrJunction(File file) throws IOException {
return WindowsFileOperations.isSymlinkOrJunction(file.getPath());
}
private static DosFileAttributes getAttribs(File file, boolean followSymlinks)
throws IOException {
return Files.readAttributes(
file.toPath(), DosFileAttributes.class, symlinkOpts(followSymlinks));
}
}