Skip to content

Commit b586c27

Browse files
vicromsBillyONeal
andauthored
[Telemetry] Detect if vcpkg is running in a container (#730)
* [wip] Implement Windows Container heuristics * Add Linux containers heuristics * Use GetUserNameW() on Windows * Add detected container metric * Reorder metrics * Test cgroup parser * Apply suggestions from code review Co-authored-by: Billy O'Neal <[email protected]> * Update src/vcpkg.cpp * Fix osx build by removing unnecessary std::moves. Co-authored-by: Billy O'Neal <[email protected]>
1 parent d775ad2 commit b586c27

File tree

9 files changed

+234
-0
lines changed

9 files changed

+234
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ node_modules/
2222
/ce/test/vcpkg-ce.test.build.log
2323
/ce/common/temp
2424
/vcpkg-root
25+
/CMakePresets.json

include/vcpkg/base/system.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ namespace vcpkg
2323
const ExpectedS<Path>& get_system_root() noexcept;
2424

2525
const ExpectedS<Path>& get_system32() noexcept;
26+
27+
std::wstring get_username();
28+
29+
bool test_registry_key(void* base_hkey, StringView sub_key);
2630
#endif
2731

2832
Optional<std::string> get_registry_string(void* base_hkey, StringView subkey, StringView valuename);

include/vcpkg/cgroup-parser.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <vcpkg/base/fwd/stringview.h>
4+
5+
#include <string>
6+
7+
namespace vcpkg
8+
{
9+
struct ControlGroup
10+
{
11+
long hierarchy_id;
12+
std::string subsystems;
13+
std::string control_group;
14+
15+
ControlGroup(long id, StringView s, StringView c);
16+
};
17+
18+
std::vector<ControlGroup> parse_cgroup_file(StringView text, StringView origin);
19+
20+
bool detect_docker_in_cgroup_file(StringView text, StringView origin);
21+
}

include/vcpkg/metrics.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ namespace vcpkg
7878

7979
enum class BoolMetric
8080
{
81+
DetectedContainer,
8182
InstallManifestMode,
8283
OptionOverlayPorts,
8384
COUNT // always keep COUNT last

src/vcpkg-test/cgroup-parser.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include <catch2/catch.hpp>
2+
3+
#include <vcpkg/base/stringview.h>
4+
5+
#include <vcpkg/cgroup-parser.h>
6+
7+
using namespace vcpkg;
8+
9+
TEST_CASE ("parse", "[cgroup-parser]")
10+
{
11+
auto ok_text = R"(
12+
3:cpu:/
13+
2:cpuset:/
14+
1:memory:/
15+
0::/
16+
)";
17+
18+
auto cgroups = parse_cgroup_file(ok_text, "ok_text");
19+
REQUIRE(cgroups.size() == 4);
20+
CHECK(cgroups[0].hierarchy_id == 3);
21+
CHECK(cgroups[0].subsystems == "cpu");
22+
CHECK(cgroups[0].control_group == "/");
23+
CHECK(cgroups[1].hierarchy_id == 2);
24+
CHECK(cgroups[1].subsystems == "cpuset");
25+
CHECK(cgroups[1].control_group == "/");
26+
CHECK(cgroups[2].hierarchy_id == 1);
27+
CHECK(cgroups[2].subsystems == "memory");
28+
CHECK(cgroups[2].control_group == "/");
29+
CHECK(cgroups[3].hierarchy_id == 0);
30+
CHECK(cgroups[3].subsystems == "");
31+
CHECK(cgroups[3].control_group == "/");
32+
33+
auto cgroups_short = parse_cgroup_file("2::", "short_text");
34+
REQUIRE(cgroups_short.size() == 1);
35+
CHECK(cgroups_short[0].hierarchy_id == 2);
36+
CHECK(cgroups_short[0].subsystems == "");
37+
CHECK(cgroups_short[0].control_group == "");
38+
39+
auto cgroups_incomplete = parse_cgroup_file("0:/", "incomplete_text");
40+
CHECK(cgroups_incomplete.empty());
41+
42+
auto cgroups_bad_id = parse_cgroup_file("ab::", "non_numeric_id_text");
43+
CHECK(cgroups_bad_id.empty());
44+
45+
auto cgroups_empty = parse_cgroup_file("", "empty");
46+
CHECK(cgroups_empty.empty());
47+
}
48+
49+
TEST_CASE ("detect docker", "[cgroup-parser]")
50+
{
51+
auto with_docker = R"(
52+
2:memory:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14
53+
1:name=systemd:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14
54+
0::/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14
55+
)";
56+
57+
auto without_docker = R"(
58+
3:cpu:/
59+
2:cpuset:/
60+
1:memory:/
61+
0::/
62+
)";
63+
64+
CHECK(detect_docker_in_cgroup_file(with_docker, "with_docker"));
65+
CHECK(!detect_docker_in_cgroup_file(without_docker, "without_docker"));
66+
}

src/vcpkg.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <vcpkg/base/system.debug.h>
99
#include <vcpkg/base/system.process.h>
1010

11+
#include <vcpkg/cgroup-parser.h>
1112
#include <vcpkg/commands.contact.h>
1213
#include <vcpkg/commands.h>
1314
#include <vcpkg/commands.version.h>
@@ -41,6 +42,47 @@ static void invalid_command(const std::string& cmd)
4142
Checks::exit_fail(VCPKG_LINE_INFO);
4243
}
4344

45+
static bool detect_container(vcpkg::Filesystem& fs)
46+
{
47+
(void)fs;
48+
#if defined(_WIN32)
49+
if (test_registry_key(HKEY_LOCAL_MACHINE, R"(SYSTEM\CurrentControlSet\Services\cexecsvc)"))
50+
{
51+
Debug::println("Detected Container Execution Service");
52+
return true;
53+
}
54+
55+
auto username = get_username();
56+
if (username == L"ContainerUser" || username == L"ContainerAdministrator")
57+
{
58+
Debug::println("Detected container username");
59+
return true;
60+
}
61+
#elif defined(__linux__)
62+
if (fs.exists("/.dockerenv", IgnoreErrors{}))
63+
{
64+
Debug::println("Detected /.dockerenv file");
65+
return true;
66+
}
67+
68+
// check /proc/1/cgroup, if we're running in a container then the control group for each hierarchy will be:
69+
// /docker/<containerid>, or
70+
// /lxc/<containerid>
71+
//
72+
// Example of /proc/1/cgroup contents:
73+
// 2:memory:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14
74+
// 1:name=systemd:/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14
75+
// 0::/docker/66a5f8000f3f2e2a19c3f7d60d870064d26996bdfe77e40df7e3fc955b811d14
76+
auto cgroup_contents = fs.read_contents("/proc/1/cgroup", IgnoreErrors{});
77+
if (detect_docker_in_cgroup_file(cgroup_contents, "/proc/1/cgroup"))
78+
{
79+
Debug::println("Detected docker in cgroup");
80+
return true;
81+
}
82+
#endif
83+
return false;
84+
}
85+
4486
static void inner(vcpkg::Filesystem& fs, const VcpkgCmdArguments& args)
4587
{
4688
// track version on each invocation
@@ -68,6 +110,14 @@ static void inner(vcpkg::Filesystem& fs, const VcpkgCmdArguments& args)
68110
}
69111
};
70112

113+
{
114+
auto metrics = LockGuardPtr<Metrics>(g_metrics);
115+
if (metrics->metrics_enabled())
116+
{
117+
metrics->track_bool_property(BoolMetric::DetectedContainer, detect_container(fs));
118+
}
119+
}
120+
71121
LockGuardPtr<Metrics>(g_metrics)->track_bool_property(BoolMetric::OptionOverlayPorts, !args.overlay_ports.empty());
72122

73123
if (const auto command_function = find_command(Commands::get_available_basic_commands()))

src/vcpkg/base/system.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#endif
1414

1515
#if defined(_WIN32)
16+
#include <lmcons.h>
17+
#include <winbase.h>
1618
// needed for mingw
1719
#include <processenv.h>
1820
#else
@@ -346,6 +348,24 @@ namespace vcpkg
346348
return hkey_type == REG_SZ || hkey_type == REG_MULTI_SZ || hkey_type == REG_EXPAND_SZ;
347349
}
348350

