Skip to content

win,fs: fix inconsistencies between platforms for rename - #1941

Closed
Shivang44 wants to merge 1 commit into
libuv:masterfrom
Shivang44:fix_rename_crossplatform
Closed

win,fs: fix inconsistencies between platforms for rename#1941
Shivang44 wants to merge 1 commit into
libuv:masterfrom
Shivang44:fix_rename_crossplatform

Conversation

@Shivang44

Copy link
Copy Markdown

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?

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!
@Shivang44

Copy link
Copy Markdown
Author

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.

@Shivang44

Copy link
Copy Markdown
Author

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?

@cjihrigcjihrig left a comment

Copy link
Copy Markdown
Contributor

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.

Comment threadsrc/win/fs.c

#include <wincrypt.h>

#pragma comment(lib, "Shlwapi.lib")

Copy link
Copy Markdown
Contributor

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.

Comment threadsrc/win/fs.c
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

Copy link
Copy Markdown
Contributor

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.

Comment threadsrc/win/fs.c


static void fs__rename(uv_fs_t* req) {
WCHAR* pathw = req->file.pathw;

Copy link
Copy Markdown
Contributor

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;

Comment threadsrc/win/fs.c
// like on Unix, rather than a EPERM error that MoveFileXW will throw on
// a nonempty directory.
if (PathIsDirectoryW(new_pathw)) {

Copy link
Copy Markdown
Contributor

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 ifs and else here and below.

Comment threadsrc/win/fs.c
if (PathIsDirectoryEmptyW(new_pathw)) {

// Attempt deletion of empty directory so MoveFileExW will succeed on rename.
if(!RemoveDirectoryW(new_pathw)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Space after if.

Comment threadsrc/win/fs.c

// Attempt deletion of empty directory so MoveFileExW will succeed on rename.
if(!RemoveDirectoryW(new_pathw)) {
SET_REQ_WIN32_ERROR(req, GetLastError());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Indentation is off.

Comment threadsrc/win/fs.c
// 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MoveFileXW -> MoveFileExW ?

Comment threadsrc/win/fs.c

} else {
// Directory is not empty, so throw a non-empty directory error.
SET_REQ_WIN32_ERROR(req, ERROR_DIR_NOT_EMPTY);

Copy link
Copy Markdown
Contributor

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.

Comment threadtest/test-fs.c
return 0;
}

TEST_IMPL(fs_rename_to_existing_empty_directory) {

Copy link
Copy Markdown
Contributor

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?

Comment threadsrc/win/fs.c


static void fs__rename(uv_fs_t* req) {
WCHAR* pathw = req->file.pathw;

Copy link
Copy Markdown
Contributor

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.

@Shivang44

Shivang44 commented Aug 21, 2018

Copy link
Copy Markdown
Author

@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.

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.

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.

It depends which test(s) you're talking about. Can you be more specific about individual tests and platforms?

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!

@cjihrig

Copy link
Copy Markdown
Contributor

Which part of the code creates a race condition? It was my understanding that the calls like PathIsDirectory RemoveDirectory block until they are returned?

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.

@refack

Copy link
Copy Markdown
Contributor

Hello @Shivang44,

I feel fixing these inconsistencies is important.

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.

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.

IMHO implementing the complex solution you suggest should be left for downstream projects (e.g. node like mkdirp, or a userland module)

As for returning ENOTEMPY I think that is a breaking change. Since you are already making breaking changes, I suggest returning ENOTEMPY for both cases (if dirB is empty or not). I would go as far as to suggest to return ENOTEMPY even on POSIX, but this requires buy-in from the Collaborators.

@refack

refack commented Aug 21, 2018

Copy link
Copy Markdown
Contributor

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 dirB.

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'

@Shivang44

Shivang44 commented Sep 1, 2018

Copy link
Copy Markdown
Author

Due to the lack of interest as well as the above mentioned (potential) issues, I will be closing this PR. If other maintainers think that this change would be a good addition to libuv, I would be happy to reopen it work on it!

Thanks for your comments and help @cjihrig@refack

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Shivang44@cjihrig@refack