mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-19 05:50:30 +00:00
Make file descriptors from stdlib non-inheritable by default (#13201)
* io: make file descriptors non-inheritable by default This prevents file descriptors/handles leakage to child processes that might cause issues like running out of file descriptors, or potential security issues like leaking a file descriptor to a restricted file. While this breaks backward compatibility, I'm rather certain that not many programs (if any) actually make use of this implementation detail. A new API `setInheritable` is provided for the few that actually want to use this functionality. * io: disable inheritance at file creation time for supported platforms Some platforms provide extension to fopen-family of functions to allow for disabling descriptor inheritance atomically during File creation. This guards against possible leaks when a child process is spawned before we managed to disable the file descriptor inheritance (ie. in a multi-threaded program). * net, nativesockets: make sockets non inheritable by default With this commit, sockets will no longer leak to child processes when you don't want it to. Should solves a lot of "address in use" that might occur when your server has just restarted. All APIs that create sockets in these modules now expose a `inheritable` flag that allow users to toggle inheritance for the resulting sockets. An implementation of `setInheritance()` is also provided for SocketHandle. While atomically disabling inheritance at creation time is supported on Windows, it's only implemented by native winsock2, which is too much for now. This support can be implemented in a future patch. * posix: add F_DUPFD_CLOEXEC This command duplicates file descriptor with close-on-exec flag set. Defined in POSIX.1-2008. * ioselectors_kqueue: don't leak file descriptors File descriptors internally used by ioselectors on BSD/OSX are now shielded from leakage. * posix: add O_CLOEXEC This flag allows file descriptors to be open() with close-on-exec flag set atomically. This flag is specified in POSIX.1-2008 * tfdleak: test for selectors leakage Also simplified the test by using handle-type agnostic APIs to test for validity. * ioselectors_epoll: mark all fd created close-on-exec File descriptors from ioselectors should no longer leaks on Linux. * tfdleak: don't check for selector leakage on Windows The getFd proc for ioselectors_select returns a hardcoded -1 * io: add NoInheritFlag at compile time * io: add support for ioctl-based close-on-exec This allows for the flag to be set/unset in one syscall. While the performance gains might be negliable, we have one less failure point to deal with. * tfdleak: add a test for setInheritable * stdlib: add nimInheritHandles to restore old behaviors * memfiles: make file handle not inheritable by default for posix * io: setInheritable now operates on OS file handle On Windows, the native handle is the only thing that's inheritable, thus we can assume that users of this function will already have the handle available to them. This also allows users to pass down file descriptors from memfiles on Windows with ease, should that be desired. With this, nativesockets.setInheritable can be made much simpler. * changelog: clarify * nativesockets: document setInheritable return value * posix_utils: atomically disable fd inheritance for mkstemp
This commit is contained in:
19
changelog.md
19
changelog.md
@@ -10,6 +10,25 @@
|
||||
- The default hash for `Ordinal` has changed to something more bit-scrambling.
|
||||
`import hashes; proc hash(x: myInt): Hash = hashIdentity(x)` recovers the old
|
||||
one in an instantiation context while `-d:nimIntHash1` recovers it globally.
|
||||
- File handles created from high-level abstractions in the stdlib will no longer
|
||||
be inherited by child processes. In particular, these modules are affected:
|
||||
`system`, `nativesockets`, `net` and `selectors`.
|
||||
|
||||
For `net` and `nativesockets`, an `inheritable` flag has been added to all
|
||||
`proc`s that create sockets, allowing the user to control whether the
|
||||
resulting socket is inheritable. This flag is provided to ease the writing of
|
||||
multi-process servers, where sockets inheritance is desired.
|
||||
|
||||
For a transistion period, define `nimInheritHandles` to enable file handle
|
||||
inheritance by default. This flag does **not** affect the `selectors` module
|
||||
due to the differing semantics between operating systems.
|
||||
|
||||
`system.setInheritable` and `nativesockets.setInheritable` is also introduced
|
||||
for setting file handle or socket inheritance. Not all platform have these
|
||||
`proc`s defined.
|
||||
|
||||
- The file descriptors created for internal bookkeeping by `ioselector_kqueue`
|
||||
and `ioselector_epoll` will no longer be leaked to child processes.
|
||||
|
||||
## Language changes
|
||||
- In newruntime it is now allowed to assign discriminator field without restrictions as long as case object doesn't have custom destructor. Discriminator value doesn't have to be a constant either. If you have custom destructor for case object and you do want to freely assign discriminator fields, it is recommended to refactor object into 2 objects like this:
|
||||
|
||||
@@ -896,6 +896,10 @@ proc `==`*(x, y: SocketHandle): bool {.borrow.}
|
||||
proc accept*(a1: SocketHandle, a2: ptr SockAddr, a3: ptr SockLen): SocketHandle {.
|
||||
importc, header: "<sys/socket.h>", sideEffect.}
|
||||
|
||||
when defined(linux) or defined(bsd):
|
||||
proc accept4*(a1: SocketHandle, a2: ptr SockAddr, a3: ptr SockLen,
|
||||
flags: cint): SocketHandle {.importc, header: "<sys/socket.h>".}
|
||||
|
||||
proc bindSocket*(a1: SocketHandle, a2: ptr SockAddr, a3: SockLen): cint {.
|
||||
importc: "bind", header: "<sys/socket.h>".}
|
||||
## is Posix's ``bind``, because ``bind`` is a reserved word
|
||||
@@ -1036,6 +1040,9 @@ proc mkstemp*(tmpl: cstring): cint {.importc, header: "<stdlib.h>", sideEffect.}
|
||||
|
||||
proc mkdtemp*(tmpl: cstring): pointer {.importc, header: "<stdlib.h>", sideEffect.}
|
||||
|
||||
when defined(linux) or defined(bsd):
|
||||
proc mkostemp*(tmpl: cstring, oflags: cint): cint {.importc, header: "<stdlib.h>", sideEffect.}
|
||||
|
||||
proc utimes*(path: cstring, times: ptr array[2, Timeval]): int {.
|
||||
importc: "utimes", header: "<sys/time.h>", sideEffect.}
|
||||
## Sets file access and modification times.
|
||||
|
||||
@@ -100,6 +100,7 @@ const EXDEV* = cint(18)
|
||||
|
||||
# <fcntl.h>
|
||||
const F_DUPFD* = cint(0)
|
||||
const F_DUPFD_CLOEXEC* = cint(1030)
|
||||
const F_GETFD* = cint(1)
|
||||
const F_SETFD* = cint(2)
|
||||
const F_GETFL* = cint(3)
|
||||
@@ -126,6 +127,7 @@ const O_ACCMODE* = cint(3)
|
||||
const O_RDONLY* = cint(0)
|
||||
const O_RDWR* = cint(2)
|
||||
const O_WRONLY* = cint(1)
|
||||
const O_CLOEXEC* = cint(524288)
|
||||
const POSIX_FADV_NORMAL* = cint(0)
|
||||
const POSIX_FADV_SEQUENTIAL* = cint(2)
|
||||
const POSIX_FADV_RANDOM* = cint(1)
|
||||
@@ -469,6 +471,7 @@ const SOCK_DGRAM* = cint(2)
|
||||
const SOCK_RAW* = cint(3)
|
||||
const SOCK_SEQPACKET* = cint(5)
|
||||
const SOCK_STREAM* = cint(1)
|
||||
const SOCK_CLOEXEC* = cint(524288)
|
||||
const SOL_SOCKET* = cint(1)
|
||||
const SOMAXCONN* = cint(128)
|
||||
const SO_REUSEPORT* = cint(15)
|
||||
|
||||
@@ -557,6 +557,9 @@ when defined(linux) or defined(nimdoc):
|
||||
else:
|
||||
var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
when defined(linux) or defined(bsd):
|
||||
var SOCK_CLOEXEC* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
when defined(macosx):
|
||||
# We can't use the NOSIGNAL flag in the ``send`` function, it has no effect
|
||||
# Instead we should use SO_NOSIGPIPE in setsockopt
|
||||
|
||||
@@ -532,6 +532,8 @@ when defined(nimdoc):
|
||||
else:
|
||||
var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
var SOCK_CLOEXEC* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
var MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
when hasSpawnH:
|
||||
|
||||
@@ -562,6 +562,9 @@ when defined(linux) or defined(nimdoc):
|
||||
else:
|
||||
var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
when defined(linux) or defined(bsd):
|
||||
var SOCK_CLOEXEC* {.importc, header: "<sys/socket.h>".}: cint
|
||||
|
||||
when defined(macosx):
|
||||
# We can't use the NOSIGNAL flag in the ``send`` function, it has no effect
|
||||
# Instead we should use SO_NOSIGPIPE in setsockopt
|
||||
|
||||
@@ -99,6 +99,7 @@ var EXDEV* {.importc: "EXDEV", header: "<errno.h>".}: cint
|
||||
|
||||
# <fcntl.h>
|
||||
var F_DUPFD* {.importc: "F_DUPFD", header: "<fcntl.h>".}: cint
|
||||
var F_DUPFD_CLOEXEC* {.importc: "F_DUPFD", header: "<fcntl.h>".}: cint
|
||||
var F_GETFD* {.importc: "F_GETFD", header: "<fcntl.h>".}: cint
|
||||
var F_SETFD* {.importc: "F_SETFD", header: "<fcntl.h>".}: cint
|
||||
var F_GETFL* {.importc: "F_GETFL", header: "<fcntl.h>".}: cint
|
||||
@@ -125,6 +126,7 @@ var O_ACCMODE* {.importc: "O_ACCMODE", header: "<fcntl.h>".}: cint
|
||||
var O_RDONLY* {.importc: "O_RDONLY", header: "<fcntl.h>".}: cint
|
||||
var O_RDWR* {.importc: "O_RDWR", header: "<fcntl.h>".}: cint
|
||||
var O_WRONLY* {.importc: "O_WRONLY", header: "<fcntl.h>".}: cint
|
||||
var O_CLOEXEC* {.importc: "O_CLOEXEC", header: "<fcntl.h>".}: cint
|
||||
var POSIX_FADV_NORMAL* {.importc: "POSIX_FADV_NORMAL", header: "<fcntl.h>".}: cint
|
||||
var POSIX_FADV_SEQUENTIAL* {.importc: "POSIX_FADV_SEQUENTIAL", header: "<fcntl.h>".}: cint
|
||||
var POSIX_FADV_RANDOM* {.importc: "POSIX_FADV_RANDOM", header: "<fcntl.h>".}: cint
|
||||
|
||||
@@ -83,7 +83,11 @@ proc mkstemp*(prefix: string): (string, File) =
|
||||
## The file is created with perms 0600.
|
||||
## Returns the filename and a file opened in r/w mode.
|
||||
var tmpl = cstring(prefix & "XXXXXX")
|
||||
let fd = mkstemp(tmpl)
|
||||
let fd =
|
||||
when declared(mkostemp):
|
||||
mkostemp(tmpl, O_CLOEXEC)
|
||||
else:
|
||||
mkstemp(tmpl)
|
||||
var f: File
|
||||
if open(f, fd, fmReadWrite):
|
||||
return ($tmpl, f)
|
||||
|
||||
@@ -81,7 +81,7 @@ proc newSelector*[T](): Selector[T] =
|
||||
# Start with a reasonable size, checkFd() will grow this on demand
|
||||
const numFD = 1024
|
||||
|
||||
var epollFD = epoll_create(MAX_EPOLL_EVENTS)
|
||||
var epollFD = epoll_create1(O_CLOEXEC)
|
||||
if epollFD < 0:
|
||||
raiseOSError(osLastError())
|
||||
|
||||
@@ -110,7 +110,7 @@ proc close*[T](s: Selector[T]) =
|
||||
raiseIOSelectorsError(osLastError())
|
||||
|
||||
proc newSelectEvent*(): SelectEvent =
|
||||
let fdci = eventfd(0, 0)
|
||||
let fdci = eventfd(0, O_CLOEXEC)
|
||||
if fdci == -1:
|
||||
raiseIOSelectorsError(osLastError())
|
||||
setNonBlocking(fdci)
|
||||
@@ -269,7 +269,7 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
|
||||
var
|
||||
newTs: Itimerspec
|
||||
oldTs: Itimerspec
|
||||
let fdi = timerfd_create(CLOCK_MONOTONIC, 0).int
|
||||
let fdi = timerfd_create(CLOCK_MONOTONIC, O_CLOEXEC).int
|
||||
if fdi == -1:
|
||||
raiseIOSelectorsError(osLastError())
|
||||
setNonBlocking(fdi.cint)
|
||||
@@ -314,7 +314,7 @@ when not defined(android):
|
||||
discard sigaddset(nmask, cint(signal))
|
||||
blockSignals(nmask, omask)
|
||||
|
||||
let fdi = signalfd(-1, nmask, 0).int
|
||||
let fdi = signalfd(-1, nmask, O_CLOEXEC).int
|
||||
if fdi == -1:
|
||||
raiseIOSelectorsError(osLastError())
|
||||
setNonBlocking(fdi.cint)
|
||||
@@ -341,7 +341,7 @@ when not defined(android):
|
||||
discard sigaddset(nmask, posix.SIGCHLD)
|
||||
blockSignals(nmask, omask)
|
||||
|
||||
let fdi = signalfd(-1, nmask, 0).int
|
||||
let fdi = signalfd(-1, nmask, O_CLOEXEC).int
|
||||
if fdi == -1:
|
||||
raiseIOSelectorsError(osLastError())
|
||||
setNonBlocking(fdi.cint)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
# This module implements BSD kqueue().
|
||||
|
||||
import posix, times, kqueue
|
||||
import posix, times, kqueue, nativesockets
|
||||
|
||||
const
|
||||
# Maximum number of events that can be returned.
|
||||
@@ -76,7 +76,7 @@ type
|
||||
|
||||
proc getUnique[T](s: Selector[T]): int {.inline.} =
|
||||
# we create duplicated handles to get unique indexes for our `fds` array.
|
||||
result = posix.fcntl(s.sock, F_DUPFD, s.sock)
|
||||
result = posix.fcntl(s.sock, F_DUPFD_CLOEXEC, s.sock)
|
||||
if result == -1:
|
||||
raiseIOSelectorsError(osLastError())
|
||||
|
||||
@@ -96,8 +96,8 @@ proc newSelector*[T](): owned(Selector[T]) =
|
||||
# we allocating empty socket to duplicate it handle in future, to get unique
|
||||
# indexes for `fds` array. This is needed to properly identify
|
||||
# {Event.Timer, Event.Signal, Event.Process} events.
|
||||
let usock = posix.socket(posix.AF_INET, posix.SOCK_STREAM,
|
||||
posix.IPPROTO_TCP).cint
|
||||
let usock = createNativeSocket(posix.AF_INET, posix.SOCK_STREAM,
|
||||
posix.IPPROTO_TCP).cint
|
||||
if usock == -1:
|
||||
let err = osLastError()
|
||||
discard posix.close(kqFD)
|
||||
|
||||
@@ -228,7 +228,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
|
||||
if result.handle != -1: discard close(result.handle)
|
||||
raiseOSError(errCode)
|
||||
|
||||
var flags = if readonly: O_RDONLY else: O_RDWR
|
||||
var flags = (if readonly: O_RDONLY else: O_RDWR) or O_CLOEXEC
|
||||
|
||||
if newFileSize != -1:
|
||||
flags = flags or O_CREAT or O_TRUNC
|
||||
|
||||
@@ -185,19 +185,53 @@ proc toSockType*(protocol: Protocol): SockType =
|
||||
of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP, IPPROTO_ICMPV6:
|
||||
SOCK_RAW
|
||||
|
||||
proc createNativeSocket*(domain: Domain = AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP): SocketHandle =
|
||||
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
|
||||
socket(toInt(domain), toInt(sockType), toInt(protocol))
|
||||
proc close*(socket: SocketHandle) =
|
||||
## closes a socket.
|
||||
when useWinVersion:
|
||||
discard winlean.closesocket(socket)
|
||||
else:
|
||||
discard posix.close(socket)
|
||||
# TODO: These values should not be discarded. An OSError should be raised.
|
||||
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
|
||||
|
||||
proc createNativeSocket*(domain: cint, sockType: cint,
|
||||
protocol: cint): SocketHandle =
|
||||
when declared(setInheritable) or defined(nimdoc):
|
||||
proc setInheritable*(s: SocketHandle, inheritable: bool): bool {.inline.} =
|
||||
## Set whether a socket is inheritable by child processes. Returns `true`
|
||||
## on success.
|
||||
##
|
||||
## This function is not implemented on all platform, test for availability
|
||||
## with `declared() <system.html#declared,untyped>`.
|
||||
setInheritable(FileHandle s, inheritable)
|
||||
|
||||
proc createNativeSocket*(domain: cint, sockType: cint, protocol: cint,
|
||||
inheritable: bool = defined(nimInheritHandles)): SocketHandle =
|
||||
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
|
||||
##
|
||||
## `inheritable` decides if the resulting SocketHandle can be inherited
|
||||
## by child processes.
|
||||
##
|
||||
## Use this overload if one of the enums specified above does
|
||||
## not contain what you need.
|
||||
socket(domain, sockType, protocol)
|
||||
let sockType =
|
||||
when (defined(linux) or defined(bsd)) and not defined(nimdoc):
|
||||
if inheritable: sockType and not SOCK_CLOEXEC else: sockType or SOCK_CLOEXEC
|
||||
else:
|
||||
sockType
|
||||
result = socket(domain, sockType, protocol)
|
||||
when declared(setInheritable) and not (defined(linux) or defined(bsd)):
|
||||
if not setInheritable(result, inheritable):
|
||||
close result
|
||||
return osInvalidSocket
|
||||
|
||||
proc createNativeSocket*(domain: Domain = AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP,
|
||||
inheritable: bool = defined(nimInheritHandles)): SocketHandle =
|
||||
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
|
||||
##
|
||||
## `inheritable` decides if the resulting SocketHandle can be inherited
|
||||
## by child processes.
|
||||
createNativeSocket(toInt(domain), toInt(sockType), toInt(protocol))
|
||||
|
||||
proc newNativeSocket*(domain: Domain = AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
@@ -215,15 +249,6 @@ proc newNativeSocket*(domain: cint, sockType: cint,
|
||||
## not contain what you need.
|
||||
createNativeSocket(domain, sockType, protocol)
|
||||
|
||||
proc close*(socket: SocketHandle) =
|
||||
## closes a socket.
|
||||
when useWinVersion:
|
||||
discard winlean.closesocket(socket)
|
||||
else:
|
||||
discard posix.close(socket)
|
||||
# TODO: These values should not be discarded. An OSError should be raised.
|
||||
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
|
||||
|
||||
proc bindAddr*(socket: SocketHandle, name: ptr SockAddr,
|
||||
namelen: SockLen): cint =
|
||||
result = bindSocket(socket, name, namelen)
|
||||
@@ -652,14 +677,25 @@ proc selectWrite*(writefds: var seq[SocketHandle],
|
||||
|
||||
pruneSocketSet(writefds, (wr))
|
||||
|
||||
proc accept*(fd: SocketHandle): (SocketHandle, string) =
|
||||
proc accept*(fd: SocketHandle, inheritable = defined(nimInheritHandles)): (SocketHandle, string) =
|
||||
## Accepts a new client connection.
|
||||
##
|
||||
## `inheritable` decides if the resulting SocketHandle can be inherited by
|
||||
## child processes.
|
||||
##
|
||||
## Returns (osInvalidSocket, "") if an error occurred.
|
||||
var sockAddress: Sockaddr_in
|
||||
var addrLen = sizeof(sockAddress).SockLen
|
||||
var sock = accept(fd, cast[ptr SockAddr](addr(sockAddress)),
|
||||
addr(addrLen))
|
||||
var sock =
|
||||
when (defined(linux) or defined(bsd)) and not defined(nimdoc):
|
||||
accept4(fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen),
|
||||
if inheritable: 0 else: SOCK_CLOEXEC)
|
||||
else:
|
||||
accept(fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen))
|
||||
when declared(setInheritable) and not (defined(linux) or defined(bsd)):
|
||||
if not setInheritable(sock, inheritable):
|
||||
close sock
|
||||
sock = osInvalidSocket
|
||||
if sock == osInvalidSocket:
|
||||
return (osInvalidSocket, "")
|
||||
else:
|
||||
|
||||
@@ -212,22 +212,32 @@ proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
|
||||
when defined(macosx) and not defined(nimdoc):
|
||||
setSockOptInt(fd, SOL_SOCKET, SO_NOSIGPIPE, 1)
|
||||
|
||||
proc newSocket*(domain, sockType, protocol: cint, buffered = true): owned(Socket) =
|
||||
proc newSocket*(domain, sockType, protocol: cint, buffered = true,
|
||||
inheritable = defined(nimInheritHandles)): owned(Socket) =
|
||||
## Creates a new socket.
|
||||
##
|
||||
## The SocketHandle associated with the resulting Socket will not be
|
||||
## inheritable by child processes by default. This can be changed via
|
||||
## the `inheritable` parameter.
|
||||
##
|
||||
## If an error occurs OSError will be raised.
|
||||
let fd = createNativeSocket(domain, sockType, protocol)
|
||||
let fd = createNativeSocket(domain, sockType, protocol, inheritable)
|
||||
if fd == osInvalidSocket:
|
||||
raiseOSError(osLastError())
|
||||
result = newSocket(fd, domain.Domain, sockType.SockType, protocol.Protocol,
|
||||
buffered)
|
||||
|
||||
proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP, buffered = true): owned(Socket) =
|
||||
protocol: Protocol = IPPROTO_TCP, buffered = true,
|
||||
inheritable = defined(nimInheritHandles)): owned(Socket) =
|
||||
## Creates a new socket.
|
||||
##
|
||||
## The SocketHandle associated with the resulting Socket will not be
|
||||
## inheritable by child processes by default. This can be changed via
|
||||
## the `inheritable` parameter.
|
||||
##
|
||||
## If an error occurs OSError will be raised.
|
||||
let fd = createNativeSocket(domain, sockType, protocol)
|
||||
let fd = createNativeSocket(domain, sockType, protocol, inheritable)
|
||||
if fd == osInvalidSocket:
|
||||
raiseOSError(osLastError())
|
||||
result = newSocket(fd, domain, sockType, protocol, buffered)
|
||||
@@ -872,7 +882,8 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
|
||||
freeaddrinfo(aiList)
|
||||
|
||||
proc acceptAddr*(server: Socket, client: var owned(Socket), address: var string,
|
||||
flags = {SocketFlag.SafeDisconn}) {.
|
||||
flags = {SocketFlag.SafeDisconn},
|
||||
inheritable = defined(nimInheritHandles)) {.
|
||||
tags: [ReadIOEffect], gcsafe, locks: 0.} =
|
||||
## Blocks until a connection is being made from a client. When a connection
|
||||
## is made sets ``client`` to the client socket and ``address`` to the address
|
||||
@@ -882,19 +893,23 @@ proc acceptAddr*(server: Socket, client: var owned(Socket), address: var string,
|
||||
## The resulting client will inherit any properties of the server socket. For
|
||||
## example: whether the socket is buffered or not.
|
||||
##
|
||||
## The SocketHandle associated with the resulting client will not be
|
||||
## inheritable by child processes by default. This can be changed via
|
||||
## the `inheritable` parameter.
|
||||
##
|
||||
## The ``accept`` call may result in an error if the connecting socket
|
||||
## disconnects during the duration of the ``accept``. If the ``SafeDisconn``
|
||||
## flag is specified then this error will not be raised and instead
|
||||
## accept will be called again.
|
||||
if client.isNil:
|
||||
new(client)
|
||||
let ret = accept(server.fd)
|
||||
let ret = accept(server.fd, inheritable)
|
||||
let sock = ret[0]
|
||||
|
||||
if sock == osInvalidSocket:
|
||||
let err = osLastError()
|
||||
if flags.isDisconnectionError(err):
|
||||
acceptAddr(server, client, address, flags)
|
||||
acceptAddr(server, client, address, flags, inheritable)
|
||||
raiseOSError(err)
|
||||
else:
|
||||
address = ret[1]
|
||||
@@ -964,10 +979,16 @@ when false: #defineSsl:
|
||||
doHandshake()
|
||||
|
||||
proc accept*(server: Socket, client: var owned(Socket),
|
||||
flags = {SocketFlag.SafeDisconn}) {.tags: [ReadIOEffect].} =
|
||||
flags = {SocketFlag.SafeDisconn},
|
||||
inheritable = defined(nimInheritHandles))
|
||||
{.tags: [ReadIOEffect].} =
|
||||
## Equivalent to ``acceptAddr`` but doesn't return the address, only the
|
||||
## socket.
|
||||
##
|
||||
## The SocketHandle associated with the resulting client will not be
|
||||
## inheritable by child processes by default. This can be changed via
|
||||
## the `inheritable` parameter.
|
||||
##
|
||||
## The ``accept`` call may result in an error if the connecting socket
|
||||
## disconnects during the duration of the ``accept``. If the ``SafeDisconn``
|
||||
## flag is specified then this error will not be raised and instead
|
||||
|
||||
@@ -259,6 +259,31 @@ else:
|
||||
IOFBF {.importc: "_IOFBF", nodecl.}: cint
|
||||
IONBF {.importc: "_IONBF", nodecl.}: cint
|
||||
|
||||
const SupportIoctlInheritCtl = (defined(linux) or defined(bsd)) and
|
||||
not defined(nimscript)
|
||||
when SupportIoctlInheritCtl:
|
||||
var
|
||||
FIOCLEX {.importc, header: "<sys/ioctl.h>".}: cint
|
||||
FIONCLEX {.importc, header: "<sys/ioctl.h>".}: cint
|
||||
|
||||
proc c_ioctl(fd: cint, request: cint): cint {.
|
||||
importc: "ioctl", header: "<sys/ioctl.h>", varargs.}
|
||||
elif defined(posix) and not defined(nimscript):
|
||||
var
|
||||
F_GETFD {.importc, header: "<fcntl.h>".}: cint
|
||||
F_SETFD {.importc, header: "<fcntl.h>".}: cint
|
||||
FD_CLOEXEC {.importc, header: "<fcntl.h>".}: cint
|
||||
|
||||
proc c_fcntl(fd: cint, cmd: cint): cint {.
|
||||
importc: "fcntl", header: "<fcntl.h>", varargs.}
|
||||
elif defined(windows):
|
||||
const HANDLE_FLAG_INHERIT = culong 0x1
|
||||
proc getOsfhandle(fd: cint): FileHandle {.
|
||||
importc: "_get_osfhandle", header: "<io.h>".}
|
||||
|
||||
proc setHandleInformation(handle: FileHandle, mask, flags: culong): cint {.
|
||||
importc: "SetHandleInformation", header: "<handleapi.h>".}
|
||||
|
||||
const
|
||||
BufSize = 4000
|
||||
|
||||
@@ -292,12 +317,29 @@ proc getOsFileHandle*(f: File): FileHandle =
|
||||
## returns the OS file handle of the file ``f``. This is only useful for
|
||||
## platform specific programming.
|
||||
when defined(windows):
|
||||
proc getOsfhandle(fd: cint): FileHandle {.
|
||||
importc: "_get_osfhandle", header: "<io.h>".}
|
||||
result = getOsfhandle(getFileHandle(f))
|
||||
else:
|
||||
result = c_fileno(f)
|
||||
|
||||
when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(windows):
|
||||
proc setInheritable*(f: FileHandle, inheritable: bool): bool =
|
||||
## control whether a file handle can be inherited by child processes. Returns
|
||||
## ``true`` on success. This requires the OS file handle, which can be
|
||||
## retrieved via `getOsFileHandle <#getOsFileHandle,File>`_.
|
||||
##
|
||||
## This procedure is not guaranteed to be available for all platforms. Test for
|
||||
## availability with `declared() <system.html#declared,untyped>`.
|
||||
when SupportIoctlInheritCtl:
|
||||
result = c_ioctl(f, if inheritable: FIONCLEX else: FIOCLEX) != -1
|
||||
elif defined(posix):
|
||||
var flags = c_fcntl(f, F_GETFD)
|
||||
if flags == -1:
|
||||
return false
|
||||
flags = if inheritable: flags and not FD_CLOEXEC else: flags or FD_CLOEXEC
|
||||
result = c_fcntl(f, F_SETFD, flags) != -1
|
||||
else:
|
||||
result = setHandleInformation(f, HANDLE_FLAG_INHERIT, culong inheritable) != 0
|
||||
|
||||
proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect],
|
||||
benign.} =
|
||||
## reads a line of text from the file `f` into `line`. May throw an IO
|
||||
@@ -501,7 +543,21 @@ else:
|
||||
importc: "freopen", nodecl.}
|
||||
|
||||
const
|
||||
FormatOpen: array[FileMode, string] = ["rb", "wb", "w+b", "r+b", "ab"]
|
||||
NoInheritFlag =
|
||||
# Platform specific flag for creating a File without inheritance.
|
||||
when not defined(nimInheritHandles):
|
||||
when defined(windows):
|
||||
"N"
|
||||
elif defined(linux) or defined(bsd):
|
||||
"e"
|
||||
else:
|
||||
""
|
||||
else:
|
||||
""
|
||||
FormatOpen: array[FileMode, string] = [
|
||||
"rb" & NoInheritFlag, "wb" & NoInheritFlag, "w+b" & NoInheritFlag,
|
||||
"r+b" & NoInheritFlag, "ab" & NoInheritFlag
|
||||
]
|
||||
#"rt", "wt", "w+t", "r+t", "at"
|
||||
# we always use binary here as for Nim the OS line ending
|
||||
# should not be translated.
|
||||
@@ -544,17 +600,25 @@ proc open*(f: var File, filename: string,
|
||||
##
|
||||
## Default mode is readonly. Returns true iff the file could be opened.
|
||||
## This throws no exception if the file could not be opened.
|
||||
##
|
||||
## The file handle associated with the resulting ``File`` is not inheritable.
|
||||
var p = fopen(filename, FormatOpen[mode])
|
||||
if p != nil:
|
||||
var f2 = cast[File](p)
|
||||
when defined(posix) and not defined(nimscript):
|
||||
# How `fopen` handles opening a directory is not specified in ISO C and
|
||||
# POSIX. We do not want to handle directories as regular files that can
|
||||
# be opened.
|
||||
var f2 = cast[File](p)
|
||||
var res: Stat
|
||||
if c_fstat(getFileHandle(f2), res) >= 0'i32 and modeIsDir(res.st_mode):
|
||||
close(f2)
|
||||
return false
|
||||
when not defined(nimInheritHandles) and declared(setInheritable) and
|
||||
NoInheritFlag.len == 0:
|
||||
if not setInheritable(getOsFileHandle(f2), false):
|
||||
close(f2)
|
||||
return false
|
||||
|
||||
result = true
|
||||
f = cast[File](p)
|
||||
if bufSize > 0 and bufSize <= high(cint).int:
|
||||
@@ -569,13 +633,27 @@ proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {.
|
||||
## file variables.
|
||||
##
|
||||
## Default mode is readonly. Returns true iff the file could be reopened.
|
||||
result = freopen(filename, FormatOpen[mode], f) != nil
|
||||
##
|
||||
## The file handle associated with `f` won't be inheritable.
|
||||
if freopen(filename, FormatOpen[mode], f) != nil:
|
||||
when not defined(nimInheritHandles) and declared(setInheritable) and
|
||||
NoInheritFlag.len == 0:
|
||||
if not setInheritable(getOsFileHandle(f), false):
|
||||
close(f)
|
||||
return false
|
||||
result = true
|
||||
|
||||
proc open*(f: var File, filehandle: FileHandle,
|
||||
mode: FileMode = fmRead): bool {.tags: [], raises: [], benign.} =
|
||||
## Creates a ``File`` from a `filehandle` with given `mode`.
|
||||
##
|
||||
## Default mode is readonly. Returns true iff the file could be opened.
|
||||
##
|
||||
## The passed file handle will no longer be inheritable.
|
||||
when not defined(nimInheritHandles) and declared(setInheritable):
|
||||
let oshandle = when defined(windows): getOsfhandle(filehandle) else: filehandle
|
||||
if not setInheritable(oshandle, false):
|
||||
return false
|
||||
f = c_fdopen(filehandle, FormatOpen[mode])
|
||||
result = f != nil
|
||||
|
||||
@@ -585,6 +663,8 @@ proc open*(filename: string,
|
||||
##
|
||||
## Default mode is readonly. Raises an ``IOError`` if the file
|
||||
## could not be opened.
|
||||
##
|
||||
## The file handle associated with the resulting ``File`` is not inheritable.
|
||||
if not open(result, filename, mode, bufSize):
|
||||
sysFatal(IOError, "cannot open: " & filename)
|
||||
|
||||
|
||||
@@ -126,6 +126,8 @@ const
|
||||
|
||||
CREATE_NO_WINDOW* = 0x08000000'i32
|
||||
|
||||
HANDLE_FLAG_INHERIT* = 0x00000001'i32
|
||||
|
||||
proc getVersionExW*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
|
||||
stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.}
|
||||
proc getVersionExA*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
|
||||
@@ -708,6 +710,9 @@ proc duplicateHandle*(hSourceProcessHandle: Handle, hSourceHandle: Handle,
|
||||
dwOptions: DWORD): WINBOOL{.stdcall, dynlib: "kernel32",
|
||||
importc: "DuplicateHandle".}
|
||||
|
||||
proc getHandleInformation*(hObject: Handle, lpdwFlags: ptr DWORD): WINBOOL {.
|
||||
stdcall, dynlib: "kernel32", importc: "GetHandleInformation".}
|
||||
|
||||
proc setHandleInformation*(hObject: Handle, dwMask: DWORD,
|
||||
dwFlags: DWORD): WINBOOL {.stdcall,
|
||||
dynlib: "kernel32", importc: "SetHandleInformation".}
|
||||
|
||||
85
tests/stdlib/tfdleak.nim
Normal file
85
tests/stdlib/tfdleak.nim
Normal file
@@ -0,0 +1,85 @@
|
||||
discard """
|
||||
exitcode: 0
|
||||
output: ""
|
||||
matrix: "; -d:nimInheritHandles"
|
||||
"""
|
||||
|
||||
import os, osproc, strutils, nativesockets, net, selectors, memfiles
|
||||
when defined(windows):
|
||||
import winlean
|
||||
else:
|
||||
import posix
|
||||
|
||||
proc leakCheck(f: int | FileHandle | SocketHandle, msg: string, expectLeak = defined(nimInheritHandles)) =
|
||||
discard startProcess(
|
||||
getAppFilename(),
|
||||
args = @[$f.int, msg, $expectLeak],
|
||||
options = {poParentStreams}
|
||||
).waitForExit -1
|
||||
|
||||
proc isValidHandle(f: int): bool =
|
||||
## Check if a handle is valid. Requires OS-native handles.
|
||||
when defined(windows):
|
||||
var flags: DWORD
|
||||
result = getHandleInformation(f.Handle, addr flags) != 0
|
||||
else:
|
||||
result = fcntl(f.cint, F_GETFD) != -1
|
||||
|
||||
proc main() =
|
||||
if paramCount() == 0:
|
||||
# Parent process
|
||||
let f = system.open("__test_fdleak", fmReadWrite)
|
||||
defer: close f
|
||||
|
||||
leakCheck(f.getOsFileHandle, "system.open()")
|
||||
|
||||
doAssert f.reopen("__test_fdleak2", fmReadWrite), "reopen failed"
|
||||
|
||||
leakCheck(f.getOsFileHandle, "reopen")
|
||||
|
||||
let sock = createNativeSocket()
|
||||
defer: close sock
|
||||
leakCheck(sock, "createNativeSocket()")
|
||||
if sock.setInheritable(true):
|
||||
leakCheck(sock, "createNativeSocket()", true)
|
||||
else:
|
||||
raiseOSError osLastError()
|
||||
|
||||
let server = newSocket()
|
||||
defer: close server
|
||||
server.bindAddr()
|
||||
server.listen()
|
||||
let (_, port) = server.getLocalAddr
|
||||
|
||||
leakCheck(server.getFd, "newSocket()")
|
||||
|
||||
let client = newSocket()
|
||||
defer: close client
|
||||
client.connect("127.0.0.1", port)
|
||||
|
||||
var input: Socket
|
||||
server.accept(input)
|
||||
|
||||
leakCheck(input.getFd, "accept()")
|
||||
|
||||
# ioselectors_select doesn't support returning a handle.
|
||||
when not defined(windows):
|
||||
let selector = newSelector[int]()
|
||||
leakCheck(selector.getFd, "selector()", false)
|
||||
|
||||
var mf = memfiles.open("__test_fdleak3", fmReadWrite, newFileSize = 1)
|
||||
defer: close mf
|
||||
when defined(windows):
|
||||
leakCheck(mf.fHandle, "memfiles.open().fHandle", false)
|
||||
leakCheck(mf.mapHandle, "memfiles.open().mapHandle", false)
|
||||
else:
|
||||
leakCheck(mf.handle, "memfiles.open().handle", false)
|
||||
else:
|
||||
let
|
||||
fd = parseInt(paramStr 1)
|
||||
expectLeak = parseBool(paramStr 3)
|
||||
msg = (if expectLeak: "not " else: "") & "leaked " & paramStr 2
|
||||
if expectLeak xor fd.isValidHandle:
|
||||
echo msg
|
||||
|
||||
when isMainModule: main()
|
||||
@@ -228,6 +228,7 @@ v("EXDEV")
|
||||
|
||||
header("<fcntl.h>")
|
||||
v("F_DUPFD")
|
||||
v("F_DUPFD_CLOEXEC")
|
||||
v("F_GETFD")
|
||||
v("F_SETFD")
|
||||
v("F_GETFL")
|
||||
@@ -254,6 +255,7 @@ v("O_ACCMODE")
|
||||
v("O_RDONLY")
|
||||
v("O_RDWR")
|
||||
v("O_WRONLY")
|
||||
v("O_CLOEXEC")
|
||||
v("POSIX_FADV_NORMAL")
|
||||
v("POSIX_FADV_SEQUENTIAL")
|
||||
v("POSIX_FADV_RANDOM")
|
||||
@@ -621,6 +623,7 @@ v("SOCK_DGRAM")
|
||||
v("SOCK_RAW")
|
||||
v("SOCK_SEQPACKET")
|
||||
v("SOCK_STREAM")
|
||||
v("SOCK_CLOEXEC", no_other = true)
|
||||
v("SOL_SOCKET")
|
||||
v("SOMAXCONN")
|
||||
v("SO_REUSEPORT", no_other = true)
|
||||
|
||||
Reference in New Issue
Block a user