Ponyboy47/Pathman

View on GitHub
Sources/Pathman/Open/Open.swift

Summary

Maintainability
A
2 hrs
Test Coverage
import struct Foundation.URL

#if os(Linux)
import func Glibc.fchmod
import func Glibc.fchown
#else
import func Darwin.fchmod
import func Darwin.fchown
#endif

public final class Open<PathType: Openable>: UpdatableStatable, Ownable, Permissionable {
    public let path: PathType
    public private(set) var descriptor: PathType.Descriptor?
    public private(set) var fileDescriptor: FileDescriptor?
    public let openOptions: PathType.OpenOptions
    private let closable: Bool

    // swiftlint:disable identifier_name
    public let _info: StatInfo
    // swiftlint:enable identifier_name

    public var url: URL { return path.url }

    /// Whether or not the path may be read
    public var isReadable: Bool {
        return path.isReadable
    }

    /// Whether or not the path may be written to
    public var isWritable: Bool {
        return path.isWritable
    }

    init(_ path: PathType,
         descriptor: PathType.Descriptor,
         fileDescriptor: FileDescriptor,
         options: PathType.OpenOptions) {
        self.path = PathType(path)
        self.descriptor = descriptor
        self.fileDescriptor = fileDescriptor
        openOptions = options
        closable = true

        _info = StatInfo(fileDescriptor)
    }

    init(descriptor: PathType.Descriptor,
         fileDescriptor: FileDescriptor,
         options: PathType.OpenOptions) {
        self.path = PathType()
        self.descriptor = descriptor
        self.fileDescriptor = fileDescriptor
        openOptions = options
        closable = false

        _info = StatInfo(fileDescriptor)
    }

    /**
     Changes the owner and/or group of the path

     - Parameter owner: The uid of the owner of the path
     - Parameter group: The gid of the group with permissions to access the path

     - Throws: `ChangeOwnershipError.permissionDenied` when the calling process does not have the proper permissions to
                modify path ownership
     - Throws: `ChangeOwnershipError.badAddress` when the path points to a location outside your addressible address
                space
     - Throws: `ChangeOwnershipError.tooManySymlinks` when too many symlinks were encounter while resolving the path
     - Throws: `ChangeOwnershipError.pathnameTooLong` when the path has more than `PATH_MAX` number of characters
     - Throws: `ChangeOwnershipError.pathDoesNotExist` when the path does not exist
     - Throws: `ChangeOwnershipError.noKernelMemory` when there is insufficient memory to change the path's ownership
     - Throws: `ChangeOwnershipError.pathComponentNotDirectory` when a component of the path is not a directory
     - Throws: `ChangeOwnershipError.readOnlyFileSystem` when the file system is in read-only mode
     - Throws: `ChangeOwnershipError.badFileDescriptor` when the file descriptor is not valid or open
     - Throws: `ChangeOwnershipError.ioError` when an I/O error occurred during the API call
     */
    public func change(owner uid: UID = ~0, group gid: GID = ~0) throws {
        guard let fileDescriptor = self.fileDescriptor else {
            throw ClosedDescriptorError.alreadyClosed
        }
        guard fchown(fileDescriptor, uid, gid) == 0 else {
            throw ChangeOwnershipError.getError()
        }
    }

    /**
     Changes the permissions of the path

     - Parameter permissions: The new permissions to use on the path

     - Throws: `ChangePermissionsError.permissionDenied` when the calling process does not have the proper permissions
                to modify path permissions
     - Throws: `ChangePermissionsError.badAddress` when the path points to a location outside your accessible address
                space
     - Throws: `ChangePermissionsError.ioError` when an I/O error occurred during the API call
     - Throws: `ChangePermissionsError.tooManySymlinks` when too many symlinks were encountered while resolving the path
     - Throws: `ChangePermissionsError.pathnameTooLong` when the path has more than `PATH_MAX` number of characters
     - Throws: `ChangePermissionsError.pathDoesNotExist` when the path does not exist
     - Throws: `ChangePermissionsError.noKernelMemory` when there is insufficient memory to change the path's permissions
     - Throws: `ChangePermissionsError.pathComponentNotDirectory` when a component of the path is not a directory
     - Throws: `ChangePermissionsError.readOnlyFileSystem` when the file system is in read-only mode
     - Throws: `ChangePermissionsError.badFileDescriptor` when the file descriptor is invalid or not open
     */
    public func change(permissions: FileMode) throws {
        guard let fileDescriptor = self.fileDescriptor else {
            throw ClosedDescriptorError.alreadyClosed
        }
        guard fchmod(fileDescriptor, permissions.rawValue) == 0 else {
            throw ChangePermissionsError.getError()
        }
    }

    public func close() throws {
        guard closable else { return }
        try PathType.close(opened: self)
        descriptor = nil
        fileDescriptor = nil
    }

    deinit {
        guard descriptor != nil else { return }
        try? close()
    }
}

extension Open: Equatable {
    public static func == <OtherPathType: Path & Openable>(lhs: Open<PathType>, rhs: Open<OtherPathType>) -> Bool {
        return lhs.path == rhs.path && lhs.fileDescriptor == rhs.fileDescriptor
    }
}

extension Open: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(path)
        hasher.combine(fileDescriptor)
        hasher.combine(openOptions)
    }
}

extension Open: CustomStringConvertible {
    public var description: String {
        var data: [(key: String, value: CustomStringConvertible)] = []

        data.append((key: "path", value: path))
        data.append((key: "options", value: String(describing: openOptions)))

        return "\(Swift.type(of: self))(\(data.map { "\($0.key): \($0.value)" }.joined(separator: ", ")))"
    }
}