Files
swift-nio/Sources/NIOFS/FileInfo.swift
George Barnett a18bddb0ac Add back NIOFileSystem (#3380)
Motivation:

We accidentally removed the 'NIOFileSystem' module from the
'_NIOFileSystem' product in the last release.

Modifications:

- Rename 'NIOFileSystem' and 'NIOFileSystemFoundationCompat' to 'NIOFS'
  and 'NIOFSFoundationCompat'
- Add back 'NIOFileSystem' which re-exports '_NIOFileSystem' (there was
  no publicly available 'NIOFileSystemFoundationCompat' module to
  remove, only '_NIOFileSystemFoundationCompat').

Result:

Fewer breaks
2025-09-23 17:29:01 +01:00

279 lines
10 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SystemPackage
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
@preconcurrency import Glibc
import CNIOLinux
#elseif canImport(Musl)
@preconcurrency import Musl
import CNIOLinux
#elseif canImport(Android)
@preconcurrency import Android
import CNIOLinux
#endif
/// Information about a file system object.
///
/// The information available for a file depends on the platform, ``FileInfo`` provides
/// convenient access to a common subset of properties. Using these properties ensures that
/// code is portable. If available, the platform specific information is made available via
/// ``FileInfo/platformSpecificStatus``. However users should take care to ensure their
/// code uses the correct platform checks when using it to ensure their code is portable.
public struct FileInfo: Hashable, Sendable {
/// Wraps `CInterop.Stat` providing `Hashable` and `Equatable` conformance.
private var _platformSpecificStatus: Stat?
/// The information about the file returned from the filesystem, if available.
///
/// This value is platform specific: you should be careful when using
/// it as some of its fields vary across platforms. In most cases prefer
/// using other properties on this type instead.
///
/// See also: the manual pages for 'stat' (`man 2 stat`)
public var platformSpecificStatus: CInterop.Stat? {
get { self._platformSpecificStatus?.stat }
set { self._platformSpecificStatus = newValue.map { Stat($0) } }
}
/// The type of the file.
public var type: FileType
/// Permissions currently set on the file.
public var permissions: FilePermissions
/// The size of the file in bytes.
public var size: Int64
/// User ID of the file.
public var userID: UserID
/// Group ID of the file.
public var groupID: GroupID
/// The last time the file was accessed.
public var lastAccessTime: Timespec
/// The last time the files data was last changed.
public var lastDataModificationTime: Timespec
/// The last time the status of the file was changed.
public var lastStatusChangeTime: Timespec
/// Creates a ``FileInfo`` by deriving values from a platform-specific value.
public init(platformSpecificStatus: CInterop.Stat) {
self._platformSpecificStatus = Stat(platformSpecificStatus)
self.type = FileType(platformSpecificMode: CInterop.Mode(platformSpecificStatus.st_mode))
self.permissions = FilePermissions(masking: CInterop.Mode(platformSpecificStatus.st_mode))
self.size = Int64(platformSpecificStatus.st_size)
self.userID = UserID(rawValue: platformSpecificStatus.st_uid)
self.groupID = GroupID(rawValue: platformSpecificStatus.st_gid)
#if canImport(Darwin)
self.lastAccessTime = Timespec(platformSpecificStatus.st_atimespec)
self.lastDataModificationTime = Timespec(platformSpecificStatus.st_mtimespec)
self.lastStatusChangeTime = Timespec(platformSpecificStatus.st_ctimespec)
#elseif canImport(Glibc) || canImport(Musl) || canImport(Android)
self.lastAccessTime = Timespec(platformSpecificStatus.st_atim)
self.lastDataModificationTime = Timespec(platformSpecificStatus.st_mtim)
self.lastStatusChangeTime = Timespec(platformSpecificStatus.st_ctim)
#endif
}
/// Creates a ``FileInfo`` from the provided values.
///
/// If you have a platform specific status value prefer calling
/// ``init(platformSpecificStatus:)``.
public init(
type: FileType,
permissions: FilePermissions,
size: Int64,
userID: UserID,
groupID: GroupID,
lastAccessTime: Timespec,
lastDataModificationTime: Timespec,
lastStatusChangeTime: Timespec
) {
self._platformSpecificStatus = nil
self.type = type
self.permissions = permissions
self.size = size
self.userID = userID
self.groupID = groupID
self.lastAccessTime = lastAccessTime
self.lastDataModificationTime = lastDataModificationTime
self.lastStatusChangeTime = lastStatusChangeTime
}
}
extension FileInfo {
/// The numeric ID of a user.
public struct UserID: Hashable, Sendable, CustomStringConvertible {
public var rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
public var description: String {
String(describing: self.rawValue)
}
}
/// The numeric ID of a group.
public struct GroupID: Hashable, Sendable, CustomStringConvertible {
public var rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
public var description: String {
String(describing: self.rawValue)
}
}
/// A time interval consisting of whole seconds and nanoseconds.
public struct Timespec: Hashable, Sendable {
#if canImport(Darwin)
private static let utimeOmit = Int(UTIME_OMIT)
private static let utimeNow = Int(UTIME_NOW)
#elseif canImport(Glibc) || canImport(Musl) || canImport(Android)
private static let utimeOmit = Int(CNIOLinux_UTIME_OMIT)
private static let utimeNow = Int(CNIOLinux_UTIME_NOW)
#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 let omit = Self(
seconds: 0,
nanoseconds: Self.utimeOmit
)
/// 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 let now = Self(
seconds: 0,
nanoseconds: Self.utimeNow
)
/// The number of seconds.
public var seconds: Int
/// The number of nanoseconds.
public var nanoseconds: Int
init(_ timespec: timespec) {
self.seconds = timespec.tv_sec
self.nanoseconds = timespec.tv_nsec
}
public init(seconds: Int, nanoseconds: Int) {
self.seconds = seconds
self.nanoseconds = nanoseconds
}
}
}
/// A wrapper providing `Hashable` and `Equatable` conformance for `CInterop.Stat`.
private struct Stat: Hashable {
var stat: CInterop.Stat
init(_ stat: CInterop.Stat) {
self.stat = stat
}
func hash(into hasher: inout Hasher) {
let stat = self.stat
// Different platforms have different underlying values; these are
// common between Darwin and Glibc.
hasher.combine(stat.st_dev)
hasher.combine(stat.st_mode)
hasher.combine(stat.st_nlink)
hasher.combine(stat.st_ino)
hasher.combine(stat.st_uid)
hasher.combine(stat.st_gid)
hasher.combine(stat.st_rdev)
hasher.combine(stat.st_size)
hasher.combine(stat.st_blocks)
hasher.combine(stat.st_blksize)
#if canImport(Darwin)
hasher.combine(FileInfo.Timespec(stat.st_atimespec))
hasher.combine(FileInfo.Timespec(stat.st_mtimespec))
hasher.combine(FileInfo.Timespec(stat.st_ctimespec))
hasher.combine(FileInfo.Timespec(stat.st_birthtimespec))
hasher.combine(stat.st_flags)
hasher.combine(stat.st_gen)
#elseif canImport(Glibc) || canImport(Musl) || canImport(Android)
hasher.combine(FileInfo.Timespec(stat.st_atim))
hasher.combine(FileInfo.Timespec(stat.st_mtim))
hasher.combine(FileInfo.Timespec(stat.st_ctim))
#endif
}
static func == (lhs: Stat, rhs: Stat) -> Bool {
let lStat = lhs.stat
let rStat = rhs.stat
// Different platforms have different underlying values; these are
// common between Darwin and Glibc.
var isEqual = lStat.st_dev == rStat.st_dev
isEqual = isEqual && lStat.st_mode == rStat.st_mode
isEqual = isEqual && lStat.st_nlink == rStat.st_nlink
isEqual = isEqual && lStat.st_ino == rStat.st_ino
isEqual = isEqual && lStat.st_uid == rStat.st_uid
isEqual = isEqual && lStat.st_gid == rStat.st_gid
isEqual = isEqual && lStat.st_rdev == rStat.st_rdev
isEqual = isEqual && lStat.st_size == rStat.st_size
isEqual = isEqual && lStat.st_blocks == rStat.st_blocks
isEqual = isEqual && lStat.st_blksize == rStat.st_blksize
#if canImport(Darwin)
isEqual =
isEqual
&& FileInfo.Timespec(lStat.st_atimespec) == FileInfo.Timespec(rStat.st_atimespec)
isEqual =
isEqual
&& FileInfo.Timespec(lStat.st_mtimespec) == FileInfo.Timespec(rStat.st_mtimespec)
isEqual =
isEqual
&& FileInfo.Timespec(lStat.st_ctimespec) == FileInfo.Timespec(rStat.st_ctimespec)
isEqual =
isEqual
&& FileInfo.Timespec(lStat.st_birthtimespec)
== FileInfo.Timespec(rStat.st_birthtimespec)
isEqual = isEqual && lStat.st_flags == rStat.st_flags
isEqual = isEqual && lStat.st_gen == rStat.st_gen
#elseif canImport(Glibc) || canImport(Musl) || canImport(Android)
isEqual = isEqual && FileInfo.Timespec(lStat.st_atim) == FileInfo.Timespec(rStat.st_atim)
isEqual = isEqual && FileInfo.Timespec(lStat.st_mtim) == FileInfo.Timespec(rStat.st_mtim)
isEqual = isEqual && FileInfo.Timespec(lStat.st_ctim) == FileInfo.Timespec(rStat.st_ctim)
#endif
return isEqual
}
}
extension FilePermissions {
internal init(masking rawValue: CInterop.Mode) {
self = .init(rawValue: rawValue & ~S_IFMT)
}
}