# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense ## An actor for filesystem monitoring. import std/[asyncdispatch, asyncfile, tables] import posix, posix/inotify import preserves import syndicate, syndicate/[bags, relays] import ./schema/inotify_actor var IN_NONBLOCK {.importc, nodecl.}: cint type BootArgs {.preservesDictionary.} = object dataspace: Cap proc toMask(sym: Symbol): uint32 = case sym.string of "IN_ACCESS": IN_ACCESS of "IN_MODIFY": IN_MODIFY of "IN_ATTRIB": IN_ATTRIB of "IN_CLOSE_WRITE": IN_CLOSE_WRITE of "IN_CLOSE_NOWRITE": IN_CLOSE_NOWRITE of "IN_CLOSE": IN_CLOSE of "IN_OPEN": IN_OPEN of "IN_MOVED_FROM": IN_MOVED_FROM of "IN_MOVED_TO": IN_MOVED_TO of "IN_MOVE": IN_MOVE of "IN_CREATE": IN_CREATE of "IN_DELETE": IN_DELETE of "IN_DELETE_SELF": IN_DELETE_SELF of "IN_MOVE_SELF": IN_MOVE_SELF else: 0 func contains(event, bit: uint32): bool = (event and bit) != 0 iterator symbols(event: uint32): Symbol = if event.contains IN_ACCESS: yield Symbol"IN_ACCESS" if event.contains IN_MODIFY: yield Symbol"IN_MODIFY" if event.contains IN_ATTRIB: yield Symbol"IN_ATTRIB" if event.contains IN_CLOSE_WRITE: yield Symbol"IN_CLOSE_WRITE" if event.contains IN_CLOSE_NOWRITE: yield Symbol"IN_CLOSE_NOWRITE" if event.contains IN_OPEN: yield Symbol"IN_OPEN" if event.contains IN_MOVED_FROM: yield Symbol"IN_MOVED_FROM" if event.contains IN_MOVED_TO: yield Symbol"IN_MOVED_TO" if event.contains IN_CREATE: yield Symbol"IN_CREATE" if event.contains IN_DELETE: yield Symbol"IN_DELETE" if event.contains IN_DELETE_SELF: yield Symbol"IN_DELETE_SELF" if event.contains IN_MOVE_SELF: yield Symbol"IN_MOVE_SELF" if event.contains (IN_CLOSE_WRITE or IN_CLOSE_NOWRITE): yield Symbol"IN_CLOSE" if event.contains (IN_MOVED_FROM or IN_MOVED_TO): yield Symbol"IN_MOVE" runActor("inotify_actor") do (root: Cap; turn: var Turn): let buf = newSeq[byte](8192) let eventPattern = ?Observe(pattern: !InotifyMessage) ?? { 0: grabLit(), 1: grabLit() } connectStdio(turn, root) during(turn, root, ?:BootArgs) do (ds: Cap): let inf = inotify_init1(IN_NONBLOCK) doAssert inf != -1, $inf & " - " & $strerror(errno) var registry = initTable[cint, string]() watchBag: Bag[cint] let anf = newAsyncFile(AsyncFD inf) facet = turn.facet var fut: Future[int] proc readEvents() {.gcsafe.} = fut = readBuffer(anf, buf[0].addr, buf.len) addCallback(fut, facet) do (turn: var Turn): let n = read(fut) doAssert n > 0 for event in inotify_events(buf[0].addr, n): var msg = InotifyMessage(path: registry[event.wd], cookie: event.cookie.BiggestInt) if event.len > 0: let n = event.len msg.name.setLen(n) copyMem(msg.name[0].addr, event.name.addr, n) for i, c in msg.name: if c == '\0': msg.name.setLen(i) break for sym in event.mask.symbols: msg.event = sym message(turn, ds, msg) readEvents() readEvents() during(turn, ds, eventPattern) do (path: string, kind: Symbol): let wd = inotify_add_watch(inf, path, kind.toMask or IN_MASK_ADD) doAssert wd > 0, $strerror(errno) registry[wd] = path discard watchBag.change(wd, 1) do: if watchBag.change(wd, -1, clamp = true) == cdPresentToAbsent: discard close(wd) registry.del(wd) do: close(anf)