Skip to content

Conversation

@HamzaHassanain
Copy link
Contributor

The createDirectory function will succeed without error if the target directory already exists.

Motivation

This change addresses issue #3404. Currently, fileSystem.createDirectory fails if the target directory already exists, forcing users to write boilerplate try/catch blocks to handle this common and expected case.

The goal is to make this function's behavior idempotent.

Modifications

To achieve this, I've made the following changes:

(Implementation) A new private helper function, _handleCreateDirectoryFileExists, was introduced. This function is responsible for:

  1. Performing a stat call on the path that failed.
  2. Checking if the existing item is a directory (S_IFDIR).
  3. Returning a success result if it's a directory, or re-throwing the original .fileExists error if it's a file or another type of entity.

(Logic) The core _createDirectory function was updated to call this new helper function whenever Syscall.mkdir fails with an EEXIST (.fileExists) error. This check is applied in both internal loops to correctly handle cases where either an intermediate directory or the final target directory already exists.

Result

With this change, users can now call the function fileSystem.createDirectory and the operation will succeed even if the directory is already present, leading to cleaner and more predictable code.

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I've left some feedback inline. We'll also need some tests for this though.

Comment on lines 807 to 825
/// Handle _createDirectory failure if the directory already exists.
private func _handleCreateDirectoryFileExists(
at path: FilePath,
) -> Result<Void, FileSystemError> {
// This function is called when _createDirectory fails with (errno = .fileExists),
// - If the path exists and is a directory, if so return success.
// - If the path exists and is not a directory, return failure.
switch Syscall.stat(path: path) {
case .success(let stat):
// Check if it's a directory
if (stat.st_mode & S_IFMT) == S_IFDIR {
return .success(())
} else {
return .failure(.mkdir(errno: .fileExists, path: path, location: .here()))
}
case let .failure(errno):
return .failure(.mkdir(errno: errno, path: path, location: .here()))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a whole new function for this, we already have the _info function which does most of this for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, will work on it!

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small fix but otherwise this looks good!

Comment on lines 852 to 853
// Unable to determine what exists at this path.
return .failure(error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this path we should return the error from mkdir. I think it'd be a bit odd to be presented with an error from stat when mkdir failed.

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@glbrntt glbrntt added the 🔨 semver/patch No public API change. label Oct 20, 2025
Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the formatter is unhappy. It looks like you have some trailing whitespace:

--- a/Sources/NIOFS/FileSystem.swift
+++ b/Sources/NIOFS/FileSystem.swift
@@ -825,7 +825,7 @@ extension FileSystem {
                 break loop
 
             case let .failure(errno):
-                if errno == .fileExists {                    
+                if errno == .fileExists {
                     switch self._info(forFileAt: path, infoAboutSymbolicLink: false) {
                     case let .success(maybeInfo):
                         if let info = maybeInfo, info.type == .directory {
diff --git a/Sources/_NIOFileSystem/FileSystem.swift b/Sources/_NIOFileSystem/FileSystem.swift
index 70491ae..53174c1 100644
--- a/Sources/_NIOFileSystem/FileSystem.swift
+++ b/Sources/_NIOFileSystem/FileSystem.swift
@@ -839,7 +839,7 @@ extension FileSystem {
                 break loop
 
             case let .failure(errno):
-                if errno == .fileExists {                    
+                if errno == .fileExists {
                     switch self._info(forFileAt: path, infoAboutSymbolicLink: false) {
                     case let .success(maybeInfo):
                         if let info = maybeInfo, info.type == .directory {
@@ -852,7 +852,7 @@ extension FileSystem {
                         // 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()))
                 }

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@glbrntt glbrntt merged commit 7124f09 into apple:main Oct 20, 2025
114 of 123 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants