std::unique_ptr FSEventsWatcher::fse_stream_make()

in watchman/watcher/fsevents.cpp [304:471]


std::unique_ptr<FSEventsStream> FSEventsWatcher::fse_stream_make(
    const std::shared_ptr<Root>& root,
    FSEventsWatcher* watcher,
    FSEventStreamEventId since,
    w_string& failure_reason) {
  auto ctx = FSEventStreamContext();
  unique_ref<CFMutableArrayRef> parray;
  unique_ref<CFStringRef> cpath;
  double latency;
  FSEventStreamCreateFlags flags;
  w_string path;

  auto fse_stream = std::make_unique<FSEventsStream>(root, watcher, since);

  // Each device has an optional journal maintained by fseventsd that keeps
  // track of the change events.  The journal may not be available if the
  // filesystem was mounted read-only.  The journal has an associated UUID
  // to track the version of the data.  In some cases the journal can become
  // invalidated and it will have a new UUID generated.  This can happen
  // if the EventId rolls over.
  // We need to lookup up the UUID for the associated path and use that to
  // help decide whether we can use a value of `since` other than SinceNow.
  struct stat st;
  if (stat(root->root_path.c_str(), &st)) {
    failure_reason = w_string::build(
        "failed to stat(",
        root->root_path,
        "): ",
        folly::errnoStr(errno),
        "\n");
    return nullptr;
  }

  // Obtain the UUID for the device associated with the root
  fse_stream->uuid =
      unique_ref<CFUUIDRef>{FSEventsCopyUUIDForDevice(st.st_dev)};
  if (since != kFSEventStreamEventIdSinceNow) {
    CFUUIDBytes a, b;

    if (!fse_stream->uuid) {
      // If there is no UUID available and we want to use an event offset,
      // we fail: a nullptr UUID means that the journal is not available.
      failure_reason = w_string::build(
          "fsevents journal is not available for dev_t=", st.st_dev, "\n");
      return nullptr;
    }
    // Compare the UUID with that of the current stream
    if (!watcher->stream_->uuid) {
      failure_reason = w_string(
          "fsevents journal was not available for prior stream",
          W_STRING_UNICODE);
      return nullptr;
    }

    a = CFUUIDGetUUIDBytes(fse_stream->uuid.get());
    b = CFUUIDGetUUIDBytes(watcher->stream_->uuid.get());

    if (memcmp(&a, &b, sizeof(a)) != 0) {
      failure_reason =
          w_string("fsevents journal UUID is different", W_STRING_UNICODE);
      return nullptr;
    }
  }

  ctx.info = fse_stream.get();

  parray.reset(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks));
  if (!parray) {
    failure_reason = w_string("CFArrayCreateMutable failed", W_STRING_UNICODE);
    return nullptr;
  }

  if (auto subdir = watcher->subdir) {
    path = *subdir;
  } else {
    path = root->root_path;
  }

  cpath.reset(CFStringCreateWithBytes(
      nullptr,
      (const UInt8*)path.data(),
      path.size(),
      kCFStringEncodingUTF8,
      false));
  if (!cpath) {
    failure_reason =
        w_string("CFStringCreateWithBytes failed", W_STRING_UNICODE);
    return nullptr;
  }

  CFArrayAppendValue(parray.get(), cpath.get());

  latency = root->config.getDouble("fsevents_latency", 0.01),
  logf(
      DBG,
      "FSEventStreamCreate for path {} with latency {} seconds\n",
      path,
      latency);

  flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagWatchRoot;
  if (watcher->hasFileWatching_) {
    flags |= kFSEventStreamCreateFlagFileEvents;
  }
  fse_stream->stream = FSEventStreamCreate(
      nullptr, fse_callback, &ctx, parray.get(), since, latency, flags);

  if (!fse_stream->stream) {
    failure_reason = w_string("FSEventStreamCreate failed", W_STRING_UNICODE);
    return nullptr;
  }

  FSEventStreamScheduleWithRunLoop(
      fse_stream->stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

  if (root->config.getBool("_use_fsevents_exclusions", true)) {
    auto& dirs_vec = root->ignore.getIgnoredDirs();

    size_t nitems = std::min(dirs_vec.size(), kMaxExclusions);
    size_t appended = 0;

    unique_ref<CFMutableArrayRef> ignarray{
        CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)};
    if (!ignarray) {
      failure_reason =
          w_string("CFArrayCreateMutable failed", W_STRING_UNICODE);
      return nullptr;
    }

    for (const auto& path : dirs_vec) {
      if (const auto& subdir = watcher->subdir) {
        if (!w_string_startswith(path, *subdir)) {
          continue;
        }
        logf(DBG, "Adding exclusion: {} for subdir: {}\n", path, *subdir);
      }

      unique_ref<CFStringRef> ignpath{CFStringCreateWithBytes(
          nullptr,
          (const UInt8*)path.data(),
          path.size(),
          kCFStringEncodingUTF8,
          false)};

      if (!ignpath) {
        failure_reason =
            w_string("CFStringCreateWithBytes failed", W_STRING_UNICODE);
        return nullptr;
      }

      CFArrayAppendValue(ignarray.get(), ignpath.get());

      appended++;
      if (appended == nitems) {
        break;
      }
    }

    if (appended != 0) {
      if (!FSEventStreamSetExclusionPaths(fse_stream->stream, ignarray.get())) {
        failure_reason =
            w_string("FSEventStreamSetExclusionPaths failed", W_STRING_UNICODE);
        return nullptr;
      }
    }
  }

  return fse_stream;
}