Implement auto subtree watching of a directory

Allow recursively watching a directory while adding newly created
subdirectories to the inotify watch list.
This commit is contained in:
T. R. Bernstein
2026-03-12 12:04:08 +01:00
parent a8d7fb017f
commit ffac6d17a5
4 changed files with 85 additions and 8 deletions

View File

@@ -24,7 +24,7 @@ public actor Inotify {
guard wd >= 0 else {
throw InotifyError.addWatchFailed(path: path, errno: cinotify_get_errno())
}
watches.add(path, withId: wd)
watches.add(path, withId: wd, mask: mask)
return wd
}
@@ -39,6 +39,13 @@ public actor Inotify {
return result
}
@discardableResult
public func addWatchWithAutomaticSubtreeWatching(forDirectory path: String, mask: InotifyEventMask) async throws -> [CInt] {
let wds = try await self.addRecursiveWatch(forDirectory: path, mask: mask)
watches.enableAutomaticSubtreeWatching(forIds: wds)
return wds
}
public func removeWatch(_ wd: CInt) throws {
guard inotify_rm_watch(self.fd, wd) == 0 else {
throw InotifyError.removeWatchFailed(watchDescriptor: wd, errno: cinotify_get_errno())
@@ -50,11 +57,24 @@ public actor Inotify {
cinotify_deinit(self.fd)
}
private func transform(_ rawEvent: RawInotifyEvent) -> InotifyEvent? {
private func transform(_ rawEvent: RawInotifyEvent) async -> InotifyEvent? {
guard let path = self.watches.path(forId: rawEvent.watchDescriptor) else { return nil }
let event = InotifyEvent.init(from: rawEvent, inDirectory: path)
await self.addWatchInCaseOfAutomaticSubtreeWatching(event)
return InotifyEvent.init(from: rawEvent, inDirectory: path)
}
private func addWatchInCaseOfAutomaticSubtreeWatching(_ event: InotifyEvent) async {
guard watches.isAutomaticSubtreeWatching(event.watchDescriptor),
event.mask.contains(.create),
event.mask.contains(.isDir) else {
return
}
guard let mask = self.watches.mask(forId: event.watchDescriptor) else { return }
let _ = try? await self.addWatchWithAutomaticSubtreeWatching(forDirectory: event.path.string, mask: mask)
}
private static func createEventReader(forFileDescriptor fd: CInt) -> (any DispatchSourceRead, AsyncStream<RawInotifyEvent>) {
let (stream, continuation) = AsyncStream<RawInotifyEvent>.makeStream(
of: RawInotifyEvent.self,

View File

@@ -1,18 +1,46 @@
struct InotifyWatchManager {
private var watchPaths: [CInt: String] = [:]
private var watchMasks: [CInt: InotifyEventMask] = [:]
private var activeWatches: Set<CInt> = []
private var watchesWithAutomaticSubtreeWatching: Set<CInt> = []
mutating func add(_ path: String, withId watchDescriptor: CInt) {
mutating func add(_ path: String, withId watchDescriptor: CInt, mask: InotifyEventMask) {
self.watchPaths[watchDescriptor] = path
self.watchMasks[watchDescriptor] = mask
self.activeWatches.insert(watchDescriptor)
}
mutating func enableAutomaticSubtreeWatching(forId watchDescriptor: CInt) {
assert(self.activeWatches.contains(watchDescriptor))
self.watchesWithAutomaticSubtreeWatching.insert(watchDescriptor)
}
mutating func enableAutomaticSubtreeWatching(forIds watchDescriptors: CInt...) {
self.enableAutomaticSubtreeWatching(forIds: watchDescriptors)
}
mutating func enableAutomaticSubtreeWatching(forIds watchDescriptors: [CInt]) {
for watchDescriptor in watchDescriptors {
self.enableAutomaticSubtreeWatching(forId: watchDescriptor)
}
}
mutating func remove(forId watchDescriptor: CInt) {
self.watchPaths.removeValue(forKey: watchDescriptor)
self.watchMasks.removeValue(forKey: watchDescriptor)
self.activeWatches.remove(watchDescriptor)
self.watchesWithAutomaticSubtreeWatching.remove(watchDescriptor)
}
func path(forId watchDescriptor: CInt) -> String? {
return self.watchPaths[watchDescriptor]
}
func mask(forId watchDescriptor: CInt) -> InotifyEventMask? {
return self.watchMasks[watchDescriptor]
}
func isAutomaticSubtreeWatching(_ watchDescriptor: CInt) -> Bool {
return self.watchesWithAutomaticSubtreeWatching.contains(watchDescriptor)
}
}