diff --git a/.gitignore b/.gitignore index 2c0658b9..e3459fb3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ swift-system.xcodeproj xcuserdata/ .*.sw? -.docc-build +.docc-build \ No newline at end of file diff --git a/Sources/System/FileStatus/FileFlags.swift b/Sources/System/FileStatus/FileFlags.swift new file mode 100644 index 00000000..b1b6cb76 --- /dev/null +++ b/Sources/System/FileStatus/FileFlags.swift @@ -0,0 +1,234 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + */ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +// FIXME: Document +@frozen +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +public struct FileFlags: OptionSet, Hashable, Codable { + /// The raw C file flags. + @_alwaysEmitIntoClient + public let rawValue: CInterop.FileFlags + + /// Create a strongly-typed file flags from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.FileFlags) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInterop.FileFlags) { self.init(rawValue: raw) } + + /// Do not dump the file. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_NODUMP` + @_alwaysEmitIntoClient + public static var noDump: FileFlags { FileFlags(_UF_NODUMP) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "noDump") + public static var UF_NODUMP: FileFlags { noDump } + + /// The file may not be changed. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_IMMUTABLE` + @_alwaysEmitIntoClient + public static var immutable: FileFlags { FileFlags(_UF_IMMUTABLE) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "immutable") + public static var UF_IMMUTABLE: FileFlags { immutable } + + /// The file may only be appended to. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_APPEND` + @_alwaysEmitIntoClient + public static var appendOnly: FileFlags { FileFlags(_UF_APPEND) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "appendOnly") + public static var UF_APPEND: FileFlags { appendOnly } + + /// The directory is opaque when viewed through a union stack. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_OPAQUE` + @_alwaysEmitIntoClient + public static var opaque: FileFlags { FileFlags(_UF_OPAQUE) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "opaque") + public static var UF_OPAQUE: FileFlags { opaque } + +// #if os(FreeBSD) +// /// The file may not be removed or renamed. Modifiable by file owner or super-user. +// /// +// /// The corresponding C constant is `UF_NOUNLINK` +// @_alwaysEmitIntoClient +// public static var noUnlink: FileFlags { FileFlags(_UF_NOUNLINK) } +// +// @_alwaysEmitIntoClient +// @available(*, unavailable, renamed: "noUnlink") +// public static var UF_NOUNLINK: FileFlags { noUnlink } +// #endif + + /// The file is compressed (some file-systems). Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_COMPRESSED` + @_alwaysEmitIntoClient + public static var compressed: FileFlags { FileFlags(_UF_COMPRESSED) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "compressed") + public static var UF_COMPRESSED: FileFlags { compressed } + + /// No notifications will be issued for deletes or renames. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_TRACKED` + @_alwaysEmitIntoClient + public static var tracked: FileFlags { FileFlags(_UF_TRACKED) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "tracked") + public static var UF_TRACKED: FileFlags { tracked } + + /// The file requires entitlement required for reading and writing. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_DATAVAULT` + @_alwaysEmitIntoClient + public static var dataVault: FileFlags { FileFlags(_UF_DATAVAULT) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "dataVault") + public static var UF_DATAVAULT: FileFlags { dataVault } + + /// The file or directory is not intended to be displayed to the user. Modifiable by file owner or super-user. + /// + /// The corresponding C constant is `UF_HIDDEN` + @_alwaysEmitIntoClient + public static var hidden: FileFlags { FileFlags(_UF_HIDDEN) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "hidden") + public static var UF_HIDDEN: FileFlags { hidden } + + /// The file has been archived. Only modifiable by the super-user. + /// + /// The corresponding C constant is `SF_ARCHIVED` + @_alwaysEmitIntoClient + public static var superUserArchived: FileFlags { FileFlags(_SF_ARCHIVED) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "superUserArchived") + public static var SF_ARCHIVED: FileFlags { superUserArchived } + + /// The file may not be changed. Only modifiable by the super-user. + /// + /// The corresponding C constant is `SF_IMMUTABLE` + @_alwaysEmitIntoClient + public static var superUserImmutable: FileFlags { FileFlags(_SF_IMMUTABLE) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "superUserImmutable") + public static var SF_IMMUTABLE: FileFlags { superUserImmutable } + + /// The file may only be appended to. Only modifiable by the super-user. + /// + /// The corresponding C constant is `SF_APPEND` + @_alwaysEmitIntoClient + public static var superUserAppend: FileFlags { FileFlags(_SF_APPEND) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "superUserAppend") + public static var SF_APPEND: FileFlags { superUserAppend } + + /// The file requires entitlement required for reading and writing. Only modifiable by the super-user. + /// + /// The corresponding C constant is `SF_RESTRICTED` + @_alwaysEmitIntoClient + public static var superUserRestricted: FileFlags { FileFlags(_SF_RESTRICTED) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "superUserRestricted") + public static var SF_RESTRICTED: FileFlags { superUserRestricted } + + /// The file may not be removed, renamed or mounted on. Only modifiable by the super-user. + /// + /// The corresponding C constant is `SF_NOUNLINK` + @_alwaysEmitIntoClient + public static var superUserNoUnlink: FileFlags { FileFlags(_SF_NOUNLINK) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "superUserNoUnlink") + public static var SF_NOUNLINK: FileFlags { superUserNoUnlink } + +// #if os(FreeBSD) +// /// The file is a snapshot file. Only modifiable by the super-user. +// /// +// /// The corresponding C constant is `SF_SNAPSHOT` +// @_alwaysEmitIntoClient +// public static var superUserSnapshot: FileFlags { FileFlags(_SF_SNAPSHOT) } +// +// @_alwaysEmitIntoClient +// @available(*, unavailable, renamed: "superUserSnapshot") +// public static var SF_SNAPSHOT: FileFlags { superUserSnapshot } +// #endif + + /// The file is a firmlink. Only modifiable by the super-user. + /// + /// The corresponding C constant is `SF_FIRMLINK` + @_alwaysEmitIntoClient + public static var superUserFirmlink: FileFlags { FileFlags(_SF_FIRMLINK) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "superUserFirmlink") + public static var SF_FIRMLINK: FileFlags { superUserFirmlink } + + /// The file is a dataless placeholder. The system will attempt to materialize it when accessed according to the dataless file materialization policy of the accessing thread or process. Cannot be modified in user-space. + /// + /// The corresponding C constant is `SF_DATALESS` + @_alwaysEmitIntoClient + public static var kernelDataless: FileFlags { FileFlags(_SF_DATALESS) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "kernelDataless") + public static var SF_DATALESS: FileFlags { kernelDataless } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileFlags + : CustomStringConvertible, CustomDebugStringConvertible +{ + /// A textual representation of the file permissions. + @inline(never) + public var description: String { + let descriptions: [(Element, StaticString)] = [ + (.noDump, ".noDump"), + (.immutable, ".immutable"), + (.appendOnly, ".appendOnly"), + (.opaque, ".opaque"), + (.compressed, ".compressed"), + (.tracked, ".tracked"), + (.dataVault, ".dataVault"), + (.hidden, ".hidden"), + (.superUserArchived, ".superUserArchived"), + (.superUserImmutable, ".superUserImmutable"), + (.superUserAppend, ".superUserAppend"), + (.superUserRestricted, ".superUserRestricted"), + (.superUserNoUnlink, ".superUserNoUnlink"), + (.superUserFirmlink, ".superUserFirmlink"), + (.kernelDataless, ".kernelDataless"), + ] + + return _buildDescription(descriptions) + } + + /// A textual representation of the file permissions, suitable for debugging. + public var debugDescription: String { self.description } +} + +#endif diff --git a/Sources/System/FileStatus/FileMode.swift b/Sources/System/FileStatus/FileMode.swift new file mode 100644 index 00000000..0c283bdc --- /dev/null +++ b/Sources/System/FileStatus/FileMode.swift @@ -0,0 +1,47 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + */ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +// FIXME: should the subtypes of Mode mask their rawValues to only allow their bits to be changed? + +/// The superset of access permissions and type for a file. +/// +/// The following example creates an instance of the `FileMode` structure +/// from a raw octal literal and reads the file type from it: +/// +/// let mode = FileMode(rawValue: 0o140000) +/// mode.type == .socket // true +@frozen +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +public struct FileMode: RawRepresentable { + /// The raw C file mode. + @_alwaysEmitIntoClient + public var rawValue: CInterop.Mode + + /// Create a strongly-typed file mode from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } + + /// Subset of mode for accessing and modifying file permissions. + @_alwaysEmitIntoClient + public var permissions: FilePermissions { + get { FilePermissions(rawValue: rawValue & _MODE_PERMISSIONS) } + set { rawValue = (rawValue & ~_MODE_PERMISSIONS) | (newValue.rawValue & _MODE_PERMISSIONS ) } + } + + /// Subset of mode for accessing and modifying file type. + @_alwaysEmitIntoClient + public var type: FileType { + get { FileType(rawValue: rawValue & _MODE_TYPE) } + set { rawValue = (rawValue & ~_MODE_TYPE) | (newValue.rawValue & _MODE_TYPE ) } + } +} + +#endif diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FileStatus/FilePermissions.swift similarity index 100% rename from Sources/System/FilePermissions.swift rename to Sources/System/FileStatus/FilePermissions.swift diff --git a/Sources/System/FileStatus/FileStatus.swift b/Sources/System/FileStatus/FileStatus.swift new file mode 100644 index 00000000..3568c7f2 --- /dev/null +++ b/Sources/System/FileStatus/FileStatus.swift @@ -0,0 +1,91 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +import Darwin + +// FIXME: Document +@frozen +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +public struct FileStatus: RawRepresentable { + /// The raw C file status. + @_alwaysEmitIntoClient + public let rawValue: CInterop.Stat + + /// Create a strongly-typed file status from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Stat) { self.rawValue = rawValue } + + // FIXME: replace with swift type DeviceID that splits out major/minor + /// ID of device containing file. + @_alwaysEmitIntoClient + public var deviceID: CInterop.DeviceID { rawValue.st_dev } + + /// Mode of file. + @_alwaysEmitIntoClient + public var mode: FileMode { FileMode(rawValue: rawValue.st_mode) } + + /// Number of hard links. + @_alwaysEmitIntoClient + public var hardLinkCount: CInterop.NumberOfLinks { rawValue.st_nlink } + + /// File serial number. + @_alwaysEmitIntoClient + public var inodeNumber: CInterop.INodeNumber { rawValue.st_ino } + + /// User ID of the file. + @_alwaysEmitIntoClient + public var userID: CInterop.UserID { rawValue.st_uid } + + /// Group ID of the file. + @_alwaysEmitIntoClient + public var groupID: CInterop.GroupID { rawValue.st_gid } + + /// Device ID. + @_alwaysEmitIntoClient + public var rDeviceID: CInterop.DeviceID { rawValue.st_rdev } + + /// Time of last access. + @_alwaysEmitIntoClient + public var accessTime: TimeSpecification { TimeSpecification(rawValue: rawValue.st_atimespec) } + + /// Time of last data modification. + @_alwaysEmitIntoClient + public var modifyTime: TimeSpecification { TimeSpecification(rawValue: rawValue.st_mtimespec) } + + /// Time of last status change. + @_alwaysEmitIntoClient + public var statusChangeTime: TimeSpecification { TimeSpecification(rawValue: rawValue.st_ctimespec) } + + /// Time of file creation. + @_alwaysEmitIntoClient + public var creationTime: TimeSpecification { TimeSpecification(rawValue: rawValue.st_birthtimespec) } + + /// File size, in bytes. + @_alwaysEmitIntoClient + public var fileSize: CInterop.Offset { rawValue.st_size } + + /// Blocks allocated for file. + @_alwaysEmitIntoClient + public var blockCount: CInterop.BlockCount { rawValue.st_blocks } + + /// Optimal block size for I/O. + @_alwaysEmitIntoClient + public var blockSize: CInterop.BlockSize { rawValue.st_blksize } + + /// User defined flags for file. + @_alwaysEmitIntoClient + public var fileFlags: FileFlags { FileFlags(rawValue: rawValue.st_flags) } + + /// File generation number. + @_alwaysEmitIntoClient + public var generationID: CInterop.GenerationID { rawValue.st_gen } +} + +#endif diff --git a/Sources/System/FileStatus/FileStatusOperations.swift b/Sources/System/FileStatus/FileStatusOperations.swift new file mode 100644 index 00000000..16933358 --- /dev/null +++ b/Sources/System/FileStatus/FileStatusOperations.swift @@ -0,0 +1,613 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + */ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +// FIXME: this is wrong and should be done with wrapping fcntrl +// FIXME: go through and figure out the right way to express `at` methods +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileDescriptor { + public struct ControlFlags { + let rawValue: Int32 + // Test stub + public static var none: ControlFlags = ControlFlags(rawValue: 0) + // Out of scope of this sketch + } +} + +// MARK: - stat + +// [x] int stat(const char *, struct stat *) +// [x] int lstat(const char *, struct stat *) +// [x] int fstat(int, struct stat *) +// [x] int fstatat(int, const char *, struct stat *, int) +// [ ] int statx_np(const char *, struct stat *, filesec_t) +// [ ] int lstatx_np(const char *, struct stat *, filesec_t) +// [ ] int fstatx_np(int, struct stat *, filesec_t) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + /// Obtain information about the file pointed to by the FilePath. + /// + /// - Parameters: + /// - followSymlinks: Whether to follow symlinks. + /// The default is `true`. + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: A `FileStatus` for the file pointed to by `self`. + /// + /// Read, write or execute permission of the pointed to file is not required, + /// but all intermediate directories must be searchable. + /// + /// The corresponding C functions are `stat` and `lstat`. + @_alwaysEmitIntoClient + public func stat( + followSymlinks: Bool = true, + retryOnInterrupt: Bool = true + ) throws -> FileStatus { + try _stat( + followSymlinks: followSymlinks, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _stat( + followSymlinks: Bool, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + let fn = followSymlinks ? system_lstat : system_stat + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + fn(ptr, &result) + }.map { FileStatus(rawValue: result) } + } + } + + /// Obtain information about the file pointed to by the FilePath relative to + /// the provided FileDescriptor. + /// + /// - Parameters: + /// - relativeTo: if `self` is relative, treat it as relative to this file descriptor + /// rather than relative to the current working directory. + /// - followSymlinks: Whether to follow symlinks. + /// The default is `true`. + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: A `FileStatus` for the file pointed to by `self`. + /// + /// Read, write or execute permission of the pointed to file is not required, + /// but all intermediate directories must be searchable. + /// + /// The corresponding C function is `fstatat`. + @_alwaysEmitIntoClient + public func stat( + relativeTo fd: FileDescriptor, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool = true + ) throws -> FileStatus { + try _stat( + relativeTo: fd, + fcntrl: fcntrl, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _stat( + relativeTo fd: FileDescriptor, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fstatat(fd.rawValue, ptr, &result, fcntrl.rawValue) + }.map { FileStatus(rawValue: result) } + } + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileDescriptor { + /// Obtain information about the file pointed to by the FileDescriptor. + /// + /// - Parameters: + /// - retryOnInterrupt: Whether to retry the read operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: A `FileStatus` for the file pointed to by `self`. + /// + /// The corresponding C function is `fstat`. + @_alwaysEmitIntoClient + public func stat(retryOnInterrupt: Bool = true) throws -> FileStatus { + try _stat( + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _stat( + retryOnInterrupt: Bool = true + ) -> Result { + var result = CInterop.Stat() + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fstat(self.rawValue, &result) + }.map { FileStatus(rawValue: result) } + } +} + +// MARK: - chmod + +// [x] int chmod(const char *, mode_t) +// [x] int lchmod(const char *, mode_t) +// [x] int fchmod(int, mode_t) +// [x] int fchmodat(int, const char *, mode_t, int) +// [ ] int chmodx_np(const char *, filesec_t) +// [ ] int fchmodx_np(int, filesec_t) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + @_alwaysEmitIntoClient + public func chmod( + permissions: FilePermissions, + followSymlinks: Bool = true, + retryOnInterrupt: Bool = true + ) throws { + try _chmod( + permissions: permissions, + followSymlinks: followSymlinks, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _chmod( + permissions: FilePermissions, + followSymlinks: Bool, + retryOnInterrupt: Bool + ) -> Result { + let _chmod = followSymlinks ? system_lchmod : system_chmod + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + _chmod(ptr, permissions.rawValue) + } + } + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileDescriptor { + @_alwaysEmitIntoClient + public func fchmod( + permissions: FilePermissions, + retryOnInterrupt: Bool = true + ) throws { + try _fchmod( + permissions: permissions, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fchmod( + permissions: FilePermissions, + retryOnInterrupt: Bool + ) -> Result { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fchmod(self.rawValue, permissions.rawValue) + } + } + + // valid flags: AT_SYMLINK_NOFOLLOW | AT_REALDEV | AT_FDONLY + @_alwaysEmitIntoClient + public func fchmodat( + path: FilePath, + permissions: FilePermissions, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool = true + ) throws { + try _fchmodat( + path: path, + permissions: permissions, + fcntrl: fcntrl, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fchmodat( + path: FilePath, + permissions: FilePermissions, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool + ) -> Result { + path.withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fchmodat( + self.rawValue, ptr, permissions.rawValue, fcntrl.rawValue) + } + } + } +} + +// MARK: - chown + +// [x] int chown(const char *, uid_t, gid_t) +// [x] int lchown(const char *, uid_t, gid_t) +// [x] int fchown(int, uid_t, gid_t) +// [x] int fchownat(int, const char *, uid_t, gid_t, int) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + @_alwaysEmitIntoClient + public func chown( + userID: CInterop.UserID, + groupID: CInterop.GroupID, + followSymlinks: Bool = true, + retryOnInterrupt: Bool = true + ) throws { + try _chown( + userID: userID, + groupID: groupID, + followSymlinks: followSymlinks, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _chown( + userID: CInterop.UserID, + groupID: CInterop.GroupID, + followSymlinks: Bool, + retryOnInterrupt: Bool + ) -> Result { + let _chown = followSymlinks ? system_lchown : system_chown + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + _chown(ptr, userID, groupID) + } + } + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileDescriptor { + @_alwaysEmitIntoClient + public func fchown( + userID: CInterop.UserID, + groupID: CInterop.GroupID, + retryOnInterrupt: Bool = true + ) throws { + try _fchown( + userID: userID, + groupID: groupID, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fchown( + userID: CInterop.UserID, + groupID: CInterop.GroupID, + retryOnInterrupt: Bool + ) -> Result { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fchown(self.rawValue, userID, groupID) + } + } + + @_alwaysEmitIntoClient + public func fchownat( + path: FilePath, + userID: CInterop.UserID, + groupID: CInterop.GroupID, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool = true + ) throws { + try _fchownat( + path: path, + userID: userID, + groupID: groupID, + fcntrl: fcntrl, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fchownat( + path: FilePath, + userID: CInterop.UserID, + groupID: CInterop.GroupID, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool + ) -> Result { + path.withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fchownat(self.rawValue, ptr, userID, groupID, fcntrl.rawValue) + } + } + } +} + +// MARK: - chflags + +// [x] int chflags(const char *, __uint32_t) +// [x] int lchflags(const char *, __uint32_t) +// [x] int fchflags(int, __uint32_t) +// [ ] int chflagsat(int, const char *, __uint32_t, int) (FreeBSD) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + @_alwaysEmitIntoClient + public func chflags( + flags: FileFlags, + followSymlinks: Bool = true, + retryOnInterrupt: Bool = true + ) throws { + try _chflags( + flags: flags, + followSymlinks: followSymlinks, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _chflags( + flags: FileFlags, + followSymlinks: Bool, + retryOnInterrupt: Bool + ) -> Result { + let _chflags = followSymlinks ? system_lchflags : system_chflags + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + _chflags(ptr, flags.rawValue) + } + } + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileDescriptor { + @_alwaysEmitIntoClient + internal func fchflags( + flags: FileFlags, + retryOnInterrupt: Bool = true + ) throws { + try _fchflags( + flags: flags, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fchflags( + flags: FileFlags, + retryOnInterrupt: Bool + ) -> Result { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fchflags(self.rawValue, flags.rawValue) + } + } +} + +// MARK: - umask + +// FIXME: umask document, shape +/// The umask() routine sets the process's file mode creation mask to newMask +/// and returns the previous value of the mask. The bits of newMask are used by +/// system calls, including open(2), mkdir(2), mkfifo(2), and mknod(2) to turn +/// off corresponding bits requested in file mode. (See chmod(2)). This clearing +/// allows each user to restrict the default access to their files. +/// +/// The default mask value is `S_IWGRP` | `S_IWOTH` (022, write access for the +/// owner only). Child processes inherit the mask of the calling process. +// @_alwaysEmitIntoClient +// public func umask(newMask: FilePermissions) -> FilePermissions { +// let oldMode = system_umask(newMask.rawValue) +// return FileMode(rawValue: oldMode).permissions +// } + +// [x] mode_t umask(mode_t) + +// MARK: - mkfifo + +// [x] int mkfifo(const char *, mode_t) +// [ ] int mkfifox_np(const char *, filesec_t) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + @_alwaysEmitIntoClient + public func mkfifo( + permissions: FilePermissions, retryOnInterrupt: Bool = true) throws { + try _mkfifo( + permissions: permissions, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _mkfifo( + permissions: FilePermissions, + retryOnInterrupt: Bool + ) -> Result { + withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_mkfifo(ptr, permissions.rawValue) + } + } + } +} + +// MARK: - mknod + +// [x] int mknod(const char *, mode_t, dev_t) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + @_alwaysEmitIntoClient + public func mknod( + permissions: FilePermissions, + device: CInterop.DeviceID, + retryOnInterrupt: Bool = true + ) throws { + try _mknod( + permissions: permissions, + device: device, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _mknod( + permissions: FilePermissions, + device: CInterop.DeviceID, + retryOnInterrupt: Bool + ) -> Result { + withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_mknod(ptr, permissions.rawValue, device) + } + } + } +} + +// MARK: - mkdir + +// [x] int mkdir(const char *, mode_t) +// [x] int mkdirat(int, const char *, mode_t) +// [ ] int mkdirx_np(const char *, filesec_t) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + @_alwaysEmitIntoClient + public func mkdir( + permissions: FilePermissions, + retryOnInterrupt: Bool = true + ) throws { + try _mkdir( + permissions: permissions, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _mkdir( + permissions: FilePermissions, + retryOnInterrupt: Bool + ) -> Result { + withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_mkdir(ptr, permissions.rawValue) + } + } + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FileDescriptor { + public func mkdirat( + path: FilePath, + permissions: FilePermissions, + retryOnInterrupt: Bool = true + ) throws { + try _mkdirat( + path: path, + permissions: permissions, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + internal func _mkdirat( + path: FilePath, + permissions: FilePermissions, + retryOnInterrupt: Bool + ) -> Result { + path.withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_mkdirat(self.rawValue, ptr, permissions.rawValue) + } + } + } +} + +// MARK: - utimens + +// [x] int futimens(int, const struct timespec[2]) +// [x] int utimensat(int, const char *, const struct timespec[2], int) + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +@available(macOS 12, iOS 15.0, watchOS 8.0, tvOS 15.0, *) +extension FileDescriptor { + @_alwaysEmitIntoClient + public func futimens( + accessTime: TimeSpecification, + modificationTime: TimeSpecification, + retryOnInterrupt: Bool = true + ) throws { + try _futimens( + accessTime: accessTime, + modificationTime: modificationTime, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _futimens( + accessTime: TimeSpecification, + modificationTime: TimeSpecification, + retryOnInterrupt: Bool + ) -> Result { + let times = [accessTime.rawValue, modificationTime.rawValue] + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_futimens(self.rawValue, times) + } + } + + @_alwaysEmitIntoClient + public func utimensat( + path: FilePath, + accessTime: TimeSpecification, + modificationTime: TimeSpecification, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool = true + ) throws { + try _utimensat( + path: path, + accessTime: accessTime, + modificationTime: modificationTime, + fcntrl: fcntrl, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _utimensat( + path: FilePath, + accessTime: TimeSpecification, + modificationTime: TimeSpecification, + fcntrl: FileDescriptor.ControlFlags, + retryOnInterrupt: Bool + ) -> Result { + let times = [accessTime.rawValue, modificationTime.rawValue] + return path.withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_utimensat(self.rawValue, ptr, times, fcntrl.rawValue) + } + } + } +} + +#endif diff --git a/Sources/System/FileStatus/FileType.swift b/Sources/System/FileStatus/FileType.swift new file mode 100644 index 00000000..fcd7e02e --- /dev/null +++ b/Sources/System/FileStatus/FileType.swift @@ -0,0 +1,109 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +// FIXME: Document +@frozen +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +public struct FileType: RawRepresentable { + /// The raw C file type. + @_alwaysEmitIntoClient + public var rawValue: CInterop.Mode + + /// Create a strongly-typed file type from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInterop.Mode) { self.init(rawValue: raw) } + + /// Named pipe (fifo) + /// + /// The corresponding C constant is `S_IFIFO` + @_alwaysEmitIntoClient + public static var fifo: FileType { FileType(_S_IFIFO) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "fifo") + public static var S_IFIFO: FileType { fifo } + + /// Character special + /// + /// The corresponding C constant is `S_IFCHR` + @_alwaysEmitIntoClient + public static var characterDevice: FileType { FileType(_S_IFCHR) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "characterDevice") + public static var S_IFCHR: FileType { characterDevice } + + /// Directory + /// + /// The corresponding C constant is `S_IFDIR` + @_alwaysEmitIntoClient + public static var directory: FileType { FileType(_S_IFDIR) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "directory") + public static var S_IFDIR: FileType { directory } + + /// Block special + /// + /// The corresponding C constant is `S_IFBLK` + @_alwaysEmitIntoClient + public static var blockDevice: FileType { FileType(_S_IFBLK) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "blockDevice") + public static var S_IFBLK: FileType { blockDevice } + + /// Regular + /// + /// The corresponding C constant is `S_IFREG` + @_alwaysEmitIntoClient + public static var regular: FileType { FileType(_S_IFREG) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "regular") + public static var S_IFREG: FileType { regular } + + /// Symbolic link + /// + /// The corresponding C constant is `S_IFLNK` + @_alwaysEmitIntoClient + public static var symbolicLink: FileType { FileType(_S_IFLNK) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "symbolicLink") + public static var S_IFLNK: FileType { symbolicLink } + + /// Socket + /// + /// The corresponding C constant is `S_IFSOCK` + @_alwaysEmitIntoClient + public static var socket: FileType { FileType(_S_IFSOCK) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "socket") + public static var S_IFSOCK: FileType { socket } + + /// Whiteout + /// + /// The corresponding C constant is `S_IFWHT` + @_alwaysEmitIntoClient + // FIXME: rename with inclusive language + public static var whiteout: FileType { FileType(_S_IFWHT) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "whiteout") + public static var S_IFWHT: FileType { whiteout } +} + +#endif diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 5e46bafe..ca39c500 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -70,4 +70,42 @@ public enum CInterop { /// on API. public typealias PlatformUnicodeEncoding = UTF8 #endif + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + /// The C `stat` type. + public typealias Stat = stat + + /// The C `uid_t` type. + public typealias UserID = uid_t + + /// The C `gid_t` type. + public typealias GroupID = gid_t + + /// The C `dev_t` type. + public typealias DeviceID = dev_t + + /// The C `nlink_t` type. + public typealias NumberOfLinks = nlink_t + + /// The C `ino_t` type. + public typealias INodeNumber = ino_t + + /// The C `timespec` type. + public typealias TimeSpec = timespec + + /// The C `off_t` type. + public typealias Offset = off_t + + /// The C `blkcnt_t` type. + public typealias BlockCount = blkcnt_t + + /// The C `blksize_t` type. + public typealias BlockSize = blksize_t + + /// The C `UInt32` type. + public typealias GenerationID = UInt32 + + /// The C `UInt32` type. + public typealias FileFlags = UInt32 + #endif } diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 85f9f3de..e1aa6320 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -22,7 +22,7 @@ import ucrt #error("Unsupported Platform") #endif -// MARK: errno +// MARK: - errno #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @_alwaysEmitIntoClient internal var _ERRNO_NOT_USED: CInt { 0 } @@ -442,7 +442,7 @@ internal var _EQFULL: CInt { EQFULL } internal var _ELAST: CInt { ELAST } #endif -// MARK: File Operations +// MARK: - File Operations @_alwaysEmitIntoClient internal var _O_RDONLY: CInt { O_RDONLY } @@ -531,3 +531,107 @@ internal var _SEEK_HOLE: CInt { SEEK_HOLE } @_alwaysEmitIntoClient internal var _SEEK_DATA: CInt { SEEK_DATA } #endif + +// MARK: - Mode Masks +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +// mode_t: type ugs rwx rwx rwx +@_alwaysEmitIntoClient +internal var _MODE_PERMISSIONS: CInterop.Mode { 0b0000_111_111_111_111 } + +@_alwaysEmitIntoClient +internal var _MODE_TYPE: CInterop.Mode { 0b1111_000_000_000_000 } +#endif + +// MARK: - File Type +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _S_IFIFO: CInterop.Mode { S_IFIFO } + +@_alwaysEmitIntoClient +internal var _S_IFCHR: CInterop.Mode { S_IFCHR } + +@_alwaysEmitIntoClient +internal var _S_IFDIR: CInterop.Mode { S_IFDIR } + +@_alwaysEmitIntoClient +internal var _S_IFBLK: CInterop.Mode { S_IFBLK } + +@_alwaysEmitIntoClient +internal var _S_IFREG: CInterop.Mode { S_IFREG } + +@_alwaysEmitIntoClient +internal var _S_IFLNK: CInterop.Mode { S_IFLNK } + +@_alwaysEmitIntoClient +internal var _S_IFSOCK: CInterop.Mode { S_IFSOCK } + +@_alwaysEmitIntoClient +internal var _S_IFWHT: CInterop.Mode { S_IFWHT } +#endif + +// MARK: - File Flags +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _UF_NODUMP: CInterop.FileFlags { UInt32(bitPattern: UF_NODUMP) } + +@_alwaysEmitIntoClient +internal var _UF_IMMUTABLE: CInterop.FileFlags { UInt32(bitPattern: UF_IMMUTABLE) } + +@_alwaysEmitIntoClient +internal var _UF_APPEND: CInterop.FileFlags { UInt32(bitPattern: UF_APPEND) } + +@_alwaysEmitIntoClient +internal var _UF_OPAQUE: CInterop.FileFlags { UInt32(bitPattern: UF_OPAQUE) } + +#if os(FreeBSD) +@_alwaysEmitIntoClient +internal var _UF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: UF_NOUNLINK) } +#endif + +@_alwaysEmitIntoClient +internal var _UF_COMPRESSED: CInterop.FileFlags { UInt32(bitPattern: UF_COMPRESSED) } + +@_alwaysEmitIntoClient +internal var _UF_TRACKED: CInterop.FileFlags { UInt32(bitPattern: UF_TRACKED) } + +@_alwaysEmitIntoClient +internal var _UF_DATAVAULT: CInterop.FileFlags { UInt32(bitPattern: UF_DATAVAULT) } + +@_alwaysEmitIntoClient +internal var _UF_HIDDEN: CInterop.FileFlags { UInt32(bitPattern: UF_HIDDEN) } + +@_alwaysEmitIntoClient +internal var _SF_ARCHIVED: CInterop.FileFlags { UInt32(bitPattern: SF_ARCHIVED) } + +@_alwaysEmitIntoClient +internal var _SF_IMMUTABLE: CInterop.FileFlags { UInt32(bitPattern: SF_IMMUTABLE) } + +@_alwaysEmitIntoClient +internal var _SF_APPEND: CInterop.FileFlags { UInt32(bitPattern: SF_APPEND) } + +@_alwaysEmitIntoClient +internal var _SF_RESTRICTED: CInterop.FileFlags { UInt32(bitPattern: SF_RESTRICTED) } + +@_alwaysEmitIntoClient +internal var _SF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: SF_NOUNLINK) } + +#if os(FreeBSD) +@_alwaysEmitIntoClient +internal var _SF_SNAPSHOT: CInterop.FileFlags { UInt32(bitPattern: SF_SNAPSHOT) } +#endif + +@_alwaysEmitIntoClient +internal var _SF_FIRMLINK: CInterop.FileFlags { UInt32(bitPattern: SF_FIRMLINK) } + +@_alwaysEmitIntoClient +internal var _SF_DATALESS: CInterop.FileFlags { UInt32(bitPattern: SF_DATALESS) } +#endif + +// MARK: - Time Specification +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _UTIME_NOW: Int { Int(UTIME_NOW) } + +@_alwaysEmitIntoClient +internal var _UTIME_OMIT: Int { Int(UTIME_OMIT) } +#endif diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 405dc342..600d8a0f 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -193,6 +193,13 @@ internal func _mockOffT( ) -> _COffT { _COffT(mockImpl(name: name, path: path, args)) } + +internal func _mockModeT( + name: String = #function, _ args: AnyHashable... +) -> CInterop.Mode { + CInterop.Mode(mockImpl(name: name, path: nil, args)) +} + #endif // ENABLE_MOCKING // Force paths to be treated as Windows syntactically if `enabled` is diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index c5c376c3..7f8fdb26 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -133,3 +133,245 @@ internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { return ftruncate(fd, length) } #endif + +#if !os(Windows) +internal func system_stat( + _ path: UnsafePointer, + _ buf: UnsafeMutablePointer +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, buf) } +#endif + return stat(path, buf) +} + +internal func system_lstat( + _ path: UnsafePointer, + _ buf: UnsafeMutablePointer +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, buf) } +#endif + return lstat(path, buf) +} + +internal func system_fstat( + _ fd: Int32, + _ buf: UnsafeMutablePointer +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, buf) } +#endif + return fstat(fd, buf) +} + +internal func system_fstatat( + _ fd: Int32, + _ path: UnsafePointer, + _ buf: UnsafeMutablePointer, + _ flag: Int32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, buf, flag) } +#endif + return fstatat(fd, path, buf, flag) +} + +internal func system_chmod( + _ path: UnsafePointer, + _ mode: CInterop.Mode +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, mode) } +#endif + return chmod(path, mode) +} + +internal func system_lchmod( + _ path: UnsafePointer, + _ mode: CInterop.Mode +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, mode) } +#endif + return lchmod(path, mode) +} + +internal func system_fchmod( + _ fd: Int32, + _ mode: CInterop.Mode +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, mode) } +#endif + return fchmod(fd, mode) +} + +internal func system_fchmodat( + _ fd: Int32, + _ path: UnsafePointer?, + _ mode: CInterop.Mode, + _ flag: Int32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, mode, flag) } +#endif + return fchmodat(fd, path, mode, flag) +} + +internal func system_chown( + _ path: UnsafePointer?, + _ userID: CInterop.UserID, + _ groupID: CInterop.GroupID +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, userID, groupID) } +#endif + return chown(path, userID, groupID) +} + +internal func system_lchown( + _ path: UnsafePointer?, + _ userID: CInterop.UserID, + _ groupID: CInterop.GroupID +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, userID, groupID) } +#endif + return lchown(path, userID, groupID) +} + +internal func system_fchown( + _ fd: Int32, + _ userID: CInterop.UserID, + _ groupID: CInterop.GroupID +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, userID, groupID) } +#endif + return fchown(fd, userID, groupID) +} + +internal func system_fchownat( + _ fd: Int32, + _ path: UnsafePointer?, + _ userID: CInterop.UserID, + _ groupID: CInterop.GroupID, + _ flag: Int32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, userID, groupID, flag) } +#endif + return fchownat(fd, path, userID, groupID, flag) +} + +internal func system_chflags( + _ path: UnsafePointer?, + _ flag: UInt32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, flag) } +#endif + return chflags(path, flag) +} + +internal func system_lchflags( + _ path: UnsafePointer?, + _ flag: UInt32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, flag) } +#endif + return lchflags(path, flag) +} + +internal func system_fchflags( + _ fd: Int32, + _ flag: UInt32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, flag) } +#endif + return fchflags(fd, flag) +} + +#if ENABLE_MOCKING +internal var currentModeMask = CInterop.Mode(S_IWGRP|S_IWOTH) +#endif +internal func system_umask( + _ cmask: CInterop.Mode +) -> CInterop.Mode { +#if ENABLE_MOCKING + if mockingEnabled { return _mockModeT(cmask) } +#endif + return umask(cmask) +} + +// mkfifo +internal func system_mkfifo( + _ path: UnsafePointer?, + _ mode: CInterop.Mode +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, mode) } +#endif + return mkfifo(path, mode) +} + +// mknod +internal func system_mknod( + _ path: UnsafePointer?, + _ mode: CInterop.Mode, + _ deviceID: CInterop.DeviceID +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, mode, deviceID) } +#endif + return mknod(path, mode, deviceID) +} + +internal func system_mkdir( + _ path: UnsafePointer?, + _ mode: CInterop.Mode +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path, mode) } +#endif + return mkdir(path, mode) +} + +internal func system_mkdirat( + _ fd: Int32, + _ path: UnsafePointer?, + _ mode: CInterop.Mode +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, mode) } +#endif + return mkdirat(fd, path, mode) +} + +@available(macOS 10.13, *) +internal func system_futimens( + _ fd: Int32, + _ times: UnsafePointer // FIXME: is this really nullable? +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, times) } +#endif + return futimens(fd, times) +} + +@available(macOS 10.13, *) +internal func system_utimensat( + _ fd: Int32, + _ path: UnsafePointer?, + _ times: UnsafePointer, // FIXME: is this really nullable? + _ flag: Int32 +) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, times, flag) } +#endif + return utimensat(fd, path, times, flag) +} +#endif diff --git a/Sources/System/TimeSpecification.swift b/Sources/System/TimeSpecification.swift new file mode 100644 index 00000000..a42fd7d5 --- /dev/null +++ b/Sources/System/TimeSpecification.swift @@ -0,0 +1,42 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 Apple Inc. and the Swift System project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information +*/ + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +import Darwin + +// FIXME: Document +@frozen +public struct TimeSpecification: RawRepresentable { + /// The raw C time spec type. + @_alwaysEmitIntoClient + public var rawValue: CInterop.TimeSpec + + /// Create a strongly-typed time specification from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.TimeSpec) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ raw: CInterop.TimeSpec) { self.init(rawValue: raw) } + + /// seconds since 1970 + @_alwaysEmitIntoClient + public var seconds: Int { + get { rawValue.tv_sec } + set { rawValue.tv_sec = newValue } + } + + /// nanoseconds + @_alwaysEmitIntoClient + public var nanoseconds: Int { + get { rawValue.tv_nsec } + set { rawValue.tv_nsec = newValue } + } +} + +#endif diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 8062aedc..fc86db0f 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -28,7 +28,8 @@ final class FileOperationsTest: XCTestCase { let writeBuf = UnsafeRawBufferPointer(rawBuf) let writeBufAddr = writeBuf.baseAddress - let syscallTestCases: Array = [ + var syscallTestCases: Array = [] + syscallTestCases += [ MockTestCase(name: "open", .interruptable, "a path", O_RDWR | O_APPEND) { retryOnInterrupt in _ = try FileDescriptor.open( @@ -83,6 +84,49 @@ final class FileOperationsTest: XCTestCase { }, ] +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + let fp = FilePath("/") + let rawFP = fp.withPlatformString { strdup($0) } + defer { free(rawFP) } + + let stat = UnsafeMutablePointer.allocate(capacity: 1) + defer { stat.deallocate() } + + syscallTestCases += [ +// MockTestCase(name: "stat", .interruptable, rawFP) { retryOnInterrupt in +// _ = try fp.stat(followSymlinks: false, retryOnInterrupt: retryOnInterrupt) +// }, +// MockTestCase(name: "lstat", .interruptable, rawFP) { retryOnInterrupt in +// _ = try fp.stat(followSymlinks: true, retryOnInterrupt: retryOnInterrupt) +// }, +// MockTestCase(name: "fstat", .interruptable, rawFP) { retryOnInterrupt in +// _ = try fd.stat(retryOnInterrupt: retryOnInterrupt) +// }, +// MockTestCase(name: "fstatat", .interruptable, rawFP) { retryOnInterrupt in +// _ = try fp.stat(relativeTo: fd, fcntrl: .none, retryOnInterrupt: retryOnInterrupt) +// }, + // fstatat + // chmod + // lchmod + // fchmod + // fchmodat + // chown + // lchown + // fchown + // fchownat + // chflags + // lchflags + // fchflags + // umask + // mkfifo + // mknod + // mkdir + // mkdirat + // futimens + // utimensat + ] +#endif + for test in syscallTestCases { test.runAllTests() } }