好的,各位观众老爷,欢迎来到今天的“文件系统侦察兵训练营”!今天咱们要聊聊C++里那些“偷窥狂”技术,哦不,是文件系统监听技术,它们能让你像个老练的侦探一样,实时掌握文件世界的风吹草动。
开场白:文件系统的秘密花园
想象一下,你的程序就像个好奇宝宝,对操作系统中的文件系统充满了好奇。它想知道:
- “有没有文件被创建了?”
- “有没有文件被修改了?”
- “有没有文件被删除了?(嘤嘤嘤,我的数据!)”
- “有没有文件夹里多了个新邻居?”
要满足这个好奇宝宝,我们就需要一些工具,让它能偷偷地(咳咳,光明正大地)监听文件系统的变化。在C++的世界里,有两个主要的“偷窥”手段:
- Linux的
inotify
: 这是一个Linux内核提供的强大API,专门用来监听文件系统事件。它就像一个训练有素的特工,轻巧灵活,效率很高。 - Windows的
ReadDirectoryChangesW
: 这是Windows API提供的功能,也能让你监视目录中的变化。它就像一个穿着西装的绅士,稳重可靠,功能全面。
今天,我们就来分别训练我们的特工和绅士,让他们成为合格的文件系统侦察兵!
第一课:inotify
特工的训练
inotify
是Linux的专属技能,它允许你监视文件和目录,当发生特定事件时,你会收到通知。
1.1 准备工作:召唤inotify
首先,我们需要包含头文件 <sys/inotify.h>
,并创建一个inotify
实例:
#include <iostream>
#include <sys/inotify.h>
#include <unistd.h> // for close()
#include <limits.h> // for PATH_MAX
#include <cstring> // for strcpy, strncpy
int main() {
// 创建 inotify 实例
int fd = inotify_init();
if (fd == -1) {
perror("inotify_init");
return 1;
}
std::cout << "Inotify instance created successfully." << std::endl;
// ... 接下来添加监视
return 0;
}
这里的 inotify_init()
函数会返回一个文件描述符,我们用它来与 inotify
系统交互。如果返回 -1,说明初始化失败,赶紧看看是不是出了什么幺蛾子。
1.2 选择目标:添加监视
接下来,我们需要告诉 inotify
我们要监视哪个文件或目录。这就要用到 inotify_add_watch()
函数:
#include <iostream>
#include <sys/inotify.h>
#include <unistd.h> // for close()
#include <limits.h> // for PATH_MAX
#include <cstring> // for strcpy, strncpy
int main() {
// 创建 inotify 实例
int fd = inotify_init();
if (fd == -1) {
perror("inotify_init");
return 1;
}
std::cout << "Inotify instance created successfully." << std::endl;
// 添加监视
const char *path = "/tmp/test_dir"; // 你要监视的目录
int wd = inotify_add_watch(fd, path, IN_CREATE | IN_MODIFY | IN_DELETE);
if (wd == -1) {
perror("inotify_add_watch");
close(fd);
return 1;
}
std::cout << "Watching " << path << "..." << std::endl;
// ... 接下来读取事件
return 0;
}
fd
:就是我们之前创建的inotify
实例的文件描述符。path
:要监视的文件或目录的路径。mask
:一个位掩码,指定要监视的事件类型。
mask
可以是以下这些值的组合(用 |
运算符连接):
事件类型 | 含义 |
---|---|
IN_ACCESS |
文件被访问时 (例如,读取文件)。 |
IN_MODIFY |
文件被修改时。 ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— |
IN_ATTRIB |
文件元数据被修改时 (例如,权限、时间戳等)。 |
IN_CREATE |
在被监视的目录中创建新文件或目录时。 |
IN_DELETE |
在被监视的目录中删除文件或目录时。 |
IN_DELETE_SELF |
监视的文件或目录自身被删除时。 |
IN_MOVE_SELF |
监视的文件或目录自身被移动时。 |
IN_MOVE_FROM |
文件从被监视的目录中移出时。 |
IN_MOVE_TO |
文件被移入被监视的目录时。 |
IN_OPEN |
文件被打开时。 |
IN_CLOSE_WRITE |
文件以可写模式关闭时。 |
IN_CLOSE_NOWRITE |
文件以只读模式关闭时。 |
IN_ALL_EVENTS |
监视所有事件。 |
IN_ONLYDIR |
只监视目录,忽略文件。 |
IN_DONT_FOLLOW |
不跟踪符号链接。 |
IN_EXCL_UNLINK |
在unlink后排除事件。 |
IN_MASK_ADD |
将事件添加到现有掩码中。 |
IN_ISDIR |
事件是目录时设置。 |
IN_ONESHOT |
只监视一次事件。 |
inotify_add_watch()
函数返回一个监视描述符 (watch descriptor),我们用它来标识这个监视。如果返回 -1,说明添加监视失败,赶紧检查路径是否正确,权限是否足够。
1.3 坚守岗位:读取事件
添加了监视之后,我们的 inotify
特工就开始坚守岗位,等待事件发生。我们需要不断地读取 inotify
实例的文件描述符,才能获取事件信息:
#include <iostream>
#include <sys/inotify.h>
#include <unistd.h> // for close()
#include <limits.h> // for PATH_MAX
#include <cstring> // for strcpy, strncpy
int main() {
// 创建 inotify 实例
int fd = inotify_init();
if (fd == -1) {
perror("inotify_init");
return 1;
}
std::cout << "Inotify instance created successfully." << std::endl;
// 添加监视
const char *path = "/tmp/test_dir"; // 你要监视的目录
int wd = inotify_add_watch(fd, path, IN_CREATE | IN_MODIFY | IN_DELETE);
if (wd == -1) {
perror("inotify_add_watch");
close(fd);
return 1;
}
std::cout << "Watching " << path << "..." << std::endl;
// 读取事件
char buffer[4096];
while (true) {
ssize_t len = read(fd, buffer, sizeof(buffer));
if (len == -1) {
perror("read");
break;
}
// 解析事件
char *ptr = buffer;
while (ptr < buffer + len) {
struct inotify_event *event = (struct inotify_event *)ptr;
if (event->mask & IN_CREATE) {
if (event->mask & IN_ISDIR) {
std::cout << "Directory created: " << event->name << std::endl;
} else {
std::cout << "File created: " << event->name << std::endl;
}
}
if (event->mask & IN_MODIFY) {
std::cout << "File modified: " << event->name << std::endl;
}
if (event->mask & IN_DELETE) {
if (event->mask & IN_ISDIR) {
std::cout << "Directory deleted: " << event->name << std::endl;
} else {
std::cout << "File deleted: " << event->name << std::endl;
}
}
ptr += sizeof(struct inotify_event) + event->len;
}
}
// 移除监视
inotify_rm_watch(fd, wd);
close(fd);
return 0;
}
这段代码会一直循环,直到发生错误或者程序被中断。read()
函数会阻塞,直到有事件发生。当有事件发生时,read()
函数会返回读取到的字节数,然后我们就可以解析 buffer
中的事件信息了。
struct inotify_event
结构体包含了事件的详细信息:
wd
:监视描述符,用于标识哪个监视触发了事件。mask
:事件类型,和我们之前设置的mask
值一样。cookie
:用于关联IN_MOVE_FROM
和IN_MOVE_TO
事件。len
:name
字段的长度。name
:发生事件的文件或目录的名称。
1.4 完成任务:移除监视
当我们不再需要监视某个文件或目录时,应该及时移除监视,释放资源:
inotify_rm_watch(fd, wd);
这里的 fd
是 inotify
实例的文件描述符,wd
是要移除的监视描述符。
1.5 优雅谢幕:关闭inotify
实例
最后,当我们不再需要使用 inotify
时,应该关闭 inotify
实例:
close(fd);
第二课:ReadDirectoryChangesW
绅士的训练
ReadDirectoryChangesW
是 Windows API 提供的功能,它也能让你监视目录中的变化。
2.1 准备工作:召唤Windows.h
首先,我们需要包含头文件 <Windows.h>
:
#include <iostream>
#include <Windows.h>
#include <vector>
#include <string>
int main() {
// ... 接下来打开目录
return 0;
}
2.2 选择目标:打开目录
接下来,我们需要打开要监视的目录:
#include <iostream>
#include <Windows.h>
#include <vector>
#include <string>
int main() {
// 打开目录
const wchar_t *path = L"C:\temp\test_dir"; // 你要监视的目录
HANDLE hDirectory = CreateFileW(
path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL
);
if (hDirectory == INVALID_HANDLE_VALUE) {
std::wcerr << L"CreateFileW failed: " << GetLastError() << std::endl;
return 1;
}
std::wcout << L"Watching " << path << "..." << std::endl;
// ... 接下来读取事件
return 0;
}
path
:要监视的目录的路径,注意这里要使用宽字符wchar_t
。GENERIC_READ
:指定我们要读取目录的权限。FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
:指定其他进程可以读取、写入和删除目录中的文件。FILE_FLAG_BACKUP_SEMANTICS
:允许我们监视没有读取权限的目录。FILE_FLAG_OVERLAPPED
:使用异步 I/O,避免阻塞主线程。
CreateFileW()
函数会返回一个目录句柄 HANDLE
,我们用它来与目录交互。如果返回 INVALID_HANDLE_VALUE
,说明打开目录失败,赶紧看看是不是路径写错了,权限不够。
2.3 坚守岗位:读取事件
打开了目录之后,我们的 ReadDirectoryChangesW
绅士就开始坚守岗位,等待事件发生。我们需要调用 ReadDirectoryChangesW()
函数来读取事件信息:
#include <iostream>
#include <Windows.h>
#include <vector>
#include <string>
int main() {
// 打开目录
const wchar_t *path = L"C:\temp\test_dir"; // 你要监视的目录
HANDLE hDirectory = CreateFileW(
path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL
);
if (hDirectory == INVALID_HANDLE_VALUE) {
std::wcerr << L"CreateFileW failed: " << GetLastError() << std::endl;
return 1;
}
std::wcout << L"Watching " << path << "..." << std::endl;
// 读取事件
BYTE buffer[4096];
DWORD bytesReturned;
OVERLAPPED overlapped;
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
while (true) {
BOOL success = ReadDirectoryChangesW(
hDirectory,
buffer,
sizeof(buffer),
TRUE, // 是否监视子目录
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY,
&bytesReturned,
&overlapped,
NULL
);
if (!success) {
std::wcerr << L"ReadDirectoryChangesW failed: " << GetLastError() << std::endl;
break;
}
// 等待 I/O 完成
DWORD waitResult = WaitForSingleObject(hDirectory, INFINITE);
if (waitResult == WAIT_FAILED) {
std::wcerr << L"WaitForSingleObject failed: " << GetLastError() << std::endl;
break;
}
// 解析事件
FILE_NOTIFY_INFORMATION *pNotify = (FILE_NOTIFY_INFORMATION *)buffer;
do {
std::wstring filename(pNotify->FileName, pNotify->FileNameLength / sizeof(wchar_t));
switch (pNotify->Action) {
case FILE_ACTION_ADDED:
std::wcout << L"File created: " << filename << std::endl;
break;
case FILE_ACTION_REMOVED:
std::wcout << L"File deleted: " << filename << std::endl;
break;
case FILE_ACTION_MODIFIED:
std::wcout << L"File modified: " << filename << std::endl;
break;
case FILE_ACTION_RENAMED_OLD_NAME:
std::wcout << L"File renamed from: " << filename << std::endl;
break;
case FILE_ACTION_RENAMED_NEW_NAME:
std::wcout << L"File renamed to: " << filename << std::endl;
break;
default:
std::wcout << L"Unknown action: " << pNotify->Action << std::endl;
break;
}
pNotify = (FILE_NOTIFY_INFORMATION *)((BYTE *)pNotify + pNotify->NextEntryOffset);
} while (pNotify->NextEntryOffset != 0);
}
// 关闭目录句柄
CloseHandle(hDirectory);
return 0;
}
hDirectory
:就是我们之前打开的目录句柄。buffer
:用于接收事件信息的缓冲区。nBufferLength
:缓冲区的大小。bWatchSubtree
:是否监视子目录。dwNotifyFilter
:一个位掩码,指定要监视的事件类型。lpBytesReturned
:实际读取到的字节数。lpOverlapped
:一个OVERLAPPED
结构体,用于异步 I/O。lpCompletionRoutine
:一个回调函数,用于处理事件,这里我们使用 NULL。
dwNotifyFilter
可以是以下这些值的组合(用 |
运算符连接):
事件类型 | 含义 |
---|