Skip to content
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(ext/fs): add ctime to Deno.stats and use it in node compat layer #24801

Merged
merged 40 commits into from
Nov 13, 2024

Conversation

lczerniawski
Copy link
Contributor

This PR fixes #24453, by introducing a ctime (using ctime for UNIX and ChangeTime for Windows) to Deno.stats.
The screenshots below show a working example of the code in action.
ctime_unix
fixed_ctime_windows
vs SetMacu tool outputs
fixed_ctime_windows_setmacu

Ok(unix_time_msec as u64)
}

const WINDOWS_TICK: i64 = 10_000; // 100-nanosecond intervals in a millisecond
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that this is a good place for consts, but had no idea where to put them, so I will be happy for some suggestions

ext/fs/std_fs.rs Outdated

let file_info = {
let mut file_info = std::mem::MaybeUninit::<FILE_BASIC_INFO>::zeroed();
if GetFileInformationByHandleEx(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before creating this method, I tried to take the value for ChangeTime from the NtQueryInformationFile used below, but it returns 0.

Copy link
Member

Choose a reason for hiding this comment

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

Are you sure about this? Libuv (which is what Node uses under the hood) gets this information from the NtQueryInformationFile syscall: https://github.com/libuv/libuv/blob/4e310d0f90af29e699e2dedad5fa0dcee181a7cc/src/win/fs.c#L1751-L1793

I think it's quite unfortunate that we would have to do another syscall just to obtain the ctime value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm pretty sure that was the case, but let me check again to make sure.

Copy link
Contributor Author

@lczerniawski lczerniawski Aug 1, 2024

Choose a reason for hiding this comment

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

So I did some digging, and here are my findings, it would be great if someone could check it on their machine, too.

In my case, at least the NtQueryInformationFile doesn't work completely!
I checked the status code for this call. It always returns some big (which might not matter) negative number meaning that it failed to get the data, but on the other hand std::io::Error::last_os_error() returns an object with a status code of 0 and information that it was successful.

So I found that we close a file handle in code before actually using it in the query_file_information method.
image
but even moving it below the call doesn't change anything at all and NtQueryInformationFile still fails the same way as before.

Now I tried to use the NtQueryInformationFile method in just plain rust program and even tried to get only basic info instead of the full one, but it also failed. I tried that on two different machines with Windows 11 64bit and ARM on board, and it doesn't work in both.

If someone could check on their end whether this is only my machine or if this doesn't seem to work at all, that would be great.

Copy link
Member

Choose a reason for hiding this comment

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

NtQueryInformationFile doesn't set last_os_error() upon failure. Instead, it returns an NTSTATUS code and you have to convert it to a win32 error (using RtlNtStatusToDosError) manually to know what the error was.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good to know, thanks, I assumed this cause that's how handling is done in deno code right now. I will have a look at this then, what about the CloseHandle placement, shouldn't it be after each call that uses the handle?

Copy link
Member

Choose a reason for hiding this comment

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

It looks like the call to RtlNtStatusToDosError is entirely missing from the current code base, which means that when NtQueryInformationFile fails we are not currently reporting an appropriate error code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you so much for your help! I'm not an expert on a Windows API topic (it is my first time touching it), but it was a lot of fun to learn about it! 😄. I managed to fix the NtQueryInformationFile call and get all the data from a single call, and I have added some proper error handling. Let me know if there is something else that can be improved.

@@ -862,10 +883,11 @@ fn stat_extra(
}

let result = get_dev(file_handle);
CloseHandle(file_handle);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one needs to be moved after we use the handle in NtQueryInformationFile otherwise ERROR_NOACCESS(998) is returned

let status = NtQueryInformationFile(
handle as _,
std::ptr::null_mut(),
io_status_block.as_mut_ptr(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With null ptr it will crash resulting in ERROR_NOACCESS (998) code

// since struct is populated with other data anyway.
/// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile#remarks

if converted_status != ERROR_MORE_DATA {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

According to Microsoft docs (https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile#remarks) it is fine to ignore it as long as we don't care about full file name "If NtQueryInformationFile fails because of a buffer overflow, drivers that implement FileNameInformation should return as many WCHAR characters of the file name as will fit in the buffer and specify the full length that is required in the FileNameLength parameter of the FILE_NAME_INFORMATION structure.". To make it work without an error, we would have to query the sys call again with size buffer of struct + length of the file name, as the FILE_ALL_INFORMATION has variable size in FILE_NAME_INFORMATION struct (https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_name_information)

@lczerniawski lczerniawski changed the title fix(node compat): Add ctime to Doen.stats and use it in node compact layer fix(node compat): Add ctime to Deno.stats and use it in node compact layer Aug 2, 2024
ino: unix ? response.ino : null,
mode: unix ? response.mode : null,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To explain why I removed that, I don't think this is true anymore; after a fix in this PR which now results in `NtQueryInformationFile' working correctly, we indeed set the mode in std_fs.rs lines 880/883/891/894.
image

Copy link
Member

Choose a reason for hiding this comment

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

This part looks fairly independent of the main change of this PR. Can you split this to another PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like the whole .js file change? Yeah, I can do that if you think it would be better. For me, it is more of a whole as by changing the underlying Rust structure, we should also update the JS API, but if you wish otherwise, sure, I can :)

Copy link
Member

Choose a reason for hiding this comment

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

Like the whole .js file change?

I meant only split mode value change. I think the change of mode value can be done independently from ctime addition, or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, I had to double-check, but as I suspected I done it cause the tests start to fail
image
https://github.com/denoland/deno/actions/runs/11594876322/job/32282184345?pr=24801

It assumes that for Windows mode should be empty, but after my fix to NtQueryInformationFile call, it is no longer true (and the whole code for setting mode was already there I didn't change that in any way).

So it is either changing the tests, but then tests will accommodate incorrect behavior or changing what we expose in API, honestly I can do both, but for me personally it makes more sense to just do it once and fix as it should be, but of course please let me know how you view this thing after my explanation, for now I left the code with rollbacked version of changes that fails the tests

@kt3k kt3k changed the title fix(node compat): Add ctime to Deno.stats and use it in node compact layer feat(ext/fs): add ctime to Deno.stats and use it in node compat layer Nov 1, 2024
@kt3k
Copy link
Member

kt3k commented Nov 1, 2024

Looks like FsInfo.mode is supposed to be existing on windows since #24434, but actually isn't (and this PR seems fixing it by moving CloseHandle call). I'll rollback this branch to the earlier state. Sorry for the confusion! @lczerniawski

@lczerniawski
Copy link
Contributor Author

Looks like FsInfo.mode is supposed to be existing on windows since #24434, but actually isn't (and this PR seems fixing it by moving CloseHandle call). I'll rollback this branch to the earlier state. Sorry for the confusion! @lczerniawski

No problem! That's what I meant, but I guess I didn't express myself particularly well 😄

Copy link
Member

@kt3k kt3k left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for your work!

Because this includes a new feature to Deno API, we are going to land this for Deno 2.1. The merge window for 2.1 is planned to be opened next week.

@kt3k kt3k merged commit 7becd83 into denoland:main Nov 13, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ctime value differs from node in windows deno
3 participants