351+
std::wstring get_username()
352+
{
353+
DWORD buffer_size = UNLEN + 1;
354+
std::wstring buffer;
355+
buffer.resize(static_cast<size_t>(buffer_size));
356+
GetUserNameW(buffer.data(), &buffer_size);
357+
buffer.resize(buffer_size);
358+
return buffer;
359+
}
360+
361+
bool test_registry_key(void* base_hkey, StringView sub_key)
362+
{
363+
HKEY k = nullptr;
364+
const LSTATUS ec =
365+
RegOpenKeyExW(reinterpret_cast<HKEY>(base_hkey), Strings::to_utf16(sub_key).c_str(), 0, KEY_READ, &k);
366+
return (ERROR_SUCCESS == ec);
367+
}
368+
349369
Optional<std::string> get_registry_string(void* base_hkey, StringView sub_key, StringView valuename)
350370
{
351371
HKEY k = nullptr;

src/vcpkg/cgroup-parser.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <vcpkg/base/parse.h>
2+
#include <vcpkg/base/stringview.h>
3+
#include <vcpkg/base/system.debug.h>
4+
#include <vcpkg/base/util.h>
5+
6+
#include <vcpkg/cgroup-parser.h>
7+
8+
namespace vcpkg
9+
{
10+
ControlGroup::ControlGroup(long id, StringView s, StringView c)
11+
: hierarchy_id(id), subsystems(s.data(), s.size()), control_group(c.data(), c.size())
12+
{
13+
}
14+
15+
// parses /proc/[pid]/cgroup file as specified in https://linux.die.net/man/5/proc
16+
// The file describes control groups to which the process/tasks belongs.
17+
// For each cgroup hierarchy there is one entry
18+
// containing colon-separated fields of the form:
19+
// 5:cpuacct,cpu,cpuset:/daemos
20+
//
21+
// The colon separated fields are, from left to right:
22+
//
23+
// 1. hierarchy ID number
24+
// 2. set of subsystems bound to the hierarchy
25+
// 3. control group in the hierarchy to which the process belongs
26+
std::vector<ControlGroup> parse_cgroup_file(StringView text, StringView origin)
27+
{
28+
using P = ParserBase;
29+
constexpr auto is_separator_or_lineend = [](auto ch) { return ch == ':' || P::is_lineend(ch); };
30+
31+
auto parser = ParserBase(text, origin);
32+
parser.skip_whitespace();
33+
34+
std::vector<ControlGroup> ret;
35+
while (!parser.at_eof())
36+
{
37+
auto id = parser.match_until(is_separator_or_lineend);
38+
auto maybe_numeric_id = Strings::strto<long>(id);
39+
if (!maybe_numeric_id || P::is_lineend(parser.cur()))
40+
{
41+
ret.clear();
42+
break;
43+
}
44+
45+
parser.next();
46+
auto subsystems = parser.match_until(is_separator_or_lineend);
47+
if (P::is_lineend(parser.cur()))
48+
{
49+
ret.clear();
50+
break;
51+
}
52+
53+
parser.next();
54+
auto control_group = parser.match_until(P::is_lineend);
55+
parser.skip_whitespace();
56+
57+
ret.emplace_back(*maybe_numeric_id.get(), subsystems, control_group);
58+
}
59+
60+
return ret;
61+
}
62+
63+
bool detect_docker_in_cgroup_file(StringView text, StringView origin)
64+
{
65+
return Util::any_of(parse_cgroup_file(text, origin), [](auto&& cgroup) {
66+
return Strings::starts_with(cgroup.control_group, "/docker") ||
67+
Strings::starts_with(cgroup.control_group, "/lxc");
68+
});
69+
}
70+
}

src/vcpkg/metrics.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ namespace vcpkg
8888
}};
8989

9090
const constexpr std::array<BoolMetricEntry, static_cast<size_t>(BoolMetric::COUNT)> all_bool_metrics{{
91+
{BoolMetric::DetectedContainer, "detected_container"},
9192
{BoolMetric::InstallManifestMode, "install_manifest_mode"},
9293
{BoolMetric::OptionOverlayPorts, "option_overlay_ports"},
9394
}};

0 commit comments

Comments
 (0)