linux inotify的一些坑
inotify使用场景
linux提供了一种机制,可以动态感知文件的变化:inotify。
使用inotify可以感知到某个目录或者某个文件所有的动态操作。
和poll结合使用
可以在poll中监听inotify_fd,来达到动态监听文件变更的目的。
遇到的坑
不过最近在使用的时候,遇到了一些坑。
最初是想要直接监听某个json文件的变更,结果发现vim文件后,收到了一堆events,并且监听也失效了,如下代码:
#include <iostream>
#include <poll.h>
#include <string.h>
#include <string>
#include <sys/inotify.h>
#include <unistd.h>
void display_event(struct inotify_event *event) {
std::string operate;
if (event->mask & IN_ACCESS)
operate = "ACCESS";
if (event->mask & IN_ATTRIB)
operate = "ATTRIB";
if (event->mask & IN_CLOSE_WRITE)
operate = "CLOSE_WRITE";
if (event->mask & IN_CLOSE_NOWRITE)
operate = "CLOSE_NOWRITE";
if (event->mask & IN_CREATE)
operate = "CREATE";
if (event->mask & IN_DELETE_SELF)
operate = "DELETE_SELF";
if (event->mask & IN_MODIFY)
operate = "MODIFY";
if (event->mask & IN_MOVE_SELF)
operate = "MOVE_SELF";
if (event->mask & IN_MOVED_FROM)
operate = "MOVED_FROM";
if (event->mask & IN_MOVED_TO)
operate = "MOVED_TO";
if (event->mask & IN_OPEN)
operate = "OPEN";
if (event->mask & IN_IGNORED)
operate = "IGNORED";
if (event->mask & IN_DELETE)
operate = "DELETE";
if (event->mask & IN_UNMOUNT)
operate = "UNMOUNT";
std::cout << "event name:" << event->name << " operate:" << operate
<< std::endl;
}
int main() {
int inotify_fd = -1;
int inotify_watch = -1;
std::string file_path("/home/minipc/test/test.json");
inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
inotify_watch =
inotify_add_watch(inotify_fd, file_path.c_str(), IN_ALL_EVENTS);
std::cout << "inotify_fd:" << inotify_fd << " inotify_watch:" << inotify_watch
<< " file path:" << file_path << std::endl;
while (true) {
constexpr int nfds = 1;
struct pollfd fds[nfds];
int16_t events = 0;
fds[0] = {inotify_fd, POLLIN, 0};
int nready = ::poll(fds, nfds, -1);
if (nready < 0) {
std::cout << "poll failed, nready:" << nready
<< " err:" << strerror(errno) << std::endl;
continue;
}
for (int i = 0; i < nfds; ++i) {
int fd = fds[i].fd;
int revents = fds[i].revents;
if (fd == inotify_fd) {
if (revents & POLLIN) {
char events[4096];
struct inotify_event *event;
int nbytes, offset;
nbytes = ::read(fd, events, sizeof(events));
for (offset = 0; offset < nbytes;) {
event = (struct inotify_event *)&events[offset];
display_event(event);
offset += sizeof(struct inotify_event) + event->len;
}
}
}
}
}
return 0;
}
- 这里第一个坑是inotify_init是阻塞式的。如果想要非阻塞,需要使用inotify_init1接口。
- 第二个坑就是直接监听文件,如果通过vim修改文件,会上来一堆事件,并且并没有截获IN_MODIFY事件,如下:
event name: operate:OPEN
event name: operate:CLOSE_NOWRITE
event name: operate:OPEN
event name: operate:CLOSE_NOWRITE
event name: operate:MOVE_SELF
event name: operate:ATTRIB
event name: operate:DELETE_SELF
event name: operate:IGNORED
并且再次通过vim修改test.json,也不会上来event事件了。
这里原因如下:
- 上来很多事件是因为vim编辑时会先将文件保存为a.swap,等编辑完毕后再移动回来或者删除,具体移动回来还是删除,取决于是退出vim时时保存操作,还是取消操作。所以这里根本没有触发IN_MODIFY事件,实际上触发的是IN_MOVE_TO事件(a文件被移出),以及IN_IGNORED事件等。
- 再次修改不会上来event事件,也是因为vim修改文件后,本质文件fd已经变了。
修改方式:
上面两个问题,可能有人会这么处理,poll轮询的时候每次都重新生成inotify_fd,再重新监听,但是这种操作比较麻烦,代码写的也不是很简洁,所以这里可以换种思路,直接监听上层目录,这样fd就不会变,也不用反复重新监听。
修改后代码如下:
#include <iostream>
#include <poll.h>
#include <string.h>
#include <string>
#include <sys/inotify.h>
#include <unistd.h>
void display_event(struct inotify_event *event) {
std::string operate;
if (event->mask & IN_ACCESS)
operate = "ACCESS";
if (event->mask & IN_ATTRIB)
operate = "ATTRIB";
if (event->mask & IN_CLOSE_WRITE)
operate = "CLOSE_WRITE";
if (event->mask & IN_CLOSE_NOWRITE)
operate = "CLOSE_NOWRITE";
if (event->mask & IN_CREATE)
operate = "CREATE";
if (event->mask & IN_DELETE_SELF)
operate = "DELETE_SELF";
if (event->mask & IN_MODIFY)
operate = "MODIFY";
if (event->mask & IN_MOVE_SELF)
operate = "MOVE_SELF";
if (event->mask & IN_MOVED_FROM)
operate = "MOVED_FROM";
if (event->mask & IN_MOVED_TO)
operate = "MOVED_TO";
if (event->mask & IN_OPEN)
operate = "OPEN";
if (event->mask & IN_IGNORED)
operate = "IGNORED";
if (event->mask & IN_DELETE)
operate = "DELETE";
if (event->mask & IN_UNMOUNT)
operate = "UNMOUNT";
if ((event->mask & IN_MODIFY) && (0 == strcmp("test.json", event->name))) {
std::cout << "event name:" << event->name << " operate:" << operate
<< std::endl;
}
}
int main() {
int inotify_fd = -1;
int inotify_watch = -1;
std::string file_path("/home/minipc/test");
inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
inotify_watch =
inotify_add_watch(inotify_fd, file_path.c_str(), IN_ALL_EVENTS);
std::cout << "inotify_fd:" << inotify_fd << " inotify_watch:" << inotify_watch
<< " file path:" << file_path << std::endl;
while (true) {
constexpr int nfds = 1;
struct pollfd fds[nfds];
int16_t events = 0;
fds[0] = {inotify_fd, POLLIN, 0};
int nready = ::poll(fds, nfds, -1);
if (nready < 0) {
std::cout << "poll failed, nready:" << nready
<< " err:" << strerror(errno) << std::endl;
continue;
}
for (int i = 0; i < nfds; ++i) {
int fd = fds[i].fd;
int revents = fds[i].revents;
if (fd == inotify_fd) {
if (revents & POLLIN) {
char events[4096];
struct inotify_event *event;
int nbytes, offset;
nbytes = ::read(fd, events, sizeof(events));
for (offset = 0; offset < nbytes;) {
event = (struct inotify_event *)&events[offset];
display_event(event);
offset += sizeof(struct inotify_event) + event->len;
}
}
}
}
}
return 0;
}
修改后结果如下:
event name:test.json operate:MODIFY