Skip to content

Commit 74c4b9d

Browse files
kavehahmadi60meta-codesync[bot]
authored andcommitted
Add EdenFS mount health check helper
Summary: Add a standalone helper that checks running EdenFS mounts against the kernel mount table and .eden access. Return structured daemon mount-health reasons without wiring the helper into periodic daemon emission yet. Reviewed By: muirdm Differential Revision: D108200400 fbshipit-source-id: bce81388d37ddcd2a54783c674c1495efa973d75
1 parent f9ddb2b commit 74c4b9d

5 files changed

Lines changed: 283 additions & 0 deletions

File tree

eden/fs/utils/BUCK

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,17 @@ cpp_library(
335335
],
336336
exported_deps = ["//folly:expected"],
337337
)
338+
339+
cpp_library(
340+
name = "mount_health_check",
341+
srcs = ["MountHealthCheck.cpp"],
342+
headers = ["MountHealthCheck.h"],
343+
deps = [
344+
":mount_info_table",
345+
"//eden/common/utils:fsdetect",
346+
"//folly:string",
347+
"//folly/logging:logging",
348+
"//folly/portability:sys_stat",
349+
"//folly/portability:unistd",
350+
],
351+
)

eden/fs/utils/MountHealthCheck.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This software may be used and distributed according to the terms of the
5+
* GNU General Public License version 2.
6+
*/
7+
8+
#include "eden/fs/utils/MountHealthCheck.h"
9+
10+
#include <atomic>
11+
#include <cerrno>
12+
#include <optional>
13+
#include <string>
14+
15+
#include <folly/String.h>
16+
#include <folly/logging/xlog.h>
17+
#include <folly/portability/SysStat.h>
18+
#include <folly/portability/Unistd.h>
19+
20+
#ifdef __linux__
21+
#include "eden/common/utils/FSDetect.h"
22+
#include "eden/fs/utils/MountInfoTable.h"
23+
#endif
24+
25+
namespace facebook::eden {
26+
namespace {
27+
28+
bool isNotConnectedErrno(int err) {
29+
return err == ENOTCONN || err == ENXIO;
30+
}
31+
32+
#ifdef __linux__
33+
bool isEdenMountInfo(
34+
const std::string& mountSource,
35+
const std::string& fsType) {
36+
return is_edenfs_fs_type(mountSource) || is_edenfs_fs_type(fsType) ||
37+
fsType == "fuse.edenfs";
38+
}
39+
#endif
40+
41+
std::optional<int> lstatError(const std::string& path) {
42+
struct stat st{};
43+
if (lstat(path.c_str(), &st) == 0) {
44+
return std::nullopt;
45+
}
46+
return errno;
47+
}
48+
49+
std::optional<EdenMountHealthCheckIssue> issueForPathAccessError(
50+
const std::string& path,
51+
int err) {
52+
if (isNotConnectedErrno(err)) {
53+
return EdenMountHealthCheckIssue{
54+
EdenMountHealthIssueReason::DaemonRunningMountNotConnected,
55+
path + ": " + folly::errnoStr(err)};
56+
}
57+
if (err == ETIMEDOUT) {
58+
return EdenMountHealthCheckIssue{
59+
EdenMountHealthIssueReason::DaemonRunningMountTimedOut,
60+
path + ": " + folly::errnoStr(err)};
61+
}
62+
63+
XLOGF(
64+
WARN,
65+
"Unexpected EdenFS mount health check lstat error for {}: {}",
66+
path,
67+
folly::errnoStr(err));
68+
return EdenMountHealthCheckIssue{
69+
EdenMountHealthIssueReason::DaemonRunningMountAccessError,
70+
path + ": " + folly::errnoStr(err)};
71+
}
72+
73+
std::optional<bool> hasEdenMountInKernelMountTable(
74+
const std::string& mountPath) {
75+
#ifdef __linux__
76+
MountInfoOptions options;
77+
options.includeMountSource = true;
78+
auto result = getMountInfoForPath(mountPath.c_str(), options);
79+
if (result.hasError()) {
80+
XLOGF(
81+
WARN,
82+
"Failed to read kernel mount table for {}: {}",
83+
mountPath,
84+
folly::errnoStr(result.error()));
85+
// The mount table check is inconclusive. Continue with the .eden probes
86+
// instead of reporting a mount-health issue from the failed lookup itself.
87+
return std::nullopt;
88+
}
89+
90+
const auto& mountInfo = result.value();
91+
if (!mountInfo.has_value()) {
92+
return false;
93+
}
94+
return isEdenMountInfo(mountInfo->mountSource, mountInfo->fsType);
95+
#else
96+
(void)mountPath;
97+
return true;
98+
#endif
99+
}
100+
101+
std::string makeExpectedMissingPath(const std::string& mountPath) {
102+
// Use a fresh negative lookup path each time so kernel/FUSE negative-entry
103+
// caching does not hide whether EdenFS can answer a lookup now.
104+
static std::atomic<uint64_t> nextHealthCheckPathId{0};
105+
auto id = nextHealthCheckPathId.fetch_add(1, std::memory_order_relaxed);
106+
return mountPath + "/.eden/edenfs-mount-health-" + std::to_string(id);
107+
}
108+
109+
} // namespace
110+
111+
std::string_view edenMountHealthIssueReasonString(
112+
EdenMountHealthIssueReason reason) {
113+
switch (reason) {
114+
case EdenMountHealthIssueReason::DaemonRunningKernelMountMissing:
115+
return "daemon_running_kernel_mount_missing";
116+
case EdenMountHealthIssueReason::DaemonRunningDotEdenMissing:
117+
return "daemon_running_dot_eden_missing";
118+
case EdenMountHealthIssueReason::DaemonRunningMountNotConnected:
119+
return "daemon_running_mount_not_connected";
120+
case EdenMountHealthIssueReason::DaemonRunningMountTimedOut:
121+
return "daemon_running_mount_timed_out";
122+
case EdenMountHealthIssueReason::DaemonRunningMountAccessError:
123+
return "daemon_running_mount_access_error";
124+
}
125+
return "unknown";
126+
}
127+
128+
std::optional<EdenMountHealthCheckIssue> checkRunningEdenMountHealth(
129+
const std::string& mountPath) {
130+
auto hasKernelMount = hasEdenMountInKernelMountTable(mountPath);
131+
if (hasKernelMount.has_value() && !hasKernelMount.value()) {
132+
return EdenMountHealthCheckIssue{
133+
EdenMountHealthIssueReason::DaemonRunningKernelMountMissing,
134+
"EdenFS mount is missing from the kernel mount table"};
135+
}
136+
// If the mount table lookup failed, keep going. The path probes below can
137+
// still identify a disconnected, missing, or hanging Eden mount.
138+
139+
auto dotEdenPath = mountPath + "/.eden";
140+
auto dotEdenError = lstatError(dotEdenPath);
141+
if (dotEdenError.has_value()) {
142+
auto err = dotEdenError.value();
143+
if (err == ENOENT) {
144+
return EdenMountHealthCheckIssue{
145+
EdenMountHealthIssueReason::DaemonRunningDotEdenMissing,
146+
dotEdenPath + ": " + folly::errnoStr(err)};
147+
}
148+
return issueForPathAccessError(dotEdenPath, err);
149+
}
150+
151+
// Since .eden was readable, probe a child path that should not exist. A
152+
// healthy mount should answer ENOENT; disconnected or hanging mounts can
153+
// surface as ENOTCONN/ENXIO/ETIMEDOUT.
154+
auto expectedMissingPath = makeExpectedMissingPath(mountPath);
155+
auto expectedMissingError = lstatError(expectedMissingPath);
156+
if (!expectedMissingError.has_value()) {
157+
// The probe path unexpectedly exists, so this check is inconclusive.
158+
return std::nullopt;
159+
}
160+
161+
auto err = expectedMissingError.value();
162+
if (err == ENOENT) {
163+
// The mount handled lookup normally and reported the probe path missing.
164+
return std::nullopt;
165+
}
166+
return issueForPathAccessError(expectedMissingPath, err);
167+
}
168+
169+
} // namespace facebook::eden

