204 lines
6.3 KiB
Nim
204 lines
6.3 KiB
Nim
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
## Common module for communicating with Nix clients and daemons.
|
|
|
|
import std/[asyncdispatch, asyncnet, sets, strtabs, strutils, tables]
|
|
from std/nativesockets import AF_UNIX, SOCK_STREAM, Protocol
|
|
|
|
import eris
|
|
import preserves, syndicate
|
|
|
|
import ./protocol
|
|
|
|
{.pragma: workerProtocol, importc, header: "worker-protocol.hh".}
|
|
|
|
type Word* = uint64
|
|
|
|
proc `[]=`*[T](attrs: var AttrSet; key: string; val: T) =
|
|
attrs[Symbol key] = val.toPreserve
|
|
|
|
const
|
|
WORKER_MAGIC_1* = 0x6E697863
|
|
WORKER_MAGIC_2* = 0x6478696F
|
|
PROTOCOL_VERSION* = 0x100 or 35
|
|
|
|
STDERR_NEXT* = 0x6F6C6d67
|
|
STDERR_READ* = 0x64617461
|
|
STDERR_WRITE* = 0x64617416
|
|
STDERR_LAST* = 0x616C7473
|
|
STDERR_ERROR* = 0x63787470
|
|
STDERR_START_ACTIVITY* = 0x53545254
|
|
STDERR_STOP_ACTIVITY* = 0x53544F50
|
|
STDERR_RESULT* = 0x52534C54
|
|
|
|
wopIsValidPath* = 1
|
|
wopHasSubstitutes* = 3
|
|
wopQueryReferrers* = 6
|
|
wopAddToStore* = 7
|
|
wopBuildPaths* = 9
|
|
wopEnsurePath* = 10
|
|
wopAddTempRoot* = 11
|
|
wopAddIndirectRoot* = 12
|
|
wopSyncWithGC* = 13
|
|
wopFindRoots* = 14
|
|
wopSetOptions* = 19
|
|
wopCollectGarbage* = 20
|
|
wopQuerySubstitutablePathInfo* = 21
|
|
wopQueryAllValidPaths* = 23
|
|
wopQueryFailedPaths* = 24
|
|
wopClearFailedPaths* = 25
|
|
wopQueryPathInfo* = 26
|
|
wopQueryPathFromHashPart* = 29
|
|
wopQuerySubstitutablePathInfos* = 30
|
|
wopQueryValidPaths* = 31
|
|
wopQuerySubstitutablePaths* = 32
|
|
wopQueryValidDerivers* = 33
|
|
wopOptimiseStore* = 34
|
|
wopVerifyStore* = 35
|
|
wopBuildDerivation* = 36
|
|
wopAddSignatures* = 37
|
|
wopNarFromPath* = 38
|
|
wopAddToStoreNar* = 39
|
|
wopQueryMissing* = 40
|
|
wopQueryDerivationOutputMap* = 41
|
|
wopRegisterDrvOutput* = 42
|
|
wopQueryRealisation* = 43
|
|
wopAddMultipleToStore* = 44
|
|
wopAddBuildLog* = 45
|
|
wopBuildPathsWithResults* = 46
|
|
|
|
type
|
|
ProtocolError* = object of IOError
|
|
Version* = uint16
|
|
|
|
Session* = ref object
|
|
socket*: AsyncSocket
|
|
buffer*: seq[Word]
|
|
version*: Version
|
|
|
|
func major*(version: Version): uint16 = version and 0xff00
|
|
func minor*(version: Version): uint16 = version and 0x00ff
|
|
|
|
proc close*(session: Session) =
|
|
close(session.socket)
|
|
reset(session.buffer)
|
|
|
|
proc send*(session: Session; words: varargs[Word]): Future[void] =
|
|
if session.buffer.len < words.len:
|
|
session.buffer.setLen(words.len)
|
|
for i, word in words: session.buffer[i] = word
|
|
send(session.socket, addr session.buffer[0], words.len shl 3)
|
|
|
|
proc send*(session: Session; s: string): Future[void] =
|
|
let wordCount = 1 + ((s.len + 7) shr 3)
|
|
if session.buffer.len < wordCount: setLen(session.buffer, wordCount)
|
|
session.buffer[0] = Word s.len
|
|
if s != "":
|
|
session.buffer[pred wordCount] = 0x00
|
|
copyMem(addr session.buffer[1], unsafeAddr s[0], s.len)
|
|
send(session.socket, addr session.buffer[0], wordCount shl 3)
|
|
|
|
proc send*(session: Session; ss: StringSeq|StringSet): Future[void] =
|
|
## Send a set of strings. The set is sent as a contiguous buffer.
|
|
session.buffer[0] = Word ss.len
|
|
var off = 1
|
|
for s in ss:
|
|
let
|
|
stringWordLen = (s.len + 7) shr 3
|
|
bufferWordLen = off+1+stringWordLen
|
|
if session.buffer.len < bufferWordLen:
|
|
setLen(session.buffer, bufferWordLen)
|
|
session.buffer[off] = Word s.len
|
|
session.buffer[off+stringWordLen] = 0 # clear the aligning bits
|
|
inc(off)
|
|
copyMem(addr session.buffer[off], unsafeAddr s[0], s.len)
|
|
inc(off, stringWordLen)
|
|
send(session.socket, addr session.buffer[0], off shl 3)
|
|
|
|
proc recvWord*(sock: AsyncSocket): Future[Word] {.async.} =
|
|
var w: Word
|
|
let n = await recvInto(sock, addr w, sizeof(Word))
|
|
if n != sizeof(Word): raise newException(ProtocolError, "short read")
|
|
return w
|
|
|
|
proc recvWord*(session: Session): Future[Word] =
|
|
recvWord(session.socket)
|
|
|
|
proc discardWords*(session: Session; n: int): Future[void] {.async.} =
|
|
if session.buffer.len < n: setLen(session.buffer, n)
|
|
let byteCount = n shl 3
|
|
let n = await recvInto(session.socket, addr session.buffer[0], byteCount)
|
|
if n != byteCount:
|
|
raise newException(ProtocolError, "short read")
|
|
|
|
proc recvString*(socket: AsyncSocket): Future[string] {.async.} =
|
|
let stringLen = int (await recvWord(socket))
|
|
if stringLen > 0:
|
|
var s = newString((stringLen + 7) and (not 7))
|
|
let n = await recvInto(socket, addr s[0], s.len)
|
|
if n != s.len:
|
|
raise newException(ProtocolError, "short read")
|
|
setLen(s, stringLen)
|
|
return s
|
|
return ""
|
|
|
|
proc recvString*(session: Session): Future[string] =
|
|
recvString(session.socket)
|
|
|
|
proc recvStringSeq*(session: Session): Future[StringSeq] {.async.} =
|
|
let count = int(await recvWord(session.socket))
|
|
var strings = newSeq[string](count)
|
|
for i in 0..<count: strings[i] = await recvString(session)
|
|
return strings
|
|
|
|
proc recvStringSet*(session: Session): Future[StringSet] {.async.} =
|
|
let count = int(await recvWord(session.socket))
|
|
var strings = initHashSet[string](count)
|
|
for i in 0..<count: incl(strings, await recvString(session))
|
|
return strings
|
|
|
|
proc newUnixSocket*(): AsyncSocket =
|
|
newAsyncSocket(
|
|
domain = AF_UNIX,
|
|
sockType = SOCK_STREAM,
|
|
protocol = cast[Protocol](0),
|
|
buffered = false,
|
|
)
|
|
|
|
proc newSession*(socket: AsyncSocket): Session =
|
|
Session(socket: socket, buffer: newSeq[Word](512))
|
|
|
|
proc newSession*(): Session =
|
|
newUnixSocket().newSession()
|
|
|
|
proc ingestChunks*(session: Session; store: ErisStore): Future[ErisCap] {.async.} =
|
|
var ingest: ErisIngest
|
|
while true:
|
|
let chunkLen = int await recvWord(session)
|
|
if ingest.isNil:
|
|
ingest = newErisIngest(
|
|
store, recommendedChunkSize(chunkLen), convergentMode)
|
|
if chunkLen == 0:
|
|
break
|
|
else:
|
|
let wordLen = (chunkLen + 7) shr 3
|
|
if session.buffer.len < wordLen: setLen(session.buffer, wordLen)
|
|
let recvLen = await recvInto(session.socket, addr session.buffer[0], chunkLen)
|
|
# each chunk must be received contiguously
|
|
if recvLen != chunkLen:
|
|
raise newException(ProtocolError, "invalid chunk read")
|
|
await append(ingest, addr session.buffer[0], chunkLen)
|
|
var cap = await cap(ingest)
|
|
return cap
|
|
|
|
proc recoverChunks*(session: Session; store: ErisStore; cap: ErisCap) {.async.} =
|
|
let stream = newErisStream(store, cap)
|
|
session.buffer.setLen(succ(cap.chunkSize.int shr 3))
|
|
while true:
|
|
let n = await stream.readBuffer(addr session.buffer[1], cap.chunkSize.int)
|
|
session.buffer[0] = Word n
|
|
await send(session.socket, addr session.buffer[0], 8+n)
|
|
if n == 0: break
|
|
close(stream)
|