Skip to content

Commit 33856a5

Browse files
authored
(120741818) Port FileManager to swift-foundation
* (120741818) Port FileManager to swift-foundation * (120741818) Fix linux test failures * (120741818) Fix build failures
1 parent 8aaaec5 commit 33856a5

25 files changed

+4896
-61
lines changed

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ let package = Package(
6464
"FoundationMacros",
6565
.product(name: "_RopeModule", package: "swift-collections"),
6666
],
67+
cSettings: [
68+
.define("_GNU_SOURCE", .when(platforms: [.linux]))
69+
],
6770
swiftSettings: [
6871
.enableExperimentalFeature("VariadicGenerics"),
6972
.enableExperimentalFeature("AccessLevelOnImport")

Sources/FoundationEssentials/CocoaError.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,6 @@ internal let NSUnderlyingErrorKey = "NSUnderlyingErrorKey"
282282
internal let NSUserStringVariantErrorKey = "NSUserStringVariantErrorKey"
283283
internal let NSFilePathErrorKey = "NSFilePathErrorKey"
284284
internal let NSURLErrorKey = "NSURLErrorKey"
285+
internal let NSSourceFilePathErrorKey = "NSSourceFilePathErrorKey"
286+
internal let NSDestinationFilePathErrorKey = "NSDestinationFilePathErrorKey"
285287
#endif

Sources/FoundationEssentials/Data/Data+Stub.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ extension Data {
7070
}
7171
}
7272

