diff --git a/.gitignore b/.gitignore index d3738a5bc2..edafc23ef8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ node_modules/ /ce/test/vcpkg-ce.test.build.log /ce/common/temp /vcpkg-root +/CMakePresets.json diff --git a/include/vcpkg/base/system.h b/include/vcpkg/base/system.h index b98506f49a..469583f035 100644 --- a/include/vcpkg/base/system.h +++ b/include/vcpkg/base/system.h @@ -23,6 +23,10 @@ namespace vcpkg const ExpectedS& get_system_root() noexcept; const ExpectedS& get_system32() noexcept; + + std::wstring get_username(); + + bool test_registry_key(void* base_hkey, StringView sub_key); #endif Optional get_registry_string(void* base_hkey, StringView subkey, StringView valuename); diff --git a/include/vcpkg/cgroup-parser.h b/include/vcpkg/cgroup-parser.h new file mode 100644 index 0000000000..aef9aeabdb --- /dev/null +++ b/include/vcpkg/cgroup-parser.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +namespace vcpkg +{ + struct ControlGroup + { + long hierarchy_id; + std::string subsystems; + std::string control_group; + + ControlGroup(long id, StringView s, StringView c); + }; + + std::vector parse_cgroup_file(StringView text, StringView origin); + + bool detect_docker_in_cgroup_file(StringView text, StringView origin); +} diff --git a/include/vcpkg/metrics.h b/include/vcpkg/metrics.h index 0f51c1fa12..498a81b3f5 100644 --- a/include/vcpkg/metrics.h +++ b/include/vcpkg/metrics.h @@ -78,6 +78,7 @@ namespace vcpkg enum class BoolMetric { + DetectedContainer, InstallManifestMode, OptionOverlayPorts, COUNT // always keep COUNT last diff --git a/src/vcpkg-test/cgroup-parser.cpp b/src/vcpkg-test/cgroup-parser.cpp new file mode 100644 index 0000000000..331a60d9e6 --- /dev/null +++ b/src/vcpkg-test/cgroup-parser.cpp @@ -0,0 +1,66 @@ +#include + +#include + +#include + +using namespace vcpkg; + +TEST_CASE ("parse", "[cgroup-parser]") +{ + auto ok_text = R"( +3:cpu:/ +2:cpuset:/ +1:memory:/ +0::/ +)"; + + auto cgroups = parse_cgroup_file(ok_text, "ok_text"); + REQUIRE(cgroups.size() == 4); + CHECK(cgroups[0].hierarchy_id == 3); + CHECK(cgroups[0].subsystems == "cpu"); + CHECK(cgroups[0].control_group == "/"); + CHECK(cgroups[1].hierarchy_id == 2); + CHECK(cgroups[1].subsystems == "cpuset"); + CHECK(cgroups[1].control_group == "/"); + CHECK(cgroups[2].hierarchy_id == 1); + CHECK(cgroups[2].subsystems == "memory"); + CHECK(cgroups[2].control_group == "/"); + CHECK(cgroups[3].hierarchy_id == 0); + CHECK(cgroups[3].subsystems == ""); + CHECK(cgroups[3].control_group == "/"); + + auto cgroups_short = parse_cgroup_file("2::", "short_text"); + REQUIRE(cgroups_short.size() == 1); + CHECK(cgroups_short[0].hierarchy_id == 2); + CHECK(cgroups_short[0].subsystems == ""); + CHECK(cgroups_short[0].control_group == ""); + + auto cgroups_incomplete = parse_cgroup_file("0:/", "incomplete_text"); + CHECK(cgroups_incomplete.empty()); + + auto cgroups_bad_id = parse_cgroup_file("ab::", "non_numeric_id_text"); + CHECK(cgroups_bad_id.empty()); + + auto cgroups_empty = parse_cgroup_file("", "empty"); + CHECK(cgroups_empty.empty()); +} + +TEST_CASE ("detect docker", "[cgroup-parser]") +{ + auto with_docker = R"( +2:memory:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14 +1:name=systemd:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14 +0::/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14 +)"; + + auto without_docker = R"( +3:cpu:/ +2:cpuset:/ +1:memory:/ +0::/ +)"; + + CHECK(detect_docker_in_cgroup_file(with_docker, "with_docker")); + CHECK(!detect_docker_in_cgroup_file(without_docker, "without_docker")); +} \ No newline at end of file diff --git a/src/vcpkg.cpp b/src/vcpkg.cpp index cdcc0b1273..021d77eddd 100644 --- a/src/vcpkg.cpp +++ b/src/vcpkg.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,47 @@ static void invalid_command(const std::string& cmd) Checks::exit_fail(VCPKG_LINE_INFO); } +static bool detect_container(vcpkg::Filesystem& fs) +{ + (void)fs; +#if defined(_WIN32) + if (test_registry_key(HKEY_LOCAL_MACHINE, R"(SYSTEM\CurrentControlSet\Services\cexecsvc)")) + { + Debug::println("Detected Container Execution Service"); + return true; + } + + auto username = get_username(); + if (username == L"ContainerUser" || username == L"ContainerAdministrator") + { + Debug::println("Detected container username"); + return true; + } +#elif defined(__linux__) + if (fs.exists("/.dockerenv", IgnoreErrors{})) + { + Debug::println("Detected /.dockerenv file"); + return true; + } + + // check /proc/1/cgroup, if we're running in a container then the control group for each hierarchy will be: + // /docker/, or + // /lxc/ + // + // Example of /proc/1/cgroup contents: + // 2:memory:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14 + // 1:name=systemd:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14 + // 0::/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14 + auto cgroup_contents = fs.read_contents("/proc/1/cgroup", IgnoreErrors{}); + if (detect_docker_in_cgroup_file(cgroup_contents, "/proc/1/cgroup")) + { + Debug::println("Detected docker in cgroup"); + return true; + } +#endif + return false; +} + static void inner(vcpkg::Filesystem& fs, const VcpkgCmdArguments& args) { // track version on each invocation @@ -68,6 +110,14 @@ static void inner(vcpkg::Filesystem& fs, const VcpkgCmdArguments& args) } }; + { + auto metrics = LockGuardPtr(g_metrics); + if (metrics->metrics_enabled()) + { + metrics->track_bool_property(BoolMetric::DetectedContainer, detect_container(fs)); + } + } + LockGuardPtr(g_metrics)->track_bool_property(BoolMetric::OptionOverlayPorts, !args.overlay_ports.empty()); if (const auto command_function = find_command(Commands::get_available_basic_commands())) diff --git a/src/vcpkg/base/system.cpp b/src/vcpkg/base/system.cpp index 7ee4ea399b..7f9e7813a1 100644 --- a/src/vcpkg/base/system.cpp +++ b/src/vcpkg/base/system.cpp @@ -13,6 +13,8 @@ #endif #if defined(_WIN32) +#include +#include // needed for mingw #include #else @@ -346,6 +348,24 @@ namespace vcpkg return hkey_type == REG_SZ || hkey_type == REG_MULTI_SZ || hkey_type == REG_EXPAND_SZ; } + std::wstring get_username() + { + DWORD buffer_size = UNLEN + 1; + std::wstring buffer; + buffer.resize(static_cast(buffer_size)); + GetUserNameW(buffer.data(), &buffer_size); + buffer.resize(buffer_size); + return buffer; + } + + bool test_registry_key(void* base_hkey, StringView sub_key) + { + HKEY k = nullptr; + const LSTATUS ec = + RegOpenKeyExW(reinterpret_cast(base_hkey), Strings::to_utf16(sub_key).c_str(), 0, KEY_READ, &k); + return (ERROR_SUCCESS == ec); + } + Optional get_registry_string(void* base_hkey, StringView sub_key, StringView valuename) { HKEY k = nullptr; diff --git a/src/vcpkg/cgroup-parser.cpp b/src/vcpkg/cgroup-parser.cpp new file mode 100644 index 0000000000..06939dd2f6 --- /dev/null +++ b/src/vcpkg/cgroup-parser.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#include + +namespace vcpkg +{ + ControlGroup::ControlGroup(long id, StringView s, StringView c) + : hierarchy_id(id), subsystems(s.data(), s.size()), control_group(c.data(), c.size()) + { + } + + // parses /proc/[pid]/cgroup file as specified in https://linux.die.net/man/5/proc + // The file describes control groups to which the process/tasks belongs. + // For each cgroup hierarchy there is one entry + // containing colon-separated fields of the form: + // 5:cpuacct,cpu,cpuset:/daemos + // + // The colon separated fields are, from left to right: + // + // 1. hierarchy ID number + // 2. set of subsystems bound to the hierarchy + // 3. control group in the hierarchy to which the process belongs + std::vector parse_cgroup_file(StringView text, StringView origin) + { + using P = ParserBase; + constexpr auto is_separator_or_lineend = [](auto ch) { return ch == ':' || P::is_lineend(ch); }; + + auto parser = ParserBase(text, origin); + parser.skip_whitespace(); + + std::vector ret; + while (!parser.at_eof()) + { + auto id = parser.match_until(is_separator_or_lineend); + auto maybe_numeric_id = Strings::strto(id); + if (!maybe_numeric_id || P::is_lineend(parser.cur())) + { + ret.clear(); + break; + } + + parser.next(); + auto subsystems = parser.match_until(is_separator_or_lineend); + if (P::is_lineend(parser.cur())) + { + ret.clear(); + break; + } + + parser.next(); + auto control_group = parser.match_until(P::is_lineend); + parser.skip_whitespace(); + + ret.emplace_back(*maybe_numeric_id.get(), subsystems, control_group); + } + + return ret; + } + + bool detect_docker_in_cgroup_file(StringView text, StringView origin) + { + return Util::any_of(parse_cgroup_file(text, origin), [](auto&& cgroup) { + return Strings::starts_with(cgroup.control_group, "/docker") || + Strings::starts_with(cgroup.control_group, "/lxc"); + }); + } +} diff --git a/src/vcpkg/metrics.cpp b/src/vcpkg/metrics.cpp index a572ea04f4..ba3625aa40 100644 --- a/src/vcpkg/metrics.cpp +++ b/src/vcpkg/metrics.cpp @@ -88,6 +88,7 @@ namespace vcpkg }}; const constexpr std::array(BoolMetric::COUNT)> all_bool_metrics{{ + {BoolMetric::DetectedContainer, "detected_container"}, {BoolMetric::InstallManifestMode, "install_manifest_mode"}, {BoolMetric::OptionOverlayPorts, "option_overlay_ports"}, }};