diff --git a/Sources/NIOFS/FileSystem.swift b/Sources/NIOFS/FileSystem.swift index b54e6cb28b0..650c516d386 100644 --- a/Sources/NIOFS/FileSystem.swift +++ b/Sources/NIOFS/FileSystem.swift @@ -825,6 +825,20 @@ extension FileSystem { break loop case let .failure(errno): + if errno == .fileExists { + switch self._info(forFileAt: path, infoAboutSymbolicLink: false) { + case let .success(maybeInfo): + if let info = maybeInfo, info.type == .directory { + break loop + } else { + // A file exists at this path. + return .failure(.mkdir(errno: errno, path: path, location: .here())) + } + case .failure: + // Unable to determine what exists at this path. + return .failure(.mkdir(errno: errno, path: path, location: .here())) + } + } guard createIntermediateDirectories, errno == .noSuchFileOrDirectory else { return .failure(.mkdir(errno: errno, path: path, location: .here())) } diff --git a/Sources/_NIOFileSystem/FileSystem.swift b/Sources/_NIOFileSystem/FileSystem.swift index 8b7df2bb1b4..53174c19a11 100644 --- a/Sources/_NIOFileSystem/FileSystem.swift +++ b/Sources/_NIOFileSystem/FileSystem.swift @@ -839,6 +839,20 @@ extension FileSystem { break loop case let .failure(errno): + if errno == .fileExists { + switch self._info(forFileAt: path, infoAboutSymbolicLink: false) { + case let .success(maybeInfo): + if let info = maybeInfo, info.type == .directory { + break loop + } else { + // A file exists at this path. + return .failure(.mkdir(errno: errno, path: path, location: .here())) + } + case .failure: + // Unable to determine what exists at this path. + return .failure(.mkdir(errno: errno, path: path, location: .here())) + } + } guard createIntermediateDirectories, errno == .noSuchFileOrDirectory else { return .failure(.mkdir(errno: errno, path: path, location: .here())) } diff --git a/Tests/NIOFSIntegrationTests/FileSystemTests.swift b/Tests/NIOFSIntegrationTests/FileSystemTests.swift index cfc0820d191..9235fd93930 100644 --- a/Tests/NIOFSIntegrationTests/FileSystemTests.swift +++ b/Tests/NIOFSIntegrationTests/FileSystemTests.swift @@ -505,6 +505,37 @@ final class FileSystemTests: XCTestCase { } } + func testCreateDirectoryIsIdempotentWhenAlreadyExists() async throws { + let path = try await self.fs.temporaryFilePath() + + try await self.fs.createDirectory(at: path, withIntermediateDirectories: false) + + try await self.fs.createDirectory(at: path, withIntermediateDirectories: false) + try await self.fs.createDirectory(at: path, withIntermediateDirectories: true) + + try await self.fs.withDirectoryHandle(atPath: path) { dir in + let info = try await dir.info() + XCTAssertEqual(info.type, .directory) + XCTAssertGreaterThan(info.size, 0) + } + } + + func testCreateDirectoryThroughSymlinkToExistingDirectoryIsIdempotent() async throws { + let realDir = try await self.fs.temporaryFilePath() + try await self.fs.createDirectory(at: realDir, withIntermediateDirectories: false) + + let linkPath = try await self.fs.temporaryFilePath() + try await self.fs.createSymbolicLink(at: linkPath, withDestination: realDir) + + try await self.fs.createDirectory(at: linkPath, withIntermediateDirectories: false) + + try await self.fs.withDirectoryHandle(atPath: linkPath) { dir in + let info = try await dir.info() + XCTAssertEqual(info.type, .directory) + XCTAssertGreaterThan(info.size, 0) + } + } + func testCurrentWorkingDirectory() async throws { let directory = try await self.fs.currentWorkingDirectory XCTAssert(!directory.underlying.isEmpty)