Jeremy Schonfeld 86c79a1f85
FileManager Entry Points for ObjC Directory Enumeration (#1047)
* (139469462) ObjC API for directory search paths should use Swift implementation

* Fix trash directory bug
2024-11-13 09:50:43 -08:00

178 lines
7.4 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
internal import Foundation_Private.NSPathUtilities
internal import DarwinPrivate.dirhelper
internal import DarwinPrivate.sysdir
internal import _ForSwiftFoundation
#endif
#if canImport(Darwin)
import Darwin.sysdir
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
}
struct _DarwinSearchPathsSequence: 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.intersection([.userDomainMask, .localDomainMask]))
case ._homeDirectory, .applicationScriptsDirectory:
state = .special(domainMask.intersection(.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 }
let path = String(cString: buffer.baseAddress!)
// strip trailing slashes because NSPathUtilities doesn't return paths with trailing slashes.
guard let endIndex = path.unicodeScalars.lastIndex(where: { $0 != "/" }) else {
// It's only slashes, so just return a single slash
return "/"
}
return String(path[...endIndex])
}
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
@_cdecl("_NSRealHomeDirectory")
internal func _NSRealHomeDirectory() -> String {
"~".replacingTildeWithRealHomeDirectory
}
extension String {
internal var replacingTildeWithRealHomeDirectory: String {
guard self == "~" || self.hasPrefix("~/") else {
return self
}
let euid = geteuid()
let trueUid = euid == 0 ? getuid() : euid
guard let home = Platform.homeDirectory(forUID: trueUid) else {
return self
}
return home.appendingPathComponent(String(self.dropFirst()))
}
}
#endif // os(macOS) && FOUNDATION_FRAMEWORK
#endif // canImport(Darwin)