eden/fs/utils/MountHealthCheck.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This software may be used and distributed according to the terms of the
5+
* GNU General Public License version 2.
6+
*/
7+
8+
#pragma once
9+
10+
#include <optional>
11+
#include <string>
12+
#include <string_view>
13+
14+
namespace facebook::eden {
15+
16+
enum class EdenMountHealthIssueReason {
17+
DaemonRunningKernelMountMissing,
18+
DaemonRunningDotEdenMissing,
19+
DaemonRunningMountNotConnected,
20+
DaemonRunningMountTimedOut,
21+
DaemonRunningMountAccessError,
22+
};
23+
24+
std::string_view edenMountHealthIssueReasonString(
25+
EdenMountHealthIssueReason reason);
26+
27+
struct EdenMountHealthCheckIssue {
28+
EdenMountHealthIssueReason reason;
29+
std::string error;
30+
};
31+
32+
// Checks a daemon-reported RUNNING mount against the kernel mount table and
33+
// lightweight .eden path probes. Returns an issue only when the mount is
34+
// clearly missing, disconnected, timing out, or returning path access errors.
35+
std::optional<EdenMountHealthCheckIssue> checkRunningEdenMountHealth(
36+
const std::string& mountPath);
37+
38+
} // namespace facebook::eden

eden/fs/utils/test/BUCK

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
load("@fbcode_macros//build_defs:cpp_benchmark.bzl", "cpp_benchmark")
22
load("@fbcode_macros//build_defs:cpp_unittest.bzl", "cpp_unittest")
3+
load("@fbsource//tools/build_defs/testinfra:network_access_utils.bzl", "network_access_utils")
34

45
oncall("scm_client_infra")
56

@@ -143,6 +144,17 @@ cpp_unittest(
143144
],
144145
)
145146

147+
cpp_unittest(
148+
name = "mount_health_check_test",
149+
srcs = ["MountHealthCheckTest.cpp"],
150+
network_access = network_access_utils.none(),
151+
supports_static_listing = False,
152+
deps = [
153+
"//eden/fs/utils:mount_health_check",
154+
"//folly/portability:gtest",
155+
],
156+
)
157+
146158
cpp_benchmark(
147159
name = "bench",
148160
srcs = [
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This software may be used and distributed according to the terms of the
5+
* GNU General Public License version 2.
6+
*/
7+
8+
#include "eden/fs/utils/MountHealthCheck.h"
9+
10+
#include <folly/portability/GTest.h>
11+
12+
namespace facebook::eden {
13+
namespace {
14+
15+
#ifdef __linux__
16+
TEST(MountHealthCheckTest, reportsMissingKernelMountForNonEdenMount) {
17+
auto issue = checkRunningEdenMountHealth("/");
18+
19+
ASSERT_TRUE(issue.has_value());
20+
EXPECT_EQ(
21+
EdenMountHealthIssueReason::DaemonRunningKernelMountMissing,
22+
issue->reason);
23+
}
24+
#endif
25+
26+
TEST(MountHealthCheckTest, reasonStringsMatchTelemetryContract) {
27+
EXPECT_EQ(
28+
"daemon_running_kernel_mount_missing",
29+
edenMountHealthIssueReasonString(
30+
EdenMountHealthIssueReason::DaemonRunningKernelMountMissing));
31+
EXPECT_EQ(
32+
"daemon_running_dot_eden_missing",
33+
edenMountHealthIssueReasonString(
34+
EdenMountHealthIssueReason::DaemonRunningDotEdenMissing));
35+
EXPECT_EQ(
36+
"daemon_running_mount_not_connected",
37+
edenMountHealthIssueReasonString(
38+
EdenMountHealthIssueReason::DaemonRunningMountNotConnected));
39+
EXPECT_EQ(
40+
"daemon_running_mount_timed_out",
41+
edenMountHealthIssueReasonString(
42+
EdenMountHealthIssueReason::DaemonRunningMountTimedOut));
43+
EXPECT_EQ(
44+
"daemon_running_mount_access_error",
45+
edenMountHealthIssueReasonString(
46+
EdenMountHealthIssueReason::DaemonRunningMountAccessError));
47+
}
48+
49+
} // namespace
50+
} // namespace facebook::eden

0 commit comments

Comments
 (0)