diff options
Diffstat (limited to 'interceptor')
l--------- | interceptor/.clang-format | 1 | ||||
-rw-r--r-- | interceptor/Android.bp | 58 | ||||
-rw-r--r-- | interceptor/CMakeLists.txt | 54 | ||||
-rw-r--r-- | interceptor/OWNERS | 2 | ||||
-rw-r--r-- | interceptor/analysis.cc | 221 | ||||
-rw-r--r-- | interceptor/interceptor.cc | 344 | ||||
-rw-r--r-- | interceptor/interceptor.h | 47 | ||||
-rw-r--r-- | interceptor/log.proto | 55 | ||||
-rw-r--r-- | interceptor/main.cc | 162 |
9 files changed, 0 insertions, 944 deletions
diff --git a/interceptor/.clang-format b/interceptor/.clang-format deleted file mode 120000 index 5f196c3..0000000 --- a/interceptor/.clang-format +++ /dev/null @@ -1 +0,0 @@ -../../../build/soong/scripts/system-clang-format-2
\ No newline at end of file diff --git a/interceptor/Android.bp b/interceptor/Android.bp deleted file mode 100644 index c538546..0000000 --- a/interceptor/Android.bp +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (C) 2021 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. - -cc_defaults { - name: "interceptor_defaults", - static_libs: [ - "libc++fs", - "libinterceptor_log", - "libprotobuf-cpp-full", - ], -} - -cc_library_host_shared { - name: "libinterceptor", - srcs: ["interceptor.cc"], - defaults: ["interceptor_defaults"], - static_libs: [ - "libbase", - ], -} - -cc_library_host_static { - name: "libinterceptor_log", - srcs: ["log.proto"], - proto: { - type: "full", - static: true, - export_proto_headers: true, - canonical_path_from_root: false, - }, -} - -cc_binary_host { - name: "interceptor", - srcs: ["main.cc"], - defaults: ["interceptor_defaults"], -} - -cc_binary_host { - name: "interceptor_analysis", - srcs: ["analysis.cc"], - defaults: ["interceptor_defaults"], - - // unused parameters in protobuf/stubs/bytestream.h - cflags: ["-Wno-error=unused-parameter"], -} diff --git a/interceptor/CMakeLists.txt b/interceptor/CMakeLists.txt deleted file mode 100644 index 13eefb0..0000000 --- a/interceptor/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -# -# Copyright (C) 2021 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. - -cmake_minimum_required(VERSION 3.21) - -Project(interceptor) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED True) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO - -set(EXECUTABLE_OUTPUT_PATH bin) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY lib64) - -find_package(Protobuf REQUIRED) - -# just enough libbase from the android-base -add_library(android-base STATIC ../../../system/libbase/strings.cpp) -target_include_directories(android-base SYSTEM PUBLIC ../../../system/libbase/include/ ) - -# interceptor_log - the protobuf library -include_directories(${Protobuf_INCLUDE_DIRS}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS log.proto) -add_library(interceptor_log STATIC ${PROTO_SRCS} ${PROTO_HDRS}) -target_link_libraries(interceptor_log ${Protobuf_LIBRARIES}) - -set(common_libraries interceptor_log android-base) - -# libinterceptor.so -add_library(libinterceptor SHARED interceptor.cc) -target_link_libraries(libinterceptor ${common_libraries}) -set_target_properties(libinterceptor PROPERTIES LIBRARY_OUTPUT_NAME interceptor) - -# interceptor -add_executable(interceptor main.cc) -target_link_libraries(interceptor ${common_libraries}) - -# interceptor_analysis -add_executable(interceptor_analysis analysis.cc) -target_link_libraries(interceptor_analysis ${common_libraries}) diff --git a/interceptor/OWNERS b/interceptor/OWNERS deleted file mode 100644 index 91a69d0..0000000 --- a/interceptor/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -maennich@google.com -elsk@google.com diff --git a/interceptor/analysis.cc b/interceptor/analysis.cc deleted file mode 100644 index 03292af..0000000 --- a/interceptor/analysis.cc +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -#include <getopt.h> -#include <google/protobuf/text_format.h> -#include <google/protobuf/util/json_util.h> -#include <sysexits.h> -#include <cstdlib> -#include <cstring> -#include <filesystem> -#include <fstream> -#include <iostream> -#include <iterator> -#include <unordered_set> - -#include "log.pb.h" - -namespace fs = std::filesystem; - -enum class OutputFormat { TEXT, COMPDB }; - -struct Options { - fs::path command_log; - OutputFormat output_format = OutputFormat::TEXT; - fs::path output; -}; - -static Options parse_arguments(int argc, char* argv[]) { - Options result; - - const static option opts[] = { - {"command-log", required_argument, nullptr, 'l'}, - {"output-type", required_argument, nullptr, 't'}, - {"output", required_argument, nullptr, 'o'}, - {nullptr, 0, nullptr, 0}, - }; - const auto usage = [&]() { - std::cerr << "usage: " << argv[0] << '\n' - << " -l|--command-log filename\n" - << " -o|--output filename\n" - << " [-t|--output-type (text|compdb)]\n"; - exit(EX_USAGE); - }; - while (true) { - int ix; - int c = getopt_long(argc, argv, "-l:t:o:", opts, &ix); - if (c == -1) { - break; - } - switch (c) { - case 'l': - result.command_log = fs::absolute(optarg); - break; - case 't': - if (strcmp(optarg, "text") == 0) { - result.output_format = OutputFormat::TEXT; - } - if (strcmp(optarg, "compdb") == 0) { - result.output_format = OutputFormat::COMPDB; - } else { - usage(); - } - break; - case 'o': - result.output = fs::absolute(optarg); - break; - default: - usage(); - } - } - - if (result.command_log.empty() || result.output.empty()) { - usage(); - } - - if (!fs::exists(result.command_log)) { - std::cerr << "No such file: " << result.command_log << "\n"; - } - - return result; -} - -interceptor::Log read_log(const fs::path& log_file) { - interceptor::Log result; - std::ifstream input(log_file); - if (!input) { - std::cerr << "Could not open input file for reading.\n"; - exit(EX_NOINPUT); - } - result.ParseFromIstream(&input); - return result; -} - -void text_to_file(const interceptor::Log& log, const fs::path& output) { - std::string content; - google::protobuf::TextFormat::PrintToString(log, &content); - std::ofstream os(output); - if (!os) { - std::cerr << "Could not open output file for writing.\n"; - exit(EX_CANTCREAT); - } - os << content; - if (!os.flush()) { - std::cerr << "Failed to write to output file.\n"; - exit(EX_CANTCREAT); - } -} - -void compdb_to_file(const interceptor::Log& log, const fs::path& output) { - static const std::unordered_set<std::string_view> kCompileExtensions = { - ".c", ".cc", ".cpp", ".cxx", ".S", - }; - static const std::unordered_set<std::string_view> kCompilers = { - "clang", - "clang++", - "gcc", - "g++", - }; - - interceptor::CompilationDatabase compdb; - - for (const auto& command : log.commands()) { - if (command.arguments().empty()) { - continue; - } - - // skip anything that is not a compiler invocation - if (!kCompilers.count(fs::path(command.arguments(0)).filename().native())) { - continue; - } - - // determine if we have a uniquely identifyable output - const std::string single_output = [&]() { - std::vector<std::string> outputs; - for (const auto& output : command.outputs()) { - // skip .d files. They are conventionally used for make dependency files - if (fs::path(output).extension() != ".d") { - outputs.push_back(output); - } - } - return (outputs.size() == 1) ? outputs[0] : ""; - }(); - - // skip preprocessor invocations - if (std::find(command.arguments().cbegin(), command.arguments().cend(), "-E") != - command.arguments().cend()) { - continue; - } - - // now iterate over all inputs, emitting an entry for each source file - for (const auto& input : command.inputs()) { - // skip anything that does not look like a source file (object files, - // force included headers, etc.) - if (!kCompileExtensions.count(fs::path(input).extension().native())) { - continue; - } - - // ok, now we have a new command - auto& compile_command = *compdb.add_commands(); - - compile_command.set_directory(fs::path(log.root_directory()) / command.current_directory()); - compile_command.set_file(input); - if (!single_output.empty()) { - compile_command.set_output(single_output); - } - *compile_command.mutable_arguments() = {command.arguments().cbegin(), - command.arguments().cend()}; - } - } - - std::ofstream out(output); - - if (!compdb.commands_size()) { - out << "[]\n"; - return; - } - - std::string out_str; - auto options = google::protobuf::util::JsonPrintOptions{}; - options.add_whitespace = true; - google::protobuf::util::MessageToJsonString(compdb, &out_str, options); - - // this would emit {"command":[yadayada]}, but we want only [yadayada] - // the additional characters come from options.add_whitespace - // - // TODO: make this better, but as of now there is not much we can do as - // util::MessageToJsonString() takes a message and that is always represented - // as a dictionary, while the top level structure of compile_command.json is - // an array. So, we have to chop of the leading and trailing characters to - // find the contained array. - const auto left_offset = out_str.find('['); - const auto length = out_str.rfind(']') - left_offset + 1; - out << std::string_view(out_str).substr(left_offset, length); -} - -int main(int argc, char* argv[]) { - const auto options = parse_arguments(argc, argv); - const auto log = read_log(options.command_log); - - switch (options.output_format) { - case OutputFormat::TEXT: - text_to_file(log, options.output); - break; - case OutputFormat::COMPDB: - compdb_to_file(log, options.output); - break; - } -} diff --git a/interceptor/interceptor.cc b/interceptor/interceptor.cc deleted file mode 100644 index 8ce5cb7..0000000 --- a/interceptor/interceptor.cc +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -#include "interceptor.h" - -#include <dlfcn.h> -#include <unistd.h> - -#include <algorithm> -#include <array> -#include <filesystem> -#include <fstream> -#include <initializer_list> -#include <iomanip> -#include <iostream> -#include <iterator> -#include <regex> -#include <sstream> -#include <string> -#include <string_view> -#include <type_traits> -#include <utility> - -#include <android-base/strings.h> -#include <google/protobuf/util/delimited_message_util.h> - -namespace fs = std::filesystem; - -// UTILITY function declarations - -// process applicable calls (i.e. programs that we might be able to handle) -static void process_command(const char* filename, char* const argv[], char* const envp[]); - -// log command if logging is enabled -static void log(const interceptor::Command&); - -// execute potentially modified command -static void execute(const interceptor::Command&, char* const envp[]); - -// OVERLOADS for LD_PRELOAD USE - -// Intercept execve calls, for that capture the original execve call -static auto const old_execve = reinterpret_cast<decltype(execve)*>(dlsym(RTLD_NEXT, "execve")); - -extern "C" { -int execve(const char* filename, char* const argv[], char* const envp[]) { - // pass on to process_command(), if unhandled, fall back to the original - // execve - process_command(filename, argv, envp); - return old_execve(filename, argv, envp); -} -} // extern "C" - -// LIBRARY IMPLEMENTATION - -namespace interceptor { - -static Command instantiate_command(const char* program, char* const argv[], char* const envp[]) { - Command result; - result.set_program(program); - result.set_current_directory(fs::current_path()); - - for (auto current_argument = argv; *current_argument; ++current_argument) { - result.add_arguments(*current_argument); - } - - for (auto current_env_var = envp; *current_env_var; ++current_env_var) { - const std::string s(*current_env_var); - const auto pos = s.find('='); - if (pos == std::string::npos) { - continue; - } - - (*result.mutable_environment_variables())[s.substr(0, pos)] = s.substr(pos + 1); - } - - return result; -} - -static void make_relative(Command* command) { - // determine the ROOT_DIR - std::string root_directory; - if (auto it = command->environment_variables().find(kEnvRootDirectory); - it != command->environment_variables().cend()) { - root_directory = it->second; - if (root_directory[root_directory.size() - 1] != '/') { - root_directory += '/'; - } - } else { - return; - } - - // determine the relative path to ROOT_DIR from the current working dir - std::string relative_root = fs::relative(root_directory); - if (relative_root[relative_root.size() - 1] != '/') { - relative_root += '/'; - } - if (relative_root == "./") { - relative_root.clear(); - } - - // TODO: This is generally bad as this means we can't make anything relative. - // This happens if the out dir is outside of the root. - if (relative_root.find(root_directory) != std::string::npos) { - return; - } - - command->set_current_directory(fs::relative(command->current_directory(), root_directory)); - - // replacement functor - const auto replace_all = [&](auto& str) { - auto pos = std::string::npos; - while ((pos = str.find(root_directory)) != std::string::npos) { - str.replace(pos, root_directory.length(), relative_root); - } - }; - - // now go and replace everything - replace_all(*command->mutable_program()); - std::for_each(command->mutable_arguments()->begin(), command->mutable_arguments()->end(), replace_all); -} - -template <typename V> -static void dump_vector(std::ostream& os, const V& vec) { - bool comma = false; - for (const auto& e : vec) { - if (comma) { - os << ", "; - } - os << std::quoted(e); - comma = true; - } -} - -std::ostream& operator<<(std::ostream& os, const interceptor::Command& command) { - - os << "[("; - dump_vector(os, command.inputs()); - os << ") => ("; - dump_vector(os, command.outputs()); - os << ")] "; - - // TODO: chain output iterators instead and find a common expression - const static auto escape = [](auto in) { - in = android::base::StringReplace(in, "\t", "\\t", true); - in = android::base::StringReplace(in, "\n", "\\n", true); - return in; - }; - - std::ostringstream cmd; - cmd << command.program(); - for (auto I = std::next(command.arguments().cbegin()), E = command.arguments().cend(); I != E; ++I) { - cmd << ' ' << escape(*I); - } - - os << cmd.str(); - return os; - -} - -static AnalysisResult analyze_command(const interceptor::Command& command); - -static void analyze(Command* command) { - auto [inputs, outputs] = analyze_command(*command); - - // TODO: this sanitizing should be done during make_relative - for (auto& input : inputs) { - if (input.rfind("./", 0) == 0) { - input = input.substr(2); - } - } - for (auto& output : outputs) { - if (output.rfind("./", 0) == 0) { - output = output.substr(2); - } - } - for (const auto& input : inputs) { - if (!fs::is_regular_file(input)) { - std::cerr << "missing input: " << input << "\n" << *command << "\n"; - exit(1); - } - } - - *command->mutable_inputs() = {inputs.cbegin(), inputs.cend()}; - *command->mutable_outputs() = {outputs.cbegin(), outputs.cend()}; -} - -/// COMMAND ANALYSIS - -using Analyzer = std::function<AnalysisResult(const std::string&, const ArgVec&, const EnvMap&)>; - -static AnalysisResult analyze_compiler_linker(const std::string&, const ArgVec& arguments, - const EnvMap&) { - static constexpr std::array kSkipNextArguments{ - "-isystem", "-I", "-L", "-m", "-soname", "-z", - }; - static constexpr std::string_view kOutputOption = "-Wp,-MMD,"; - - AnalysisResult result; - bool next_is_out = false; - bool skip_next = false; - // skip arguments[0] as this is the program itself - for (auto it = arguments.cbegin() + 1; it != arguments.cend(); ++it) { - const auto& argument = *it; - if (argument == "-o") { - next_is_out = true; - continue; - } - if (next_is_out) { - result.outputs.push_back(argument); - next_is_out = false; - continue; - } - if (argument.rfind(kOutputOption, 0) == 0) { - result.outputs.push_back(argument.substr(kOutputOption.size())); - } - if (skip_next) { - skip_next = false; - continue; - } - if (std::find(kSkipNextArguments.cbegin(), kSkipNextArguments.cend(), argument) != - kSkipNextArguments.cend()) { - skip_next = true; - } - // ignore test compilations - if (argument == "/dev/null" || argument == "-") { - return {}; - } - if (argument[0] == '-') { // ignore flags - continue; - } - result.inputs.push_back(argument); - } - - return result; -} - -static AnalysisResult analyze_archiver(const std::string&, const ArgVec& arguments, const EnvMap&) { - AnalysisResult result; - - if (arguments.size() < 3) { - return result; - } - // skip arguments[0] as this is the program itself - // skip arguments[1] are the archiver flags - // arguments[2] is the output - result.outputs.push_back(arguments[2]); - // arguments[3:] are the inputs - result.inputs.insert(result.inputs.cend(), arguments.cbegin() + 3, arguments.cend()); - return result; -} - -static const std::initializer_list<std::pair<std::regex, Analyzer>> analyzers{ - { - std::regex("^(.*/)?(clang|clang\\+\\+|gcc|g\\+\\+|ld(\\.lld)?|llvm-strip)$"), - analyze_compiler_linker, - }, - { - std::regex("^(.*/)?(llvm-)?ar$"), - analyze_archiver, - }, -}; - -static AnalysisResult analyze_command(const Command& command) { - for (const auto& [regex, analyzer] : analyzers) { - if (std::regex_match(command.arguments()[0], regex)) { - return analyzer(command.program(), command.arguments(), command.environment_variables()); - } - } - return {}; -} - -} // namespace interceptor - -/// UTILITY FUNCTIONS - -static void process_command(const char* filename, char* const argv[], char* const envp[]) { - // First, try to find out whether we at all can handle this command. If not, - // simply return and fall back to the original handler. - - if (!fs::is_regular_file(filename)) { - return; - } - - // Ok, we can handle that one, let's transform it. - - auto command = interceptor::instantiate_command(filename, argv, envp); - - // rewrite all command line arguments (including the program itself) to use - // paths relative to ROOT_DIR. This is essential for reproducible builds and - // furthermore necessary to produce cache hits in RBE. - make_relative(&command); - - analyze(&command); - - log(command); - - // pass down the transformed command to execve - execute(command, envp); -} - -static void log(const interceptor::Command& command) { - const auto& env = command.environment_variables(); - - if (const auto env_it = env.find(kEnvCommandLog); env_it != env.cend()) { - std::ofstream file; - file.open(std::string(env_it->second), - std::ofstream::out | std::ofstream::app | std::ofstream::binary); - interceptor::Message message; - *message.mutable_command() = command; - message.mutable_command()->clear_environment_variables(); - if (file.is_open()) { - google::protobuf::util::SerializeDelimitedToOstream(message, &file); - } - } -} - -static void execute(const interceptor::Command& command, char* const envp[]) { - std::vector<const char*> c_arguments; - c_arguments.reserve(command.arguments().size() + 1); - c_arguments[command.arguments().size()] = nullptr; - for (const auto& arg : command.arguments()) { - c_arguments.push_back(arg.data()); - } - // TODO: at this point, we could free some memory that is held in Command. - // While the arguments vector is reused for arguments, we could free - // the EnvMap and the original arguments. - - // does not return - old_execve(command.program().c_str(), const_cast<char**>(c_arguments.data()), envp); -} diff --git a/interceptor/interceptor.h b/interceptor/interceptor.h deleted file mode 100644 index 6bf349e..0000000 --- a/interceptor/interceptor.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -#include <functional> -#include <optional> -#include <string> -#include <type_traits> -#include <unordered_map> -#include <vector> - -#include "log.pb.h" - -// Options passed via environment variables from the interceptor starter -constexpr static auto kEnvCommandLog = "INTERCEPTOR_command_log"; -constexpr static auto kEnvRootDirectory = "INTERCEPTOR_root_directory"; - -namespace interceptor { - -// Some type definitions to gain some type safety -using ArgVec = std::remove_pointer_t<decltype(Command().mutable_arguments())>; -using EnvMap = std::remove_pointer_t<decltype(Command().mutable_environment_variables())>; - -using Inputs = std::vector<std::string>; -using Outputs = Inputs; - -std::ostream& operator<<(std::ostream& os, const interceptor::Command& command); - -// Command analysis - -struct AnalysisResult { - Inputs inputs; - Outputs outputs; -}; -} // namespace interceptor diff --git a/interceptor/log.proto b/interceptor/log.proto deleted file mode 100644 index 7bd027c..0000000 --- a/interceptor/log.proto +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -syntax = "proto3"; - -package interceptor; - -// A Command as traced by intercepting an execve() invocation. -message Command { - string program = 1; - repeated string arguments = 2; - map<string, string> environment_variables = 3; - string current_directory = 4; - repeated string inputs = 5; - repeated string outputs = 6; -}; - -// A single message as emitted by an intercepted process. It can contain any of -// the above messages. -message Message { - oneof message { Command command = 1; } -} - -// The entirety of a the final log should be stored as just this Log. -message Log { - string root_directory = 1; // ${ROOT_DIR} or cwd() if unset - repeated Command commands = 2; -} - -// A compile_command.json entry as specified in -// https://clang.llvm.org/docs/JSONCompilationDatabase.html -message CompileCommand { - string directory = 1; - string file = 2; - string command = 3; - repeated string arguments = 4; - string output = 5; -} - -message CompilationDatabase { - repeated CompileCommand commands = 1; -} diff --git a/interceptor/main.cc b/interceptor/main.cc deleted file mode 100644 index 16082a7..0000000 --- a/interceptor/main.cc +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -#include <getopt.h> -#include <stdlib.h> -#include <sysexits.h> -#include <cstdlib> -#include <filesystem> -#include <fstream> -#include <iostream> -#include <optional> -#include <sstream> - -#include <google/protobuf/util/delimited_message_util.h> - -#include "interceptor.h" - -namespace fs = std::filesystem; - -struct Options { - std::string command_line; - std::optional<fs::path> command_log; -}; - -static Options parse_args(int argc, char* argv[]) { - Options result; - - while (1) { - static struct option long_options[] = {{"command-log", required_argument, 0, 'l'}, - {0, 0, 0, 0}}; - /* getopt_long stores the option index here. */ - int option_index = 0; - - auto c = getopt_long(argc, argv, "l:", long_options, &option_index); - - /* Detect the end of the options. */ - if (c == -1) { - break; - } - - switch (c) { - case 'l': - result.command_log = fs::absolute(optarg); - break; - - case '?': - /* getopt_long already printed an error message. */ - break; - - default: - abort(); - } - } - - std::stringstream ss; - if (optind < argc) { - while (optind < argc) { - ss << argv[optind++]; - ss << ' '; - } - } - result.command_line = ss.str(); - - return result; -} - -static void setup_interceptor_library_path() { - auto interceptor_library = fs::read_symlink("/proc/self/exe").parent_path().parent_path() / - "lib64" / "libinterceptor.so"; - while (fs::is_symlink(interceptor_library)) { - interceptor_library = fs::read_symlink(interceptor_library); - } - if (!fs::is_regular_file(interceptor_library)) { - std::cerr << "Interceptor library could not be found!\n"; - exit(EX_CONFIG); - } - setenv("LD_PRELOAD", interceptor_library.c_str(), 1); -} - -static fs::path set_up_root_directory() { - const auto root_directory = getenv("ROOT_DIR"); - fs::path result; - if (root_directory != nullptr) { - result = root_directory; - } else { - result = fs::current_path(); - } - - setenv(kEnvRootDirectory, result.c_str(), 1); - - return result; -} - -class CommandLog { - const decltype(Options::command_log) command_log_file_; - const fs::path root_directory_; - - public: - CommandLog(decltype(command_log_file_) command_log_file, const fs::path& root_directory) - : command_log_file_(std::move(command_log_file)), root_directory_(root_directory) { - if (command_log_file_) { - setenv(kEnvCommandLog, command_log_file_->c_str(), 1); - std::ofstream command_log(command_log_file_->c_str(), std::ios_base::trunc); - if (!command_log) { - std::cerr << "Could not open command log for writing: " << *command_log_file_ << "\n"; - exit(EX_CANTCREAT); - } - } - } - - ~CommandLog() { - if (command_log_file_) { - // compact the log by re-reading the individual log::Message's to combine - // them to a log::Log - interceptor::Log log; - log.set_root_directory(root_directory_); - { - std::ifstream command_log(command_log_file_->c_str(), std::ios_base::binary); - - google::protobuf::io::IstreamInputStream input_stream(&command_log); - interceptor::Message message; - while (true) { - if (!google::protobuf::util::ParseDelimitedFromZeroCopyStream(&message, &input_stream, - nullptr)) { - break; - } - if (message.has_command()) { - log.add_commands()->Swap(message.release_command()); - } - } - } - std::ofstream command_log(command_log_file_->c_str(), std::ios_base::binary); - log.SerializeToOstream(&command_log); - } - } -}; - -int main(int argc, char* argv[]) { - const auto& options = parse_args(argc, argv); - - setup_interceptor_library_path(); - const auto root_directory = set_up_root_directory(); - - CommandLog command_log(options.command_log, root_directory); - - // TODO: cleanly to google::protobuf::ShutdownProtobufLibrary(); - - return std::system(options.command_line.c_str()); -} |