Commit 07c1dd3d1d

Andrew Kelley <andrew@ziglang.org>
2024-02-13 22:04:00
std.os.windows.OpenFile: add missing error
Encountered in a recent CI run on an aarch64-windows dev kit. Pretty sure I disabled the virus scanner but it looks like it turned itself back on with a Windows Update. Rather than marking the new error code as unreachable in the places where it is unexpected, this commit makes it return `error.Unexpected`.
1 parent a23ab33
lib/std/fs/Dir.zig
@@ -1179,6 +1179,8 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOp
     };
 }
 
+pub const RealPathError = posix.RealPathError;
+
 ///  This function returns the canonicalized absolute pathname of
 /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
 /// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
@@ -1186,7 +1188,7 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOp
 /// This function is not universally supported by all platforms.
 /// Currently supported hosts are: Linux, macOS, and Windows.
 /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
-pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
+pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 {
     if (builtin.os.tag == .wasi) {
         @compileError("realpath is not available on WASI");
     }
@@ -1200,7 +1202,7 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
 
 /// Same as `Dir.realpath` except `pathname` is null-terminated.
 /// See also `Dir.realpath`, `realpathZ`.
-pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
+pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 {
     if (builtin.os.tag == .windows) {
         const pathname_w = try posix.windows.cStrToPrefixedFileW(self.fd, pathname);
         return self.realpathW(pathname_w.span(), out_buffer);
@@ -1219,7 +1221,9 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
     };
 
     const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
-        error.FileLocksNotSupported => unreachable,
+        error.FileLocksNotSupported => return error.Unexpected,
+        error.FileBusy => return error.Unexpected,
+        error.WouldBlock => return error.Unexpected,
         else => |e| return e,
     };
     defer posix.close(fd);
@@ -1244,7 +1248,7 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
 
 /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
 /// See also `Dir.realpath`, `realpathW`.
-pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
+pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
     const w = std.os.windows;
 
     const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
@@ -1265,27 +1269,31 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
     };
     defer w.CloseHandle(h_file);
 
-    // Use of MAX_PATH_BYTES here is valid as the realpath function does not
-    // have a variant that takes an arbitrary-size buffer.
-    // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
-    // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
-    // paths. musl supports passing NULL but restricts the output to PATH_MAX
-    // anyway.
-    var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
-    const out_path = try posix.getFdPath(h_file, &buffer);
-
-    if (out_path.len > out_buffer.len) {
+    var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
+    const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
+    var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+    const end_index = std.unicode.utf16leToUtf8(&big_out_buf, wide_slice) catch |e| switch (e) {
+        // TODO: Windows file paths can be arbitrary arrays of u16 values and
+        // must not fail with InvalidUtf8.
+        error.DanglingSurrogateHalf,
+        error.ExpectedSecondSurrogateHalf,
+        error.UnexpectedSecondSurrogateHalf,
+        error.CodepointTooLarge,
+        error.Utf8CannotEncodeSurrogateHalf,
+        => return error.InvalidUtf8,
+    };
+    if (end_index > out_buffer.len)
         return error.NameTooLong;
-    }
-
-    const result = out_buffer[0..out_path.len];
-    @memcpy(result, out_path);
+    const result = out_buffer[0..end_index];
+    @memcpy(result, big_out_buf[0..end_index]);
     return result;
 }
 
+pub const RealPathAllocError = RealPathError || Allocator.Error;
+
 /// Same as `Dir.realpath` except caller must free the returned memory.
 /// See also `Dir.realpath`.
-pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) ![]u8 {
+pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 {
     // Use of MAX_PATH_BYTES here is valid as the realpath function does not
     // have a variant that takes an arbitrary-size buffer.
     // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
lib/std/fs/File.zig
@@ -48,6 +48,12 @@ pub const OpenError = error{
     Unexpected,
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
+    /// On Windows, antivirus software is enabled by default. It can be
+    /// disabled, but Windows Update sometimes ignores the user's preference
+    /// and re-enables it. When enabled, antivirus software on Windows
+    /// intercepts file system operations and makes them significantly slower
+    /// in addition to possibly failing with this error code.
+    AntivirusInterference,
 } || posix.OpenError || posix.FlockError;
 
 pub const OpenMode = enum {
lib/std/os/windows.zig
@@ -41,6 +41,7 @@ pub const OpenError = error{
     NameTooLong,
     WouldBlock,
     NetworkNotFound,
+    AntivirusInterference,
 };
 
 pub const OpenFileOptions = struct {
@@ -145,6 +146,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
                 std.time.sleep(std.time.ns_per_ms);
                 continue;
             },
+            .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
             else => return unexpectedStatus(rc),
         }
     }
@@ -637,9 +639,10 @@ pub fn CreateSymbolicLink(
         .filter = if (is_directory) .dir_only else .file_only,
     }) catch |err| switch (err) {
         error.IsDir => return error.PathAlreadyExists,
-        error.NotDir => unreachable,
-        error.WouldBlock => unreachable,
-        error.PipeBusy => unreachable,
+        error.NotDir => return error.Unexpected,
+        error.WouldBlock => return error.Unexpected,
+        error.PipeBusy => return error.Unexpected,
+        error.AntivirusInterference => return error.Unexpected,
         else => |e| return e,
     };
     defer CloseHandle(symlink_handle);
@@ -1158,14 +1161,15 @@ pub fn GetFinalPathNameByHandle(
                 .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
                 .creation = FILE_OPEN,
             }) catch |err| switch (err) {
-                error.IsDir => unreachable,
-                error.NotDir => unreachable,
-                error.NoDevice => unreachable,
-                error.AccessDenied => unreachable,
-                error.PipeBusy => unreachable,
-                error.PathAlreadyExists => unreachable,
-                error.WouldBlock => unreachable,
-                error.NetworkNotFound => unreachable,
+                error.IsDir => return error.Unexpected,
+                error.NotDir => return error.Unexpected,
+                error.NoDevice => return error.Unexpected,
+                error.AccessDenied => return error.Unexpected,
+                error.PipeBusy => return error.Unexpected,
+                error.PathAlreadyExists => return error.Unexpected,
+                error.WouldBlock => return error.Unexpected,
+                error.NetworkNotFound => return error.Unexpected,
+                error.AntivirusInterference => return error.Unexpected,
                 else => |e| return e,
             };
             defer CloseHandle(mgmt_handle);
lib/std/zig/system.zig
@@ -766,6 +766,7 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
         error.PipeBusy => unreachable, // Windows-only
         error.SharingViolation => unreachable, // Windows-only
         error.NetworkNotFound => unreachable, // Windows-only
+        error.AntivirusInterference => unreachable, // Windows-only
         error.FileLocksNotSupported => unreachable, // No lock requested.
         error.NoSpaceLeft => unreachable, // read-only
         error.PathAlreadyExists => unreachable, // read-only
@@ -1003,6 +1004,7 @@ fn detectAbiAndDynamicLinker(
                 error.FileLocksNotSupported => unreachable,
                 error.WouldBlock => unreachable,
                 error.FileBusy => unreachable, // opened without write permissions
+                error.AntivirusInterference => unreachable, // Windows-only error
 
                 error.IsDir,
                 error.NotDir,
lib/std/child_process.zig
@@ -668,13 +668,14 @@ pub const ChildProcess = struct {
                 .sa = &saAttr,
                 .creation = windows.OPEN_EXISTING,
             }) catch |err| switch (err) {
-                error.PathAlreadyExists => unreachable, // not possible for "NUL"
-                error.PipeBusy => unreachable, // not possible for "NUL"
-                error.FileNotFound => unreachable, // not possible for "NUL"
-                error.AccessDenied => unreachable, // not possible for "NUL"
-                error.NameTooLong => unreachable, // not possible for "NUL"
-                error.WouldBlock => unreachable, // not possible for "NUL"
-                error.NetworkNotFound => unreachable, // not possible for "NUL"
+                error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL"
+                error.PipeBusy => return error.Unexpected, // not possible for "NUL"
+                error.FileNotFound => return error.Unexpected, // not possible for "NUL"
+                error.AccessDenied => return error.Unexpected, // not possible for "NUL"
+                error.NameTooLong => return error.Unexpected, // not possible for "NUL"
+                error.WouldBlock => return error.Unexpected, // not possible for "NUL"
+                error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
+                error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
                 else => |e| return e,
             }
         else
lib/std/os.zig
@@ -2602,6 +2602,12 @@ pub const RenameError = error{
     PipeBusy,
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
+    /// On Windows, antivirus software is enabled by default. It can be
+    /// disabled, but Windows Update sometimes ignores the user's preference
+    /// and re-enables it. When enabled, antivirus software on Windows
+    /// intercepts file system operations and makes them significantly slower
+    /// in addition to possibly failing with this error code.
+    AntivirusInterference,
 } || UnexpectedError;
 
 /// Change the name or location of a file.
@@ -2927,9 +2933,10 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!v
         .creation = windows.FILE_CREATE,
         .filter = .dir_only,
     }) catch |err| switch (err) {
-        error.IsDir => unreachable,
-        error.PipeBusy => unreachable,
-        error.WouldBlock => unreachable,
+        error.IsDir => return error.Unexpected,
+        error.PipeBusy => return error.Unexpected,
+        error.WouldBlock => return error.Unexpected,
+        error.AntivirusInterference => return error.Unexpected,
         else => |e| return e,
     };
     windows.CloseHandle(sub_dir_handle);
@@ -3006,9 +3013,10 @@ pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
         .creation = windows.FILE_CREATE,
         .filter = .dir_only,
     }) catch |err| switch (err) {
-        error.IsDir => unreachable,
-        error.PipeBusy => unreachable,
-        error.WouldBlock => unreachable,
+        error.IsDir => return error.Unexpected,
+        error.PipeBusy => return error.Unexpected,
+        error.WouldBlock => return error.Unexpected,
+        error.AntivirusInterference => return error.Unexpected,
         else => |e| return e,
     };
     windows.CloseHandle(sub_dir_handle);
@@ -5347,6 +5355,13 @@ pub const RealPathError = error{
     NetworkNotFound,
 
     PathAlreadyExists,
+
+    /// On Windows, antivirus software is enabled by default. It can be
+    /// disabled, but Windows Update sometimes ignores the user's preference
+    /// and re-enables it. When enabled, antivirus software on Windows
+    /// intercepts file system operations and makes them significantly slower
+    /// in addition to possibly failing with this error code.
+    AntivirusInterference,
 } || UnexpectedError;
 
 /// Return the canonicalized absolute pathname.
@@ -5441,15 +5456,17 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
 
 pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
     return switch (os.tag) {
-        // zig fmt: off
         .windows,
-        .macos, .ios, .watchos, .tvos,
+        .macos,
+        .ios,
+        .watchos,
+        .tvos,
         .linux,
         .solaris,
         .illumos,
         .freebsd,
         => true,
-        // zig fmt: on
+
         .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
         .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
         else => false,
@@ -5469,8 +5486,10 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
             var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
             const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
 
-            // Trust that Windows gives us valid UTF-16LE.
-            const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable;
+            // TODO: Windows file paths can be arbitrary arrays of u16 values
+            // and must not fail with InvalidUtf8.
+            const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch
+                return error.InvalidUtf8;
             return out_buffer[0..end_index];
         },
         .macos, .ios, .watchos, .tvos => {