Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Telemetry] Detect if vcpkg is running in a container #730

Merged
merged 9 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ node_modules/
/ce/test/vcpkg-ce.test.build.log
/ce/common/temp
/vcpkg-root
/CMakePresets.json
4 changes: 4 additions & 0 deletions include/vcpkg/base/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ namespace vcpkg
const ExpectedS<Path>& get_system_root() noexcept;

const ExpectedS<Path>& get_system32() noexcept;

std::wstring get_username();

bool test_registry_key(void* base_hkey, StringView sub_key);
#endif

Optional<std::string> get_registry_string(void* base_hkey, StringView subkey, StringView valuename);
Expand Down
21 changes: 21 additions & 0 deletions include/vcpkg/cgroup-parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <vcpkg/base/fwd/stringview.h>

#include <string>

namespace vcpkg
{
struct ControlGroup
{
long hierarchy_id;
std::string subsystems;
std::string control_group;

ControlGroup(long id, StringView s, StringView c);
};

std::vector<ControlGroup> parse_cgroup_file(StringView text, StringView origin);

bool detect_docker_in_cgroup_file(StringView text, StringView origin);
}
1 change: 1 addition & 0 deletions include/vcpkg/metrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ namespace vcpkg

enum class BoolMetric
{
DetectedContainer,
InstallManifestMode,
OptionOverlayPorts,
COUNT // always keep COUNT last
Expand Down
66 changes: 66 additions & 0 deletions src/vcpkg-test/cgroup-parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <catch2/catch.hpp>

#include <vcpkg/base/stringview.h>

#include <vcpkg/cgroup-parser.h>

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"));
}
50 changes: 50 additions & 0 deletions src/vcpkg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <vcpkg/base/system.debug.h>
#include <vcpkg/base/system.process.h>

#include <vcpkg/cgroup-parser.h>
#include <vcpkg/commands.contact.h>
#include <vcpkg/commands.h>
#include <vcpkg/commands.version.h>
Expand Down Expand Up @@ -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)
BillyONeal marked this conversation as resolved.
Show resolved Hide resolved
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/<containerid>, or
// /lxc/<containerid>
//
// 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
Expand Down Expand Up @@ -68,6 +110,14 @@ static void inner(vcpkg::Filesystem& fs, const VcpkgCmdArguments& args)
}
};

{
auto metrics = LockGuardPtr<Metrics>(g_metrics);
if (metrics->metrics_enabled())
{
metrics->track_bool_property(BoolMetric::DetectedContainer, detect_container(fs));
}
}

LockGuardPtr<Metrics>(g_metrics)->track_bool_property(BoolMetric::OptionOverlayPorts, !args.overlay_ports.empty());

if (const auto command_function = find_command(Commands::get_available_basic_commands()))
Expand Down
20 changes: 20 additions & 0 deletions src/vcpkg/base/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#endif

#if defined(_WIN32)
#include <lmcons.h>
#include <winbase.h>
// needed for mingw
#include <processenv.h>
#else
Expand Down Expand Up @@ -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<size_t>(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<HKEY>(base_hkey), Strings::to_utf16(sub_key).c_str(), 0, KEY_READ, &k);
return (ERROR_SUCCESS == ec);
}

Optional<std::string> get_registry_string(void* base_hkey, StringView sub_key, StringView valuename)
{
HKEY k = nullptr;
Expand Down
70 changes: 70 additions & 0 deletions src/vcpkg/cgroup-parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <vcpkg/base/parse.h>
#include <vcpkg/base/stringview.h>
#include <vcpkg/base/system.debug.h>
#include <vcpkg/base/util.h>

#include <vcpkg/cgroup-parser.h>

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<ControlGroup> 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❣️

parser.skip_whitespace();

std::vector<ControlGroup> ret;
while (!parser.at_eof())
{
auto id = parser.match_until(is_separator_or_lineend);
auto maybe_numeric_id = Strings::strto<long>(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");
});
}
}
1 change: 1 addition & 0 deletions src/vcpkg/metrics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ namespace vcpkg
}};

const constexpr std::array<BoolMetricEntry, static_cast<size_t>(BoolMetric::COUNT)> all_bool_metrics{{
{BoolMetric::DetectedContainer, "detected_container"},
{BoolMetric::InstallManifestMode, "install_manifest_mode"},
{BoolMetric::OptionOverlayPorts, "option_overlay_ports"},
}};
Expand Down