diff options
author | Yifan Hong <elsk@google.com> | 2024-05-07 22:12:55 -0700 |
---|---|---|
committer | Yifan Hong <elsk@google.com> | 2024-05-13 16:56:21 -0700 |
commit | cb67cfc5870c2d0c2b5f13313c4717a7d6665148 (patch) | |
tree | 2b0e502f33a7cd4b69f1b228b4b96a458abf02fd | |
parent | 5dae65a58d7a523c89adb7a92caf4a109e2bfc55 (diff) | |
download | build-cb67cfc5870c2d0c2b5f13313c4717a7d6665148.tar.gz |
kleaf: Wrap hermetic tools with C++ binary.
Instead of symlinking to a script that uses
${0%/*}, use a native binary that executes
$(readlink /proc/self/exe) $@ <hermetic_tools.extra_args>
This way, the tool can be executed anywhere
even when $0 is clobbered, e.g. when another
symlink is created against the tool. The only
remaining requirement is that this other symlink
must have the same tool_name.
In particular, this allows using
PATH={hermetic_tools} to run integration tests.
Bug: 338263410
Change-Id: If0ec635ef0c0bc7cfa4cf3758a5d6a8eedd8f556
-rw-r--r-- | BUILD.bazel | 22 | ||||
-rwxr-xr-x | build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync | 3 | ||||
-rwxr-xr-x | build-tools/kleaf_internal_do_not_use_path/linux-x86/tar | 8 | ||||
-rw-r--r-- | kleaf/hermetic_tools.bzl | 157 | ||||
-rw-r--r-- | kleaf/impl/BUILD.bazel | 6 | ||||
-rw-r--r-- | kleaf/impl/arg_wrapper.cpp | 110 |
6 files changed, 210 insertions, 96 deletions
diff --git a/BUILD.bazel b/BUILD.bazel index 958984da..67431f58 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -155,6 +155,7 @@ _TOYS = [ "sort", "stat", "tail", + "tar", "tee", "test", "timeout", @@ -172,12 +173,20 @@ _TOYS = [ "whoami", "xargs", "xxd", - # Real tar binary that shouldn't be used directly - "kleaf_internal_do_not_use/tar", ] hermetic_tools( name = "hermetic-tools", + extra_args = { + "rsync": ["--no-group"], + "tar": [ + "--mtime=@0", + "--owner=0", + "--group=0", + "--numeric-owner", + "--sort=name", + ], + }, symlinks = select({ "//build/kernel/kleaf:debug_cache_dir_conflict_needs_flock": { ":toybox_flock": "flock", @@ -211,14 +220,7 @@ hermetic_tools( "@kleaf_host_tools//:bash": "bash", "@kleaf_host_tools//:sh": "sh", "@kleaf_host_tools//:perl": "perl", - - # Real rsync that shouldn't be used directly - "@kleaf_host_tools//:rsync": "kleaf_internal_do_not_use/rsync", - # Embed additional args that calls kleaf_internal_do_not_use/rsync - "build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync": "rsync", - - # Embeds additional args that calls kleaf_internal_do_not_use/tar - "build-tools/kleaf_internal_do_not_use_path/linux-x86/tar": "tar", + "@kleaf_host_tools//:rsync": "rsync", # Additional symlinks other than in //prebuilts/build-tools "//prebuilts/kernel-build-tools:linux-x86/bin/depmod": ":".join([ diff --git a/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync b/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync deleted file mode 100755 index 5abcde96..00000000 --- a/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -${0%/*}/kleaf_internal_do_not_use/rsync "$@" --no-group diff --git a/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar b/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar deleted file mode 100755 index 46c7d65a..00000000 --- a/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -${0%/*}/kleaf_internal_do_not_use/tar "$@" \ - --mtime=@0 \ - --owner=0 \ - --group=0 \ - --numeric-owner \ - --sort=name diff --git a/kleaf/hermetic_tools.bzl b/kleaf/hermetic_tools.bzl index 13aa70d4..b5093a2a 100644 --- a/kleaf/hermetic_tools.bzl +++ b/kleaf/hermetic_tools.bzl @@ -57,41 +57,66 @@ def _get_single_file(ctx, target): )) return files_list[0] -def _handle_hermetic_symlinks(ctx): - hermetic_symlinks_dict = {} - for actual_target, tool_names in ctx.attr.symlinks.items(): - for tool_name in tool_names.split(":"): - out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, tool_name)) - target_file = _get_single_file(ctx, actual_target) - ctx.actions.symlink( - output = out, - target_file = target_file, - is_executable = True, - progress_message = "Creating symlinks to in-tree tools {}/{}".format( - ctx.label, - tool_name, - ), - ) - hermetic_symlinks_dict[tool_name] = out - - return hermetic_symlinks_dict +def _handle_tool(ctx, tool_name, actual_target): + out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, tool_name)) + target_file = _get_single_file(ctx, actual_target) + + if tool_name not in ctx.attr.extra_args: + ctx.actions.symlink( + output = out, + target_file = target_file, + is_executable = True, + progress_message = "Creating symlink to in-tree tool {}/{}".format( + ctx.label, + tool_name, + ), + ) + return [out] + + internal_symlink = ctx.actions.declare_file("{}/kleaf_internal_do_not_use/{}".format(ctx.attr.name, tool_name)) + ctx.actions.symlink( + output = internal_symlink, + target_file = target_file, + is_executable = True, + progress_message = "Creating internal symlink to in-tree tool {}/{}".format( + ctx.label, + tool_name, + ), + ) -def _hermetic_tools_impl(ctx): - deps = [] + ctx.files.deps + ctx.actions.symlink( + output = out, + target_file = ctx.executable._arg_wrapper, + is_executable = True, + progress_message = "Creating symlink to in-tree tool {}/{}".format( + ctx.label, + tool_name, + ), + ) + extra_args = "\n".join(ctx.attr.extra_args[tool_name]) + extra_args_file = ctx.actions.declare_file("{}/kleaf_internal_do_not_use/{}_args.txt".format(ctx.attr.name, tool_name)) + ctx.actions.write(extra_args_file, extra_args) + return [out, internal_symlink, extra_args_file] + +def _handle_hermetic_symlinks(ctx, symlinks_attr): all_outputs = [] + for actual_target, tool_names in symlinks_attr.items(): + for tool_name in tool_names.split(":"): + tool_outs = _handle_tool(ctx, tool_name, actual_target) + all_outputs.extend(tool_outs) - hermetic_outs_dict = _handle_hermetic_symlinks(ctx) + return all_outputs - hermetic_outs = hermetic_outs_dict.values() - all_outputs += hermetic_outs - deps += hermetic_outs +def _hermetic_tools_impl(ctx): + all_outputs = _handle_hermetic_symlinks(ctx, ctx.attr.symlinks) if ctx.attr._disable_symlink_source[BuildSettingInfo].value: transitive_deps = [] else: transitive_deps = [target.files for target in ctx.attr.symlinks] - deps_depset = depset(deps, transitive = transitive_deps) + transitive_deps += [target.files for target in ctx.attr.deps] + transitive_deps.append(ctx.attr._arg_wrapper.files) fail_hard = """ # error on failures @@ -125,76 +150,58 @@ def _hermetic_tools_impl(ctx): """.format(path = hermetic_base_short) hermetic_toolchain_info = _HermeticToolchainInfo( - deps = deps_depset, + deps = depset(all_outputs, transitive = transitive_deps), setup = setup, run_setup = run_setup, run_additional_setup = run_additional_setup, ) - default_info_files = [ - file - for file in all_outputs - if "kleaf_internal_do_not_use" not in file.path - ] - infos = [ - DefaultInfo(files = depset(default_info_files)), + DefaultInfo(files = depset(all_outputs)), platform_common.ToolchainInfo( hermetic_toolchain_info = hermetic_toolchain_info, ), - OutputGroupInfo( - **{file.basename: depset([file]) for file in all_outputs} - ), + OutputGroupInfo(**{ + file.basename: depset([file]) + for file in all_outputs + if "kleaf_internal_do_not_use" not in file.path + }), ] return infos -_hermetic_tools = rule( +hermetic_tools = rule( implementation = _hermetic_tools_impl, - doc = "", + doc = "Provide tools for a hermetic build.", attrs = { - "deps": attr.label_list(doc = "Additional_deps", allow_files = True), + "deps": attr.label_list( + doc = "additional dependencies. These aren't added to the `PATH`.", + allow_files = True, + ), "symlinks": attr.label_keyed_string_dict( - doc = "symlinks to labels", allow_files = True, + doc = """A dictionary, where keys are labels to an executable, and + values are names to the tool, separated with `:`. e.g. + + ``` + {"//label/to:toybox": "cp:realpath"} + ``` + """, + ), + "extra_args": attr.string_list_dict( + doc = """Keys are names to the tool (see `symlinks`). Values are + extra arguments added to the tool at the end.""", ), "_disable_symlink_source": attr.label( default = "//build/kernel/kleaf:incompatible_disable_hermetic_tools_symlink_source", ), + "_arg_wrapper": attr.label( + default = "//build/kernel/kleaf/impl:arg_wrapper", + executable = True, + # Prevent inadvertent exec transition that messes up the + # path calculation. Exec transition needs to be done on the whole + # hermetic_tools target. + cfg = "target", + ), }, ) - -def hermetic_tools( - name, - deps = None, - symlinks = None, - **kwargs): - """Provide tools for a hermetic build. - - Args: - name: Name of the target. - symlinks: A dictionary, where keys are labels to an executable, and - values are names to the tool, separated with `:`. e.g. - - ``` - {"//label/to:toybox": "cp:realpath"} - ``` - deps: additional dependencies. These aren't added to the `PATH`. - **kwargs: Additional attributes to the internal rule, e.g. - [`visibility`](https://docs.bazel.build/versions/main/visibility.html). - See complete list - [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common - """ - - if symlinks == None: - symlinks = {} - - if deps == None: - deps = [] - - _hermetic_tools( - name = name, - deps = deps, - symlinks = symlinks, - **kwargs - ) diff --git a/kleaf/impl/BUILD.bazel b/kleaf/impl/BUILD.bazel index 51964f90..56e26176 100644 --- a/kleaf/impl/BUILD.bazel +++ b/kleaf/impl/BUILD.bazel @@ -485,3 +485,9 @@ python_runtime_files( name = "python_runtime_files", visibility = ["//build/kernel:__pkg__"], ) + +cc_binary( + name = "arg_wrapper", + srcs = ["arg_wrapper.cpp"], + visibility = ["//build/kernel:__pkg__"], +) diff --git a/kleaf/impl/arg_wrapper.cpp b/kleaf/impl/arg_wrapper.cpp new file mode 100644 index 00000000..ee9c95ce --- /dev/null +++ b/kleaf/impl/arg_wrapper.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// 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. + +// Helper wrapper for hermetic tools to wrap arguments. +// +// This roughly equivalent to: +// 1. readlink /proc/self/exe, then dirname multiple times to determine the path +// internal_dir = +// <execroot>/build/kernel/hermetic-tools/kleaf_internal_do_not_use +// 2. tool_name = basename($0) +// 3. call <internal_dir>/<tool_name> $@ \\ +// $(cat <internal_dir>/<tool_name>_args.txt) +// +// This is a C++ binary instead of a shell / Python script so that +// /proc/self/exe is a proper anchor to find internal_dir. If this were a +// script, /proc/self/exe would be the path to the interpreter. +// This also avoids using any hermetic tools in order to determine the path to +// them. + +#include <linux/limits.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include <filesystem> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +namespace { + +// <execroot>/build/kernel/hermetic-tools/kleaf_internal_do_not_use +std::filesystem::path get_kleaf_internal_dir() { + std::error_code ec; + auto my_path = std::filesystem::read_symlink("/proc/self/exe", ec); + if (ec.value() != 0) { + std::cerr << "ERROR: read_symlink /proc/self/exe: " << ec.message() + << std::endl; + exit(EX_SOFTWARE); + } + return my_path.parent_path().parent_path().parent_path() / "hermetic-tools" / + "kleaf_internal_do_not_use"; +} + +// Loads <tool_name>_args.txt from hermetic_tools.extra_args +std::vector<std::string> load_arg_file(const std::filesystem::path& path) { + std::ifstream ifs(path); + if (!ifs) { + int saved_errno = errno; + std::cerr << "Unable to open " << path << ": " << strerror(saved_errno) + << std::endl; + exit(EX_SOFTWARE); + } + std::vector<std::string> args; + for (std::string arg; std::getline(ifs, arg);) { + args.push_back(arg); + } + return args; +} + +} // namespace + +int main(int argc, char* argv[]) { + auto internal_dir = get_kleaf_internal_dir(); + + if (argc < 1) { + std::cerr << "ERROR: argc == " << argc << " < 1" << std::endl; + return EX_SOFTWARE; + } + std::string tool_name(std::filesystem::path(argv[0]).filename()); + + // The actual executable we are going to call. Cast to string to use + // in new_argv. + std::string real_executable = internal_dir / tool_name; + + std::vector<char*> new_argv; + new_argv.push_back(real_executable.data()); + + for (int i = 1; i < argc; i++) { + new_argv.push_back(argv[i]); + } + + auto extra_args_file = internal_dir / (tool_name + "_args.txt"); + auto preset_args = load_arg_file(extra_args_file); + for (auto& preset_arg : preset_args) { + new_argv.push_back(preset_arg.data()); + } + new_argv.push_back(nullptr); + + if (-1 != execv(real_executable.c_str(), new_argv.data())) { + int saved_errno = errno; + std::cerr << "ERROR: execv: " << real_executable << ": " + << strerror(saved_errno) << std::endl; + return EX_SOFTWARE; + } + std::cerr << "ERROR: execv returns!" << std::endl; + return EX_SOFTWARE; +} |