Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sketch] openat, fsync, and sync #43

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions Sources/System/FileOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,109 @@ extension FileDescriptor {
fatalError("Not implemented")
}
}

extension FileDescriptor {
/// Opens or creates a file for reading or writing.
///
/// - Parameters:
/// - path: The location of the file to open.
/// - at: if `path` is relative, treat it as relative to this file descriptor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we should not use prepositions as variable names.

Suggested change
/// - at: if `path` is relative, treat it as relative to this file descriptor
/// - base: if `path` is relative, treat it as relative to this file descriptor

Or we could just call it fd.

/// rather than relative to the current working directory
/// - mode: The read and write access to use.
/// - options: The behavior for opening the file.
/// - permissions: The file permissions to use for created files.
/// - retryOnInterrupt: Whether to retry the open operation
/// if it throws ``Errno/interrupted``.
/// The default is `true`.
/// Pass `false` to try only once and throw an error upon interruption.
/// - Returns: A file descriptor for the open file
///
/// The corresponding C function is `openat`.
@_alwaysEmitIntoClient
public static func open(
_ path: FilePath,
at: FileDescriptor,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
at: FileDescriptor,
at base: FileDescriptor,

(etc, throughout this PR.)

_ mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(),
permissions: FilePermissions? = nil,
retryOnInterrupt: Bool = true
) throws -> FileDescriptor {
try path.withPlatformString {
try FileDescriptor._open(
$0, at: at, mode, options: options, permissions: permissions,
retryOnInterrupt: retryOnInterrupt
).get()
}
}

@usableFromInline
internal static func _open(
_ path: UnsafePointer<CInterop.PlatformChar>,
at: FileDescriptor,
_ mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions,
permissions: FilePermissions?,
retryOnInterrupt: Bool
) -> Result<FileDescriptor, Errno> {
let oFlag = mode.rawValue | options.rawValue
let descOrError: Result<CInt, Errno> = valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
if let permissions = permissions {
return system_openat(at.rawValue, path, oFlag, permissions.rawValue)
}
precondition(!options.contains(.create),
"Create must be given permissions")
return system_openat(at.rawValue, path, oFlag)
}
return descOrError.map { FileDescriptor(rawValue: $0) }
}

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "open")
public static func openat() throws -> FileDescriptor {
fatalError("Not implemented")
}

/// Synchronize a file's state with that on disk.
///
/// Note that while this will flush all data from the host to the drive
/// (i.e. the "permanent storage device"), the drive itself may not
/// physically write the data to the platters for quite some time and it
/// may be written in an out-of-order sequence.
///
/// For applications that require tighter guarantees about the integrity of
/// their data, Mac OS X provides the F_FULLFSYNC fcntl.
///
/// The corresponding C function is `fsync`.
@_alwaysEmitIntoClient
public func sync(retryOnInterrupt: Bool = true) throws {
try _sync(retryOnInterrupt: retryOnInterrupt).get() }

@usableFromInline
internal func _sync(retryOnInterrupt: Bool) -> Result<(), Errno> {
nothingOrErrno(retryOnInterrupt: retryOnInterrupt) {
system_fsync(self.rawValue)
}
}

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "sync")
public static func fsync() throws -> FileDescriptor {
fatalError("Not implemented")
}

// TODO: Figure out the deal with fdatasync. Darwin does not have it available.

}

/// A namespace for global, file-system operations
public
enum FileSystem {}

extension FileSystem {
/// Synchronize disk block status with that on disk.
///
/// The corresponding C function is `sync`.
public static func sync() {
system_sync()
}
}
36 changes: 36 additions & 0 deletions Sources/System/Internals/Syscalls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ internal func system_open(
return open(path, oflag, mode)
}

// NOTE: To workaround compiler liminations, we mock `at` after `path`.
internal func system_openat(
_ at: CInt, _ path: UnsafePointer<CInterop.PlatformChar>, _ oflag: Int32
) -> CInt {
#if ENABLE_MOCKING
if mockingEnabled { return _mock(path: path, at, oflag) }
#endif
return openat(at, path, oflag)
}

internal func system_openat(
_ at: CInt,
_ path: UnsafePointer<CInterop.PlatformChar>,
_ oflag: Int32, _ mode: CInterop.Mode
) -> CInt {
#if ENABLE_MOCKING
if mockingEnabled { return _mock(path: path, at, oflag, mode) }
#endif
return openat(at, path, oflag, mode)
}

// close
internal func system_close(_ fd: Int32) -> Int32 {
#if ENABLE_MOCKING
Expand Down Expand Up @@ -115,3 +136,18 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 {
#endif
return dup2(fd, fd2)
}

internal func system_fsync(_ fd: Int32) -> Int32 {
#if ENABLE_MOCKING
if mockingEnabled { return _mock(fd) }
#endif
return fsync(fd)
}

internal func system_sync() {
#if ENABLE_MOCKING
if mockingEnabled { _ = _mock(); return }
#endif
return sync()
}

42 changes: 42 additions & 0 deletions Tests/SystemTests/FileOperationsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,48 @@ final class FileOperationsTest: XCTestCase {
for test in syscallTestCases { test.runAllTests() }
}

// Version 2 because availability...
func testSysCallsv2() {
let fd = FileDescriptor(rawValue: 1)

let rawBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 100, alignment: 4)
defer { rawBuf.deallocate() }
let rawFD = fd.rawValue

// NOTE: To workaround compiler liminations, we mock `at` after `path`.
let syscallTestCases: Array<MockTestCase> = [
MockTestCase(
name: "openat", .interruptable, "a path", rawFD, O_RDWR | O_APPEND
) {
retry in
_ = try FileDescriptor.open(
"a path", at: fd, .readWrite,
options: [.append], retryOnInterrupt: retry)
},

MockTestCase(
name: "openat", .interruptable, "a path", rawFD, O_WRONLY | O_CREAT | O_APPEND, 0o777
) {
retry in
_ = try FileDescriptor.open(
"a path", at: fd, .writeOnly,
options: [.create, .append],
permissions: [.groupReadWriteExecute, .ownerReadWriteExecute, .otherReadWriteExecute],
retryOnInterrupt: retry)
},

MockTestCase(name: "fsync", .interruptable, rawFD) { retry in
_ = try fd.sync(retryOnInterrupt: retry)
},

MockTestCase(name: "sync", .noError) { _ in
FileSystem.sync()
},
]

syscallTestCases.forEach { $0.runAllTests() }
}

func testHelpers() {
// TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter
}
Expand Down