From c59faea0e808e02e64f04b5e13481ba96c3e8ca0 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Thu, 14 Nov 2024 09:13:01 -0800 Subject: [PATCH] Fix up busybox detection for relative symlinks (#4522) Handling for relative symlinks is new (comment talks about relative symlinks). Relocated to add tests though, to do extra checking of logic. --- toolchain/install/BUILD | 21 ++++ toolchain/install/busybox_info.h | 47 +++++++ toolchain/install/busybox_info_test.cpp | 156 ++++++++++++++++++++++++ toolchain/install/busybox_main.cpp | 44 ++----- 4 files changed, 231 insertions(+), 37 deletions(-) create mode 100644 toolchain/install/busybox_info.h create mode 100644 toolchain/install/busybox_info_test.cpp diff --git a/toolchain/install/BUILD b/toolchain/install/BUILD index 87a56629ae8d2..9ddf3b41a6913 100644 --- a/toolchain/install/BUILD +++ b/toolchain/install/BUILD @@ -71,6 +71,26 @@ cc_library( ], ) +cc_library( + name = "busybox_info", + hdrs = ["busybox_info.h"], + deps = [ + "//common:error", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "busybox_info_test", + srcs = ["busybox_info_test.cpp"], + deps = [ + ":busybox_info", + "//common:check", + "//testing/base:gtest_main", + "@googletest//:gtest", + ], +) + # This target doesn't include prelude libraries. To get a target that has the # prelude available, use //toolchain. cc_binary( @@ -78,6 +98,7 @@ cc_binary( srcs = ["busybox_main.cpp"], env = cc_env(), deps = [ + ":busybox_info", ":install_paths", "//common:all_llvm_targets", "//common:bazel_working_dir", diff --git a/toolchain/install/busybox_info.h b/toolchain/install/busybox_info.h new file mode 100644 index 0000000000000..72fad4a20a145 --- /dev/null +++ b/toolchain/install/busybox_info.h @@ -0,0 +1,47 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_ +#define CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_ + +#include +#include +#include + +#include "common/error.h" +#include "llvm/ADT/StringRef.h" + +namespace Carbon { + +struct BusyboxInfo { + // The path to `carbon-busybox`. + std::filesystem::path bin_path; + // The mode, such as `carbon` or `clang`. + std::optional mode; +}; + +// Returns the busybox information, given argv[0]. This primarily handles +// resolving symlinks that point at the busybox. +inline auto GetBusyboxInfo(llvm::StringRef argv0) -> ErrorOr { + BusyboxInfo info = BusyboxInfo{argv0.str(), std::nullopt}; + while (true) { + std::string filename = info.bin_path.filename(); + if (filename == "carbon-busybox") { + return info; + } + std::error_code ec; + auto symlink_target = std::filesystem::read_symlink(info.bin_path, ec); + if (ec) { + return ErrorBuilder() + << "expected carbon-busybox symlink at `" << info.bin_path << "`"; + } + info.mode = filename; + // Do a path join, to handle relative symlinks. + info.bin_path = info.bin_path.parent_path() / symlink_target; + } +} + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_ diff --git a/toolchain/install/busybox_info_test.cpp b/toolchain/install/busybox_info_test.cpp new file mode 100644 index 0000000000000..29c7abc1b9646 --- /dev/null +++ b/toolchain/install/busybox_info_test.cpp @@ -0,0 +1,156 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/install/busybox_info.h" + +#include +#include + +#include +#include + +#include "common/check.h" + +namespace Carbon { +namespace { + +using ::testing::Eq; + +class BusyboxInfoTest : public ::testing::Test { + protected: + // Set up a temp directory for the test case. + explicit BusyboxInfoTest() { + const char* tmpdir = std::getenv("TEST_TMPDIR"); + CARBON_CHECK(tmpdir); + dir_ = MakeDir( + std::filesystem::path(tmpdir) / + ::testing::UnitTest::GetInstance()->current_test_info()->name()); + } + + // Delete the test case's temp directory. + ~BusyboxInfoTest() override { + std::error_code ec; + std::filesystem::remove_all(dir_, ec); + CARBON_CHECK(!ec, "error removing {0}: {1}", dir_, ec.message()); + } + + // Creates a stub file. Returns the input file for easier use. + auto MakeFile(std::filesystem::path file) -> std::filesystem::path { + std::ofstream out(file.c_str()); + out << "stub"; + CARBON_CHECK(out, "error creating {0}", file); + return file; + } + + // Creates a symlink to the target. Returns the input file for easier use. + auto MakeSymlink(std::filesystem::path file, auto target) + -> std::filesystem::path { + std::error_code ec; + std::filesystem::create_symlink(target, file, ec); + CARBON_CHECK(!ec, "error creating {0}: {1}", file, ec.message()); + return file; + } + + // Creates a directory. Returns the input file for easier use. + auto MakeDir(std::filesystem::path dir) -> std::filesystem::path { + std::error_code ec; + std::filesystem::create_directory(dir, ec); + CARBON_CHECK(!ec, "error creating {0}: {1}", dir, ec.message()); + return dir; + } + + // The test's temp directory, deleted on destruction. + std::filesystem::path dir_; +}; + +TEST_F(BusyboxInfoTest, Direct) { + auto busybox = MakeFile(dir_ / "carbon-busybox"); + + auto info = GetBusyboxInfo(busybox.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(busybox)); + EXPECT_THAT(info->mode, Eq(std::nullopt)); +} + +TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectory) { + MakeFile(dir_ / "carbon-busybox"); + auto target = MakeSymlink(dir_ / "carbon", "carbon-busybox"); + + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); + EXPECT_THAT(info->mode, Eq("carbon")); +} + +TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectoryWithDot) { + MakeFile(dir_ / "carbon-busybox"); + auto target = MakeSymlink(dir_ / "carbon", "./carbon-busybox"); + + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "./carbon-busybox")); + EXPECT_THAT(info->mode, Eq("carbon")); +} + +TEST_F(BusyboxInfoTest, ExtraSymlink) { + MakeFile(dir_ / "carbon-busybox"); + MakeSymlink(dir_ / "carbon", "carbon-busybox"); + auto target = MakeSymlink(dir_ / "c", "carbon"); + + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); + EXPECT_THAT(info->mode, Eq("carbon")); +} + +TEST_F(BusyboxInfoTest, BusyboxIsSymlink) { + MakeFile(dir_ / "actual-busybox"); + auto target = MakeSymlink(dir_ / "carbon-busybox", "actual-busybox"); + + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(target)); + EXPECT_THAT(info->mode, Eq(std::nullopt)); +} + +TEST_F(BusyboxInfoTest, BusyboxIsSymlinkToNowhere) { + auto target = MakeSymlink(dir_ / "carbon-busybox", "nonexistent"); + + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); +} + +TEST_F(BusyboxInfoTest, RelativeSymlink) { + MakeDir(dir_ / "lib"); + MakeDir(dir_ / "lib/carbon"); + MakeFile(dir_ / "lib/carbon/carbon-busybox"); + MakeDir(dir_ / "bin"); + auto target = + MakeSymlink(dir_ / "bin/carbon", "../lib/carbon/carbon-busybox"); + + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "bin/../lib/carbon/carbon-busybox")); + EXPECT_THAT(info->mode, Eq("carbon")); +} + +TEST_F(BusyboxInfoTest, NotBusyboxFile) { + auto target = MakeFile(dir_ / "file"); + + auto info = GetBusyboxInfo(target.string()); + EXPECT_FALSE(info.ok()); +} + +TEST_F(BusyboxInfoTest, NotBusyboxSymlink) { + MakeFile(dir_ / "file"); + auto target = MakeSymlink(dir_ / "carbon", "file"); + + auto info = GetBusyboxInfo(target.string()); + EXPECT_FALSE(info.ok()); +} + +} // namespace +} // namespace Carbon diff --git a/toolchain/install/busybox_main.cpp b/toolchain/install/busybox_main.cpp index 4a081e879b744..a90a40a4fb276 100644 --- a/toolchain/install/busybox_main.cpp +++ b/toolchain/install/busybox_main.cpp @@ -5,7 +5,6 @@ #include #include -#include #include "common/bazel_working_dir.h" #include "common/error.h" @@ -15,58 +14,29 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/LLVMDriver.h" #include "toolchain/driver/driver.h" +#include "toolchain/install/busybox_info.h" #include "toolchain/install/install_paths.h" namespace Carbon { -namespace { -struct BusyboxInfo { - // The path to `carbon-busybox`. - std::filesystem::path bin_path; - // The mode, such as `carbon` or `clang`. - std::optional mode; -}; -} // namespace - -// Returns the busybox information, given argv[0]. This primarily handles -// resolving symlinks that point at the busybox. -static auto GetBusyboxInfo(llvm::StringRef argv0) -> ErrorOr { - BusyboxInfo info = BusyboxInfo{argv0.str(), std::nullopt}; - while (true) { - std::string filename = info.bin_path.filename(); - if (filename == "carbon-busybox") { - return info; - } - std::error_code ec; - auto symlink_target = std::filesystem::read_symlink(info.bin_path, ec); - if (ec) { - return ErrorBuilder() - << "expected carbon-busybox symlink at `" << info.bin_path << "`"; - } - info.mode = filename; - info.bin_path = symlink_target; - } -} - // The actual `main` implementation. Can return an exit code or an `Error` // (which causes EXIT_FAILRUE). static auto Main(int argc, char** argv) -> ErrorOr { - Carbon::InitLLVM init_llvm(argc, argv); + InitLLVM init_llvm(argc, argv); // Start by resolving any symlinks. - CARBON_ASSIGN_OR_RETURN(auto busybox_info, Carbon::GetBusyboxInfo(argv[0])); + CARBON_ASSIGN_OR_RETURN(auto busybox_info, GetBusyboxInfo(argv[0])); auto fs = llvm::vfs::getRealFileSystem(); // Resolve paths before calling SetWorkingDirForBazel. - std::string exe_path = - Carbon::FindExecutablePath(busybox_info.bin_path.string()); - const auto install_paths = Carbon::InstallPaths::MakeExeRelative(exe_path); + std::string exe_path = FindExecutablePath(busybox_info.bin_path.string()); + const auto install_paths = InstallPaths::MakeExeRelative(exe_path); if (install_paths.error()) { return Error(*install_paths.error()); } - Carbon::SetWorkingDirForBazel(); + SetWorkingDirForBazel(); llvm::SmallVector args; args.reserve(argc + 1); @@ -75,7 +45,7 @@ static auto Main(int argc, char** argv) -> ErrorOr { } args.append(argv + 1, argv + argc); - Carbon::Driver driver(*fs, &install_paths, llvm::outs(), llvm::errs()); + Driver driver(*fs, &install_paths, llvm::outs(), llvm::errs()); bool success = driver.RunCommand(args).success; return success ? EXIT_SUCCESS : EXIT_FAILURE; }