Last active
July 10, 2024 07:30
-
-
Save iby/2f16cc6a5c995ed9e18b45075a801a96 to your computer and use it in GitHub Desktop.
At attempt to better understand Darwin's standard streams (stdout, stderr) and file descriptors.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
fileprivate enum StdStream { | |
case out | |
case err | |
fileprivate var fileDescriptor: FileDescriptor { | |
switch self { | |
case .out: return FileDescriptor(STDOUT_FILENO, "stdout") | |
case .err: return FileDescriptor(STDERR_FILENO, "stderr") | |
} | |
} | |
fileprivate func silent<T>(_ action: () -> T) -> T { | |
var original: FileDescriptor? | |
do { original = try self.mute() } catch { NSLog(error.localizedDescription) } | |
let result = action() | |
do { if let original { try self.unmute(from: original) } } catch { NSLog(error.localizedDescription) } | |
return result | |
} | |
// Redirects the specified file descriptor output to /dev/null and returns the duplicated original descriptor. | |
fileprivate func mute() throws -> FileDescriptor { | |
do { | |
let copy = try self.fileDescriptor.duplicate() | |
let null = try FileDescriptor(path: "/dev/null", flags: O_WRONLY) | |
defer { try? null.close() } | |
try null.duplicate(to: self.fileDescriptor) | |
return copy | |
} catch { | |
throw FileDescriptor.Error("Can’t mute “\(self.fileDescriptor)” descriptor: \(error.localizedDescription)") | |
} | |
} | |
fileprivate func unmute(from fileDescriptor: FileDescriptor) throws { | |
do { | |
try fileDescriptor.duplicate(to: self.fileDescriptor) | |
} catch { | |
throw FileDescriptor.Error("Can’t unmute “\(self.fileDescriptor)” from “\(fileDescriptor)” descriptor: \(error.localizedDescription)") | |
} | |
} | |
} | |
fileprivate struct FileDescriptor: CustomStringConvertible { | |
fileprivate init(_ identifier: Int32, _ path: String? = nil) { | |
self.identifier = identifier | |
self.path = path | |
} | |
fileprivate let identifier: Int32 | |
fileprivate let path: String? | |
fileprivate var description: String { self.path ?? "\(identifier)" } | |
} | |
extension FileDescriptor { | |
fileprivate struct Error: Swift.Error, LocalizedError { | |
init(_ reason: String) { self.reason = reason } | |
fileprivate let reason: String | |
fileprivate var errorDescription: String? { self.reason } | |
} | |
} | |
extension FileDescriptor { | |
/// Wraps around `open` function, opens a file and creates a new descriptor that can be used for reading, writing, or both, depending on the flags specified. | |
/// Note: improper use of the open function can lead to resource leaks if file descriptors are not properly closed after use, and careful attention should | |
/// be paid to handling file permissions and modes to ensure security and proper access control. | |
fileprivate init(path: String, flags: Int32) throws { | |
let result = Darwin.open("/dev/null", flags) | |
if result == -1 { throw Error("Can’t create “\(path)” descriptor: \(String(cString: strerror(errno)))") } | |
self = Self(result, path) | |
} | |
/// Wraps around `close` function, closes an open file descriptor, freeing the file descriptor for reuse. Note: closing a file descriptor that | |
/// is already closed or was never open can result in an error, and any subsequent operations on that file descriptor will fail. | |
fileprivate func close() throws { | |
let result = Darwin.close(self.identifier) | |
if result == -1 { throw Error("Can’t create “\(path)” descriptor: \(String(cString: strerror(errno)))") } | |
} | |
/// Wraps around `dup`, duplicates an existing file descriptor, returning a new file descriptor that refers to the same open file description. | |
/// Note: the new file descriptor will share the same file offset, file status flags, and file access mode as the original. | |
fileprivate func duplicate() throws -> Self { | |
let result = Darwin.dup(self.identifier) | |
if result == -1 { throw Error("Can’t duplicate “\(self)” descriptor: \(String(cString: strerror(errno)))") } | |
return Self(result, self.path) | |
} | |
/// Wraps around `dup2`, duplicates an existing file descriptor to a specified file descriptor number, closing the specified descriptor first if it is already open. | |
/// Note: `dup2` guarantees the duplication even if the specified file descriptor is already in use, which may lead to unexpected behavior if not handled properly. | |
fileprivate func duplicate(to newDescriptor: Self) throws { | |
let result = Darwin.dup2(self.identifier, newDescriptor.identifier) | |
if result == -1 { throw Error("Can’t duplicate “\(self)” to “\(newDescriptor)” descriptor: \(String(cString: strerror(errno)))") } | |
do { try self.close() } catch { throw Error("Can’t duplicate “\(self)” to “\(newDescriptor)” descriptor: \(error.localizedDescription)") } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment