bool InotifyWatcher::process_inotify_event()

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