458 lines
16 KiB
Nim
458 lines
16 KiB
Nim
#
|
|
# Chronos' OS helpers
|
|
#
|
|
# (c) Copyright 2022-Present Status Research & Development GmbH
|
|
#
|
|
# Licensed under either of
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
# MIT license (LICENSE-MIT)
|
|
import results
|
|
import "."/[osdefs, oserrno]
|
|
|
|
export results
|
|
|
|
{.push raises: [].}
|
|
|
|
when defined(windows) or defined(nimdoc):
|
|
import stew/base10
|
|
const
|
|
PipeHeaderName* = r"\\.\pipe\LOCAL\chronos\"
|
|
SignalPrefixName* = cstring(r"Local\chronos-events-")
|
|
MaxSignalEventLength* = 64
|
|
MaxSignalSuffixLength* = MaxSignalEventLength -
|
|
(len(SignalPrefixName) + Base10.maxLen(uint64) + 2)
|
|
|
|
type
|
|
DescriptorFlag* {.pure.} = enum
|
|
CloseOnExec, NonBlock
|
|
|
|
const
|
|
AsyncDescriptorDefault* = {
|
|
DescriptorFlag.CloseOnExec, DescriptorFlag.NonBlock}
|
|
|
|
when defined(windows):
|
|
type
|
|
WINDESCRIPTOR* = SocketHandle|HANDLE
|
|
|
|
template handleEintr*(body: untyped): untyped =
|
|
discard
|
|
|
|
proc setDescriptorInheritance*(s: WINDESCRIPTOR,
|
|
value: bool): Result[void, OSErrorCode] =
|
|
var flags = 0'u32
|
|
let fd = when s is SocketHandle: HANDLE(s) else: s
|
|
if getHandleInformation(fd, flags) == FALSE:
|
|
return err(osLastError())
|
|
if value != ((flags and HANDLE_FLAG_INHERIT) == HANDLE_FLAG_INHERIT):
|
|
let mode = if value: HANDLE_FLAG_INHERIT else: 0'u32
|
|
if setHandleInformation(fd, HANDLE_FLAG_INHERIT, mode) == FALSE:
|
|
return err(osLastError())
|
|
ok()
|
|
|
|
proc getDescriptorInheritance*(s: WINDESCRIPTOR
|
|
): Result[bool, OSErrorCode] =
|
|
var flags = 0'u32
|
|
let fd = when s is SocketHandle: HANDLE(s) else: s
|
|
if getHandleInformation(fd, flags) == FALSE:
|
|
return err(osLastError())
|
|
ok((flags and HANDLE_FLAG_INHERIT) == HANDLE_FLAG_INHERIT)
|
|
|
|
proc setDescriptorBlocking*(s: SocketHandle,
|
|
value: bool): Result[void, OSErrorCode] =
|
|
var mode = clong(ord(not value))
|
|
if ioctlsocket(s, osdefs.FIONBIO, addr(mode)) == -1:
|
|
return err(osLastError())
|
|
ok()
|
|
|
|
proc setDescriptorFlags*(s: WINDESCRIPTOR, nonblock,
|
|
cloexec: bool): Result[void, OSErrorCode] =
|
|
? setDescriptorBlocking(s, not(nonblock))
|
|
? setDescriptorInheritance(s, not(cloexec))
|
|
ok()
|
|
|
|
proc closeFd*(s: SocketHandle): int =
|
|
int(osdefs.closesocket(s))
|
|
|
|
proc closeFd*(s: HANDLE): int =
|
|
if osdefs.closeHandle(s) == TRUE: 0 else: -1
|
|
|
|
proc toWideBuffer*(s: openArray[char],
|
|
d: var openArray[WCHAR]): Result[int, OSErrorCode] =
|
|
if len(s) == 0: return ok(0)
|
|
let res = multiByteToWideChar(CP_UTF8, 0'u32, unsafeAddr s[0], cint(-1),
|
|
addr d[0], cint(len(d)))
|
|
if res == 0:
|
|
err(osLastError())
|
|
else:
|
|
ok(res)
|
|
|
|
proc toMultibyteBuffer*(s: openArray[WCHAR],
|
|
d: var openArray[char]): Result[int, OSErrorCode] =
|
|
if len(s) == 0: return ok(0)
|
|
let res = wideCharToMultiByte(CP_UTF8, 0'u32, unsafeAddr s[0], cint(-1),
|
|
addr d[0], cint(len(d)), nil, nil)
|
|
if res == 0:
|
|
err(osLastError())
|
|
else:
|
|
ok(res)
|
|
|
|
proc toWideString*(s: string): Result[LPWSTR, OSErrorCode] =
|
|
if len(s) == 0:
|
|
ok(cast[LPWSTR](alloc0(sizeof(WCHAR))))
|
|
else:
|
|
let charsNeeded = multiByteToWideChar(CP_UTF8, 0'u32,
|
|
cast[ptr char](unsafeAddr s[0]),
|
|
cint(len(s)), nil, cint(0))
|
|
if charsNeeded <= cint(0):
|
|
return err(osLastError())
|
|
var buffer = cast[LPWSTR](alloc0((charsNeeded + 1) * sizeof(WCHAR)))
|
|
let res = multiByteToWideChar(CP_UTF8, 0'u32,
|
|
cast[ptr char](unsafeAddr s[0]),
|
|
cint(len(s)), buffer, charsNeeded)
|
|
if res != charsNeeded:
|
|
err(osLastError())
|
|
else:
|
|
ok(buffer)
|
|
|
|
proc toString*(w: LPWSTR): Result[string, OSErrorCode] =
|
|
if isNil(w):
|
|
ok("")
|
|
else:
|
|
let bytesNeeded = wideCharToMultiByte(CP_UTF8, 0'u32, w, cint(-1), nil,
|
|
cint(0), nil, nil)
|
|
if bytesNeeded <= cint(0):
|
|
return err(osLastError())
|
|
|
|
var buffer = newString(bytesNeeded)
|
|
let res = wideCharToMultiByte(CP_UTF8, 0'u32, w, cint(-1),
|
|
addr buffer[0], cint(len(buffer)), nil, nil)
|
|
if res != bytesNeeded:
|
|
err(osLastError())
|
|
else:
|
|
# We need to strip trailing `\x00`.
|
|
for i in countdown(len(buffer) - 1, 0):
|
|
if buffer[i] != '\x00':
|
|
buffer.setLen(i + 1)
|
|
break
|
|
ok(buffer)
|
|
|
|
proc free*(w: LPWSTR) =
|
|
if not(isNil(w)):
|
|
dealloc(cast[pointer](w))
|
|
|
|
proc createOsPipe*(readset, writeset: set[DescriptorFlag]
|
|
): Result[tuple[read: HANDLE, write: HANDLE], OSErrorCode] =
|
|
var
|
|
pipeIn, pipeOut: HANDLE
|
|
widePipeName: LPWSTR
|
|
uniq = 0'u64
|
|
rsa = getSecurityAttributes(DescriptorFlag.CloseOnExec notin readset)
|
|
wsa = getSecurityAttributes(DescriptorFlag.CloseOnExec notin writeset)
|
|
|
|
while true:
|
|
queryPerformanceCounter(uniq)
|
|
let pipeName = PipeHeaderName & Base10.toString(uniq)
|
|
|
|
let openMode =
|
|
if DescriptorFlag.NonBlock in readset:
|
|
osdefs.FILE_FLAG_FIRST_PIPE_INSTANCE or osdefs.FILE_FLAG_OVERLAPPED or
|
|
osdefs.PIPE_ACCESS_INBOUND
|
|
else:
|
|
osdefs.FILE_FLAG_FIRST_PIPE_INSTANCE or osdefs.PIPE_ACCESS_INBOUND
|
|
|
|
let pipeMode = osdefs.PIPE_TYPE_BYTE or osdefs.PIPE_READMODE_BYTE or
|
|
osdefs.PIPE_WAIT
|
|
widePipeName =
|
|
block:
|
|
let res = pipeName.toWideString()
|
|
if res.isErr():
|
|
return err(res.error())
|
|
res.get()
|
|
pipeIn = createNamedPipe(widePipeName, openMode, pipeMode,
|
|
1'u32, osdefs.DEFAULT_PIPE_SIZE,
|
|
osdefs.DEFAULT_PIPE_SIZE, 0'u32, addr rsa)
|
|
if pipeIn == osdefs.INVALID_HANDLE_VALUE:
|
|
let errorCode = osLastError()
|
|
free(widePipeName)
|
|
# If error in {ERROR_ACCESS_DENIED, ERROR_PIPE_BUSY}, then named pipe
|
|
# with such name already exists.
|
|
if (errorCode == osdefs.ERROR_ACCESS_DENIED) or
|
|
(errorCode == osdefs.ERROR_PIPE_BUSY):
|
|
continue
|
|
return err(errorCode)
|
|
else:
|
|
break
|
|
|
|
let openMode = osdefs.GENERIC_WRITE or osdefs.FILE_WRITE_DATA or
|
|
osdefs.SYNCHRONIZE
|
|
let openFlags =
|
|
if DescriptorFlag.NonBlock in writeset:
|
|
osdefs.FILE_FLAG_OVERLAPPED
|
|
else:
|
|
DWORD(0)
|
|
|
|
pipeOut = createFile(widePipeName, openMode, 0, addr wsa,
|
|
osdefs.OPEN_EXISTING, openFlags, HANDLE(0))
|
|
if pipeOut == osdefs.INVALID_HANDLE_VALUE:
|
|
let errorCode = osLastError()
|
|
free(widePipeName)
|
|
discard closeFd(pipeIn)
|
|
return err(errorCode)
|
|
|
|
var ovl = osdefs.OVERLAPPED()
|
|
let res =
|
|
if DescriptorFlag.NonBlock in writeset:
|
|
connectNamedPipe(pipeIn, addr ovl)
|
|
else:
|
|
connectNamedPipe(pipeIn, nil)
|
|
if res == 0:
|
|
let cleanupFlag =
|
|
block:
|
|
let errorCode = osLastError()
|
|
case errorCode
|
|
of ERROR_PIPE_CONNECTED:
|
|
false
|
|
of ERROR_IO_PENDING:
|
|
if DescriptorFlag.NonBlock in writeset:
|
|
var bytesRead = 0.DWORD
|
|
if getOverlappedResult(pipeIn, addr ovl, bytesRead, 1) == FALSE:
|
|
true
|
|
else:
|
|
false
|
|
else:
|
|
true
|
|
else:
|
|
true
|
|
if cleanupFlag:
|
|
let errorCode = osLastError()
|
|
free(widePipeName)
|
|
discard closeFd(pipeIn)
|
|
discard closeFd(pipeOut)
|
|
return err(errorCode)
|
|
ok((read: pipeIn, write: pipeOut))
|
|
|
|
proc getSignalName*(signal: int): cstring =
|
|
## Convert Windows SIGNAL identifier to string representation.
|
|
##
|
|
## This procedure supports only SIGINT, SIGTERM and SIGQUIT values.
|
|
case signal
|
|
of SIGINT: cstring("sigint")
|
|
of SIGTERM: cstring("sigterm")
|
|
of SIGQUIT: cstring("sigquit")
|
|
else:
|
|
raiseAssert "Signal is not supported"
|
|
|
|
proc getEventPath*(suffix: cstring): array[MaxSignalEventLength, WCHAR] =
|
|
## Create Windows' Event object name suffixed by ``suffix``. This name
|
|
## is create in local session namespace with name like this:
|
|
## ``Local\chronos-events-<process id>-<suffix>``.
|
|
##
|
|
## This procedure is GC-free, so it could be used in other threads.
|
|
doAssert(len(suffix) < MaxSignalSuffixLength)
|
|
var
|
|
resMc: array[MaxSignalEventLength, char]
|
|
resWc: array[MaxSignalEventLength, WCHAR]
|
|
|
|
var offset = 0
|
|
let
|
|
pid = osdefs.getCurrentProcessId()
|
|
pid10 = Base10.toBytes(uint64(pid))
|
|
copyMem(addr resMc[offset], SignalPrefixName, len(SignalPrefixName))
|
|
offset += len(SignalPrefixName)
|
|
copyMem(addr resMc[offset], unsafeAddr pid10.data[0], pid10.len)
|
|
offset += pid10.len
|
|
resMc[offset] = '-'
|
|
offset += 1
|
|
copyMem(addr resMc[offset], suffix, len(suffix))
|
|
offset += len(suffix)
|
|
resMc[offset] = '\x00'
|
|
let res = toWideBuffer(resMc, resWc)
|
|
if res.isErr():
|
|
raiseAssert "Invalid suffix value, got " & osErrorMsg(res.error())
|
|
resWc
|
|
|
|
proc raiseEvent(suffix: cstring): Result[bool, OSErrorCode] =
|
|
var sa = getSecurityAttributes()
|
|
let
|
|
eventName = getEventPath(suffix)
|
|
# We going to fire event, so we can try to create it already signaled.
|
|
event = createEvent(addr sa, FALSE, TRUE, unsafeAddr eventName[0])
|
|
errorCode = osLastError()
|
|
|
|
if event == HANDLE(0):
|
|
err(errorCode)
|
|
else:
|
|
if errorCode == ERROR_ALREADY_EXISTS:
|
|
let res = setEvent(event)
|
|
if res == FALSE:
|
|
err(osLastError())
|
|
else:
|
|
ok(true)
|
|
else:
|
|
ok(false)
|
|
|
|
proc raiseSignal*(signal: cint): Result[bool, OSErrorCode] =
|
|
## This is helper procedure which could help to raise Unix signals in
|
|
## Windows GUI / Service application. Console applications are handled
|
|
## automatically.
|
|
##
|
|
## This procedure does not use Nim's GC, so it can be placed in any handler
|
|
## of your application even in code which is running in different thread.
|
|
raiseEvent(getSignalName(signal))
|
|
|
|
proc raiseConsoleCtrlSignal*(groupId = 0'u32): Result[void, OSErrorCode] =
|
|
## Raise CTRL+C event in current console.
|
|
if generateConsoleCtrlEvent(CTRL_C_EVENT, groupId) == FALSE:
|
|
err(osLastError())
|
|
else:
|
|
ok()
|
|
|
|
proc openEvent*(suffix: string): Result[HANDLE, OSErrorCode] =
|
|
## Open or create Windows event object with suffix ``suffix``.
|
|
var sa = getSecurityAttributes()
|
|
let
|
|
# We going to wait for created event, so we don't need to create it in
|
|
# signaled state.
|
|
eventName = getEventPath(suffix)
|
|
event = createEvent(addr sa, FALSE, FALSE, unsafeAddr eventName[0])
|
|
if event == HANDLE(0):
|
|
let errorCode = osLastError()
|
|
err(errorCode)
|
|
else:
|
|
ok(event)
|
|
|
|
else:
|
|
|
|
template handleEintr*(body: untyped): untyped =
|
|
var res = 0
|
|
while true:
|
|
res = body
|
|
if not((res == -1) and (osLastError() == oserrno.EINTR)):
|
|
break
|
|
res
|
|
|
|
proc setDescriptorBlocking*(s: cint,
|
|
value: bool): Result[void, OSErrorCode] =
|
|
let flags = handleEintr(osdefs.fcntl(s, osdefs.F_GETFL))
|
|
if flags == -1:
|
|
return err(osLastError())
|
|
if value != not((flags and osdefs.O_NONBLOCK) == osdefs.O_NONBLOCK):
|
|
let mode =
|
|
if value:
|
|
flags and not(osdefs.O_NONBLOCK)
|
|
else:
|
|
flags or osdefs.O_NONBLOCK
|
|
if handleEintr(osdefs.fcntl(s, osdefs.F_SETFL, mode)) == -1:
|
|
return err(osLastError())
|
|
ok()
|
|
|
|
proc setDescriptorBlocking*(s: SocketHandle,
|
|
value: bool): Result[void, OSErrorCode] =
|
|
setDescriptorBlocking(cint(s), value)
|
|
|
|
proc setDescriptorInheritance*(s: cint,
|
|
value: bool): Result[void, OSErrorCode] =
|
|
let flags = handleEintr(osdefs.fcntl(s, osdefs.F_GETFD))
|
|
if flags == -1:
|
|
return err(osLastError())
|
|
if value != not((flags and osdefs.FD_CLOEXEC) == osdefs.FD_CLOEXEC):
|
|
let mode =
|
|
if value:
|
|
flags and not(osdefs.FD_CLOEXEC)
|
|
else:
|
|
flags or osdefs.FD_CLOEXEC
|
|
if handleEintr(osdefs.fcntl(s, osdefs.F_SETFD, mode)) == -1:
|
|
return err(osLastError())
|
|
ok()
|
|
|
|
proc getDescriptorInheritance*(s: cint): Result[bool, OSErrorCode] =
|
|
let flags = handleEintr(osdefs.fcntl(s, osdefs.F_GETFD))
|
|
if flags == -1:
|
|
return err(osLastError())
|
|
ok((flags and osdefs.FD_CLOEXEC) == osdefs.FD_CLOEXEC)
|
|
|
|
proc setDescriptorFlags*(s: cint, nonblock,
|
|
cloexec: bool): Result[void, OSErrorCode] =
|
|
? setDescriptorBlocking(s, not(nonblock))
|
|
? setDescriptorInheritance(s, not(cloexec))
|
|
ok()
|
|
|
|
proc closeFd*(s: cint): int =
|
|
handleEintr(osdefs.close(s))
|
|
|
|
proc closeFd*(s: SocketHandle): int =
|
|
handleEintr(osdefs.close(cint(s)))
|
|
|
|
proc acceptConn*(a1: cint, a2: ptr SockAddr, a3: ptr SockLen,
|
|
a4: set[DescriptorFlag]): Result[cint, OSErrorCode] =
|
|
when declared(accept4):
|
|
let flags =
|
|
block:
|
|
var res: cint = 0
|
|
if DescriptorFlag.CloseOnExec in a4:
|
|
res = res or osdefs.SOCK_CLOEXEC
|
|
if DescriptorFlag.NonBlock in a4:
|
|
res = res or osdefs.SOCK_NONBLOCK
|
|
res
|
|
let res = cint(handleEintr(accept4(a1, a2, a3, flags)))
|
|
if res == -1:
|
|
return err(osLastError())
|
|
ok(res)
|
|
else:
|
|
let sock = cint(handleEintr(cint(accept(SocketHandle(a1), a2, a3))))
|
|
if sock == -1:
|
|
return err(osLastError())
|
|
let
|
|
cloexec = DescriptorFlag.CloseOnExec in a4
|
|
nonblock = DescriptorFlag.NonBlock in a4
|
|
let res = setDescriptorFlags(sock, nonblock, cloexec)
|
|
if res.isErr():
|
|
discard closeFd(sock)
|
|
return err(res.error())
|
|
ok(sock)
|
|
|
|
proc createOsPipe*(readset, writeset: set[DescriptorFlag]
|
|
): Result[tuple[read: cint, write: cint], OSErrorCode] =
|
|
when declared(pipe2):
|
|
var fds: array[2, cint]
|
|
let readFlags =
|
|
block:
|
|
var res = cint(0)
|
|
if DescriptorFlag.CloseOnExec in readset:
|
|
res = res or osdefs.O_CLOEXEC
|
|
if DescriptorFlag.NonBlock in readset:
|
|
res = res or osdefs.O_NONBLOCK
|
|
res
|
|
if osdefs.pipe2(fds, readFlags) == -1:
|
|
return err(osLastError())
|
|
if readset != writeset:
|
|
let res = setDescriptorFlags(fds[1],
|
|
DescriptorFlag.NonBlock in writeset,
|
|
DescriptorFlag.CloseOnExec in writeset)
|
|
if res.isErr():
|
|
discard closeFd(fds[0])
|
|
discard closeFd(fds[1])
|
|
return err(res.error())
|
|
ok((read: fds[0], write: fds[1]))
|
|
else:
|
|
var fds: array[2, cint]
|
|
if osdefs.pipe(fds) == -1:
|
|
return err(osLastError())
|
|
block:
|
|
let res = setDescriptorFlags(fds[0],
|
|
DescriptorFlag.NonBlock in readset,
|
|
DescriptorFlag.CloseOnExec in readset)
|
|
if res.isErr():
|
|
discard closeFd(fds[0])
|
|
discard closeFd(fds[1])
|
|
return err(res.error())
|
|
block:
|
|
let res = setDescriptorFlags(fds[1],
|
|
DescriptorFlag.NonBlock in writeset,
|
|
DescriptorFlag.CloseOnExec in writeset)
|
|
if res.isErr():
|
|
discard closeFd(fds[0])
|
|
discard closeFd(fds[1])
|
|
return err(res.error())
|
|
ok((read: fds[0], write: fds[1]))
|