nix_actor/src/nix_actor/daemons.nim

206 lines
7.4 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, asyncnet, sets, streams, strutils]
from std/algorithm import sort
import eris
import preserves, syndicate
from syndicate/protocols/dataspace import Observe
import ./protocol, ./sockets
type Value = Preserve[void]
proc merge(items: varargs[Value]): Value =
# TODO: just a hack, not a proper imlementation
# https://preserves.dev/preserves.html#appendix-merging-values
result = initDictionary()
for e in items:
for (key, val) in e.pairs:
result[key] = val
cannonicalize(result)
type Observe = dataspace.Observe[Ref]
proc recvError(daemon: Session): Future[string] {.async.} =
discard #[typ]# await recvString(daemon)
discard #[lvl]# await recvWord(daemon)
discard #[name]# await recvString(daemon)
let msg = #[msg]# await recvString(daemon)
discard #[havePos]# await recvWord(daemon)
let nrTraces = await recvWord(daemon)
for i in 1..nrTraces:
discard #[havPos]# await recvWord(daemon)
discard #[msg]# await recvString(daemon)
return msg
proc recvFields(daemon: Session) {.async.} =
let count = await recvWord(daemon)
for i in 0..<count:
let typ = await recvWord(daemon)
case typ
of 0: discard await recvWord(daemon)
of 1: discard await recvString(daemon)
else: raiseAssert "unknown field type " & $typ
proc recvWork(daemon: Session) {.async.} =
while true:
let word = await recvWord(daemon)
case word
of STDERR_WRITE:
discard await recvString(daemon)
of STDERR_READ:
await send(daemon, "")
of STDERR_ERROR:
let err = await recvError(daemon)
raise newException(ProtocolError, "Nix daemon: " & err)
of STDERR_NEXT:
let msg = await recvString(daemon)
stderr.writeLine("Nix daemon: ", msg)
of STDERR_START_ACTIVITY:
discard await recvWord(daemon) # id
discard await recvWord(daemon) # level
discard await recvWord(daemon) # type
discard await recvString(daemon) # text
await recvFields(daemon) # fields
discard await recvWord(daemon) # parent
of STDERR_STOP_ACTIVITY:
discard await recvWord(daemon) # id
of STDERR_RESULT:
discard await recvWord(daemon) # id
discard await recvWord(daemon) # type
await recvFields(daemon) # fields
of STDERR_LAST:
break
else:
raise newException(ProtocolError, "unknown work verb " & $word)
proc connectDaemon(daemon: Session; socketPath: string) {.async.} =
await connectUnix(daemon.socket, socketPath)
await send(daemon, WORKER_MAGIC_1)
let daemonMagic = await recvWord(daemon)
if daemonMagic != WORKER_MAGIC_2:
raise newException(ProtocolError, "bad magic from daemon")
let daemonVersion = await recvWord(daemon)
daemon.version = min(Version daemonVersion, PROTOCOL_VERSION)
await send(daemon, Word daemon.version)
await send(daemon, 0) # CPU affinity
await send(daemon, 0) # reserve space
if daemon.version.minor >= 33:
discard await recvString(daemon) # version
if daemon.version.minor >= 35:
discard await recvWord(daemon) # remoteTrustsUs
await recvWork(daemon)
proc queryMissing(daemon: Session; targets: StringSeq): Future[Missing] {.async.} =
var miss = Missing(targets: targets)
await send(daemon, wopQueryMissing)
await send(daemon, miss.targets)
await recvWork(daemon)
miss.willBuild = await recvStringSet(daemon)
miss.willSubstitute = await recvStringSet(daemon)
miss.unknown = await recvStringSet(daemon)
miss.downloadSize = BiggestInt await recvWord(daemon)
miss.narSize = BiggestInt await recvWord(daemon)
return miss
proc queryPathInfo(daemon: Session; path: string): Future[LegacyPathAttrs] {.async.} =
var info: LegacyPathAttrs
await send(daemon, wopQueryPathInfo)
await send(daemon, path)
await recvWork(daemon)
let valid = await recvWord(daemon)
if valid != 0:
info.deriver = await recvString(daemon)
info.narHash = await recvString(daemon)
info.references = await recvStringSeq(daemon)
sort(info.references)
info.registrationTime = BiggestInt await recvWord(daemon)
info.narSize = BiggestInt await recvWord(daemon)
info.ultimate = (await recvWord(daemon)) != 0
info.sigs = await recvStringSet(daemon)
info.ca = await recvString(daemon)
return info
proc recvLegacyPathAttrs(daemon: Session): Future[AddToStoreAttrs] {.async.} =
var info: AddToStoreAttrs
info.deriver = await recvString(daemon)
info.narHash = await recvString(daemon)
info.references = await recvStringSeq(daemon)
sort(info.references)
info.registrationTime = BiggestInt await recvWord(daemon)
info.narSize = BiggestInt await recvWord(daemon)
assert daemon.version.minor >= 16
info.ultimate = (await recvWord(daemon)) != 0
info.sigs = await recvStringSet(daemon)
info.ca = await recvString(daemon)
return info
proc addToStore(daemon: Session; store: ErisStore; request: AddToStoreClientAttrs): Future[(string, AddToStoreAttrs)] {.async.} =
let
erisCap = parseCap(request.eris)
stream = newErisStream(store, erisCap)
await send(daemon, wopAddToStore)
await send(daemon, request.name)
await send(daemon, string request.`ca-method`)
await send(daemon, request.references)
await send(daemon, 0) # repair
await recoverChunks(daemon, store, erisCap)
await recvWork(daemon)
let path = await recvString(daemon)
var info = await recvLegacyPathAttrs(daemon)
info.eris = request.eris
info.`ca-method` = request.`ca-method`
info.name = request.name
info.references = request.references
return (path, info)
proc callDaemon(turn: var Turn; path: string; action: proc (daemon: Session; turn: var Turn) {.gcsafe.}): Session =
let
daemon = newSession()
fut = connectDaemon(daemon, path)
addCallback(fut, turn) do (turn: var Turn):
read(fut)
action(daemon, turn)
return daemon
proc bootDaemonSide*(turn: var Turn; ds: Ref; store: ErisStore; socketPath: string) =
during(turn, ds, ?Observe(pattern: !Missing) ?? {0: grab()}) do (a: Preserve[Ref]):
# cannot use `grabLit` here because an array is a compound
# TODO: unpack to a `Pattern`
let daemon = callDaemon(turn, socketPath) do (daemon: Session; turn: var Turn):
var targets: StringSeq
doAssert targets.fromPreserve(unpackLiterals(a))
# unpack <arr [<lit " …">]>
let missFut = queryMissing(daemon, targets)
addCallback(missFut, turn) do (turn: var Turn):
close(daemon)
var miss = read(missFut)
discard publish(turn, ds, miss)
do:
close(daemon)
during(turn, ds, ?Observe(pattern: !PathInfo) ?? {0: grabLit()}) do (path: string):
let daemon = callDaemon(turn, socketPath) do (daemon: Session; turn: var Turn):
let infoFut = queryPathInfo(daemon, path)
addCallback(infoFut, turn) do (turn: var Turn):
close(daemon)
var info = read(infoFut)
discard publish(turn, ds, initRecord("path", path.toPreserve, info.toPreserve))
do:
close(daemon)
during(turn, ds, ?Observe(pattern: !PathInfo) ?? {1: grabDict()}) do (pat: Value):
var daemon: Session
var request: AddToStoreClientAttrs
if request.fromPreserve(unpackLiterals pat):
daemon = callDaemon(turn, socketPath) do (daemon: Session; turn: var Turn):
let fut = addToStore(daemon, store, request)
addCallback(fut, turn) do (turn: var Turn):
close(daemon)
var (path, info) = read(fut)
discard publish(turn, ds, initRecord("path", path.toPreserve, info.toPreserve))
do:
close(daemon)