diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 0071bd275e..93adba9a82 100644 --- a/lib/std/child_process.zig +++ b/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 diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 9ff22a3df9..5accd7b404 100644 --- a/lib/std/fs/Dir.zig +++ b/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 diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 4371cc4768..20b3976ea6 100644 --- a/lib/std/fs/File.zig +++ b/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 { diff --git a/lib/std/os.zig b/lib/std/os.zig index d43d5ac55b..3d0d558d66 100644 --- a/lib/std/os.zig +++ b/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 => { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 2a19462a7c..ab62df65e7 100644 --- a/lib/std/os/windows.zig +++ b/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); diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index c2a9fa4f9f..7e0aaaafa0 100644 --- a/lib/std/zig/system.zig +++ b/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,