99
1010// MARK: - API
1111
12- public func withTemporaryPath< R> (
12+ /// Create a temporary path for the duration of the closure.
13+ ///
14+ /// - Parameters:
15+ /// - basename: The base name for the temporary path.
16+ /// - body: The closure to execute.
17+ ///
18+ /// Creates a temporary directory with a name based on the given `basename`,
19+ /// executes `body`, passing in the path of the created directory, then
20+ /// deletes the directory and all of its contents before returning.
21+ public func withTemporaryFilePath< R> (
1322 basename: FilePath . Component ,
1423 _ body: ( FilePath ) throws -> R
1524) throws -> R {
1625 let temporaryDir = try createUniqueTemporaryDirectory ( basename: basename)
1726 defer {
18- try ? recursiveRemove ( at: temporaryDir)
27+ try ? _recursiveRemove ( at: temporaryDir)
1928 }
2029
2130 return try body ( temporaryDir)
2231}
2332
2433// MARK: - Internals
2534
26- #if os(Windows)
27- import WinSDK
28-
29- fileprivate func getTemporaryDirectory( ) throws -> FilePath {
30- return try withUnsafeTemporaryAllocation ( of: CInterop . PlatformChar. self,
31- capacity: Int ( MAX_PATH) + 1 ) {
32- buffer in
33-
34- guard GetTempPath2W ( DWORD ( buffer. count) , buffer. baseAddress) != 0 else {
35- throw Errno ( windowsError: GetLastError ( ) )
36- }
37-
38- return FilePath ( SystemString ( platformString: buffer. baseAddress!) )
39- }
40- }
41-
42- fileprivate func forEachFile(
43- at path: FilePath ,
44- _ body: ( WIN32_FIND_DATAW ) throws -> ( )
45- ) rethrows {
46- let searchPath = path. appending ( " \\ * " )
47-
48- try searchPath. withPlatformString { szPath in
49- var findData = WIN32_FIND_DATAW ( )
50- let hFind = FindFirstFileW ( szPath, & findData)
51- if hFind == INVALID_HANDLE_VALUE {
52- throw Errno ( windowsError: GetLastError ( ) )
53- }
54- defer {
55- FindClose ( hFind)
56- }
57-
58- repeat {
59- // Skip . and ..
60- if findData. cFileName. 0 == 46
61- && ( findData. cFileName. 1 == 0
62- || ( findData. cFileName. 1 == 46
63- && findData. cFileName. 2 == 0 ) ) {
64- continue
65- }
66-
67- try body ( findData)
68- } while FindNextFileW ( hFind, & findData)
69- }
70- }
71-
72- fileprivate func recursiveRemove( at path: FilePath ) throws {
73- // First, deal with subdirectories
74- try forEachFile ( at: path) { findData in
75- if ( findData. dwFileAttributes & DWORD ( FILE_ATTRIBUTE_DIRECTORY) ) != 0 {
76- let name = withUnsafeBytes ( of: findData. cFileName) {
77- return SystemString ( platformString: $0. assumingMemoryBound (
78- to: CInterop . PlatformChar. self) . baseAddress!)
79- }
80- let component = FilePath . Component ( name) !
81- let subpath = path. appending ( component)
82-
83- try recursiveRemove ( at: subpath)
84- }
85- }
86-
87- // Now delete everything else
88- try forEachFile ( at: path) { findData in
89- let name = withUnsafeBytes ( of: findData. cFileName) {
90- return SystemString ( platformString: $0. assumingMemoryBound (
91- to: CInterop . PlatformChar. self) . baseAddress!)
92- }
93- let component = FilePath . Component ( name) !
94- let subpath = path. appending ( component)
95-
96- if ( findData. dwFileAttributes & DWORD ( FILE_ATTRIBUTE_DIRECTORY) ) == 0 {
97- try subpath. withPlatformString {
98- if !DeleteFileW( $0) {
99- throw Errno ( windowsError: GetLastError ( ) )
100- }
101- }
102- }
103- }
104-
105- // Finally, delete the parent
106- try path. withPlatformString {
107- if !RemoveDirectoryW( $0) {
108- throw Errno ( windowsError: GetLastError ( ) )
109- }
110- }
111- }
112-
113- #else
114- fileprivate func getTemporaryDirectory( ) throws -> FilePath {
115- #if SYSTEM_PACKAGE_DARWIN
116- var capacity = 1024
117- while true {
118- let path : FilePath ? = withUnsafeTemporaryAllocation (
119- of: CInterop . PlatformChar. self,
120- capacity: capacity
121- ) { buffer in
122- let len = system_confstr ( SYSTEM_CS_DARWIN_USER_TEMP_DIR,
123- buffer. baseAddress!,
124- buffer. count)
125- if len == 0 {
126- // Fall back to "/tmp" if we can't read the temp directory
127- return " /tmp "
128- }
129- // If it was truncated, increase capaciy and try again
130- if len > buffer. count {
131- capacity = len
132- return nil
133- }
134- return FilePath ( SystemString ( platformString: buffer. baseAddress!) )
135- }
136- if let path = path {
137- return path
138- }
139- }
140- #else
141- return " /tmp "
142- #endif
143- }
144-
145- fileprivate func recursiveRemove( at path: FilePath ) throws {
146- let dirfd = try FileDescriptor . open ( path, . readOnly, options: . directory)
147- defer {
148- try ? dirfd. close ( )
149- }
150-
151- let dot : ( CInterop . PlatformChar , CInterop . PlatformChar ) = ( 46 , 0 )
152- try withUnsafeBytes ( of: dot) {
153- try recursiveRemove (
154- in: dirfd. rawValue,
155- path: $0. assumingMemoryBound ( to: CInterop . PlatformChar. self) . baseAddress!
156- )
157- }
158-
159- try path. withPlatformString {
160- if system_rmdir ( $0) != 0 {
161- throw Errno . current
162- }
163- }
164- }
165-
166- fileprivate func impl_opendirat(
167- _ dirfd: CInt ,
168- _ path: UnsafePointer < CInterop . PlatformChar >
169- ) -> UnsafeMutablePointer < system_DIR > ? {
170- let fd = system_openat ( dirfd, path,
171- FileDescriptor . AccessMode. readOnly. rawValue
172- | FileDescriptor . OpenOptions. directory. rawValue)
173- if fd < 0 {
174- return nil
175- }
176- return system_fdopendir ( fd)
177- }
178-
179- fileprivate func forEachFile(
180- in dirfd: CInt , path: UnsafePointer < CInterop . PlatformChar > ,
181- _ body: ( system_dirent ) throws -> ( )
182- ) throws {
183- guard let dir = impl_opendirat ( dirfd, path) else {
184- throw Errno . current
185- }
186- defer {
187- _ = system_closedir ( dir)
188- }
189-
190- while let dirent = system_readdir ( dir) {
191- // Skip . and ..
192- if dirent. pointee. d_name. 0 == 46
193- && ( dirent. pointee. d_name. 1 == 0
194- || ( dirent. pointee. d_name. 1 == 46
195- && dirent. pointee. d_name. 2 == 0 ) ) {
196- continue
197- }
198-
199- try body ( dirent. pointee)
200- }
201- }
202-
203- internal func recursiveRemove(
204- in dirfd: CInt ,
205- path: UnsafePointer < CInterop . PlatformChar >
206- ) throws {
207- // First, deal with subdirectories
208- try forEachFile ( in: dirfd, path: path) { dirent in
209- if dirent. d_type == SYSTEM_DT_DIR {
210- try withUnsafeBytes ( of: dirent. d_name) {
211- try recursiveRemove (
212- in: dirfd,
213- path: $0. assumingMemoryBound ( to: CInterop . PlatformChar. self)
214- . baseAddress!
215- )
216- }
217- }
218- }
219-
220- // Now delete the contents of this directory
221- try forEachFile ( in: dirfd, path: path) { dirent in
222- let flag : CInt
223-
224- if dirent. d_type == SYSTEM_DT_DIR {
225- flag = SYSTEM_AT_REMOVE_DIR
226- } else {
227- flag = 0
228- }
229-
230- let result = withUnsafeBytes ( of: dirent. d_name) {
231- system_unlinkat ( dirfd,
232- $0. assumingMemoryBound ( to: CInterop . PlatformChar. self)
233- . baseAddress!,
234- flag)
235- }
236-
237- if result != 0 {
238- throw Errno . current
239- }
240- }
241- }
242- #endif
243-
24435fileprivate let base64 = Array < UInt8 > (
24536 " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ " . utf8
24637)
24738
248- fileprivate func makeTempDirectory( at: FilePath ) throws -> Bool {
249- return try at. withPlatformString {
39+ /// Create a directory that is only accessible to the current user.
40+ ///
41+ /// - Parameters:
42+ /// - path: The path of the directory to create.
43+ /// - Returns: `true` if a new directory was created.
44+ ///
45+ /// This function will throw if there is an error, except if the error
46+ /// is that the directory exists, in which case it returns `false`.
47+ fileprivate func makeLockedDownDirectory( at path: FilePath ) throws -> Bool {
48+ return try path. withPlatformString {
25049 if system_mkdir ( $0, 0o700 ) == 0 {
25150 return true
25251 }
@@ -259,6 +58,11 @@ fileprivate func makeTempDirectory(at: FilePath) throws -> Bool {
25958 }
26059}
26160
61+ /// Generate a random string of base64 filename safe characters.
62+ ///
63+ /// - Parameters:
64+ /// - length: The number of characters in the returned string.
65+ /// - Returns: A random string of length `length`.
26266fileprivate func createRandomString( length: Int ) -> String {
26367 return String (
26468 decoding: ( 0 ..< length) . map {
@@ -268,16 +72,25 @@ fileprivate func createRandomString(length: Int) -> String {
26872 )
26973}
27074
271- internal func createUniqueTemporaryDirectory(
75+ /// Given a base name, create a uniquely named temporary directory.
76+ ///
77+ /// - Parameters:
78+ /// - basename: The base name for the new directory.
79+ /// - Returns: The path to the new directory.
80+ ///
81+ /// Creates a directory in the system temporary directory whose name
82+ /// starts with `basename`, followed by a `.` and then a random
83+ /// string of characters.
84+ fileprivate func createUniqueTemporaryDirectory(
27285 basename: FilePath . Component
27386) throws -> FilePath {
274- var tempDir = try getTemporaryDirectory ( )
87+ var tempDir = try _getTemporaryDirectory ( )
27588 tempDir. append ( basename)
27689
27790 while true {
27891 tempDir. extension = createRandomString ( length: 16 )
27992
280- if try makeTempDirectory ( at: tempDir) {
93+ if try makeLockedDownDirectory ( at: tempDir) {
28194 return tempDir
28295 }
28396 }
0 commit comments