File: FileManager%2BUtilities.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (289 lines) | stat: -rw-r--r-- 12,648 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//===----------------------------------------------------------------------===//
//
// 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 XPCPrivate
internal import _ForSwiftFoundation
internal import Foundation_Private.NSFileManager
internal import DarwinPrivate

#if os(macOS)
internal import QuarantinePrivate
#endif
#endif

#if canImport(Darwin)
import Darwin
#elseif os(Android)
import Android
#elseif canImport(Glibc)
import Glibc
internal import _FoundationCShims
#elseif canImport(Musl)
import Musl
internal import _FoundationCShims
#elseif os(Windows)
import CRT
import WinSDK
#elseif os(WASI)
import WASILibc
#endif

#if os(Windows)
extension FILETIME {
    package var timeIntervalSince1970: TimeInterval {
        var count: Double = Double((UInt64(self.dwHighDateTime) << 32) | UInt64(self.dwLowDateTime))
        count /= 1e7 // 100 nanoseconds to seconds
        return count - Date.timeIntervalBetween1601AndReferenceDate + Date.timeIntervalBetween1970AndReferenceDate
    }
}
#endif

#if !os(Windows)
extension stat {
    var isDirectory: Bool {
        (self.st_mode & S_IFMT) == S_IFDIR
    }
    
    var isRegular: Bool {
        (self.st_mode & S_IFMT) == S_IFREG
    }
    
    var isSymbolicLink: Bool {
        (self.st_mode & S_IFMT) == S_IFLNK
    }
    
    var isSpecial: Bool {
        let type = self.st_mode & S_IFMT
        return type == S_IFBLK || type == S_IFCHR
    }
}
#endif

#if FOUNDATION_FRAMEWORK && os(macOS)
extension URLResourceKey {
    static var _finderInfoKey: Self { URLResourceKey("_NSURLFinderInfoKey") }
}
#endif

extension _FileManagerImpl {
    #if os(macOS) && FOUNDATION_FRAMEWORK
    private struct _HFSFinderInfo {
        var fileInfo: FndrFileInfo
        var extendedFileInfo: FndrExtendedFileInfo
    }
    #endif
    
    static func _catInfo(for url: URL, statInfo: stat, into attributes: inout [FileAttributeKey : Any]) throws {
        #if FOUNDATION_FRAMEWORK
        // Get the info we care about for the file (creatorCode, fileTypeCode, extensionHidden, creationDate, fileBusy) and set validFields for each of them.
        #if os(macOS)
        let keys: Set<URLResourceKey> = [.hasHiddenExtensionKey, .creationDateKey, ._finderInfoKey]
        #else
        let keys: Set<URLResourceKey> = [.hasHiddenExtensionKey, .creationDateKey]
        #endif
        let values = try url.resourceValues(forKeys: keys)
        #if os(macOS)
        if let finderInfoData = values.allValues[._finderInfoKey] as? Data {
            let finderInfo = finderInfoData.withUnsafeBytes({ $0.load(as: _HFSFinderInfo.self) })
            // Record the creator and file type of a file.
            if statInfo.isRegular {
                attributes[.hfsCreatorCode] = _writeFileAttributePrimitive(finderInfo.fileInfo.fdCreator, as: UInt.self)
                attributes[.hfsTypeCode] = _writeFileAttributePrimitive(finderInfo.fileInfo.fdType, as: UInt.self)
            } else if statInfo.isSymbolicLink {
                attributes[.hfsCreatorCode] = _writeFileAttributePrimitive(kSymLinkCreator, as: UInt.self)
                attributes[.hfsTypeCode] = _writeFileAttributePrimitive(kSymLinkFileType, as: UInt.self)
            }
            // To preserve historical behavior, only set this attribute if the value is true
            let isBusy = (finderInfo.extendedFileInfo.extended_flags & 0x80 /*kExtendedFlagObjectIsBusy*/) != 0
            if isBusy {
                attributes[.busy] = _writeFileAttributePrimitive(true)
            }
        }
        #endif
        
        // Record whether or not the file or directory's name extension is hidden.
        if let value = values.hasHiddenExtension {
            attributes[.extensionHidden] = _writeFileAttributePrimitive(value)
        }

        // Record the creation date of the object.
        attributes[.creationDate] = values.creationDate
        #else
        return // TODO: implement fetching cat info attributes in swift-foundation
        #endif
    }
    
