Coverage Report

Created: 2023-05-31 04:38

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/DirectoryWatcher/mac/DirectoryWatcher-mac.cpp
Line
Count
Source (jump to first uncovered line)
1
//===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
2
//
3
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4
// See https://llvm.org/LICENSE.txt for license information.
5
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
//
7
//===----------------------------------------------------------------------===//
8
9
#include "DirectoryScanner.h"
10
#include "clang/DirectoryWatcher/DirectoryWatcher.h"
11
12
#include "llvm/ADT/STLExtras.h"
13
#include "llvm/ADT/StringRef.h"
14
#include "llvm/Support/Error.h"
15
#include "llvm/Support/Path.h"
16
#include <CoreServices/CoreServices.h>
17
#include <TargetConditionals.h>
18
19
using namespace llvm;
20
using namespace clang;
21
22
#if TARGET_OS_OSX
23
24
static void stopFSEventStream(FSEventStreamRef);
25
26
namespace {
27
28
/// This implementation is based on FSEvents API which implementation is
29
/// aggressively coallescing events. This can manifest as duplicate events.
30
///
31
/// For example this scenario has been observed:
32
///
33
/// create foo/bar
34
/// sleep 5 s
35
/// create DirectoryWatcherMac for dir foo
36
/// receive notification: bar EventKind::Modified
37
/// sleep 5 s
38
/// modify foo/bar
39
/// receive notification: bar EventKind::Modified
40
/// receive notification: bar EventKind::Modified
41
/// sleep 5 s
42
/// delete foo/bar
43
/// receive notification: bar EventKind::Modified
44
/// receive notification: bar EventKind::Modified
45
/// receive notification: bar EventKind::Removed
46
class DirectoryWatcherMac : public clang::DirectoryWatcher {
47
public:
48
  DirectoryWatcherMac(
49
      dispatch_queue_t Queue, FSEventStreamRef EventStream,
50
      std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
51
          Receiver,
52
      llvm::StringRef WatchedDirPath)
53
      : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
54
8
        WatchedDirPath(WatchedDirPath) {}
55
56
8
  ~DirectoryWatcherMac() override {
57
    // FSEventStreamStop and Invalidate must be called after Start and
58
    // SetDispatchQueue to follow FSEvents API contract. The call to Receiver
59
    // also uses Queue to not race with the initial scan.
60
8
    dispatch_sync(Queue, ^{
61
8
      stopFSEventStream(EventStream);
62
8
      EventStream = nullptr;
63
8
      Receiver(
64
8
          DirectoryWatcher::Event(
65
8
              DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
66
8
          false);
67
8
    });
68
69
    // Balance initial creation.
70
8
    dispatch_release(Queue);
71
8
  }
72
73
private:
74
  dispatch_queue_t Queue;
75
  FSEventStreamRef EventStream;
76
  std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
77
  const std::string WatchedDirPath;
78
};
79
80
struct EventStreamContextData {
81
  std::string WatchedPath;
82
  std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
83
84
  EventStreamContextData(
85
      std::string &&WatchedPath,
86
      std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
87
          Receiver)
88
8
      : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
89
90
  // Needed for FSEvents
91
8
  static void dispose(const void *ctx) {
92
8
    delete static_cast<const EventStreamContextData *>(ctx);
93
8
  }
94
};
95
} // namespace
96
97
constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
98
    kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
99
    kFSEventStreamEventFlagMustScanSubDirs;
100
101
constexpr const FSEventStreamEventFlags ModifyingFileEvents =
102
    kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
103
    kFSEventStreamEventFlagItemModified;
104
105
static void eventStreamCallback(ConstFSEventStreamRef Stream,
106
                                void *ClientCallBackInfo, size_t NumEvents,
107
                                void *EventPaths,
108
                                const FSEventStreamEventFlags EventFlags[],
109
4
                                const FSEventStreamEventId EventIds[]) {
110
4
  auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
111
112
4
  std::vector<DirectoryWatcher::Event> Events;
113
12
  for (size_t i = 0; i < NumEvents; 
++i8
) {
114
9
    StringRef Path = ((const char **)EventPaths)[i];
115
9
    const FSEventStreamEventFlags Flags = EventFlags[i];
116
117
9
    if (Flags & StreamInvalidatingFlags) {
118
0
      Events.emplace_back(DirectoryWatcher::Event{
119
0
          DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
120
0
      break;
121
9
    } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
122
      // Subdirectories aren't supported - if some directory got removed it
123
      // must've been the watched directory itself.
124
4
      if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
125
4
          
Path == ctx->WatchedPath1
) {
126
1
        Events.emplace_back(DirectoryWatcher::Event{
127
1
            DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
128
1
        Events.emplace_back(DirectoryWatcher::Event{
129
1
            DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
130
1
        break;
131
1
      }
132
      // No support for subdirectories - just ignore everything.
133
3
      continue;
134
5
    } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
135
1
      Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
136
1
                          llvm::sys::path::filename(Path));
137
1
      continue;
138
4
    } else if (Flags & ModifyingFileEvents) {
139
4
      if (!getFileStatus(Path).has_value()) {
140
0
        Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
141
0
                            llvm::sys::path::filename(Path));
142
4
      } else {
143
4
        Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
144
4
                            llvm::sys::path::filename(Path));
145
4
      }
146
4
      continue;
147
4
    }
148
149
    // default
150
0
    Events.emplace_back(DirectoryWatcher::Event{
151
0
        DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
152
0
    llvm_unreachable("Unknown FSEvent type.");
153
0
  }
154
155
4
  if (!Events.empty()) {
156
4
    ctx->Receiver(Events, /*IsInitial=*/false);
157
4
  }
158
4
}
159
160
FSEventStreamRef createFSEventStream(
161
    StringRef Path,
162
    std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
163
8
    dispatch_queue_t Queue) {
164
8
  if (Path.empty())
165
0
    return nullptr;
166
167
8
  CFMutableArrayRef PathsToWatch = [&]() {
168
8
    CFMutableArrayRef PathsToWatch =
169
8
        CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
170
8
    CFStringRef CfPathStr =
171
8
        CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
172
8
                                Path.size(), kCFStringEncodingUTF8, false);
173
8
    CFArrayAppendValue(PathsToWatch, CfPathStr);
174
8
    CFRelease(CfPathStr);
175
8
    return PathsToWatch;
176
8
  }();
177
178
8
  FSEventStreamContext Context = [&]() {
179
8
    std::string RealPath;
180
8
    {
181
8
      SmallString<128> Storage;
182
8
      StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
183
8
      char Buffer[PATH_MAX];
184
8
      if (::realpath(P.begin(), Buffer) != nullptr)
185
8
        RealPath = Buffer;
186
0
      else
187
0
        RealPath = Path.str();
188
8
    }
189
190
8
    FSEventStreamContext Context;
191
8
    Context.version = 0;
192
8
    Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
193
8
    Context.retain = nullptr;
194
8
    Context.release = EventStreamContextData::dispose;
195
8
    Context.copyDescription = nullptr;
196
8
    return Context;
197
8
  }();
198
199
8
  FSEventStreamRef Result = FSEventStreamCreate(
200
8
      nullptr, eventStreamCallback, &Context, PathsToWatch,
201
8
      kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
202
8
      kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
203
8
  CFRelease(PathsToWatch);
204
205
8
  return Result;
206
8
}
207
208
8
void stopFSEventStream(FSEventStreamRef EventStream) {
209
8
  if (!EventStream)
210
0
    return;
211
8
  FSEventStreamStop(EventStream);
212
8
  FSEventStreamInvalidate(EventStream);
213
8
  FSEventStreamRelease(EventStream);
214
8
}
215
216
llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
217
    StringRef Path,
218
    std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
219
8
    bool WaitForInitialSync) {
220
8
  dispatch_queue_t Queue =
221
8
      dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
222
223
8
  if (Path.empty())
224
0
    llvm::report_fatal_error(
225
0
        "DirectoryWatcher::create can not accept an empty Path.");
226
227
8
  auto EventStream = createFSEventStream(Path, Receiver, Queue);
228
8
  assert(EventStream && "EventStream expected to be non-null");
229
230
8
  std::unique_ptr<DirectoryWatcher> Result =
231
8
      std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
232
233
  // We need to copy the data so the lifetime is ok after a const copy is made
234
  // for the block.
235
8
  const std::string CopiedPath = Path.str();
236
237
8
  auto InitWork = ^{
238
    // We need to start watching the directory before we start scanning in order
239
    // to not miss any event. By dispatching this on the same serial Queue as
240
    // the FSEvents will be handled we manage to start watching BEFORE the
241
    // inital scan and handling events ONLY AFTER the scan finishes.
242
8
    FSEventStreamSetDispatchQueue(EventStream, Queue);
243
8
    FSEventStreamStart(EventStream);
244
8
    Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
245
8
  };
246
247
8
  if (WaitForInitialSync) {
248
6
    dispatch_sync(Queue, InitWork);
249
6
  } else {
250
2
    dispatch_async(Queue, InitWork);
251
2
  }
252
253
8
  return Result;
254
8
}
255
256
#else // TARGET_OS_OSX
257
258
llvm::Expected<std::unique_ptr<DirectoryWatcher>>
259
clang::DirectoryWatcher::create(
260
    StringRef Path,
261
    std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
262
    bool WaitForInitialSync) {
263
  return llvm::make_error<llvm::StringError>(
264
      "DirectoryWatcher is not implemented for this platform!",
265
      llvm::inconvertibleErrorCode());
266
}
267
268
#endif // TARGET_OS_OSX