-
Notifications
You must be signed in to change notification settings - Fork 644
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
Add API for setting last accessed and last modified file times #2735
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -143,6 +143,26 @@ public protocol FileHandleProtocol { | |
/// | ||
/// After closing the handle calls to other functions will throw an appropriate error. | ||
func close() async throws | ||
|
||
/// Sets the file's last access and last data modification times to the given values. | ||
/// | ||
/// If **either** time is `nil`, the current value will not be changed. | ||
/// If **both** times are `nil`, then both times will be set to the current time. | ||
/// | ||
/// > Important: Times are only considered valid if their nanoseconds components are one of the following: | ||
/// > - `UTIME_NOW` (you can use ``FileInfo/Timespec/now`` to get a Timespec set to this value), | ||
/// > - `UTIME_OMIT` (you can use ``FileInfo/Timespec/omit`` to get a Timespec set to this value),, | ||
/// > - Greater than zero and no larger than 1000 million | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we remove this line as we clamp the value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought it was better to explain that we will clamp/update the value to the closest valid value instead. Otherwise if people are willingly using an invalid value they could be confused as to why the times are being set to the "wrong" thing. Let me know if you don't agree. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah I'm happy with that. |
||
/// | ||
/// - Parameters: | ||
/// - lastAccessTime: The new value of the file's last access time, as time elapsed since the Epoch. | ||
/// - lastDataModificationTime: The new value of the file's last data modification time, as time elapsed since the Epoch. | ||
/// | ||
/// - Throws: If there's an error updating the times. If this happens, the original values won't be modified. | ||
func setTimes( | ||
lastAccess: FileInfo.Timespec?, | ||
lastDataModification: FileInfo.Timespec? | ||
) async throws | ||
} | ||
|
||
// MARK: - Readable | ||
|
@@ -486,6 +506,13 @@ extension WritableFileHandleProtocol { | |
) async throws -> Int64 { | ||
try await self.write(contentsOf: buffer.readableBytesView, toAbsoluteOffset: offset) | ||
} | ||
|
||
/// Sets the file's last access and last data modification times to the current time. | ||
/// | ||
/// - Throws: If there's an error updating the times. If this happens, the original values won't be modified. | ||
public func touch() async throws { | ||
try await self.setTimes(lastAccess: nil, lastDataModification: nil) | ||
} | ||
} | ||
|
||
/// A file handle which is suitable for reading and writing. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,10 @@ import SystemPackage | |
import Darwin | ||
#elseif canImport(Glibc) | ||
import Glibc | ||
import CNIOLinux | ||
#elseif canImport(Musl) | ||
import Musl | ||
import CNIOLinux | ||
#endif | ||
|
||
/// Information about a file system object. | ||
|
@@ -145,6 +147,29 @@ extension FileInfo { | |
|
||
/// A time interval consisting of whole seconds and nanoseconds. | ||
public struct Timespec: Hashable, Sendable { | ||
#if canImport(Darwin) | ||
private static let UTIME_OMIT_INT = Int(UTIME_OMIT) | ||
private static let UTIME_NOW_INT = Int(UTIME_NOW) | ||
#elseif canImport(Glibc) || canImport(Musl) | ||
private static let UTIME_OMIT_INT = Int(CNIOLinux_UTIME_OMIT) | ||
private static let UTIME_NOW_INT = Int(CNIOLinux_UTIME_NOW) | ||
gjcairo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#endif | ||
|
||
/// A timespec where the seconds are set to zero and the nanoseconds set to `UTIME_OMIT`. | ||
/// In syscalls such as `futimens`, this means the time component set to this value will be ignored. | ||
public static var omit = Self.init( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 This should either be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also let's avoid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh god, why did I use var. Good catch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Easily done 🙂 |
||
seconds: 0, | ||
nanoseconds: Self.UTIME_OMIT_INT | ||
) | ||
|
||
/// A timespec where the seconds are set to zero and the nanoseconds set to `UTIME_NOW`. | ||
/// In syscalls such as `futimens`, this means the time component set to this value will be | ||
/// be set to the current time or the largest value supported by the platform, whichever is smaller. | ||
public static var now = Self.init( | ||
seconds: 0, | ||
nanoseconds: Self.UTIME_NOW_INT | ||
) | ||
|
||
/// The number of seconds. | ||
public var seconds: Int | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1066,6 +1066,44 @@ extension FileSystemError { | |||||
location: location | ||||||
) | ||||||
} | ||||||
|
||||||
@_spi(Testing) | ||||||
public static func futimens( | ||||||
gjcairo marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
errno: Errno, | ||||||
path: FilePath, | ||||||
lastAccessTime: FileInfo.Timespec?, | ||||||
lastDataModificationTime: FileInfo.Timespec?, | ||||||
location: SourceLocation | ||||||
) -> FileSystemError { | ||||||
let code: FileSystemError.Code | ||||||
let message: String | ||||||
|
||||||
switch errno { | ||||||
case .permissionDenied, .notPermitted: | ||||||
code = .permissionDenied | ||||||
message = "Not permited to change last access or last data modification times for \(path)." | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ this one didn't get fixed |
||||||
|
||||||
case .readOnlyFileSystem: | ||||||
code = .unsupported | ||||||
message = "Not permitted to change last access or last data modification times for \(path): this is a read-only file system." | ||||||
|
||||||
case .badFileDescriptor: | ||||||
code = .closed | ||||||
message = "Could not change last access or last data modification dates for \(path): file is closed." | ||||||
|
||||||
default: | ||||||
code = .unknown | ||||||
message = "Could not change last access or last data modification dates for \(path)." | ||||||
} | ||||||
|
||||||
return FileSystemError( | ||||||
code: code, | ||||||
message: message, | ||||||
systemCall: "futimens", | ||||||
errno: errno, | ||||||
location: location | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO "clamp/update the value to the closest valid value" is good/fine, but the API design still bothers me.
I would have preferred these 2 lines to have been obvious in the API design.
Perhaps using an (structified) enum as the single parameter that the function accepts. Or anything better. Maybe splitting up the responsibility into 2/3 different functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's clear from
setTimes(lastAccess: t)
thatlastDataModification
isn't modified.It's fair to say though that setting both to
nil
isn't an obvious API:setTimes(lastAccess: nil, lastDataModification: nil)
but that's why we also have thetouch()
wrapper which many developers are familiar with.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we had separate methods for each, we'd unavoidably have to make two separate sys calls, which is expensive. We'd also not be setting the times to the exact same value, as the current time will have changed if we use the
UTIME_NOW
timespec
.However, I've added two new convenience methods that default one of the parameters to
nil
to have nicer API:setLastAccessTime(to:)
andsetLastDataModificationTime(to:)
.