115 lines
3.5 KiB
Nim
115 lines
3.5 KiB
Nim
# 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)
|