73-
internal struct FileAttributeKey: RawRepresentable, Equatable, Hashable {
74-
typealias RawValue = String
75-
let rawValue: String
76-
}
77-
7873
// Placeholder for Progress
7974
internal final class Progress {
8075
var completedUnitCount: Int64
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if canImport(Darwin)
14+
import Darwin
15+
#elseif canImport(Glibc)
16+
import Glibc
17+
#endif
18+
19+
internal struct _FileManagerImpl {
20+
weak var _manager: FileManager?
21+
weak var delegate: FileManagerDelegate?
22+
23+
var fileManager: FileManager {
24+
guard let _manager else {
25+
fatalError("_FileManagerImpl called without a valid reference to a FileManager")
26+
}
27+
return _manager
28+
}
29+
30+
var safeDelegate: FileManagerDelegate? {
31+
#if FOUNDATION_FRAMEWORK
32+
fileManager._safeDelegate() as? FileManagerDelegate
33+
#else
34+
self.delegate
35+
#endif
36+
}
37+
38+
init() {}
39+
40+
#if FOUNDATION_FRAMEWORK
41+
func displayName(atPath path: String) -> String {
42+
// We lie to filePath:directoryHint: to avoid the extra stat. Since this URL isn't used as a base URL for another URL, it shouldn't make any difference.
43+
let url = URL(filePath: path, directoryHint: .notDirectory)
44+
45+
if let storedName = try? url.resourceValues(forKeys: [.localizedNameKey]).localizedName {
46+
return storedName
47+
}
48+
49+
return path.lastPathComponent.replacing(":", with: "/")
50+
}
51+
#endif
52+
53+
func contents(atPath path: String) -> Data? {
54+
try? Data(contentsOfFile: path)
55+
}
56+
57+
func contentsEqual(
58+
atPath path: String,
59+
andPath other: String
60+
) -> Bool {
61+
func _openFD(_ path: UnsafePointer<CChar>) -> Int32? {
62+
var statBuf = stat()
63+
let fd = open(path, 0, 0)
64+
guard fd >= 0 else { return nil }
65+
if fstat(fd, &statBuf) < 0 || statBuf.st_mode & S_IFMT == S_IFDIR {
66+
close(fd)
67+
return nil
68+
}
69+
return fd
70+
}
71+
72+
// compares contents in efficient manner
73+
// note that symlinks are not traversed!
74+
guard let myInfo = fileManager._fileStat(path), let otherInfo = fileManager._fileStat(other) else {
75+
return false
76+
}
77+
78+
/* check for being hard links */
79+
if myInfo.st_dev == otherInfo.st_dev && myInfo.st_ino == otherInfo.st_ino {
80+
return true
81+
}
82+
83+
/* check for being same type */
84+
if myInfo.st_mode & S_IFMT != otherInfo.st_mode & S_IFMT {
85+
return false
86+
}
87+
88+
if myInfo.isSpecial {
89+
return myInfo.st_rdev == otherInfo.st_rdev // different inodes aiming at same device
90+
}
91+
92+
if myInfo.isRegular {
93+
if myInfo.st_size != otherInfo.st_size {
94+
return false
95+
}
96+
return fileManager.withFileSystemRepresentation(for: path) { pathPtr in
97+
guard let pathPtr, let fd1 = _openFD(pathPtr) else { return false }
98+
defer { close(fd1) }
99+
return fileManager.withFileSystemRepresentation(for: other) { otherPtr in
100+
guard let otherPtr, let fd2 = _openFD(otherPtr) else { return false }
101+
defer { close(fd2) }
102+
#if canImport(Darwin)
103+
_ = fcntl(fd1, F_NOCACHE, 1)
104+
_ = fcntl(fd2, F_NOCACHE, 1)
105+
#endif
106+
let quantum = 8 * 1024
107+
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: quantum) { buf1 in
108+
buf1.initialize(repeating: 0)
109+
defer { buf1.deinitialize() }
110+
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: quantum) { buf2 in
111+
buf2.initialize(repeating: 0)
112+
defer { buf2.deinitialize() }
113+
var readBytes = 0
114+
while true {
115+
readBytes = read(fd1, buf1.baseAddress!, quantum)
116+
guard readBytes > 0 else { break }
117+
if read(fd2, buf2.baseAddress!, quantum) != readBytes {
118+
return false
119+
}
120+
if !buf1.elementsEqual(buf2) {
121+
return false
122+
}
123+
}
124+
if readBytes < -1 { return false }
125+
return true
126+
}
127+
}
128+
}
129+
}
130+
} else if myInfo.isSymbolicLink {
131+
return (try? fileManager.destinationOfSymbolicLink(atPath: path) == fileManager.destinationOfSymbolicLink(atPath: other)) ?? false
132+
} else if myInfo.isDirectory {
133+
guard let myContents = try? fileManager.contentsOfDirectory(atPath: path),
134+
let otherContents = try? Set(fileManager.contentsOfDirectory(atPath: other)),
135+
myContents.count == otherContents.count else { return false }
136+
for item in myContents {
137+
guard otherContents.contains(item) else { return false }
138+
let myItemPath = "\(path)/\(item)"
139+
let otherItemPath = "\(other)/\(item)"
140+
// Ok to call to self here because it's the same function
141+
if !self.contentsEqual(atPath: myItemPath, andPath: otherItemPath) {
142+
return false
143+
}
144+
}
145+
return true
146+
}
147+
148+
fatalError("Unknown file type 0x\(String(myInfo.st_mode, radix: 16)) for file \(path)")
149+
}
150+
151+
func fileSystemRepresentation(withPath path: String) -> UnsafePointer<CChar>? {
152+
path.withFileSystemRepresentation { ptr -> UnsafePointer<CChar>? in
153+
guard let ptr else {
154+
return nil
155+
}
156+
157+
let len = strlen(ptr) + 1
158+
let newPtr = UnsafeMutablePointer<CChar>.allocate(capacity: len)
159+
memcpy(newPtr, ptr, len)
160+
return UnsafePointer(newPtr)
161+
}
162+
}
163+
164+
// SPI
165+
func getFileSystemRepresentation(_ buffer: UnsafeMutablePointer<CChar>, maxLength: UInt, with path: String) -> Bool {
166+
guard !path.isEmpty else {
167+
return false
168+
}
169+
return path.withFileSystemRepresentation { ptr in
170+
guard let ptr else {
171+
return false
172+
}
173+
let lengthOfData = strlen(ptr) + 1
174+
guard lengthOfData <= maxLength else {
175+
return false
176+
}
177+
178+
memcpy(buffer, ptr, lengthOfData)
179+
return true
180+
}
181+
}
182+
183+
func string(
184+
withFileSystemRepresentation str: UnsafePointer<CChar>,
185+
length len: Int
186+
) -> String {
187+
UnsafeBufferPointer(start: str, count: len).withMemoryRebound(to: UInt8.self) { buffer in
188+
String(decoding: buffer, as: UTF8.self)
189+
}
190+
}
191+
}
192+
193+
extension FileManager {
194+
#if FOUNDATION_FRAMEWORK
195+
@nonobjc
196+
func withFileSystemRepresentation<R>(for path: String, _ body: (UnsafePointer<CChar>?) throws -> R) rethrows -> R {
197+
var selfType: Any.Type { Self.self }
198+
if selfType != FileManager.self {
199+
// Subclasses can override getFileSystemRepresentation. Continue to call into that function to preserve subclassing behavior
200+
return try withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
201+
guard self.getFileSystemRepresentation(buffer.baseAddress!, maxLength: FileManager.MAX_PATH_SIZE, withPath: path) else {
202+
return try body(nil)
203+
}
204+
return try body(buffer.baseAddress)
205+
}
206+
}
207+
// We don't have a subclass, so we can call this directly to avoid the temp allocation + copy
208+
return try path.withFileSystemRepresentation(body)
209+
}
210+
#endif
211+
212+
@nonobjc
213+
func _fileStat(_ path: String) -> stat? {
214+
let result = self.withFileSystemRepresentation(for: path) { rep -> stat? in
215+
var s = stat()
216+
guard let rep, lstat(rep, &s) == 0 else {
217+
return nil
218+
}
219+
return s
220+
}
221+
222+
guard let result else { return nil }
223+
return result
224+
}
225+
226+
@nonobjc
227+
static var MAX_PATH_SIZE: Int { 1026 }
228+
}

0 commit comments

Comments
 (0)