-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
SetFileInformationByHandle uses incorrect parameters on Win32 #95096
Comments
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsDescription
This breaks MSBuild, among other things, as seen here: dotnet/sdk#13808 Reproduction Stepsmount NFS
source codeConsole.WriteLine("Hello, dotnet " + Environment.Version);
var t = DateTime.Now.Subtract(TimeSpan.FromDays(1));
System.IO.File.SetLastWriteTime(@"T:\thefile.txt", t); output
Expected behaviorLast modification time should be set correctly Actual behaviorAn unhandled exception is thrown
Regression?It appears to be an old standing issue, present at least since 2019. Known WorkaroundsNone Configuration.NET 8.0.100, on Windows 10 21H2 x64. Other informationI found the following linked issues. It's been observed in
|
I took a quick look at the implementation: runtime/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs Lines 464 to 495 in e440ebc
Basically all .NET does is calling SetFileInformationByHandle sys-call provided by Windows OS. The sys-call doc mentions some file systems that are not supported: NFS is not listed, but my guess is that it's not supported. We should find out whether on purpose or by accident (a bug).
It would be great to find out which sys-call |
Here's a trace of
And can be interpreted as: If i do the same with the .NET sample:
And the information being set is:
So I could try to make a basic program doing |
Ok, i broke down the issue in the following C code. #include <stdio.h>
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
int main(int argc, char *argv[]){
HANDLE hFile;
OBJECT_ATTRIBUTES attribs;
UNICODE_STRING filePath;
RtlInitUnicodeString(&filePath, L"\\??\\T:\\thefile.txt");
InitializeObjectAttributes(&attribs, &filePath, OBJ_CASE_INSENSITIVE, 0, 0);
IO_STATUS_BLOCK stb, stb2;
NTSTATUS status = NtCreateFile(
&hFile,
FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
&attribs, &stb,
NULL, 0,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT,
NULL, 0);
if(!NT_SUCCESS(status)){
printf("fail open\n");
return EXIT_FAILURE;
}
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
FILE_BASIC_INFO info = {
.CreationTime = -1,
.ChangeTime = -1,
.LastAccessTime = 0,
//.LastAccessTime = -1 // gives 0xC000000D (STATUS_INVALID_PARAMETER)
//.LastAccessTime = {.HighPart = ft.dwHighDateTime, .LowPart = ft.dwLowDateTime }, // works
.LastWriteTime = {
.HighPart = ft.dwHighDateTime,
.LowPart = ft.dwLowDateTime
},
.FileAttributes = 0xF9
};
status = NtSetInformationFile(hFile, &stb2, &info, sizeof(info), FileBasicInformation);
CloseHandle(hFile);
if(!NT_SUCCESS(status)){
printf("fail set (0x%08X)\n", status);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
} |
Is there any reason why -1 is used instead of 0?
So i'd assume 0 would make more sense in this context (and perhaps the NFS implementation doesn't support tracking of subsequent operations on the same file handle?) |
This is actually a bug in the Dotnet Windows filesystem implementation, which has implications for NTFS too. These functions update only the specified component of the open file handle, leaving the other parameters to the default -1. Update: it looks like this behavior has existed from the very beginning (at least it was present in December 2014): https://github.com/dotnet/corefx/blob/fda256397e23e60a15a4f0639783d99bd55069b4/src/System.IO.FileSystem/src/Interop/Interop.Windows.cs#L308 The following C# POC can be used to perform a "shadow write", i.e. opening a file and writing something to it without updating the Last modification time of the file Console.WriteLine("Hello, dotnet " + Environment.Version);
using var fh = File.OpenHandle(@"D:\tmp\thefile.txt",
FileMode.OpenOrCreate,
FileAccess.ReadWrite,
FileShare.None,
FileOptions.None, 0);
// set last access time (and trigger the -1 LastWriteTime)
File.SetLastAccessTime(fh, DateTime.Now.Subtract(TimeSpan.FromDays(1)));
Console.WriteLine("before:" + File.GetLastWriteTime(fh));
var fs = new FileStream(fh, FileAccess.ReadWrite);
var length = fs.Length;
var wr = new StreamWriter(new FileStream(fh, FileAccess.ReadWrite));
wr.BaseStream.Seek(0, SeekOrigin.Begin);
for(int i=0; i<20; i++){
wr.WriteLine("now: " + DateTime.Now);
Thread.Sleep(1000);
}
Console.WriteLine("after: " + File.GetLastWriteTime(fh));
wr.Close();
fh.Close(); Output |
@smx-smx big thanks for digging so deep into the issue!
I suspect that it was an oversight (the docs for the sys-call and FILE_BASIC_INFO don't mention the meaning of these values) or a performance optimization (OS has less work to do on every file write). The perf optimization would be acceptable if the API was only internal or the name would imply that. But anyway it's a bug and it needs to get fixed. Would you like to send a PR with a fix?
FWIW in .NET Framework we were using a different sys-call: SetFileTime https://referencesource.microsoft.com/#mscorlib/system/io/file.cs,497 |
According to MS-FSCC, "a value of -1 indicates to the server that it MUST NOT change this attribute for all subsequent operations" This behavior is incorrect in the scope of .NET, since a call to File.SetLastAccessTime on a open file handle will cause all future writes to not update the last write tim. It also triggers STATUS_INVALID_PARAMETER on NFS, since the Windows driver doesn't seem to implement the -1 value (tracking of subsequent write operations) Fixes dotnet#95096
Description
System.IO.File.SetLastWriteTime
throws an exception on NFS, without setting the modification time.NFS does however supports setting the last modification time (the
touch
command from Cygwin works, for example)This breaks MSBuild, among other things, as seen here: dotnet/sdk#13808
Reproduction Steps
mount NFS
source code
output
Expected behavior
Last modification time should be set correctly
Actual behavior
An unhandled exception is thrown
Regression?
It appears to be an old standing issue, present at least since 2019.
Known Workarounds
None
Configuration
.NET 8.0.100, on Windows 10 21H2 x64.
Other information
I found the following linked issues. It's been observed in
mono
tooThe text was updated successfully, but these errors were encountered: