From fb96822f913b88260735e50c03a6a5750abb61f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 6 Nov 2026 16:29:12 +0900 Subject: [PATCH 1/2] Avoid filesystem dependencies in pathless Unix errno mapping Select parent-directory handling through a static-abstract interface and sealed policy classes so pathless callers do not retain filesystem APIs. Preserve existing exception mappings and path-aware behavior. This provides a 1.84% size saving for the size-optimized WASI NativeAOT Release Hello World: 823,214 to 808,084 bytes, saving 15,130 bytes. Validated WASI Release/Checked errno mappings against the original implementation, filesystem error paths, Sleep, Preinitialization, and Hello World. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9bc3dfee-1040-4648-bdad-86922a749613 --- .../src/Interop/Unix/Interop.IOErrors.cs | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs b/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs index 85284922454c94..5cab85ee44a735 100644 --- a/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs +++ b/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs @@ -104,6 +104,9 @@ internal static TSafeHandle CheckIo(TSafeHandle handle, string? pat return handle; } + internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo) => + GetExceptionForIoErrno(errorInfo, path: null, isDirError: false); + /// /// Gets an Exception to represent the supplied error info. /// @@ -111,7 +114,11 @@ internal static TSafeHandle CheckIo(TSafeHandle handle, string? pat /// The path with which this error is associated. This may be null. /// true if error is caused by a directory issue. /// - internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path = null, bool isDirError = false) + internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirError = false) => + GetExceptionForIoErrno(errorInfo, path, isDirError); + + private static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirError) + where TPathHandling : IPathHandling { // Translate the errno into a known set of exception types. For cases where multiple errnos map // to the same exception type, include an inner exception with the details. @@ -120,7 +127,7 @@ internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? pa case Error.ENOENT: // For Windows compatibility, throw DirectoryNotFoundException instead of FileNotFoundException // when the parent folder does not exist. - if (!isDirError && (path is null || ParentDirectoryExists(path))) + if (!isDirError && (path is null || TPathHandling.ParentDirectoryExists(path))) { return !string.IsNullOrEmpty(path) ? new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path) : @@ -170,13 +177,26 @@ internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? pa default: return GetIOException(errorInfo, path); + } + } + + private interface IPathHandling + { + static abstract bool ParentDirectoryExists(string fullPath); + } + + private sealed class NoPathHandling : IPathHandling + { + public static bool ParentDirectoryExists(string fullPath) => true; + } - static bool ParentDirectoryExists(string fullPath) - { - string? parentPath = Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(fullPath)); + private sealed class PathHandling : IPathHandling + { + public static bool ParentDirectoryExists(string fullPath) + { + string? parentPath = Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(fullPath)); - return Directory.Exists(parentPath); - } + return Directory.Exists(parentPath); } } From a5a84d353fd569cf192e43b4fa2027ab7107c6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Sat, 7 Nov 2026 14:05:58 +0900 Subject: [PATCH 2/2] Separate path-aware errno handling without generics Handle the ENOENT parent-directory check in the path-aware overload and share exception mapping through GetExceptionForIoErrnoCore. Document the separation needed for better trimming of pathless callers. Validated WASI Release and Checked errno mappings, filesystem checks, Sleep, and Preinitialization. Matching-toolchain stripped Hello World saves 15,635 bytes (1.83%) versus the original mapper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a33cda55-cbd2-452e-9a41-5aa6a1c97aca --- .../src/Interop/Unix/Interop.IOErrors.cs | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs b/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs index 5cab85ee44a735..e14b275e1c4baf 100644 --- a/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs +++ b/src/libraries/Common/src/Interop/Unix/Interop.IOErrors.cs @@ -105,7 +105,7 @@ internal static TSafeHandle CheckIo(TSafeHandle handle, string? pat } internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo) => - GetExceptionForIoErrno(errorInfo, path: null, isDirError: false); + GetExceptionForIoErrnoCore(errorInfo, path: null, isDirError: false); /// /// Gets an Exception to represent the supplied error info. @@ -114,20 +114,29 @@ internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo) => /// The path with which this error is associated. This may be null. /// true if error is caused by a directory issue. /// - internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirError = false) => - GetExceptionForIoErrno(errorInfo, path, isDirError); + internal static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirError = false) + { + // Keep path/directory operations separate from GetExceptionForIoErrnoCore to allow better trimming: + // pathless callers do not pull in Path and Directory APIs. + // For Windows compatibility, throw DirectoryNotFoundException instead of FileNotFoundException + // when the parent folder does not exist. + if (errorInfo.Error == Error.ENOENT && !isDirError && path is not null) + { + string? parentPath = Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(path)); + isDirError = !Directory.Exists(parentPath); + } + + return GetExceptionForIoErrnoCore(errorInfo, path, isDirError); + } - private static Exception GetExceptionForIoErrno(ErrorInfo errorInfo, string? path, bool isDirError) - where TPathHandling : IPathHandling + private static Exception GetExceptionForIoErrnoCore(ErrorInfo errorInfo, string? path, bool isDirError) { // Translate the errno into a known set of exception types. For cases where multiple errnos map // to the same exception type, include an inner exception with the details. switch (errorInfo.Error) { case Error.ENOENT: - // For Windows compatibility, throw DirectoryNotFoundException instead of FileNotFoundException - // when the parent folder does not exist. - if (!isDirError && (path is null || TPathHandling.ParentDirectoryExists(path))) + if (!isDirError) { return !string.IsNullOrEmpty(path) ? new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path) : @@ -180,26 +189,6 @@ private static Exception GetExceptionForIoErrno(ErrorInfo errorIn } } - private interface IPathHandling - { - static abstract bool ParentDirectoryExists(string fullPath); - } - - private sealed class NoPathHandling : IPathHandling - { - public static bool ParentDirectoryExists(string fullPath) => true; - } - - private sealed class PathHandling : IPathHandling - { - public static bool ParentDirectoryExists(string fullPath) - { - string? parentPath = Path.GetDirectoryName(Path.TrimEndingDirectorySeparator(fullPath)); - - return Directory.Exists(parentPath); - } - } - internal static Exception GetIOException(Interop.ErrorInfo errorInfo, string? path = null) { string msg = errorInfo.GetErrorMessage();