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/Path.h" 15 #include <CoreServices/CoreServices.h> 16 17 using namespace llvm; 18 using namespace clang; 19 20 static FSEventStreamRef createFSEventStream( 21 StringRef Path, 22 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>, 23 dispatch_queue_t); 24 static void stopFSEventStream(FSEventStreamRef); 25 26 namespace { 27 28 class DirectoryWatcherMac : public clang::DirectoryWatcher { 29 public: 30 DirectoryWatcherMac( 31 FSEventStreamRef EventStream, 32 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> 33 Receiver, 34 llvm::StringRef WatchedDirPath) 35 : EventStream(EventStream), Receiver(Receiver), 36 WatchedDirPath(WatchedDirPath) {} 37 38 ~DirectoryWatcherMac() override { 39 stopFSEventStream(EventStream); 40 EventStream = nullptr; 41 // Now it's safe to use Receiver as the only other concurrent use would have 42 // been in EventStream processing. 43 Receiver(DirectoryWatcher::Event( 44 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""), 45 false); 46 } 47 48 private: 49 FSEventStreamRef EventStream; 50 std::function<void(llvm::ArrayRef<Event>, bool)> Receiver; 51 const std::string WatchedDirPath; 52 }; 53 54 struct EventStreamContextData { 55 std::string WatchedPath; 56 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver; 57 58 EventStreamContextData( 59 std::string &&WatchedPath, 60 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> 61 Receiver) 62 : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {} 63 64 // Needed for FSEvents 65 static void dispose(const void *ctx) { 66 delete static_cast<const EventStreamContextData *>(ctx); 67 } 68 }; 69 } // namespace 70 71 constexpr const FSEventStreamEventFlags StreamInvalidatingFlags = 72 kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped | 73 kFSEventStreamEventFlagMustScanSubDirs; 74 75 constexpr const FSEventStreamEventFlags ModifyingFileEvents = 76 kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed | 77 kFSEventStreamEventFlagItemModified; 78 79 static void eventStreamCallback(ConstFSEventStreamRef Stream, 80 void *ClientCallBackInfo, size_t NumEvents, 81 void *EventPaths, 82 const FSEventStreamEventFlags EventFlags[], 83 const FSEventStreamEventId EventIds[]) { 84 auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo); 85 86 std::vector<DirectoryWatcher::Event> Events; 87 for (size_t i = 0; i < NumEvents; ++i) { 88 StringRef Path = ((const char **)EventPaths)[i]; 89 const FSEventStreamEventFlags Flags = EventFlags[i]; 90 91 if (Flags & StreamInvalidatingFlags) { 92 Events.emplace_back(DirectoryWatcher::Event{ 93 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); 94 break; 95 } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) { 96 // Subdirectories aren't supported - if some directory got removed it 97 // must've been the watched directory itself. 98 if ((Flags & kFSEventStreamEventFlagItemRemoved) && 99 Path == ctx->WatchedPath) { 100 Events.emplace_back(DirectoryWatcher::Event{ 101 DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""}); 102 Events.emplace_back(DirectoryWatcher::Event{ 103 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); 104 break; 105 } 106 // No support for subdirectories - just ignore everything. 107 continue; 108 } else if (Flags & kFSEventStreamEventFlagItemRemoved) { 109 Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed, 110 llvm::sys::path::filename(Path)); 111 continue; 112 } else if (Flags & ModifyingFileEvents) { 113 if (!getFileStatus(Path).hasValue()) { 114 Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed, 115 llvm::sys::path::filename(Path)); 116 } else { 117 Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified, 118 llvm::sys::path::filename(Path)); 119 } 120 continue; 121 } 122 123 // default 124 Events.emplace_back(DirectoryWatcher::Event{ 125 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); 126 llvm_unreachable("Unknown FSEvent type."); 127 } 128 129 if (!Events.empty()) { 130 ctx->Receiver(Events, /*IsInitial=*/false); 131 } 132 } 133 134 FSEventStreamRef createFSEventStream( 135 StringRef Path, 136 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, 137 dispatch_queue_t Queue) { 138 if (Path.empty()) 139 return nullptr; 140 141 CFMutableArrayRef PathsToWatch = [&]() { 142 CFMutableArrayRef PathsToWatch = 143 CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); 144 CFStringRef CfPathStr = 145 CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(), 146 Path.size(), kCFStringEncodingUTF8, false); 147 CFArrayAppendValue(PathsToWatch, CfPathStr); 148 CFRelease(CfPathStr); 149 return PathsToWatch; 150 }(); 151 152 FSEventStreamContext Context = [&]() { 153 std::string RealPath; 154 { 155 SmallString<128> Storage; 156 StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage); 157 char Buffer[PATH_MAX]; 158 if (::realpath(P.begin(), Buffer) != nullptr) 159 RealPath = Buffer; 160 else 161 RealPath = Path; 162 } 163 164 FSEventStreamContext Context; 165 Context.version = 0; 166 Context.info = new EventStreamContextData(std::move(RealPath), Receiver); 167 Context.retain = nullptr; 168 Context.release = EventStreamContextData::dispose; 169 Context.copyDescription = nullptr; 170 return Context; 171 }(); 172 173 FSEventStreamRef Result = FSEventStreamCreate( 174 nullptr, eventStreamCallback, &Context, PathsToWatch, 175 kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0, 176 kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer); 177 CFRelease(PathsToWatch); 178 179 return Result; 180 } 181 182 void stopFSEventStream(FSEventStreamRef EventStream) { 183 if (!EventStream) 184 return; 185 FSEventStreamStop(EventStream); 186 FSEventStreamInvalidate(EventStream); 187 FSEventStreamRelease(EventStream); 188 } 189 190 std::unique_ptr<DirectoryWatcher> clang::DirectoryWatcher::create( 191 StringRef Path, 192 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, 193 bool WaitForInitialSync) { 194 dispatch_queue_t Queue = 195 dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL); 196 197 if (Path.empty()) 198 return nullptr; 199 200 auto EventStream = createFSEventStream(Path, Receiver, Queue); 201 if (!EventStream) { 202 return nullptr; 203 } 204 205 std::unique_ptr<DirectoryWatcher> Result = 206 llvm::make_unique<DirectoryWatcherMac>(EventStream, Receiver, Path); 207 208 // We need to copy the data so the lifetime is ok after a const copy is made 209 // for the block. 210 const std::string CopiedPath = Path; 211 212 auto InitWork = ^{ 213 // We need to start watching the directory before we start scanning in order 214 // to not miss any event. By dispatching this on the same serial Queue as 215 // the FSEvents will be handled we manage to start watching BEFORE the 216 // inital scan and handling events ONLY AFTER the scan finishes. 217 FSEventStreamSetDispatchQueue(EventStream, Queue); 218 FSEventStreamStart(EventStream); 219 // We need to decrement the ref count for Queue as initialize() will return 220 // and FSEvents has incremented it. Since we have to wait for FSEvents to 221 // take ownership it's the easiest to do it here rather than main thread. 222 dispatch_release(Queue); 223 Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true); 224 }; 225 226 if (WaitForInitialSync) { 227 dispatch_sync(Queue, InitWork); 228 } else { 229 dispatch_async(Queue, InitWork); 230 } 231 232 return Result; 233 } 234