Skip to content

Commit bebdce9

Browse files
committed
[目录监控功能增强]:支持更多编译选项和文件系统监控方式
- 新增了对不同编译选项的支持:在`build.yml`文件中,增加了对`Debug`、`Release`和`MinSizeRel`编译选项的支持,以便于在不同的构建类型下进行测试和构建。 - 对`MonitorDir`目录监控功能的增强:在`MonitorDir`目录下的`CMakeLists.txt`中,增加了对`fanotify`的支持,这是一种比`inotify`更强大的文件系统监控机制,尤其是在需要监控整个文件系统时。同时,也提供了对`inotify`的替代实现,以适应不同的使用场景。 - 代码结构和可读性的改进:在`main.cc`中,通过使用`std::filesystem`来获取当前路径,提高了代码的可读性和易用性。同时,对`monitordir_linux_fanotify.cc`和`monitordir_linux_inotify.cc`文件进行了重构和优化,以支持新的监控方式。 - 对MacOS监控实现的优化:在`monitordir_mac.cc`中,增加了对更多文件系统事件的处理,使得在MacOS上的目录监控更加全面。 - 对Windows监控实现的优化:在`monitordir_win.cc`中,增加了对文件操作的详细描述,使得在Windows系统上的目录监控更加精确。 - 更新了`README.md`文件:在`README.md`中,增加了对新添加的`fanotify`监控方式的说明,以及对如何在普通用户下运行监控的建议。 - 其他脚本和配置文件的更新:对`setVsDev.ps1`和`vcpkg.json`文件进行了更新,以适应新的开发环境和依赖管理需求。
1 parent cd13cdf commit bebdce9

10 files changed

+517
-121
lines changed

.github/workflows/build.yml

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626
- macos-latest
2727
- ubuntu-latest
2828
build_type:
29+
- "Debug"
30+
- "Release"
31+
- "MinSizeRel"
2932
- "RelWithDebInfo"
3033
generators:
3134
- "Ninja"

MonitorDir/CMakeLists.txt

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
if(CMAKE_HOST_WIN32)
2-
add_executable(MonitorDir main.cc monitordir_win.cc monitordir.cc
3-
monitordir.hpp)
2+
add_executable(monitor_dir main.cc monitordir_win.cc monitordir.cc
3+
monitordir.hpp)
44
elseif(CMAKE_HOST_APPLE)
5-
add_executable(MonitorDir main.cc monitordir_mac.cc monitordir.cc
6-
monitordir.hpp)
7-
target_link_libraries(MonitorDir "-framework CoreServices")
5+
add_executable(monitor_dir main.cc monitordir_mac.cc monitordir.cc
6+
monitordir.hpp)
7+
target_link_libraries(monitor_dir "-framework CoreServices")
88
elseif(CMAKE_HOST_LINUX)
9-
add_executable(MonitorDir main.cc monitordir_linux.cc monitordir.cc
10-
monitordir.hpp)
11-
target_link_libraries(MonitorDir PRIVATE pthread)
9+
add_executable(monitor_dir_inotify main.cc monitordir_linux_inotify.cc
10+
monitordir.cc monitordir.hpp)
11+
target_link_libraries(monitor_dir_inotify PRIVATE pthread)
12+
13+
include(CheckSymbolExists)
14+
check_symbol_exists(fanotify_init "sys/fanotify.h" HAVE_FANOTIFY_INIT)
15+
if(HAVE_FANOTIFY_INIT)
16+
message(STATUS "Fanotify support is available.")
17+
add_executable(monitor_dir_fanotify main.cc monitordir_linux_fanotify.cc
18+
monitordir.cc monitordir.hpp)
19+
target_link_libraries(monitor_dir_fanotify PRIVATE pthread)
20+
endif()
1221
endif()

MonitorDir/main.cc

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
#include "monitordir.hpp"
22

3+
#include <filesystem>
34
#include <iostream>
45
#include <thread>
56

