in watchman/root/iothread.cpp [620:836]
void InMemoryView::statPath(
const RootConfig& root,
const CookieSync& cookies,
ViewDatabase& view,
PendingChanges& coll,
const PendingChange& pending,
const DirEntry* pre_stat) {
bool recursive = pending.flags.contains(W_PENDING_RECURSIVE);
const bool via_notify = pending.flags.contains(W_PENDING_VIA_NOTIFY);
const PendingFlags desynced_flag = pending.flags & W_PENDING_IS_DESYNCED;
if (root.ignore.isIgnoreDir(pending.path)) {
logf(DBG, "{} matches ignore_dir rules\n", pending.path);
return;
}
auto& path = pending.path;
w_check(path, "must have path");
auto dir_name = pending.path.dirName();
auto file_name = pending.path.baseName();
w_check(dir_name, "must have dir_name");
auto parentDir = view.resolveDir(dir_name, true);
auto file = parentDir->getChildFile(file_name);
auto dir_ent = parentDir->getChildDir(file_name);
FileInformation st;
std::error_code errcode;
if (pre_stat && pre_stat->has_stat) {
st = pre_stat->stat;
} else {
try {
st = fileSystem_.getFileInformation(path.c_str(), root.case_sensitive);
log(DBG,
"getFileInformation(",
path,
") file=",
fmt::ptr(file),
" dir=",
fmt::ptr(dir_ent),
"\n");
} catch (const std::system_error& exc) {
errcode = exc.code();
log(DBG,
"getFileInformation(",
path,
") file=",
fmt::ptr(file),
" dir=",
fmt::ptr(dir_ent),
" failed: ",
exc.what(),
"\n");
}
}
if (processedPaths_) {
processedPaths_->write(PendingChangeLogEntry{pending, errcode, st});
}
if (errcode == error_code::no_such_file_or_directory ||
errcode == error_code::not_a_directory) {
/* it's not there, update our state */
if (dir_ent) {
view.markDirDeleted(*watcher_, dir_ent, getClock(pending.now), true);
log(DBG,
"getFileInformation(",
path,
") -> ",
errcode.message(),
" so stopping watch\n");
}
if (file) {
if (file->exists) {
log(DBG,
"getFileInformation(",
path,
") -> ",
errcode.message(),
" so marking ",
file->getName(),
" deleted\n");
file->exists = false;
view.markFileChanged(*watcher_, file, getClock(pending.now));
}
} else {
// It was created and removed before we could ever observe it
// in the filesystem. We need to generate a deleted file
// representation of it now, so that subscription clients can
// be notified of this event
file = view.getOrCreateChildFile(
*watcher_, parentDir, file_name, getClock(pending.now));
log(DBG,
"getFileInformation(",
path,
") -> ",
errcode.message(),
" and file node was NULL. "
"Generating a deleted node.\n");
file->exists = false;
view.markFileChanged(*watcher_, file, getClock(pending.now));
}
if (root.case_sensitive == CaseSensitivity::CaseInSensitive &&
dir_name != root.root_path && parentDir->last_check_existed) {
/* If we rejected the name because it wasn't canonical,
* we need to ensure that we look in the parent dir to discover
* the new item(s) */
logf(
DBG,
"we're case insensitive, and {} is ENOENT, "
"speculatively look at parent dir {}\n",
path,
dir_name);
coll.add(dir_name, pending.now, W_PENDING_CRAWL_ONLY);
}
} else if (errcode.value()) {
log(ERR,
"getFileInformation(",
path,
") failed and not handled! -> ",
errcode.message(),
" value=",
errcode.value(),
" category=",
errcode.category().name(),
"\n");
} else {
if (!file) {
file = view.getOrCreateChildFile(
*watcher_, parentDir, file_name, getClock(pending.now));
}
if (!file->exists) {
/* we're transitioning from deleted to existing,
* so we're effectively new again */
file->ctime.ticks = mostRecentTick_;
file->ctime.timestamp = std::chrono::system_clock::to_time_t(pending.now);
/* if a dir was deleted and now exists again, we want
* to crawl it again */
recursive = true;
}
if (!file->exists || via_notify || did_file_change(&file->stat, &st)) {
logf(
DBG,
"file changed exists={} via_notify={} stat-changed={} isdir={} size={} {}\n",
file->exists,
via_notify,
file->exists && !via_notify,
st.isDir(),
st.size,
path);
file->exists = true;
view.markFileChanged(*watcher_, file, getClock(pending.now));
// If the inode number changed then we definitely need to recursively
// examine any children because we cannot assume that the kernel will
// have given us the correct hints about this change. BTRFS is one
// example of a filesystem where this has been observed to happen.
if (file->stat.ino != st.ino) {
recursive = true;
}
}
memcpy(&file->stat, &st, sizeof(file->stat));
if (st.isDir()) {
if (dir_ent == NULL) {
recursive = true;
} else {
// Ensure that we believe that this node exists
dir_ent->last_check_existed = true;
}
// Don't recurse if our parent is an ignore dir
if (!root.ignore.isIgnoreVCS(dir_name) ||
// but do if we're looking at the cookie dir (stat_path is never
// called for the root itself)
cookies.isCookieDir(pending.path)) {
if (recursive) {
/* we always need to crawl if we're recursive, this can happen when a
* directory is created */
coll.add(
pending.path,
pending.now,
desynced_flag | W_PENDING_RECURSIVE | W_PENDING_CRAWL_ONLY);
} else if (pending.flags & W_PENDING_NONRECURSIVE_SCAN) {
/* on file changes, we receive a notification on the directory and
* thus we just need to crawl this one directory to consider all
* the pending files. */
coll.add(
pending.path,
pending.now,
desynced_flag | W_PENDING_NONRECURSIVE_SCAN |
W_PENDING_CRAWL_ONLY);
} else {
if (watcher_->flags & WATCHER_HAS_PER_FILE_NOTIFICATIONS) {
/* we get told about changes on the child, so we don't need to do
* anything */
} else {
/* in all the other cases, crawl */
coll.add(
pending.path,
pending.now,
desynced_flag | W_PENDING_CRAWL_ONLY);
}
}
}
} else if (dir_ent) {
// We transitioned from dir to file (see fishy.php), so we should prune
// our former tree here
view.markDirDeleted(*watcher_, dir_ent, getClock(pending.now), true);
}
}
}