Skip to content

Commit

Permalink
[功能增强]:增加Linux平台目录监控支持fanotify,提升监控能力
Browse files Browse the repository at this point in the history
- 在`.github/workflows/build.yml`中,扩展了构建类型选项,新增了"Debug"、"Release"、"MinSizeRel"三种构建类型,以适应不同的构建需求。
- 在`MonitorDir/CMakeLists.txt`中,通过预处理器指令检查`fanotify_init`符号是否存在,根据检查结果选择使用`monitordir_linux_fanotify.cc`或`monitordir_linux_inotify.cc`作为Linux平台的目录监控实现,增强了目录监控的灵活性和兼容性。
- 在`MonitorDir/main.cc`中,增加了使用`std::filesystem`获取并打印当前路径的功能,并修改了`MonitorDir`类的构造函数,使其可以接受一个路径参数,提高了程序的可用性和灵活性。
- 新增`MonitorDir/monitordir_linux_fanotify.cc`文件,实现了基于`fanotify`的目录监控功能,增强了Linux平台下的目录监控能力。
- 将原有的`MonitorDir/monitordir_linux.cc`重命名为`MonitorDir/monitordir_linux_inotify.cc`,并进行了相应的代码调整,以支持`inotify`监控机制。
- 在`README.md`中,更新了`MonitorDir`的描述,增加了对`fanotify`的支持说明,并解释了`fanotify`在全局监控方面相较于`inotify`的优势。
- 在`scripts/windows/setVsDev.ps1`中,增加了对`arm64`架构的支持,并调整了`Enter-VsDevShell`命令的参数,以适应不同的架构需求。
- 在`vcpkg.json`中,更新了`builtin-baseline`的值,以确保依赖项的一致性。
  • Loading branch information
RealChuan committed Dec 20, 2024
1 parent cd13cdf commit 14894fe
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 24 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
- macos-latest
- ubuntu-latest
build_type:
- "Debug"
- "Release"
- "MinSizeRel"
- "RelWithDebInfo"
generators:
- "Ninja"
Expand Down
16 changes: 13 additions & 3 deletions MonitorDir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ elseif(CMAKE_HOST_APPLE)
monitordir.hpp)
target_link_libraries(MonitorDir "-framework CoreServices")
elseif(CMAKE_HOST_LINUX)
add_executable(MonitorDir main.cc monitordir_linux.cc monitordir.cc
monitordir.hpp)
target_link_libraries(MonitorDir PRIVATE pthread)
include(CheckSymbolExists)
check_symbol_exists(fanotify_init "sys/fanotify.h" HAVE_FANOTIFY_INIT)
if(HAVE_FANOTIFY_INIT)
message(STATUS "Fanotify support is available.")
add_executable(MonitorDir main.cc monitordir_linux_fanotify.cc
monitordir.cc monitordir.hpp)
target_link_libraries(MonitorDir PRIVATE pthread)
else()
message(STATUS "Fanotify support is not available.")
add_executable(MonitorDir main.cc monitordir_linux_inotify.cc monitordir.cc
monitordir.hpp)
target_link_libraries(MonitorDir PRIVATE pthread)
endif()
endif()
6 changes: 5 additions & 1 deletion MonitorDir/main.cc
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#include "monitordir.hpp"

#include <filesystem>
#include <iostream>
#include <thread>

