-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
win,fs: fix inconsistencies between platforms for rename #1941
Conversation
Renaming directories has odd inconsistencies between Unix and Win platforms. For example, renaming dirA to dirB when dirB is empty fails on Win while succeeds on Linux. This commit fixes that by removing dirB if dirB is empty. Additionally, renaming dirA to dirB when dirB is NOT empty, on Linux, returns a ENOTEMPTY error, while on Win, returns a EPERM error. This fixes this consistency be returning a ENOTEMPTY error on windows as well. Related to nodejs/node#21957 Note to reviewers: I am very new to C/C++ and I am sure I made mistakes, both in my method and my test case. I would appreciate any in-depth feedback on my code. Thank you!
Also, I am not sure if that pragma is in the best spot (or if I should even be using it). I added it because it will not compile without that directive. |
I realize windows is not that popular of platform for nodejs, but I feel fixing these inconsistencies is important. Can somebody please review this PR? |
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.
Note about tests: The tests pass, but on some rare occasions they will fail. I assuming there are just flaky tests?
It depends which test(s) you're talking about. Can you be more specific about individual tests and platforms?
Overall, I'm a little torn on this. If it aligns the behavior across platforms, that's good. However, it also introduces a bit of a potential race condition due to the extra file system calls. There is also the question of whether this would classify as a bug fix or breaking change. I'd like to know what other collaborators think.
@@ -37,6 +37,9 @@ | |||
|
|||
#include <wincrypt.h> | |||
|
|||
#pragma comment(lib, "Shlwapi.lib") |
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.
We don't really use this style in the codebase. There is currently #1890, which adds it, but it's unclear that it will get merged.
WCHAR* pathw = req->file.pathw; | ||
WCHAR* new_pathw = req->fs.info.new_pathw; | ||
|
||
// These checks allow us to mimic the rename behavior on Unix platforms when |
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.
The indentation on these comments is off. Also, please use /* */
style comments.
@@ -1301,6 +1304,34 @@ static void fs__fstat(uv_fs_t* req) { | |||
|
|||
|
|||
static void fs__rename(uv_fs_t* req) { | |||
WCHAR* pathw = req->file.pathw; |
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.
Please separate the declaration and assignment. So:
WCHAR* pathw;
pathw = req->file.pathw;
// like on Unix, rather than a EPERM error that MoveFileXW will throw on | ||
// a nonempty directory. | ||
if (PathIsDirectoryW(new_pathw)) { | ||
|
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.
Please remove the blank lines around the if
s and else
here and below.
if (PathIsDirectoryEmptyW(new_pathw)) { | ||
|
||
// Attempt deletion of empty directory so MoveFileExW will succeed on rename. | ||
if(!RemoveDirectoryW(new_pathw)) { |
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.
Space after if
.
|
||
// Attempt deletion of empty directory so MoveFileExW will succeed on rename. | ||
if(!RemoveDirectoryW(new_pathw)) { | ||
SET_REQ_WIN32_ERROR(req, GetLastError()); |
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.
Indentation is off.
// If the new path is a directory, check if it is empty. | ||
// If it is, remove the directory so MoveFileExW and rename will succeed. | ||
// If the directory is not empty, return by throwing a ENOTEMPY error, | ||
// like on Unix, rather than a EPERM error that MoveFileXW will throw on |
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.
MoveFileXW -> MoveFileExW ?
|
||
} else { | ||
// Directory is not empty, so throw a non-empty directory error. | ||
SET_REQ_WIN32_ERROR(req, ERROR_DIR_NOT_EMPTY); |
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 think you could just use SET_REQ_UV_ERROR()
here.
@@ -2805,6 +2805,38 @@ TEST_IMPL(fs_rename_to_existing_file) { | |||
return 0; | |||
} | |||
|
|||
TEST_IMPL(fs_rename_to_existing_empty_directory) { |
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.
Should there also be a test case for ENOTEMPY
?
@@ -1301,6 +1304,34 @@ static void fs__fstat(uv_fs_t* req) { | |||
|
|||
|
|||
static void fs__rename(uv_fs_t* req) { | |||
WCHAR* pathw = req->file.pathw; |
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.
This variable appears to be unused.
@cjihrig Thanks for reviewing the PR! I will revise the PR according to your comments, but want to discuss it before I do so time is not wasted if this change is deemed to be not right by other maintainers.
Which part of the code creates a race condition? It was my understanding that the calls like PathIsDirectory RemoveDirectory block until they are returned? So there wouldn't be a race condition right? Again, my understanding of these calls and C are limited.
I will rerun the tests when I get a chance and let you know! Overall, I made this PR because I thought this inconsistency in fs was an important one to fix. If other maintainers think that this difference across platforms is not that big of a deal, then I am completely okay with skipping this PR all together! |
I was referring to the window of time between the calls. It's possible for another process to modify the contents of the directory while this function is executing. For example, the directory could go from empty to not empty between checking if it's empty and trying to remove it. It's definitely an edge case, but that's why I wanted to see what others thought. |
Hello @Shivang44,
I'm sure you are aware there are numerous other inconsistencies, for example; case sensitive names, timestamp accuracy, no links on FAT, different ACL mechanisms.
IMHO implementing the complex solution you suggest should be left for downstream projects (e.g. As for returning |
P.S. I just found what I believe is a bug on Windows: > mkdir dirA
> echo " " > dirB
> node -e "fs.renameSync('dirA', 'dirB')"
> This will overwrite For reference, on Ubuntu 16.04: $ mkdir dirA
$ echo " " > dirB
$ node -e "fs.renameSync('dirA', 'dirB')"
fs.js:119
throw err;
^
Error: ENOTDIR: not a directory, rename 'dirA' -> 'dirB' |
Renaming directories has odd inconsistencies between Unix and Win
platforms. For example, renaming dirA to dirB when dirB is empty fails
on Win while succeeds on Linux. This commit fixes that by removing dirB
if dirB is empty. Additionally, renaming dirA to dirB when dirB is NOT
empty, on Linux, returns a ENOTEMPTY error, while on Win, returns a
EPERM error. This fixes this consistency be returning a ENOTEMPTY error
on windows as well.
Related to nodejs/node#21957
Note to reviewers: I am very new to C/C++ and I am sure I made mistakes,
both in my method and my test case. I would appreciate any in-depth feedback
on my code. Thank you!
Note about tests: The tests pass, but on some rare occasions they will fail. I assuming there are just flaky tests?