67
auto main(int argc, char *argv[]) -> int
78
{
8-
MonitorDir monitorDir(".");
9+
auto filepath = std::filesystem::current_path();
10+
std::cout << filepath << std::endl;
11+
12+
MonitorDir monitorDir(filepath);
913
monitorDir.start();
1014

1115
std::this_thread::sleep_for(std::chrono::seconds(60));
+293
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
#include "monitordir.hpp"
2+
3+
#include <cassert>
4+
#include <climits>
5+
#include <cstring>
6+
#include <fcntl.h>
7+
#include <iostream>
8+
#include <string>
9+
#include <sys/fanotify.h>
10+
#include <sys/stat.h>
11+
#include <thread>
12+
#include <unistd.h>
13+
14+
#define BUF_SIZE 256
15+
16+
std::string getPathFromFd(int fd)
17+
{
18+
char path[PATH_MAX];
19+
char procfd_path[PATH_MAX];
20+
snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", fd);
21+
ssize_t len = readlink(procfd_path, path, sizeof(path) - 1);
22+
if (len >= 0) {
23+
path[len] = '\0'; // 确保字符串以空字符结尾
24+
return std::string(path);
25+
}
26+
return "Unknown"; // 读取失败时的处理
27+
}
28+
29+
std::string maskToString(unsigned long long mask)
30+
{
31+
if ((mask & FAN_ACCESS) != 0U) {
32+
return "FAN_ACCESS: ";
33+
}
34+
if ((mask & FAN_MODIFY) != 0U) {
35+
return "FAN_MODIFY: ";
36+
}
37+
if ((mask & FAN_ATTRIB) != 0U) {
38+
return "FAN_ATTRIB: ";
39+
}
40+
if ((mask & FAN_CLOSE_WRITE) != 0U) {
41+
return "FAN_CLOSE_WRITE: ";
42+
}
43+
if ((mask & FAN_CLOSE_NOWRITE) != 0U) {
44+
return "FAN_CLOSE_NOWRITE: ";
45+
}
46+
if ((mask & FAN_OPEN) != 0U) {
47+
return "FAN_OPEN: ";
48+
}
49+
if ((mask & FAN_MOVED_FROM) != 0U) {
50+
return "FAN_MOVED_FROM: ";
51+
}
52+
if ((mask & FAN_MOVED_TO) != 0U) {
53+
return "FAN_MOVED_TO: ";
54+
}
55+
if ((mask & FAN_CREATE) != 0U) {
56+
return "FAN_CREATE: ";
57+
}
58+
if ((mask & FAN_DELETE) != 0U) {
59+
return "FAN_DELETE: ";
60+
}
61+
if ((mask & FAN_DELETE_SELF) != 0U) {
62+
return "FAN_DELETE_SELF: ";
63+
}
64+
if ((mask & FAN_MOVE_SELF) != 0U) {
65+
return "FAN_MOVE_SELF: ";
66+
}
67+
if ((mask & FAN_OPEN_EXEC) != 0U) {
68+
return "FAN_OPEN_EXEC: ";
69+
}
70+
if ((mask & FAN_Q_OVERFLOW) != 0U) {
71+
return "FAN_Q_OVERFLOW: ";
72+
}
73+
if ((mask & FAN_FS_ERROR) != 0U) {
74+
return "FAN_FS_ERROR: ";
75+
}
76+
if ((mask & FAN_OPEN_PERM) != 0U) {
77+
return "FAN_OPEN_PERM: ";
78+
}
79+
if ((mask & FAN_ACCESS_PERM) != 0U) {
80+
return "FAN_ACCESS_PERM: ";
81+
}
82+
if ((mask & FAN_OPEN_EXEC_PERM) != 0U) {
83+
return "FAN_OPEN_EXEC_PERM: ";
84+
}
85+
if ((mask & FAN_EVENT_ON_CHILD) != 0U) {
86+
return "FAN_EVENT_ON_CHILD: ";
87+
}
88+
if ((mask & FAN_RENAME) != 0U) {
89+
return "FAN_RENAME: ";
90+
}
91+
if ((mask & FAN_ONDIR) != 0U) {
92+
return "FAN_ONDIR: ";
93+
}
94+
if ((mask & FAN_CLOSE) != 0U) {
95+
return "FAN_CLOSE: ";
96+
}
97+
if ((mask & FAN_MOVE) != 0U) {
98+
return "FAN_MOVE: ";
99+
}
100+
return "Unknown mask " + std::to_string(mask) + ": ";
101+
}
102+
103+
class MonitorDir::MonitorDirPrivate
104+
{
105+
public:
106+
explicit MonitorDirPrivate(MonitorDir *q)
107+
: q_ptr(q)
108+
{}
109+
110+
~MonitorDirPrivate() = default;
111+
112+
auto createFd() -> bool
113+
{
114+
mountFd = open(dir.c_str(), O_DIRECTORY | O_RDONLY);
115+
if (mountFd == -1) {
116+
perror("open");
117+
exit(EXIT_FAILURE);
118+
}
119+
std::cout << "mountFd: " << mountFd << std::endl;
120+
121+
unsigned int flags = FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_UNLIMITED_QUEUE
122+
| FAN_UNLIMITED_MARKS | FAN_ENABLE_AUDIT | FAN_REPORT_FID
123+
| FAN_REPORT_DFID_NAME;
124+
unsigned int event_f_flags = O_RDONLY | O_LARGEFILE | O_CLOEXEC;
125+
// 普通用户级别flags
126+
// flags = FAN_CLASS_NOTIF | FAN_CLOEXEC | FAN_REPORT_FID | FAN_REPORT_DFID_NAME;
127+
fanotifyFd = fanotify_init(flags, event_f_flags);
128+
if (fanotifyFd == -1) {
129+
perror("fanotify_init");
130+
exit(EXIT_FAILURE);
131+
}
132+
std::cout << "fanotifyFd: " << fanotifyFd << std::endl;
133+
return true;
134+
}
135+
136+
void closeFd()
137+
{
138+
if (mountFd != -1) {
139+
close(mountFd);
140+
mountFd = -1;
141+
}
142+
if (fanotifyFd != -1) {
143+
close(fanotifyFd);
144+
fanotifyFd = -1;
145+
}
146+
}
147+
148+
auto addWatch() -> bool
149+
{
150+
// FAN_MARK_FILESYSTEM 全局监控,需要 CAP_SYS_ADMIN 能力,即 root 权限
151+
unsigned int flags = FAN_MARK_ADD | FAN_MARK_FILESYSTEM;
152+
unsigned int mask = FAN_ACCESS | FAN_MODIFY | FAN_ATTRIB | FAN_CLOSE_WRITE | FAN_MOVED_FROM
153+
| FAN_MOVED_TO | FAN_CREATE | FAN_DELETE | FAN_DELETE_SELF
154+
| FAN_MOVE_SELF | FAN_OPEN_EXEC | FAN_EVENT_ON_CHILD | FAN_RENAME
155+
| FAN_ONDIR | FAN_MOVE;
156+
if (fanotify_mark(fanotifyFd, flags, mask, AT_FDCWD, dir.c_str()) == -1) {
157+
perror("fanotify_mark");
158+
exit(EXIT_FAILURE);
159+
}
160+
std::cout << "add watch: " << dir << std::endl;
161+
162+
return true;
163+
}
164+
165+
void monitor()
166+
{
167+
char buf[BUF_SIZE];
168+
auto len = read(fanotifyFd, buf, sizeof(buf));
169+
if (!isRunning.load()) {
170+
return;
171+
}
172+
if (len <= 0) {
173+
std::cerr << "read failed" << std::endl;
174+
return;
175+
}
176+
177+
fanotify_event_metadata *event = nullptr;
178+
for (event = reinterpret_cast<struct fanotify_event_metadata *>(buf);
179+
FAN_EVENT_OK(event, len);
180+
event = FAN_EVENT_NEXT(event, len)) {
181+
if (!isRunning.load()) {
182+
return; // 全局监控,会产生大量事件,需要及时处理退出事件
183+
}
184+
185+
const auto *fid = reinterpret_cast<const struct fanotify_event_info_fid *>(event + 1);
186+
auto *file_handle = (struct file_handle *) fid->handle;
187+
188+
std::string path;
189+
auto event_fd = open_by_handle_at(mountFd, file_handle, O_RDONLY);
190+
if (event_fd >= 0) {
191+
path = getPathFromFd(event_fd);
192+
close(event_fd);
193+
if (path.find(dir.string()) == std::string::npos) {
194+
continue; // 全局监控,不在目标目录下的文件不处理
195+
}
196+
} else {
197+
if (errno == ESTALE) {
198+
std::cerr << "File handle is no longer valid. File has been deleted"
199+
<< std::endl;
200+
continue;
201+
}
202+
perror("open_by_handle_at");
203+
// exit(EXIT_FAILURE);
204+
}
205+
206+
std::string filename;
207+
if (fid->hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) {
208+
auto *file_name = file_handle->f_handle + file_handle->handle_bytes;
209+
filename = std::string(file_name,
210+
file_name
211+
+ strlen(reinterpret_cast<const char *>(file_name)));
212+
} else {
213+
std::cerr << "Received unexpected event info type: "
214+
<< std::to_string(fid->hdr.info_type) << std::endl;
215+
}
216+
217+
auto fileEvent = maskToString(event->mask) + path;
218+
if (!filename.empty()) {
219+
fileEvent += "/" + filename;
220+
}
221+
std::cout << fileEvent << std::endl;
222+
}
223+
}
224+
225+
void run()
226+
{
227+
if (!createFd()) {
228+
return;
229+
}
230+
231+
if (!addWatch()) {
232+
closeFd();
233+
return;
234+
}
235+
isRunning.store(true);
236+
while (isRunning.load()) {
237+
monitor();
238+
}
239+
240+
closeFd();
241+
}
242+
243+
MonitorDir *q_ptr;
244+
245+
int mountFd = -1;
246+
int fanotifyFd = -1;
247+
248+
std::filesystem::path dir;
249+
std::atomic_bool isRunning;
250+
std::thread monitorThread;
251+
};
252+
253+
MonitorDir::MonitorDir(const std::filesystem::path &dir)
254+
: d_ptr(std::make_unique<MonitorDirPrivate>(this))
255+
, m_dir(dir)
256+
, m_isRunning(false)
257+
{
258+
assert(std::filesystem::is_directory(dir) && std::filesystem::exists(dir));
259+
d_ptr->dir = dir;
260+
}
261+
262+
MonitorDir::~MonitorDir()
263+
{
264+
stop();
265+
}
266+
267+
auto MonitorDir::start() -> bool
268+
{
269+
if (m_isRunning) {
270+
std::cerr << "MonitorDir is already running" << std::endl;
271+
return false;
272+
}
273+
274+
m_isRunning.store(true);
275+
d_ptr->monitorThread = std::thread([this] {
276+
d_ptr->run();
277+
m_isRunning.store(false);
278+
});
279+
280+
return true;
281+
}
282+
283+
void MonitorDir::stop()
284+
{
285+
if (!m_isRunning.load()) {
286+
std::cerr << "MonitorDir is not running" << std::endl;
287+
return;
288+
}
289+
d_ptr->isRunning.store(false);
290+
if (d_ptr->monitorThread.joinable()) {
291+
d_ptr->monitorThread.join();
292+
}
293+
}

0 commit comments

Comments
 (0)