blob: 4f096d4fb930992f6d89a4848187c3dfe7f9eaa2 [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.vfs;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
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.BasicFileAttributes;
/**
* Jury-rigged file system 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};
@Override
protected void createSymbolicLink(Path linkPath, PathFragment targetFragment)
throws IOException {
// TODO(lberki): Add some JNI to create hard links/junctions instead of calling out to
// cmd.exe
File file = getIoFile(linkPath);
try {
File targetFile = new File(targetFragment.getPathString());
if (targetFile.isDirectory()) {
createDirectoryJunction(targetFile, file);
} else {
Files.copy(targetFile.toPath(), file.toPath());
}
} 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
public boolean supportsSymbolicLinksNatively() {
return false;
}
private void createDirectoryJunction(File sourceDirectory, File targetPath) throws IOException {
StringBuilder cl = new StringBuilder("cmd.exe /c ");
cl.append("mklink /J ");
cl.append('"');
cl.append(targetPath.getAbsolutePath());
cl.append('"');
cl.append(' ');
cl.append('"');
cl.append(sourceDirectory.getAbsolutePath());
cl.append('"');
Process process = Runtime.getRuntime().exec(cl.toString());
try {
process.waitFor();
if (process.exitValue() != 0) {
throw new IOException("Command failed " + cl);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Command failed ", e);
}
}
@Override
protected boolean fileIsSymbolicLink(File file) {
try {
if (file.isDirectory() && isJunction(file.toPath())) {
return true;
}
} catch (IOException e) {
// Did not work, try in another way
}
return super.fileIsSymbolicLink(file);
}
private LinkOption[] linkOpts(boolean followSymlinks) {
return followSymlinks ? NO_OPTIONS : NO_FOLLOW;
}
@Override
protected FileStatus stat(Path path, boolean followSymlinks) throws IOException {
File file = getIoFile(path);
final BasicFileAttributes attributes;
try {
attributes = Files.readAttributes(
file.toPath(), BasicFileAttributes.class, linkOpts(followSymlinks));
} catch (java.nio.file.FileSystemException 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 (isJunction(getIoFile(path).toPath())) {
return false;
}
} catch (IOException e) {
return false;
}
}
return super.isDirectory(path, followSymlinks);
}
private static boolean isJunction(java.nio.file.Path p) throws IOException {
// Jury-rigged
return p.compareTo(p.toRealPath()) != 0;
}
}