in watchman/watcher/inotify.cpp [235:402]
bool InotifyWatcher::process_inotify_event(
const std::shared_ptr<Root>& root,
PendingChanges& coll,
struct inotify_event* ine,
std::chrono::system_clock::time_point now) {
char flags_label[128];
w_expand_flags(inflags, ine->mask, flags_label, sizeof(flags_label));
logf(
DBG,
"notify: wd={} mask={:x} {} {}\n",
ine->wd,
ine->mask,
flags_label,
ine->len > 0 ? ine->name : "");
if (ringBuffer_) {
ringBuffer_->write(InotifyLogEntry{ine});
}
if (ine->wd == -1 && (ine->mask & IN_Q_OVERFLOW)) {
/* we missed something, will need to re-crawl */
root->scheduleRecrawl("IN_Q_OVERFLOW");
} else if (ine->wd != -1) {
w_string name;
char buf[WATCHMAN_NAME_MAX];
PendingFlags pending_flags = W_PENDING_VIA_NOTIFY;
w_string dir_name;
{
auto rlock = maps.rlock();
auto it = rlock->wd_to_name.find(ine->wd);
if (it != rlock->wd_to_name.end()) {
dir_name = it->second;
}
}
if (dir_name) {
if (ine->len > 0) {
// TODO: What if this truncates?
snprintf(
buf,
sizeof(buf),
"%.*s/%s",
int(dir_name.size()),
dir_name.data(),
ine->name);
name = w_string(buf, W_STRING_BYTE);
} else {
name = dir_name;
}
}
if (ine->len > 0 &&
(ine->mask & (IN_MOVED_FROM | IN_ISDIR)) ==
(IN_MOVED_FROM | IN_ISDIR)) {
// record this as a pending move, so that we can automatically
// watch the target when we get the other side of it.
{
auto wlock = maps.wlock();
wlock->move_map.emplace(ine->cookie, pending_move(now, name));
}
log(DBG, "recording move_from ", ine->cookie, " ", name, "\n");
}
if (ine->len > 0 &&
(ine->mask & (IN_MOVED_TO | IN_ISDIR)) == (IN_MOVED_FROM | IN_ISDIR)) {
auto wlock = maps.wlock();
auto it = wlock->move_map.find(ine->cookie);
if (it != wlock->move_map.end()) {
auto& old = it->second;
int wd =
inotify_add_watch(infd.fd(), name.c_str(), WATCHMAN_INOTIFY_MASK);
if (wd == -1) {
if (errno == ENOSPC || errno == ENOMEM) {
// Limits exceeded, no recovery from our perspective
set_poison_state(
name,
now,
"inotify-add-watch",
std::error_code(errno, inotify_category()));
} else {
watchman::log(
watchman::DBG,
"add_watch: ",
name,
" ",
inotify_category().message(errno),
"\n");
}
} else {
logf(DBG, "moved {} -> {}\n", old.name.c_str(), name.c_str());
// TODO: assert that there is no entry in wd_to_name
wlock->wd_to_name[wd] = name;
}
} else {
logf(
DBG,
"move: cookie={:x} not found in move map {}\n",
ine->cookie,
name);
}
}
if (dir_name) {
if ((ine->mask &
(IN_UNMOUNT | IN_IGNORED | IN_DELETE_SELF | IN_MOVE_SELF))) {
if (w_string_equal(root->root_path, name)) {
logf(
ERR,
"root dir {} has been (re)moved, canceling watch\n",
root->root_path);
return true;
}
// We need to examine the parent and potentially crawl down
auto pname = name.dirName();
logf(DBG, "mask={:x}, focus on parent: {}\n", ine->mask, pname);
name = pname;
}
if (ine->mask & (IN_CREATE | IN_DELETE)) {
pending_flags.set(W_PENDING_RECURSIVE);
}
logf(
DBG,
"add_pending for inotify mask={:x} {}\n",
ine->mask,
name.c_str());
coll.add(name, now, pending_flags);
if (ine->mask & (IN_CREATE | IN_DELETE)) {
// When a directory's child is created or unlinked, inotify does not
// tell us its parent has also changed. It should be rescanned, so
// synthesize an event for the IO thread here.
coll.add(name.dirName(), now, W_PENDING_VIA_NOTIFY);
}
// The kernel removed the wd -> name mapping, so let's update
// our state here also
if (ine->mask & IN_IGNORED) {
logf(
DBG,
"mask={:x}: remove watch {} {}\n",
ine->mask,
ine->wd,
dir_name);
auto wlock = maps.wlock();
wlock->wd_to_name.erase(ine->wd);
}
} else if ((ine->mask & (IN_MOVE_SELF | IN_IGNORED)) == 0) {
// If we can't resolve the dir, and this isn't notification
// that it has gone away, then we want to recrawl to fix
// up our state.
logf(
ERR,
"wanted dir {} for mask {:x} but not found {}\n",
ine->wd,
ine->mask,
ine->name);
root->scheduleRecrawl("dir missing from internal state");
}
}
return false;
}