mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-23 22:13:35 +08:00
238 lines
9.9 KiB
Swift
238 lines
9.9 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2023 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if FOUNDATION_FRAMEWORK
|
|
@_implementationOnly import Foundation_Private.NSFileManager
|
|
@_implementationOnly import Foundation_Private.NSPathUtilities
|
|
@_implementationOnly import DarwinPrivate.dirhelper
|
|
@_implementationOnly import DarwinPrivate.sysdir
|
|
@_implementationOnly import _ForSwiftFoundation
|
|
#endif
|
|
|
|
#if canImport(Darwin)
|
|
import Darwin.sysdir
|
|
|
|
extension FileManager.SearchPathDirectory {
|
|
#if FOUNDATION_FRAMEWORK
|
|
fileprivate static var _homeDirectory: Self {
|
|
Self(rawValue: NSSearchPathDirectory_Private.homeDirectory.rawValue)!
|
|
}
|
|
#endif
|
|
}
|
|
|
|
extension FileManager.SearchPathDomainMask {
|
|
#if FOUNDATION_FRAMEWORK
|
|
#if os(macOS)
|
|
static var _sharedUserDomainMask: Self {
|
|
Self(rawValue: NSSearchPathDomainMask_Private.sharedUserDomainMask.rawValue)
|
|
}
|
|
#endif
|
|
|
|
static var _partitionedSystemDomainMask: Self {
|
|
Self(rawValue: NSSearchPathDomainMask_Private.partitionedSystemDomainMask.rawValue)
|
|
}
|
|
#endif
|
|
|
|
fileprivate var firstMask: Self? {
|
|
guard !self.isEmpty else { return nil }
|
|
return Self(rawValue: 1 << self.rawValue.trailingZeroBitCount)
|
|
}
|
|
}
|
|
|
|
private func foundation_sysdir_start_search_path_enumeration(_ directory: UInt, _ domainMask: UInt) -> sysdir_search_path_enumeration_state {
|
|
#if FOUNDATION_FRAMEWORK
|
|
sysdir_start_search_path_enumeration_private(
|
|
sysdir_search_path_directory_t(UInt32(truncatingIfNeeded: directory)),
|
|
sysdir_search_path_domain_private_mask_t(rawValue: UInt32(truncatingIfNeeded: domainMask))
|
|
)
|
|
#else
|
|
sysdir_start_search_path_enumeration(
|
|
sysdir_search_path_directory_t(UInt32(truncatingIfNeeded: directory)),
|
|
sysdir_search_path_domain_mask_t(rawValue: UInt32(truncatingIfNeeded: domainMask))
|
|
)
|
|
#endif
|
|
}
|
|
|
|
private struct _SearchPathsSequence: Sequence {
|
|
let directory: FileManager.SearchPathDirectory
|
|
let domainMask: FileManager.SearchPathDomainMask
|
|
|
|
final class Iterator: IteratorProtocol {
|
|
let directory: FileManager.SearchPathDirectory
|
|
let domainMask: FileManager.SearchPathDomainMask
|
|
|
|
private enum State {
|
|
case sysdir(sysdir_search_path_enumeration_state)
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
case special(FileManager.SearchPathDomainMask)
|
|
#endif
|
|
}
|
|
private var state: State
|
|
|
|
init(directory: FileManager.SearchPathDirectory, domainMask: FileManager.SearchPathDomainMask) {
|
|
self.directory = directory
|
|
self.domainMask = domainMask
|
|
|
|
switch directory {
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
case .trashDirectory:
|
|
state = .special(domainMask.union([.userDomainMask, .localDomainMask]))
|
|
case ._homeDirectory, .applicationScriptsDirectory:
|
|
state = .special(domainMask.union(.userDomainMask))
|
|
#endif
|
|
|
|
default:
|
|
state = .sysdir(foundation_sysdir_start_search_path_enumeration(directory.rawValue, domainMask.rawValue))
|
|
}
|
|
}
|
|
|
|
func next() -> String? {
|
|
switch state {
|
|
case .sysdir(let sysdirState):
|
|
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
|
|
let newState = sysdir_get_next_search_path_enumeration(sysdirState, buffer.baseAddress!)
|
|
state = .sysdir(newState)
|
|
if newState != 0 {
|
|
return FileManager.default.string(withFileSystemRepresentation: buffer.baseAddress!, length: strlen(buffer.baseAddress!))
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
case .special(var mask):
|
|
defer { state = .special(mask) }
|
|
while let currentMask = mask.firstMask {
|
|
mask.remove(currentMask)
|
|
if let result = _specialFind(directory, in: currentMask) {
|
|
return result
|
|
}
|
|
}
|
|
return nil
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
private func _specialFindReturn(_ buffer: UnsafeMutableBufferPointer<CChar>) -> String? {
|
|
guard buffer.baseAddress!.pointee != 0 else { return nil }
|
|
|
|
// strip trailing slashes because NSPathUtilities doesn't return paths with trailing slashes.
|
|
let len = strlen(buffer.baseAddress!)
|
|
let lastNonSlash = buffer.prefix(upTo: len).lastIndex {
|
|
$0 != 0x2F // Slash Character
|
|
}
|
|
return FileManager.default.string(withFileSystemRepresentation: buffer.baseAddress!, length: lastNonSlash ?? len)
|
|
}
|
|
|
|
private func _specialFind(_ directory: FileManager.SearchPathDirectory, in mask: FileManager.SearchPathDomainMask) -> String? {
|
|
withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { cpath in
|
|
switch (directory, mask) {
|
|
case (.trashDirectory, .userDomainMask):
|
|
// get the trash relative to the home directory without checking to see if the directory exists
|
|
return String.homeDirectoryPath().withFileSystemRepresentation { homePathPtr -> String? in
|
|
guard let homePathPtr else { return nil }
|
|
if __user_relative_dirname(geteuid(), DIRHELPER_RELATIVE_TRASH, homePathPtr, cpath.baseAddress!, FileManager.MAX_PATH_SIZE) != nil {
|
|
var buff = stat()
|
|
if lstat(cpath.baseAddress!, &buff) == 0 {
|
|
return _specialFindReturn(cpath)?.abbreviatingWithTildeInPath
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
case (.trashDirectory, .localDomainMask):
|
|
// get the trash on the boot volume without checking to see if the directory exists
|
|
if __user_relative_dirname(geteuid(), DIRHELPER_RELATIVE_TRASH, "/", cpath.baseAddress!, FileManager.MAX_PATH_SIZE) != nil {
|
|
var buff = stat()
|
|
if lstat(cpath.baseAddress!, &buff) == 0 {
|
|
return _specialFindReturn(cpath)
|
|
}
|
|
}
|
|
return nil
|
|
|
|
case (.applicationScriptsDirectory, .userDomainMask):
|
|
guard let id = _NSCodeSigningIdentifierForCurrentProcess() else {
|
|
return nil
|
|
}
|
|
return "\("~".replacingTildeWithRealHomeDirectory)/Library/Application Scripts/\(id)"
|
|
|
|
case (._homeDirectory, .userDomainMask):
|
|
return "~"
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func makeIterator() -> Iterator {
|
|
Iterator(directory: directory, domainMask: domainMask)
|
|
}
|
|
}
|
|
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
extension String {
|
|
fileprivate var replacingTildeWithRealHomeDirectory: String {
|
|
guard self == "~" || self.hasPrefix("~/") else {
|
|
return self
|
|
}
|
|
var bufSize = sysconf(_SC_GETPW_R_SIZE_MAX)
|
|
if bufSize == -1 {
|
|
bufSize = 4096 // A generous guess.
|
|
}
|
|
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: bufSize) { pwBuff in
|
|
var pw: UnsafeMutablePointer<passwd>?
|
|
var pwd = passwd()
|
|
let euid = geteuid()
|
|
let trueUid = euid == 0 ? getuid() : euid
|
|
guard getpwuid_r(trueUid, &pwd, pwBuff.baseAddress!, bufSize, &pw) == 0, let pw else {
|
|
return self
|
|
}
|
|
return String(cString: pw.pointee.pw_dir).appendingPathComponent(String(self.dropFirst()))
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
func _SearchPaths(for directory: FileManager.SearchPathDirectory, in domain: FileManager.SearchPathDomainMask, expandTilde: Bool) -> some Sequence<String> {
|
|
let basic = _SearchPathsSequence(directory: directory, domainMask: domain).lazy.map {
|
|
if expandTilde {
|
|
return $0.expandingTildeInPath
|
|
} else {
|
|
return $0
|
|
}
|
|
}
|
|
|
|
#if os(macOS) && FOUNDATION_FRAMEWORK
|
|
// NSSharedUserDomainMask is basically just a wrapper around NSUserDomainMask.
|
|
let compatibleSharedUserDomainMask = domain != .allDomainsMask && (domain.rawValue & 16) != 0
|
|
if domain.contains(._sharedUserDomainMask) || compatibleSharedUserDomainMask {
|
|
var result = Array(basic)
|
|
for path in _SearchPathsSequence(directory: directory, domainMask: .userDomainMask) {
|
|
let expandedPath = expandTilde ? path.replacingTildeWithRealHomeDirectory : path
|
|
// Avoid duplicates, which would occur with (NSUserDomainMask | NSSharedUserDomainMask) in non-sandboxed apps.
|
|
if !result.contains(expandedPath) {
|
|
// Insert this path after NSUserDomainMask and before any of the more general paths.
|
|
let insertionIndex = domain.contains(.userDomainMask) ? 1 : 0
|
|
result.insert(expandedPath, at: insertionIndex)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
#endif
|
|
return Array(basic)
|
|
}
|
|
|
|
#endif
|