From 6ef042ff2f21a049bf810d83b573e359d3d9d9a2 Mon Sep 17 00:00:00 2001 From: Cheru Berhanu Date: Wed, 20 Aug 2025 02:14:00 -0700 Subject: [PATCH] POSIX: don't force-unwrap result of fdopendir(3) Motivation: It's possible for fdopendir(3) to return 0 if the file descriptor's backing filesystem is force-unmounted. This should return an error, but instead crashes the library. Result: If fdopendir fails, FileDescriptor.opendir will return the error contained in errno (set by the stat(2) call in libc fdopendir). --- .../Internal/System Calls/Errno.swift | 21 +++++++++++++++++++ .../FileDescriptor+Syscalls.swift | 2 +- .../Internal/System Calls/Syscalls.swift | 4 ++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Sources/NIOFileSystem/Internal/System Calls/Errno.swift b/Sources/NIOFileSystem/Internal/System Calls/Errno.swift index 7ae8059879..cfb7f458e6 100644 --- a/Sources/NIOFileSystem/Internal/System Calls/Errno.swift +++ b/Sources/NIOFileSystem/Internal/System Calls/Errno.swift @@ -146,3 +146,24 @@ public func valueOrErrno( } } } + +/// As `valueOrErrno` but returns `Errno` if result is nil. +@_spi(Testing) +public func unwrapValueOrErrno( + retryOnInterrupt: Bool = true, + _ fn: () -> R? +) -> Result { + while true { + Errno.clear() + if let result = fn() { + return .success(result) + } else { + let errno = Errno._current + if errno == .interrupted, retryOnInterrupt { + continue + } else { + return .failure(errno) + } + } + } +} diff --git a/Sources/NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift b/Sources/NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift index 2941243999..e59d01dea2 100644 --- a/Sources/NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift +++ b/Sources/NIOFileSystem/Internal/System Calls/FileDescriptor+Syscalls.swift @@ -191,7 +191,7 @@ extension FileDescriptor { /// caller should not modify the descriptor or close the descriptor via `close()`. Once /// directory iteration has been completed then `Libc.closdir(_:)` must be called. internal func opendir() -> Result { - valueOrErrno(retryOnInterrupt: false) { + unwrapValueOrErrno(retryOnInterrupt: false) { libc_fdopendir(self.rawValue) } } diff --git a/Sources/NIOFileSystem/Internal/System Calls/Syscalls.swift b/Sources/NIOFileSystem/Internal/System Calls/Syscalls.swift index a871876025..f75a5a01e7 100644 --- a/Sources/NIOFileSystem/Internal/System Calls/Syscalls.swift +++ b/Sources/NIOFileSystem/Internal/System Calls/Syscalls.swift @@ -368,8 +368,8 @@ internal func system_futimens( /// fdopendir(3): Opens a directory stream for the file descriptor internal func libc_fdopendir( _ fd: FileDescriptor.RawValue -) -> CInterop.DirPointer { - fdopendir(fd)! +) -> CInterop.DirPointer? { + fdopendir(fd) } /// readdir(3): Returns a pointer to the next directory entry