Skip to content

Instantly share code, notes, and snippets.

@iby
Last active July 10, 2024 07:30
Show Gist options
  • Save iby/2f16cc6a5c995ed9e18b45075a801a96 to your computer and use it in GitHub Desktop.
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.
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