Skip to content

Commit

Permalink
sandbox: Show user-friendly mount paths in the namespace-sandbox debu…
Browse files Browse the repository at this point in the history
…g log.

Improvement for #424.

--
MOS_MIGRATED_REVID=102566748
  • Loading branch information
philwo authored and damienmg committed Sep 8, 2015
1 parent d93922b commit 17ef915
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public void exec(Spawn spawn, ActionExecutionContext actionExecutionContext)
ImmutableMap<Path, Path> mounts;
try {
// Gather all necessary mounts for the sandbox.
mounts = getMounts(spawn, sandboxPath, actionExecutionContext);
mounts = getMounts(spawn, actionExecutionContext);
createTestTmpDir(spawn, sandboxPath);
} catch (IllegalArgumentException | IOException e) {
throw new UserExecException("Could not prepare mounts for sandbox execution", e);
Expand Down Expand Up @@ -207,7 +207,7 @@ private int getTimeout(Spawn spawn) throws UserExecException {
try {
return Integer.parseInt(timeoutStr);
} catch (NumberFormatException e) {
throw new UserExecException("could not parse timeout: " + e);
throw new UserExecException("Could not parse timeout", e);
}
}
return -1;
Expand All @@ -227,15 +227,15 @@ private void createTestTmpDir(Spawn spawn, Path sandboxPath) throws IOException
}

private ImmutableMap<Path, Path> getMounts(
Spawn spawn, Path sandboxPath, ActionExecutionContext executionContext) throws IOException {
Spawn spawn, ActionExecutionContext executionContext) throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();
mounts.putAll(mountUsualUnixDirs(sandboxPath));
mounts.putAll(withRecursedDirs(setupBlazeUtils(sandboxPath)));
mounts.putAll(withRecursedDirs(mountRunfilesFromManifests(spawn, sandboxPath)));
mounts.putAll(withRecursedDirs(mountRunfilesFromSuppliers(spawn, sandboxPath)));
mounts.putAll(withRecursedDirs(mountInputs(spawn, sandboxPath, executionContext)));
mounts.putAll(withRecursedDirs(mountRunUnderCommand(spawn, sandboxPath)));
return validateMounts(sandboxPath, withResolvedSymlinks(sandboxPath, mounts));
mounts.putAll(mountUsualUnixDirs());
mounts.putAll(withRecursedDirs(setupBlazeUtils()));
mounts.putAll(withRecursedDirs(mountRunfilesFromManifests(spawn)));
mounts.putAll(withRecursedDirs(mountRunfilesFromSuppliers(spawn)));
mounts.putAll(withRecursedDirs(mountInputs(spawn, executionContext)));
mounts.putAll(withRecursedDirs(mountRunUnderCommand(spawn)));
return validateMounts(withResolvedSymlinks(mounts));
}

/**
Expand All @@ -244,7 +244,7 @@ private ImmutableMap<Path, Path> getMounts(
* @return an ImmutableMap of all mounts.
*/
@VisibleForTesting
static ImmutableMap<Path, Path> validateMounts(Path sandboxPath, Map<Path, Path> mounts) {
static ImmutableMap<Path, Path> validateMounts(Map<Path, Path> mounts) {
ImmutableMap.Builder<Path, Path> validatedMounts = ImmutableMap.builder();
for (Entry<Path, Path> mount : mounts.entrySet()) {
Path target = mount.getKey();
Expand All @@ -253,11 +253,6 @@ static ImmutableMap<Path, Path> validateMounts(Path sandboxPath, Map<Path, Path>
// The source must exist.
Preconditions.checkArgument(source.exists(), "%s does not exist", source.toString());

// Mounts must always mount into the sandbox, otherwise they might corrupt the host system.
Preconditions.checkArgument(
target.startsWith(sandboxPath),
String.format("(%s -> %s) does not mount into sandbox", source, target));

validatedMounts.put(target, source);
}
return validatedMounts.build();
Expand All @@ -270,7 +265,7 @@ static ImmutableMap<Path, Path> validateMounts(Path sandboxPath, Map<Path, Path>
* @return a new mounts multimap with the added mounts.
*/
@VisibleForTesting
static MountMap<Path, Path> withResolvedSymlinks(Path sandboxPath, Map<Path, Path> mounts)
static MountMap<Path, Path> withResolvedSymlinks(Map<Path, Path> mounts)
throws IOException {
MountMap<Path, Path> fixedMounts = new MountMap<>();
for (Entry<Path, Path> mount : mounts.entrySet()) {
Expand All @@ -279,9 +274,8 @@ static MountMap<Path, Path> withResolvedSymlinks(Path sandboxPath, Map<Path, Pat
fixedMounts.put(target, source);

if (source.isSymbolicLink()) {
source = source.resolveSymbolicLinks();
target = sandboxPath.getRelative(source.asFragment().relativeTo("/"));
fixedMounts.put(target, source);
Path symlinkTarget = source.resolveSymbolicLinks();
fixedMounts.put(symlinkTarget, symlinkTarget);
}
}
return fixedMounts;
Expand Down Expand Up @@ -316,21 +310,21 @@ static MountMap<Path, Path> withRecursedDirs(Map<Path, Path> mounts) throws IOEx
* Mount a certain set of unix directories to make the usual tools and libraries available to the
* spawn that runs.
*/
private MountMap<Path, Path> mountUsualUnixDirs(Path sandboxPath) throws IOException {
private MountMap<Path, Path> mountUsualUnixDirs() throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();
FileSystem fs = blazeDirs.getFileSystem();
mounts.put(sandboxPath.getRelative("bin"), fs.getPath("/bin"));
mounts.put(sandboxPath.getRelative("etc"), fs.getPath("/etc"));
mounts.put(fs.getPath("/bin"), fs.getPath("/bin"));
mounts.put(fs.getPath("/etc"), fs.getPath("/etc"));
for (String entry : FilesystemUtils.readdir("/")) {
if (entry.startsWith("lib")) {
mounts.put(sandboxPath.getRelative(entry), fs.getRootDirectory().getRelative(entry));
Path libDir = fs.getRootDirectory().getRelative(entry);
mounts.put(libDir, libDir);
}
}
for (String entry : FilesystemUtils.readdir("/usr")) {
if (!entry.equals("local")) {
mounts.put(
sandboxPath.getRelative("usr").getRelative(entry),
fs.getPath("/usr").getRelative(entry));
Path usrDir = fs.getPath("/usr").getRelative(entry);
mounts.put(usrDir, usrDir);
}
}
return mounts;
Expand All @@ -339,74 +333,69 @@ private MountMap<Path, Path> mountUsualUnixDirs(Path sandboxPath) throws IOExcep
/**
* Mount the embedded tools.
*/
private MountMap<Path, Path> setupBlazeUtils(Path sandboxPath) throws IOException {
private MountMap<Path, Path> setupBlazeUtils() throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();
Path source = blazeDirs.getEmbeddedBinariesRoot().getRelative("build-runfiles");
Path target = sandboxPath.getRelative(source.asFragment().relativeTo("/"));
mounts.put(target, source);
Path mount = blazeDirs.getEmbeddedBinariesRoot().getRelative("build-runfiles");
mounts.put(mount, mount);
return mounts;
}

/**
* Mount all runfiles that the spawn needs as specified in its runfiles manifests.
*/
private MountMap<Path, Path> mountRunfilesFromManifests(Spawn spawn, Path sandboxPath)
private MountMap<Path, Path> mountRunfilesFromManifests(Spawn spawn)
throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();
for (Entry<PathFragment, Artifact> manifest : spawn.getRunfilesManifests().entrySet()) {
String manifestFilePath = manifest.getValue().getPath().getPathString();
Preconditions.checkState(!manifest.getKey().isAbsolute());
Path targetDirectory = execRoot.getRelative(manifest.getKey());

mounts.putAll(parseManifestFile(sandboxPath, targetDirectory, new File(manifestFilePath)));
mounts.putAll(parseManifestFile(targetDirectory, new File(manifestFilePath)));
}
return mounts;
}

static MountMap<Path, Path> parseManifestFile(
Path sandboxPath, Path targetDirectory, File manifestFile) throws IOException {
Path targetDirectory, File manifestFile) throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();
for (String line : Files.readLines(manifestFile, Charset.defaultCharset())) {
String[] fields = line.trim().split(" ");
Path source;
Path targetPath = targetDirectory.getRelative(fields[0]);
Path targetInSandbox = sandboxPath.getRelative(targetPath.asFragment().relativeTo("/"));
switch (fields.length) {
case 1:
source = sandboxPath.getFileSystem().getPath("/dev/null");
source = targetDirectory.getFileSystem().getPath("/dev/null");
break;
case 2:
source = sandboxPath.getFileSystem().getPath(fields[1]);
source = targetDirectory.getFileSystem().getPath(fields[1]);
break;
default:
throw new IllegalStateException("'" + line + "' splits into more than 2 parts");
}
mounts.put(targetInSandbox, source);
mounts.put(targetPath, source);
}
return mounts;
}

/**
* Mount all runfiles that the spawn needs as specified via its runfiles suppliers.
*/
private MountMap<Path, Path> mountRunfilesFromSuppliers(Spawn spawn, Path sandboxPath)
private MountMap<Path, Path> mountRunfilesFromSuppliers(Spawn spawn)
throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();
FileSystem fs = blazeDirs.getFileSystem();
Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings =
spawn.getRunfilesSupplier().getMappings();
for (Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings :
rootsAndMappings.entrySet()) {
PathFragment root = rootAndMappings.getKey();
if (root.isAbsolute()) {
root = root.relativeTo("/");
}
Path root = fs.getRootDirectory().getRelative(rootAndMappings.getKey());
for (Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) {
Artifact sourceArtifact = mapping.getValue();
Path source = (sourceArtifact != null) ? sourceArtifact.getPath() : fs.getPath("/dev/null");

Preconditions.checkArgument(!mapping.getKey().isAbsolute());
Path target = sandboxPath.getRelative(root.getRelative(mapping.getKey()));
Path target = root.getRelative(mapping.getKey());
mounts.put(target, source);
}
}
Expand All @@ -417,7 +406,7 @@ private MountMap<Path, Path> mountRunfilesFromSuppliers(Spawn spawn, Path sandbo
* Mount all inputs of the spawn.
*/
private MountMap<Path, Path> mountInputs(
Spawn spawn, Path sandboxPath, ActionExecutionContext actionExecutionContext)
Spawn spawn, ActionExecutionContext actionExecutionContext)
throws IOException {
MountMap<Path, Path> mounts = new MountMap<>();

Expand All @@ -436,9 +425,8 @@ private MountMap<Path, Path> mountInputs(
if (input.getExecPathString().contains("internal/_middlemen/")) {
continue;
}
Path source = execRoot.getRelative(input.getExecPathString());
Path target = sandboxPath.getRelative(source.asFragment().relativeTo("/"));
mounts.put(target, source);
Path mount = execRoot.getRelative(input.getExecPathString());
mounts.put(mount, mount);
}
return mounts;
}
Expand All @@ -451,27 +439,26 @@ private MountMap<Path, Path> mountInputs(
* <p>If --run_under= refers to a label, it is automatically provided in the spawn's input files,
* so mountInputs() will catch that case.
*/
private MountMap<Path, Path> mountRunUnderCommand(Spawn spawn, Path sandboxPath) {
private MountMap<Path, Path> mountRunUnderCommand(Spawn spawn) {
MountMap<Path, Path> mounts = new MountMap<>();

if (spawn.getResourceOwner() instanceof TestRunnerAction) {
TestRunnerAction testRunnerAction = ((TestRunnerAction) spawn.getResourceOwner());
RunUnder runUnder = testRunnerAction.getExecutionSettings().getRunUnder();
if (runUnder != null && runUnder.getCommand() != null) {
PathFragment sourceFragment = new PathFragment(runUnder.getCommand());
Path source;
Path mount;
if (sourceFragment.isAbsolute()) {
source = blazeDirs.getFileSystem().getPath(sourceFragment);
mount = blazeDirs.getFileSystem().getPath(sourceFragment);
} else if (blazeDirs.getExecRoot().getRelative(sourceFragment).exists()) {
source = blazeDirs.getExecRoot().getRelative(sourceFragment);
mount = blazeDirs.getExecRoot().getRelative(sourceFragment);
} else {
List<Path> searchPath =
SearchPath.parse(blazeDirs.getFileSystem(), clientEnv.get("PATH"));
source = SearchPath.which(searchPath, runUnder.getCommand());
mount = SearchPath.which(searchPath, runUnder.getCommand());
}
if (source != null) {
Path target = sandboxPath.getRelative(source.asFragment().relativeTo("/"));
mounts.put(target, source);
if (mount != null) {
mounts.put(mount, mount);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,12 @@ public void run(
for (ImmutableMap.Entry<Path, Path> mount : mounts.entrySet()) {
args.add("-M");
args.add(mount.getValue().getPathString());
args.add("-m");
args.add(mount.getKey().getPathString());

// The file is mounted in a custom location inside the sandbox.
if (!mount.getValue().equals(mount.getKey())) {
args.add("-m");
args.add(mount.getKey().getPathString());
}
}

args.add("--");
Expand Down
62 changes: 43 additions & 19 deletions src/main/tools/namespace-sandbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,14 @@ static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) {
break;
case 'S':
if (opt->sandbox_root == NULL) {
opt->sandbox_root = optarg;
char *sandbox_root = strdup(optarg);

// Make sure that the sandbox_root path has no trailing slash.
if (sandbox_root[strlen(sandbox_root) - 1] == '/') {
sandbox_root[strlen(sandbox_root) - 1] = 0;
}

opt->sandbox_root = sandbox_root;
} else {
Usage(argc, argv,
"Multiple sandbox roots (-S) specified, expected one.");
Expand Down Expand Up @@ -183,26 +190,23 @@ static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) {
}
break;
case 'M':
if (optarg[0] != '/') {
Usage(argc, argv, "The -M option must be used with absolute paths only.");
}
// The last -M flag wasn't followed by an -m flag, so assume that the source should be mounted in the sandbox in the same path as outside.
if (opt->mount_sources[opt->num_mounts] != NULL) {
Usage(argc, argv, "The -M option must be followed by an -m option.");
opt->mount_targets[opt->num_mounts] = opt->mount_sources[opt->num_mounts];
opt->num_mounts++;
}
opt->mount_sources[opt->num_mounts] = optarg;
break;
case 'm':
if (optarg[0] != '/') {
Usage(argc, argv, "The -m option must be used with absolute paths only.");
}
if (opt->mount_sources[opt->num_mounts] == NULL) {
Usage(argc, argv, "The -m option must be preceded by an -M option.");
}
if (opt->sandbox_root == NULL) {
Usage(argc, argv,
"The sandbox root must be set via the -S option before "
"specifying an"
" -m option.");
}
if (strstr(optarg, opt->sandbox_root) != optarg) {
Usage(argc, argv,
"A path passed to the -m option must start with the sandbox "
"root.");
}
opt->mount_targets[opt->num_mounts++] = optarg;
break;
case 'D':
Expand Down Expand Up @@ -237,9 +241,11 @@ static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) {
Usage(argc, argv, "Sandbox root (-S) must be specified");
}

// The last -M flag wasn't followed by an -m flag, assume that the source should be mounted in the sandbox in the same path as outside.
if (opt->mount_sources[opt->num_mounts] != NULL &&
opt->mount_sources[opt->num_mounts] == NULL) {
Usage(argc, argv, "An -m option is missing.");
opt->mount_targets[opt->num_mounts] == NULL) {
opt->mount_targets[opt->num_mounts] = opt->mount_sources[opt->num_mounts];
opt->num_mounts++;
}

opt->args = argv + optind;
Expand Down Expand Up @@ -381,10 +387,28 @@ static void SetupDirectories(struct Options *opt) {
struct stat sb;
stat(opt->mount_sources[i], &sb);

PRINT_DEBUG("mount -o rbind,ro %s %s\n", opt->mount_sources[i],
opt->mount_targets[i]);
CHECK_CALL(CreateTarget(opt->mount_targets[i], S_ISDIR(sb.st_mode)));
CHECK_CALL(mount(opt->mount_sources[i], opt->mount_targets[i], NULL,
if (global_debug) {
if (strcmp(opt->mount_sources[i], opt->mount_targets[i]) == 0) {
// The file is mounted to the same path inside the sandbox, as outside (e.g. /home/user -> <sandbox>/home/user), so we'll just show a simplified version of the mount command.
PRINT_DEBUG("mount: %s\n", opt->mount_sources[i]);
} else {
// The file is mounted to a custom location inside the sandbox.
// Create a user-friendly string for the sandboxed path and show it.
char *user_friendly_mount_target =
malloc(strlen("<sandbox>") + strlen(opt->mount_targets[i]) + 1);
strcpy(user_friendly_mount_target, "<sandbox>");
strcat(user_friendly_mount_target, opt->mount_targets[i]);
PRINT_DEBUG("mount: %s -> %s\n", opt->mount_sources[i],
user_friendly_mount_target);
free(user_friendly_mount_target);
}
}

char *full_sandbox_path = malloc(strlen(opt->sandbox_root) + strlen(opt->mount_targets[i]) + 1);
strcpy(full_sandbox_path, opt->sandbox_root);
strcat(full_sandbox_path, opt->mount_targets[i]);
CHECK_CALL(CreateTarget(full_sandbox_path, S_ISDIR(sb.st_mode)));
CHECK_CALL(mount(opt->mount_sources[i], full_sandbox_path, NULL,
MS_REC | MS_BIND | MS_RDONLY, NULL));
}
}
Expand Down
Loading

0 comments on commit 17ef915

Please sign in to comment.