@@ -250,35 +250,82 @@ extension _FileManagerImpl {
250
250
251
251
try fileManager. createDirectory ( atPath: path, withIntermediateDirectories: createIntermediates, attributes: attributes)
252
252
}
253
+
254
+ #if os(Windows)
255
+ /// If `path` is absolute, this is the same as `path.withNTPathRepresentation`.
256
+ /// If `path` is relative, this creates an absolute path of `path` relative to `currentDirectoryPath` and runs
257
+ /// `body` with that path.
258
+ private func withAbsoluteNTPathRepresentation< Result> (
259
+ of path: String ,
260
+ _ body: ( UnsafePointer < WCHAR > ) throws -> Result
261
+ ) throws -> Result {
262
+ try path. withNTPathRepresentation { pwszPath in
263
+ if !PathIsRelativeW( pwszPath) {
264
+ // We already have an absolute path. Nothing to do
265
+ return try body ( pwszPath)
266
+ }
267
+ guard let currentDirectoryPath else {
268
+ preconditionFailure ( " We should always have a current directory on Windows " )
269
+ }
270
+
271
+ // We have a relateive path. Make it absolute.
272
+ let absoluteUrl = URL (
273
+ filePath: path,
274
+ directoryHint: . isDirectory,
275
+ relativeTo: URL ( filePath: currentDirectoryPath, directoryHint: . isDirectory)
276
+ )
277
+ return try absoluteUrl. path. withNTPathRepresentation { pwszPath in
278
+ return try body ( pwszPath)
279
+ }
280
+ }
281
+ }
282
+ #endif
253
283
254
284
func createDirectory(
255
285
atPath path: String ,
256
286
withIntermediateDirectories createIntermediates: Bool ,
257
287
attributes: [ FileAttributeKey : Any ] ? = nil
258
288
) throws {
259
289
#if os(Windows)
260
- try path. withNTPathRepresentation { pwszPath in
261
- if createIntermediates {
262
- var isDirectory : Bool = false
263
- if fileManager. fileExists ( atPath: path, isDirectory: & isDirectory) {
264
- guard isDirectory else {
265
- throw CocoaError . errorWithFilePath ( path, win32: ERROR_FILE_EXISTS, reading: false )
266
- }
267
- return
290
+ var saAttributes : SECURITY_ATTRIBUTES =
291
+ SECURITY_ATTRIBUTES ( nLength: DWORD ( MemoryLayout< SECURITY_ATTRIBUTES> . size) ,
292
+ lpSecurityDescriptor: nil ,
293
+ bInheritHandle: false )
294
+ // `SHCreateDirectoryExW` creates intermediate directories while `CreateDirectoryW` does not.
295
+ if createIntermediates {
296
+ // `SHCreateDirectoryExW` requires an absolute path while `CreateDirectoryW` works based on the current working
297
+ // directory.
298
+ try withAbsoluteNTPathRepresentation ( of: path) { pwszPath in
299
+ let errorCode = SHCreateDirectoryExW ( nil , pwszPath, & saAttributes)
300
+ guard let errorCode = DWORD ( exactly: errorCode) else {
301
+ // `SHCreateDirectoryExW` returns `Int` but all error codes are defined in terms of `DWORD`, aka
302
+ // `UInt`. We received an unknown error code.
303
+ throw CocoaError . errorWithFilePath ( . fileWriteUnknown, path)
268
304
}
269
-
270
- let parent = path. deletingLastPathComponent ( )
271
- if !parent. isEmpty {
272
- try createDirectory ( atPath: parent, withIntermediateDirectories: true , attributes: attributes)
305
+ switch errorCode {
306
+ case ERROR_SUCCESS:
307
+ if let attributes {
308
+ try ? fileManager. setAttributes ( attributes, ofItemAtPath: path)
309
+ }
310
+ case ERROR_ALREADY_EXISTS:
311
+ var isDirectory : Bool = false
312
+ if fileExists ( atPath: path, isDirectory: & isDirectory) , isDirectory {
313
+ // A directory already exists at this path, which is not an error if we have
314
+ // `createIntermediates == true`.
315
+ break
316
+ }
317
+ // A file (not a directory) exists at the given path or the file creation failed and the item
318
+ // at this path has been deleted before the call to `fileExists`. Throw the original error.
319
+ fallthrough
320
+ default :
321
+ throw CocoaError . errorWithFilePath ( path, win32: errorCode, reading: false )
273
322
}
274
323
}
275
-
276
- var saAttributes : SECURITY_ATTRIBUTES =
277
- SECURITY_ATTRIBUTES ( nLength: DWORD ( MemoryLayout< SECURITY_ATTRIBUTES> . size) ,
278
- lpSecurityDescriptor: nil ,
279
- bInheritHandle: false )
280
- guard CreateDirectoryW ( pwszPath, & saAttributes) else {
281
- throw CocoaError . errorWithFilePath ( path, win32: GetLastError ( ) , reading: false )
324
+ } else {
325
+ try path. withNTPathRepresentation { pwszPath in
326
+ guard CreateDirectoryW ( pwszPath, & saAttributes) else {
327
+ throw CocoaError . errorWithFilePath ( path, win32: GetLastError ( ) , reading: false )
328
+ }
282
329
}
283
330
if let attributes {
284
331
try ? fileManager. setAttributes ( attributes, ofItemAtPath: path)
0 commit comments