    private static let _catInfoKeys: [FileAttributeKey] = [.hfsCreatorCode, .hfsTypeCode, .busy, .extensionHidden, .creationDate]
    private static let _swiftFoundationUnsupportedKeys: [FileAttributeKey] = [.hfsCreatorCode, .hfsTypeCode, .busy, .extensionHidden]
    static func _setCatInfoAttributes(_ attributes: [FileAttributeKey : Any], path: String) throws {
        let hasRelevantKeys = attributes.keys.contains(where: { _catInfoKeys.contains($0) })
        guard hasRelevantKeys else { return }
        
        #if !FOUNDATION_FRAMEWORK
        // Exclude some attributes (like .creationDate) from this check since they are unconditionally, implicitly included in `attributesForItem(atPath:)` results
        if attributes.keys.contains(where: { _swiftFoundationUnsupportedKeys.contains($0) }) {
            throw CocoaError.errorWithFilePath(.featureUnsupported, path)
        } else {
            return // TODO: support relevant cat info keys in swift-foundation
        }
        #else
        // -setAttributes:ofItemAtPath:error: follows symlinks (<rdar://5815920>), but the NSURL resource value API doesn't, so we have to manually resolve the symlink.
        // We lie to fileURLWithPath:isDirectory: to avoid the extra stat. Since this URL isn't used as a base URL for another URL, it shouldn't make any difference.
        var url = URL(fileURLWithPath: path.resolvingSymlinksInPath, isDirectory: false)
        var urlAttributes: [URLResourceKey : Any] = [:]
        #if os(macOS)
        let creatorCode = _readFileAttributePrimitive(attributes[.hfsCreatorCode], as: UInt32.self)
        let fileTypeCode = _readFileAttributePrimitive(attributes[.hfsTypeCode], as: UInt32.self)
        let fileBusy = _readFileAttributePrimitive(attributes[.busy], as: Bool.self)
        if creatorCode != nil || fileTypeCode != nil || fileBusy != nil {
            let finderInfoData = try url.resourceValues(forKeys: [._finderInfoKey]).allValues[._finderInfoKey] as? Data
            if var finderInfo = finderInfoData?.withUnsafeBytes({ $0.load(as: _HFSFinderInfo.self) }) {
                if let creatorCode {
                    finderInfo.fileInfo.fdCreator = creatorCode
                }
                if let fileTypeCode {
                    finderInfo.fileInfo.fdType = fileTypeCode
                }
                if let fileBusy {
                    if fileBusy {
                        finderInfo.extendedFileInfo.extended_flags |= 0x0080 // kExtendedFlagObjectIsBusy
                    } else {
                        finderInfo.extendedFileInfo.extended_flags &= ~0x0080 // kExtendedFlagObjectIsBusy
                    }
                }
                withUnsafeBytes(of: &finderInfo) { buffer in
                    urlAttributes[._finderInfoKey] = Data(buffer)
                }
            }
        }
        #endif
        
        if let extensionHidden = attributes[.extensionHidden] {
            urlAttributes[.hasHiddenExtensionKey] = extensionHidden
        }
        if let creationDate = attributes[.creationDate] {
            urlAttributes[.creationDateKey] = creationDate
        }
        try url.setResourceValues(URLResourceValues(values: urlAttributes))
        #endif
    }

#if !os(Windows) && !os(WASI)
    static func _setAttribute(_ key: UnsafePointer<CChar>, value: Data, at path: UnsafePointer<CChar>, followSymLinks: Bool) throws {
        try value.withUnsafeBytes { buffer in
            #if canImport(Darwin)
            let result = setxattr(path, key, buffer.baseAddress!, buffer.count, 0, followSymLinks ? 0 : XATTR_NOFOLLOW)
            #else
            var result: Int32
            if followSymLinks {
                result = lsetxattr(path, key, buffer.baseAddress!, buffer.count, 0)
            } else {
                result = setxattr(path, key, buffer.baseAddress!, buffer.count, 0)
            }
            #endif
            #if os(macOS) && FOUNDATION_FRAMEWORK
            // if setxaddr failed and its a permission error for a sandbox app trying to set quaratine attribute, ignore it since its not
            // permitted, the attribute will be put on the file by the quaratine MAC hook
            if result == -1 && errno == EPERM && _xpc_runtime_is_app_sandboxed() && strcmp(key, "com.apple.quarantine") == 0 {
                return
            }
            #endif
            if result == -1 {
                throw CocoaError.errorWithFilePath(String(cString: path), errno: errno, reading: false)
            }
        }
    }

    static func _setAttributes(_ attributes: [String : Data], at path: UnsafePointer<CChar>, followSymLinks: Bool) throws {
        for (key, value) in attributes {
            try key.withCString {
                try Self._setAttribute($0, value: value, at: path, followSymLinks: followSymLinks)
            }
        }
    }
#endif

    #if FOUNDATION_FRAMEWORK
    static func _fileProtectionValueForPath(_ fileSystemRepresentation: UnsafePointer<CChar>) -> Int32? {
        var attrList = attrlist()
        attrList.bitmapcount = u_short(ATTR_BIT_MAP_COUNT)
        attrList.commonattr = attrgroup_t(ATTR_CMN_DATA_PROTECT_FLAGS)
        typealias Buffer = (length: UInt32, class: Int32)
        var attributesBuffer: Buffer = (0, 0)
        let result = withUnsafeMutableBytes(of: &attributesBuffer) { buffer in
            getattrlist(fileSystemRepresentation, &attrList, buffer.baseAddress!, buffer.count, .init(FSOPT_NOFOLLOW))
        }
        guard result == 0 else {
            return nil
        }
        return attributesBuffer.class
    }
    
    static func _setFileProtectionValueForPath(_ path: String, _ fileSystemRepresentation: UnsafePointer<CChar>, newValue: Int32) throws {
        // It's probably better to do a single getattrlist than and open()/fcntl()/close(), so skip the work in case the value is already set correctly.
        guard Self._fileProtectionValueForPath(fileSystemRepresentation) != newValue else {
            return
        }
        
        var fd = open(fileSystemRepresentation, O_WRONLY)
        var dir: UnsafeMutablePointer<DIR>?
        defer {
            // For opendir(), the DIR structure owns the fd. Don't attempt to close it ourselves. 14323986.
            if let dir {
                closedir(dir)
            } else if fd >= 0 {
                close(fd)
            }
        }
        
        // If open() failed because the file is a directory, try again using opendir/dirfd.
        if fd < 0 && errno == EISDIR {
            dir = opendir(fileSystemRepresentation)
            if let dir {
                fd = dirfd(dir)
            }
        }
        
        if fd >= 0 {
            if fcntl(fd, F_SETPROTECTIONCLASS, newValue) != 0 {
                guard errno == ENOTSUP else {
                    throw CocoaError.errorWithFilePath(path, errno: errno, reading: true)
                }
                
                // If we fail with ENOTSUP because the volume doesn't support file protection, then no-op.
                var s = statfs()
                guard fstatfs(fd, &s) != 0 || s.f_flags & UInt32(MNT_CPROTECT) == 0 else {
                    throw CocoaError.errorWithFilePath(path, errno: ENOTSUP, reading: true)
                }
            }
        } else if errno == EACCES {
            // We don't have any alternative API for setting the protection class, so we must open() for fnctl(). If we don't have write permissions, the open() will fail with EACCES. None of the other NSFileManager attributes fail in this case, so it is unreasonable (and binary incompatible) to cause the NSFileManager methods to fail when this happens. <rdar://7796261>
            // This results in silent failures, but we simply don't have any other alternatives. <rdar://7837261> was a request for a path-based API with similar semantics to chmod, etc., but the OS team decided not to fix it.
            return
        } else {
            throw CocoaError.errorWithFilePath(path, errno: errno, reading: true)
        }
    }
    #endif
}

extension FileManager {
    @nonobjc
    var safeDelegate: FileManagerDelegate? {
#if FOUNDATION_FRAMEWORK
        self._safeDelegate() as? FileManagerDelegate
#else
        self.delegate
#endif
    }
}