void InMemoryView::statPath()

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);
    }
  }
}