auto main(int argc, char *argv[]) -> int
{
MonitorDir monitorDir(".");
auto filepath = std::filesystem::current_path();
std::cout << filepath << std::endl;

MonitorDir monitorDir(filepath);
monitorDir.start();

std::this_thread::sleep_for(std::chrono::seconds(60));
Expand Down
267 changes: 267 additions & 0 deletions MonitorDir/monitordir_linux_fanotify.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
#include "monitordir.hpp"

#include <cassert>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <limits.h>
#include <string>
#include <sys/fanotify.h>
#include <sys/stat.h>
#include <thread>
#include <unistd.h>

#define BUF_SIZE 256

std::string getPathFromFd(int fd)
{
char path[PATH_MAX];
char procfd_path[PATH_MAX];
snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", fd);
ssize_t len = readlink(procfd_path, path, sizeof(path) - 1);
if (len >= 0) {
path[len] = '\0'; // 确保字符串以空字符结尾
return std::string(path);
}
return "Unknown"; // 读取失败时的处理
}

class MonitorDir::MonitorDirPrivate
{
public:
explicit MonitorDirPrivate(MonitorDir *q)
: q_ptr(q)
{}

~MonitorDirPrivate() = default;

auto createFd() -> bool
{
mountFd = open(dir.c_str(), O_DIRECTORY | O_RDONLY);
if (mountFd == -1) {
std::cerr << "mountFd open failed" << std::endl;
return false;
}
std::cout << "mountFd: " << mountFd << std::endl;

unsigned int flags = FAN_CLOEXEC | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS
| FAN_ENABLE_AUDIT | FAN_REPORT_FID | FAN_REPORT_DFID_NAME
| FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME;
unsigned int event_f_flags = O_RDONLY | O_LARGEFILE | O_CLOEXEC;
// flags = FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME;
// event_f_flags = 0;
fanotifyFd = fanotify_init(flags, event_f_flags);
if (fanotifyFd == -1) {
std::cerr << "fanotify_init failed" << std::endl;
return false;
}
std::cout << "fanotifyFd: " << fanotifyFd << std::endl;
return true;
}

void closeFd()
{
if (mountFd != -1) {
close(mountFd);
mountFd = -1;
}
if (fanotifyFd != -1) {
close(fanotifyFd);
fanotifyFd = -1;
}
}

auto addWatch() -> bool
{
// FAN_MARK_FILESYSTEM 全局监控
unsigned int flags = FAN_MARK_ADD | FAN_MARK_FILESYSTEM;
unsigned int mask = FAN_ACCESS | FAN_MODIFY | FAN_CLOSE_WRITE | FAN_MOVED_FROM
| FAN_MOVED_TO | FAN_CREATE | FAN_DELETE | FAN_DELETE_SELF
| FAN_MOVE_SELF | FAN_OPEN_EXEC | FAN_EVENT_ON_CHILD | FAN_ONDIR;
// FAN_CLOSE_NOWRITE | FAN_OPEN;
// flags = FAN_MARK_ADD | FAN_MARK_ONLYDIR;
// mask = FAN_CREATE | FAN_ONDIR;
if (fanotify_mark(fanotifyFd, flags, mask, AT_FDCWD, dir.c_str()) == -1) {
std::cerr << "fanotify_mark failed" << std::endl;
return false;
}
std::cout << "add watch: " << dir << std::endl;

return true;
}

void removeWatch()
{
// Fanotify 不需要显式移除监控,关闭文件描述符时会自动清理
}

void monitor()
{
char buf[BUF_SIZE];
auto len = read(fanotifyFd, buf, sizeof(buf));
if (!isRunning.load()) {
return;
}
if (len <= 0) {
std::cerr << "read failed" << std::endl;
return;
}

std::string fileEvent;
fanotify_event_metadata *event = nullptr;
for (event = reinterpret_cast<struct fanotify_event_metadata *>(buf);
FAN_EVENT_OK(event, len);
event = FAN_EVENT_NEXT(event, len)) {
const auto *fid = reinterpret_cast<const struct fanotify_event_info_fid *>(event + 1);
auto *file_handle = (struct file_handle *) fid->handle;

std::string path;
auto event_fd = open_by_handle_at(mountFd, file_handle, O_RDONLY);
if (event_fd >= 0) {
path = getPathFromFd(event_fd);
close(event_fd);
if (path.find(dir.string()) == std::string::npos) {
continue; // 全局监控,不在目标目录下的文件不处理
}
} else {
std::cerr << "Failed to open file by handle: " << event_fd << std::endl;
}

std::string filename;
if (fid->hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) {
auto *file_name = file_handle->f_handle + file_handle->handle_bytes;
filename = std::string(file_name,
file_name
+ strlen(reinterpret_cast<const char *>(file_name)));
} else {
std::cerr << "Received unexpected event info type: " << fid->hdr.info_type
<< std::endl;
}

if ((event->mask & FAN_ACCESS) != 0U) {
fileEvent = "FAN_ACCESS: ";
} else if ((event->mask & FAN_MODIFY) != 0U) {
fileEvent = "FAN_MODIFY: ";
} else if ((event->mask & FAN_ATTRIB) != 0U) {
fileEvent = "FAN_ATTRIB: ";
} else if ((event->mask & FAN_CLOSE_WRITE) != 0U) {
fileEvent = "FAN_CLOSE_WRITE: ";
} else if ((event->mask & FAN_CLOSE_NOWRITE) != 0U) {
fileEvent = "FAN_CLOSE_NOWRITE: ";
} else if ((event->mask & FAN_OPEN) != 0U) {
fileEvent = "FAN_OPEN: ";
} else if ((event->mask & FAN_MOVED_FROM) != 0U) {
fileEvent = "FAN_MOVED_FROM: ";
} else if ((event->mask & FAN_MOVED_TO) != 0U) {
fileEvent = "FAN_MOVED_TO: ";
} else if ((event->mask & FAN_CREATE) != 0U) {
fileEvent = "FAN_CREATE: ";
} else if ((event->mask & FAN_DELETE) != 0U) {
fileEvent = "FAN_DELETE: ";
} else if ((event->mask & FAN_DELETE_SELF) != 0U) {
fileEvent = "FAN_DELETE_SELF: ";
} else if ((event->mask & FAN_MOVE_SELF) != 0U) {
fileEvent = "FAN_MOVE_SELF: ";
} else if ((event->mask & FAN_OPEN_EXEC) != 0U) {
fileEvent = "FAN_OPEN_EXEC: ";
} else if ((event->mask & FAN_Q_OVERFLOW) != 0U) {
fileEvent = "FAN_Q_OVERFLOW: ";
} else if ((event->mask & FAN_FS_ERROR) != 0U) {
fileEvent = "FAN_FS_ERROR: ";
} else if ((event->mask & FAN_OPEN_PERM) != 0U) {
fileEvent = "FAN_OPEN_PERM: ";
} else if ((event->mask & FAN_ACCESS_PERM) != 0U) {
fileEvent = "FAN_ACCESS_PERM: ";
} else if ((event->mask & FAN_OPEN_EXEC_PERM) != 0U) {
fileEvent = "FAN_OPEN_EXEC_PERM: ";
} else if ((event->mask & FAN_EVENT_ON_CHILD) != 0U) {
fileEvent = "FAN_EVENT_ON_CHILD: ";
} else if ((event->mask & FAN_RENAME) != 0U) {
fileEvent = "FAN_RENAME: ";
} else if ((event->mask & FAN_ONDIR) != 0U) {
fileEvent = "FAN_ONDIR: ";
} else if ((event->mask & FAN_CLOSE) != 0U) {
fileEvent = "FAN_CLOSE: ";
} else if ((event->mask & FAN_MOVE) != 0U) {
fileEvent = "FAN_MOVE: ";
} else {
fileEvent = "Unknown: " + std::to_string(event->mask);
}

fileEvent += path;
if (!filename.empty()) {
fileEvent += "/" + filename;
}
std::cout << fileEvent << std::endl;
}
}

void run()
{
if (!createFd()) {
return;
}

if (!addWatch()) {
closeFd();
return;
}
isRunning.store(true);
while (isRunning.load()) {
monitor();
}

closeFd();
}

MonitorDir *q_ptr;

int mountFd = -1;
int fanotifyFd = -1;

std::filesystem::path dir;
std::atomic_bool isRunning;
std::thread monitorThread;
};

MonitorDir::MonitorDir(const std::filesystem::path &dir)
: d_ptr(std::make_unique<MonitorDirPrivate>(this))
, m_dir(dir)
, m_isRunning(false)
{
assert(std::filesystem::is_directory(dir) && std::filesystem::exists(dir));
d_ptr->dir = dir;
}

MonitorDir::~MonitorDir()
{
stop();
}

auto MonitorDir::start() -> bool
{
if (m_isRunning) {
std::cerr << "MonitorDir is already running" << std::endl;
return false;
}

m_isRunning.store(true);
d_ptr->monitorThread = std::thread([this] {
d_ptr->run();
m_isRunning.store(false);
});

return true;
}

void MonitorDir::stop()
{
if (!m_isRunning.load()) {
std::cerr << "MonitorDir is not running" << std::endl;
return;
}
d_ptr->isRunning.store(false);
if (d_ptr->monitorThread.joinable()) {
d_ptr->monitorThread.join();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MonitorDir::MonitorDirPrivate

~MonitorDirPrivate() = default;

bool createFd()
auto createFd() -> bool
{
// 创建inotify实例
inotifyFd = inotify_init();
Expand Down Expand Up @@ -60,7 +60,7 @@ class MonitorDir::MonitorDirPrivate
void monitor()
{
char buf[1024];
struct inotify_event *event;

auto len = read(inotifyFd, buf, sizeof(buf));
if (!isRunning.load()) {
return;
Expand All @@ -71,28 +71,34 @@ class MonitorDir::MonitorDirPrivate
}

std::string fileEvent;
struct inotify_event *event;
for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) {
event = reinterpret_cast<struct inotify_event *>(ptr);
if ((event->mask & IN_CREATE) != 0U) {
fileEvent = "IN_CREATE: ";
} else if ((event->mask & IN_DELETE) != 0U) {
fileEvent = "IN_DELETE: ";

if ((event->mask & IN_ACCESS) != 0U) {
fileEvent = "IN_ACCESS: ";
} else if ((event->mask & IN_MODIFY) != 0U) {
fileEvent = "IN_MODIFY: ";
} else if ((event->mask & IN_MOVED_FROM) != 0U) {
fileEvent = "IN_MOVED_FROM: ";
} else if ((event->mask & IN_MOVED_TO) != 0U) {
fileEvent = "IN_MOVED_TO: ";
} else if ((event->mask & IN_ATTRIB) != 0U) {
fileEvent = "IN_ATTRIB: ";
} else if ((event->mask & IN_CLOSE_WRITE) != 0U) {
fileEvent = "IN_CLOSE_WRITE: ";
} else if ((event->mask & IN_CLOSE_NOWRITE) != 0U) {
fileEvent = "IN_CLOSE_NOWRITE: ";
} else if ((event->mask & IN_ACCESS) != 0U) {
fileEvent = "IN_ACCESS: ";
} else if ((event->mask & IN_ATTRIB) != 0U) {
fileEvent = "IN_ATTRIB: ";
} else if ((event->mask & IN_CLOSE) != 0U) {
fileEvent = "IN_CLOSE: ";
} else if ((event->mask & IN_OPEN) != 0U) {
fileEvent = "IN_OPEN: ";
} else if ((event->mask & IN_MOVED_FROM) != 0U) {
fileEvent = "IN_MOVED_FROM: ";
} else if ((event->mask & IN_MOVED_TO) != 0U) {
fileEvent = "IN_MOVED_TO: ";
} else if ((event->mask & IN_MOVE) != 0U) {
fileEvent = "IN_MOVE: ";
} else if ((event->mask & IN_CREATE) != 0U) {
fileEvent = "IN_CREATE: ";
} else if ((event->mask & IN_DELETE) != 0U) {
fileEvent = "IN_DELETE: ";
} else if ((event->mask & IN_DELETE_SELF) != 0U) {
fileEvent = "IN_DELETE_SELF: ";
} else if ((event->mask & IN_MOVE_SELF) != 0U) {
Expand All @@ -106,7 +112,7 @@ class MonitorDir::MonitorDirPrivate
} else if ((event->mask & IN_ISDIR) != 0U) {
fileEvent = "IN_ISDIR: ";
} else {
fileEvent = "UNKNOWN: ";
fileEvent = "UNKNOWN: " + std::to_string(event->mask);
}
fileEvent += event->name;
std::cout << fileEvent << std::endl;
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
19. [Icmp](/Icmp/icmp.hpp)——linux icmp协议的简单封装;
20. [LinkedList](/LinkedList/linkedlist.hpp)——链表的相关操作,插入、移除、反转、打印;
21. [Memcpy](/Memcpy/memcpy.hpp)——`memcpy`函数实现;
22. [MonitorDir](/MonitorDir/monitordir.hpp)——windows(`ReadDirectoryChangesW`),macos(`FSEvents`)和linux(`inotify`)目录监控的简单例子;
22. [MonitorDir](/MonitorDir/monitordir.hpp)——windows(`ReadDirectoryChangesW`),macos(`FSEvents`)和linux(`fanotify``inotify`)目录监控的简单例子;
1. `fanotify`中使用global模式,在`fanotify_mark`中使用`FAN_MARK_FILESYSTEM``flag`,可以所有在指定文件系统上的事件都会被监控,然后可以根据指定的监控的文件夹目录过滤需要的事件,这个功能比`inotify`更强大;
23. [Mutex](/Mutex/mutex.hpp)——使用std::atomic_flag实现的简单互斥锁和自旋锁;
24. [OpenSSL](/OpenSSL)——openssl的一些例子;
1. [aes](/OpenSSL/openssl_aes.cc)——aes加解密的例子;
Expand Down
Loading

0 comments on commit 14894fe

Please sign in to comment.