-
Notifications
You must be signed in to change notification settings - Fork 712
feat(NIOFileSystem): Add idempotent directory creation behavior (#3404) #3410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(NIOFileSystem): Add idempotent directory creation behavior (#3404) #3410
Conversation
There was a problem hiding this 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.
| /// 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())) | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this 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!
| // Unable to determine what exists at this path. | ||
| return .failure(error) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
There was a problem hiding this 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()))
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
The createDirectory function will succeed without error if the target directory already exists.
Motivation
This change addresses issue #3404. Currently,
fileSystem.createDirectoryfails 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:statcall on the path that failed.S_IFDIR)..fileExistserror if it's a file or another type of entity.(Logic) The core
_createDirectoryfunction was updated to call this new helper function wheneverSyscall.mkdirfails with anEEXIST(.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.createDirectoryand the operation will succeed even if the directory is already present, leading to cleaner and more predictable code.