| // 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.sandbox; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.io.Files; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Creates an execRoot for a Spawn that contains input files as symlinks to hardlinks of the |
| * original input files. |
| */ |
| public class HardlinkedExecRoot implements SandboxExecRoot { |
| |
| private final Path execRoot; |
| private final Path sandboxPath; |
| private final Path sandboxExecRoot; |
| private final PrintWriter errWriter; |
| |
| public HardlinkedExecRoot( |
| Path execRoot, Path sandboxPath, Path sandboxExecRoot, PrintWriter errWriter) { |
| this.execRoot = execRoot; |
| this.sandboxPath = sandboxPath; |
| this.sandboxExecRoot = sandboxExecRoot; |
| this.errWriter = errWriter; |
| } |
| |
| @Override |
| public void createFileSystem( |
| Map<PathFragment, Path> inputs, Collection<PathFragment> outputs, Set<Path> writableDirs) |
| throws IOException { |
| Set<Path> createdDirs = new HashSet<>(); |
| FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, sandboxExecRoot); |
| createDirectoriesForOutputs(outputs, createdDirs); |
| |
| // Create all needed directories. |
| for (Path createDir : writableDirs) { |
| if (errWriter != null) { |
| errWriter.printf("createdir: %s\n", createDir.getPathString()); |
| } |
| FileSystemUtils.createDirectoryAndParents(createDir); |
| } |
| |
| // Link all the inputs. |
| linkInputs(inputs); |
| } |
| |
| private void createDirectoriesForOutputs(Collection<PathFragment> outputs, Set<Path> createdDirs) |
| throws IOException { |
| // Prepare the output directories in the sandbox. |
| for (PathFragment output : outputs) { |
| FileSystemUtils.createDirectoryAndParentsWithCache( |
| createdDirs, sandboxExecRoot.getRelative(output.getParentDirectory())); |
| } |
| } |
| |
| /** |
| * Make all specified inputs available in the sandbox. |
| * |
| * <p>We want the sandboxed process to have access only to these input files and not anything else |
| * from the workspace. Furthermore, the process should not be able to modify these input files. We |
| * achieve this by hardlinking all input files into a temporary "inputs" directory, then |
| * symlinking them into their correct place inside the sandbox. |
| * |
| * <p>The hardlinks / symlinks combination (as opposed to simply directly hardlinking to the final |
| * destination) is necessary, because we build a solib symlink tree for shared libraries where the |
| * original file and the created symlink have two different file names (libblaze_util.so vs. |
| * src_Stest_Scpp_Sblaze_Uutil_Utest.so) and our cc_wrapper.sh needs to be able to figure out both |
| * names (by following solib symlinks back) to modify the paths to the shared libraries in |
| * cc_binaries. |
| */ |
| private void linkInputs(Map<PathFragment, Path> inputs) throws IOException { |
| // Create directory for input files. |
| Path inputsDir = sandboxPath.getRelative("inputs"); |
| if (!inputsDir.exists()) { |
| inputsDir.createDirectory(); |
| } |
| |
| for (ImmutableMap.Entry<PathFragment, Path> entry : inputs.entrySet()) { |
| // Hardlink, resolve symlink here instead in finalizeLinks. |
| Path target = entry.getValue().resolveSymbolicLinks(); |
| Path hardlinkName = |
| target.startsWith(execRoot) |
| ? inputsDir.getRelative(target.relativeTo(execRoot)) |
| : inputsDir.getRelative(entry.getKey()); |
| if (errWriter != null) { |
| errWriter.printf("hardlink: %s -> %s\n", hardlinkName, target); |
| } |
| try { |
| createHardLink(hardlinkName, target); |
| } catch (IOException e) { |
| // Creating a hardlink might fail when the input file and the sandbox directory are not on |
| // the same filesystem / device. Then we use symlink instead. |
| hardlinkName.createSymbolicLink(target); |
| } |
| |
| // symlink |
| Path symlinkName = sandboxExecRoot.getRelative(entry.getKey()); |
| if (errWriter != null) { |
| errWriter.printf("symlink: %s -> %s\n", symlinkName, hardlinkName); |
| } |
| FileSystemUtils.createDirectoryAndParents(symlinkName.getParentDirectory()); |
| symlinkName.createSymbolicLink(hardlinkName); |
| } |
| } |
| |
| private void createHardLink(Path target, Path source) throws IOException { |
| java.nio.file.Path targetNio = java.nio.file.Paths.get(target.toString()); |
| java.nio.file.Path sourceNio = java.nio.file.Paths.get(source.toString()); |
| |
| if (!source.exists() || target.exists()) { |
| return; |
| } |
| // Regular file |
| if (source.isFile()) { |
| Path parentDir = target.getParentDirectory(); |
| if (!parentDir.exists()) { |
| FileSystemUtils.createDirectoryAndParents(parentDir); |
| } |
| java.nio.file.Files.createLink(targetNio, sourceNio); |
| // Directory |
| } else if (source.isDirectory()) { |
| Collection<Path> subpaths = source.getDirectoryEntries(); |
| for (Path sourceSubpath : subpaths) { |
| Path targetSubpath = target.getRelative(sourceSubpath.relativeTo(source)); |
| createHardLink(targetSubpath, sourceSubpath); |
| } |
| } |
| } |
| |
| @Override |
| public void copyOutputs(Path execRoot, Collection<PathFragment> outputs) throws IOException { |
| for (PathFragment output : outputs) { |
| Path source = sandboxExecRoot.getRelative(output); |
| Path target = execRoot.getRelative(output); |
| if (source.isFile() || source.isSymbolicLink()) { |
| Files.move(source.getPathFile(), target.getPathFile()); |
| } else if (source.isDirectory()) { |
| try { |
| source.renameTo(target); |
| } catch (IOException e) { |
| // Failed to move directory directly, thus move it recursively. |
| target.createDirectory(); |
| FileSystemUtils.moveTreesBelow(source, target); |
| } |
| } |
| } |
| } |
| } |