@@ -250,35 +250,82 @@ extension _FileManagerImpl {
250250
251251 try fileManager. createDirectory ( atPath: path, withIntermediateDirectories: createIntermediates, attributes: attributes)
252252 }
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
253283
254284 func createDirectory(
255285 atPath path: String ,
256286 withIntermediateDirectories createIntermediates: Bool ,
257287 attributes: [ FileAttributeKey : Any ] ? = nil
258288 ) throws {
259289#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)
268304 }
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 )
273322 }
274323 }
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+ }
282329 }
283330 if let attributes {
284331 try ? fileManager. setAttributes ( attributes, ofItemAtPath: path)
0 commit comments