Skip to content

Conversation

@tmds
Copy link
Member

@tmds tmds commented Jun 13, 2022

On Windows, APIs throw DirectoryNotFoundException instead of
FileNotFoundException when the base components of a path don't exist.

SafeFileHandle, and FileSystemInfo have some specific code to detect
that case and do the same on Unix.

This change moves that mapping to Interop.IOErrors so it applies
to all users of GetExceptionForIoErrno.

…onForIoErrno.

On Windows, APIs throw DirectoryNotFoundException instead of
FileNotFoundException when the base components of a path don't exist.

SafeFileHandle, and FileSystemInfo have some specific code to detect
that case and to the same on Unix.

This change moves that mapping to Interop.IOErrors so it applies
to all users of GetExceptionForIoErrno.
@ghost ghost added area-System.IO community-contribution Indicates that the PR has been added by a community member labels Jun 13, 2022
@ghost
Copy link

ghost commented Jun 13, 2022

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

On Windows, APIs throw DirectoryNotFoundException instead of
FileNotFoundException when the base components of a path don't exist.

SafeFileHandle, and FileSystemInfo have some specific code to detect
that case and to the same on Unix.

This change moves that mapping to Interop.IOErrors so it applies
to all users of GetExceptionForIoErrno.

Author: tmds
Assignees: -
Labels:

area-System.IO

Milestone: -

@tmds
Copy link
Member Author

tmds commented Jun 13, 2022

/__w/1/s/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Stat.cs(65,37): error SYSLIB1053: 'LibraryImportAttribute.StringMarshalling=StringMarshalling.Utf8' has no equivalent in 'DllImportAtttribute' and will not be forwarded [/__w/1/s/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj]

@danmoseley @stephentoub I don't understand the error. What does this mean?

@tmds
Copy link
Member Author

tmds commented Jun 24, 2022

On Windows, APIs return different errors for the parent directory not being there (ERROR_PATH_NOT_FOUND), or the file itself (ERROR_FILE_NOT_FOUND). .NET throws DirectoryNotFoundException vs FileNotFoundException for these.

On Unix, ENOENT is used for both.
For Windows compatibility, on ENOENT .NET does a directory check for the parent to throw the same Exception type as Windows.

Currently those checks are implemented at different places.
This PR centralizes them so they apply to users of GetExceptionForIoErrno.
That will improve Windows compatibility because more locations will throw the same type as Windows.

To implement the exist check in Interop.GetExceptionForIoErrno I'm using Interop.Stat directly because I want to be clear that it doesn't end up calling GetExceptionForIoErrno in its implementation.

CI gives me an error I don't understand for using Interop.Stat in System.IO.Ports.csproj:

/__w/1/s/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Stat.cs(65,37): error SYSLIB1053: 'LibraryImportAttribute.StringMarshalling=StringMarshalling.Utf8' has no equivalent in 'DllImportAtttribute' and will not be forwarded [/__w/1/s/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj]

@dotnet/area-system-io @stephentoub ptal.

if (isDirectory && errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND)
errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND;
if (isDirectory && errorCode == Interop.Errors.ERROR_ACCESS_DENIED && ignoreAccessDenied)
if (ignoreAccessDenied && errorCode == Interop.Errors.ERROR_ACCESS_DENIED)
Copy link
Member Author

Choose a reason for hiding this comment

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

This change is unrelated. I can move it to a separate PR.

@tmds
Copy link
Member Author

tmds commented Jul 5, 2022

@dotnet/area-system-io ptal.

Example where this change changes applies:

DirectoryInfo di = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));

FileInfo fi = new FileInfo(Path.Combine(di.FullName, "file"));
fi.OpenWrite().Dispose();
fi.LastWriteTimeUtc = DateTime.UtcNow;

Directory.Delete(di.FullName, recursive: true);

fi.LastWriteTimeUtc = DateTime.UtcNow;

On Linux, the last statement currently throws FileNotFoundException. And with the change, it throws DirectoryNotFoundException as on Windows.

@tmds
Copy link
Member Author

tmds commented Jul 8, 2022

/__w/1/s/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Stat.cs(65,37): error SYSLIB1053: 'LibraryImportAttribute.StringMarshalling=StringMarshalling.Utf8' has no equivalent in 'DllImportAtttribute' and will not be forwarded [/__w/1/s/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj]

@dotnet/interop-contrib what does this error mean? And what can I do to fix it?

@jkoritzinsky
Copy link
Member

System.IO.Ports supports downlevel TFMs. On those TFMs, we try to have the generator generate an old-style DllImport. Some functionality cannot be translated automatically to the old system. I’d recommend that you use an #if NET7_0_OR_GREATER condition around the LibraryImport method and have an old DllImport style P/Invoke for downlevel TFMs.

@tmds
Copy link
Member Author

tmds commented Jul 29, 2022

CI fail is unrelated.

@dotnet/area-system-io ptal.

@tmds
Copy link
Member Author

tmds commented Aug 5, 2022

@adamsitnik @jeffhandley ptal.

@tmds tmds requested a review from adamsitnik August 10, 2022 13:40
Comment on lines -211 to -217
// Windows distinguishes between whether the directory or the file isn't found,
// and throws a different exception in these cases. We attempt to approximate that
// here; there is a race condition here, where something could change between
// when the error occurs and our checks, but it's the best we can do, and the
// worst case in such a race condition (which could occur if the file system is
// being manipulated concurrently with these checks) is that we throw a
// FileNotFoundException instead of DirectoryNotFoundException.
Copy link
Member

Choose a reason for hiding this comment

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

Is it worth keeping this comment inside GetExceptionForIoErrno?

Comment on lines +284 to +287
// macOS returns ENOTDIR when the path refers to a file and ends with '/'.
// Trim the separator so we get EEXIST instead.
ReadOnlySpan<char> path = PathInternal.TrimEndingDirectorySeparator(fullPath.AsSpan());
int result = Interop.Sys.MkDir(path, (int)unixCreateMode);
Copy link
Member

Choose a reason for hiding this comment

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

I assume there is no test that ensures we don't get DirectoryNotFoundException when the path ends with '/'? Could you please add it?


if (attributes == (FileAttributes)(-1))
FileSystemInfo.ThrowNotFound(fullPath);
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), fullPath);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), fullPath);
throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath);


if (mode == (UnixFileMode)(-1))
FileSystemInfo.ThrowNotFound(fullPath);
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), fullPath);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
throw Interop.GetExceptionForIoErrno(new Interop.ErrorInfo(Interop.Error.ENOENT), fullPath);
throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath);

@jozkee jozkee merged commit 68139cd into dotnet:main Aug 15, 2022
@directhex
Copy link
Contributor

This appears to have broken main, at least Android AOT builds:

/home/directhex/Projects/runtime/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs(53,70): error CS1739: The best overload for 'ThrowExceptionForIoErrno' does not have a parameter named 'isDirectory' [/home/directhex/Projects/runtime/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj]

directhex added a commit to directhex/runtime that referenced this pull request Aug 15, 2022
@BruceForstall
Copy link
Contributor

@jozkee This seems to have broken many, many builds. E.g., https://dev.azure.com/dnceng/public/_build/results?buildId=1944354&view=results

@radical
Copy link
Member

radical commented Aug 15, 2022

@jozkee
Copy link
Member

jozkee commented Aug 15, 2022

Sorry for that, I don't know why that was not caught by this PR's build.

akoeplinger pushed a commit that referenced this pull request Aug 15, 2022
@radical
Copy link
Member

radical commented Aug 15, 2022

Sorry for that, I don't know why that was not caught by this PR's build.

The breaking call site was added a few days ago. And the CI build here was 18 days ago. that's why it didn't get caught.

@danmoseley
Copy link
Member

It is because the validation ran 18 days ago. In the meantime this change went in, and collided. It happens, but for something touching common code like this it might be good to ensure validation is fresher.

commit 9fd407a
Author: Eric Erhardt eric.erhardt@microsoft.com
Date: Wed Aug 10 07:43:04 2022 -0500
Add Directory.CreateTempSubdirectory (#73408)

@ghost ghost locked as resolved and limited conversation to collaborators Sep 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.IO community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants