diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 425e220b1c..85ab6b77d4 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -940,6 +940,7 @@ pub const RenameError = error{ /// Attempted to replace a nonempty directory. DirNotEmpty, PermissionDenied, + /// The file attempted to be moved or replaced is a running executable. FileBusy, DiskQuota, IsDir, @@ -952,7 +953,6 @@ pub const RenameError = error{ ReadOnlyFileSystem, CrossDevice, NoDevice, - SharingViolation, PipeBusy, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, @@ -1167,6 +1167,8 @@ pub const ReadLinkError = error{ /// intercepts file system operations and makes them significantly slower /// in addition to possibly failing with this error code. AntivirusInterference, + /// File attempted to be opened is a running executable. + FileBusy, } || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Obtain target of a symbolic link. @@ -1791,6 +1793,8 @@ pub const CreateFileAtomicError = error{ NotDir, WouldBlock, ReadOnlyFileSystem, + /// The file attempted to be created is a running executable. + FileBusy, } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; /// Create an unnamed ephemeral file that can eventually be atomically diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 6323a39454..e537755a33 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -249,7 +249,6 @@ pub const CreateFlags = struct { }; pub const OpenError = error{ - SharingViolation, PipeBusy, NoDevice, /// On Windows, `\\server` or `\\server\share` was not found. @@ -757,7 +756,7 @@ pub const RealPathError = error{ NoSpaceLeft, FileSystem, DeviceBusy, - SharingViolation, + FileBusy, PipeBusy, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 38f6199f46..01bcacbc29 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3635,7 +3635,7 @@ fn dirCreateFileWindows( // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -3648,7 +3648,7 @@ fn dirCreateFileWindows( // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -3668,10 +3668,10 @@ fn dirCreateFileWindows( .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), - .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), - .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), - else => |err| return syscall.unexpectedNtstatus(err), + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + else => |status| return syscall.unexpectedNtstatus(status), }; errdefer windows.CloseHandle(handle); @@ -3819,7 +3819,6 @@ fn dirCreateFileAtomic( error.DiskQuota, error.PathAlreadyExists, error.LinkQuotaExceeded, - error.SharingViolation, error.PipeBusy, error.FileTooBig, error.DeviceBusy, @@ -3889,11 +3888,9 @@ fn dirCreateFileAtomic( error.DiskQuota, error.PathAlreadyExists, error.LinkQuotaExceeded, - error.SharingViolation, error.PipeBusy, error.FileTooBig, error.FileLocksUnsupported, - error.FileBusy, error.DeviceBusy, => return error.Unexpected, @@ -3926,7 +3923,6 @@ fn atomicFileInit( error.PathAlreadyExists => continue, error.DeviceBusy => continue, error.FileBusy => continue, - error.SharingViolation => continue, error.IsDir => return error.Unexpected, // No path components. error.FileTooBig => return error.Unexpected, // Creating, not opening. @@ -4236,7 +4232,7 @@ pub fn dirOpenFileWtf16( // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -4258,7 +4254,7 @@ pub fn dirOpenFileWtf16( // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -6353,8 +6349,7 @@ fn dirSymLinkWindows( // Target path does not use sliceToPrefixedFileW because certain paths // are handled differently when creating a symlink than they would be - // when converting to an NT namespaced path. CreateSymbolicLink in - // symLinkW will handle the necessary conversion. + // when converting to an NT namespaced path. var target_path_w: w.PathSpace = undefined; target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); target_path_w.data[target_path_w.len] = 0; @@ -6465,7 +6460,7 @@ fn dirSymLinkWindows( @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const rc = w.DeviceIoControl(symlink_handle, w.FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); + const rc = w.DeviceIoControl(symlink_handle, .SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); switch (rc) { .SUCCESS => {}, .PRIVILEGE_NOT_HELD => return error.PermissionDenied, @@ -6572,44 +6567,189 @@ fn dirSymLinkPosix( } } -const dirReadLink = switch (native_os) { - .windows => dirReadLinkWindows, - .wasi => dirReadLinkWasi, - else => dirReadLinkPosix, -}; - -fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { +fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const w = windows; + switch (native_os) { + .windows => return dirReadLinkWindows(dir, sub_path, buffer), + .wasi => return dirReadLinkWasi(dir, sub_path, buffer), + else => return dirReadLinkPosix(dir, sub_path, buffer), + } +} +fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { + // This gets used once for `sub_path` and then reused again temporarily + // before converting back to `buffer`. var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w = sub_path_w_buf.span(); + const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w.ptr), + }; + const attr: windows.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = .{ + .INHERIT = false, + }, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var result_handle: windows.HANDLE = undefined; - const syscall: Syscall = try .start(); - const result_w = while (true) { - if (w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data)) |res| { + // There are multiple kernel bugs being worked around with retries. + const max_attempts = 13; + var attempt: u5 = 0; + + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateFile( + &result_handle, + .{ + .SPECIFIC = .{ .FILE = .{ + .READ_ATTRIBUTES = true, + } }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + &attr, + &io_status_block, + null, + .{ .NORMAL = true }, + .VALID_FLAGS, + .OPEN, + .{ + .DIRECTORY_FILE = false, + .NON_DIRECTORY_FILE = false, + .IO = .ASYNCHRONOUS, + .OPEN_REPARSE_POINT = true, + }, + null, + 0, + )) { + .SUCCESS => { syscall.finish(); - break res; - } else |err| switch (err) { - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return syscall.fail(e), - } + break; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .SHARING_VIOLATION => { + // This occurs if the file attempting to be opened is a running + // executable. However, there's a kernel bug: the error may be + // incorrectly returned for an indeterminate amount of time + // after an executable file is closed. Here we work around the + // kernel bug with retry attempts. + syscall.finish(); + if (max_attempts - attempt == 0) return error.FileBusy; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + attempt += 1; + syscall = try .start(); + continue; + }, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. Here, we simulate the kernel bug being + // fixed by sleeping and retrying until the error goes away. + syscall.finish(); + if (max_attempts - attempt == 0) return error.FileBusy; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + attempt += 1; + syscall = try .start(); + continue; + }, + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found + .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.FileNotFound), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_BUSY => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.FileNotFound), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + else => |status| return syscall.unexpectedNtstatus(status), + }; + defer windows.CloseHandle(result_handle); + + var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; + + syscall = try .start(); + while (true) switch (windows.ntdll.NtFsControlFile( + result_handle, + null, // event + null, // APC routine + null, // APC context + &io_status_block, + .GET_REPARSE_POINT, + null, // input buffer + 0, // input buffer length + &reparse_buf, + reparse_buf.len, + )) { + .SUCCESS => { + syscall.finish(); + break; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .NOT_A_REPARSE_POINT => return syscall.fail(error.NotLink), + else => |status| return syscall.unexpectedNtstatus(status), }; + const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf)); + const IoReparseTagInt = @typeInfo(windows.IO_REPARSE_TAG).@"struct".backing_integer.?; + const result_w = switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) { + @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.SYMLINK)) => r: { + const buf: *const windows.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + const is_relative = buf.Flags & windows.SYMLINK_FLAG_RELATIVE != 0; + break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w_buf.data); + }, + @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.MOUNT_POINT)) => r: { + const buf: *const windows.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w_buf.data); + }, + else => return error.UnsupportedReparsePointType, + }; const len = std.unicode.calcWtf8Len(result_w); if (len > buffer.len) return error.NameTooLong; return std.unicode.wtf16LeToWtf8(buffer, result_w); } -fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { - if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer); +fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { + path: { + if (is_relative) break :path; + return windows.ntToWin32Namespace(path, out_buffer) catch |err| switch (err) { + error.NameTooLong => |e| return e, + error.NotNtPath => break :path, + }; + } + if (out_buffer.len < path.len) return error.NameTooLong; + const dest = out_buffer[0..path.len]; + @memcpy(dest, path); + return dest; +} - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; +fn dirReadLinkWasi(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { + if (builtin.link_libc) return dirReadLinkPosix(dir, sub_path, buffer); var n: usize = undefined; const syscall: Syscall = try .start(); @@ -6644,10 +6784,7 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer } } -fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - +fn dirReadLinkPosix(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { var sub_path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer); @@ -8709,45 +8846,41 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); return Io.Dir.realPathFileAbsolute(ioBasic(t), symlink_path, out_buffer) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only + error.FileBusy => unreachable, // Windows-only else => |e| return e, }; }, .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { error.UnsupportedReparsePointType => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only + error.FileBusy => unreachable, // Windows-only else => |e| return e, }, .illumos => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { error.UnsupportedReparsePointType => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only + error.FileBusy => unreachable, // Windows-only else => |e| return e, }, .freebsd, .dragonfly => { var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; var out_len: usize = out_buffer.len; const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { - .SUCCESS => { - syscall.finish(); - return out_len - 1; // discard terminating NUL - }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .FAULT => |err| return errnoBug(err), - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + while (true) switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { + .SUCCESS => { + syscall.finish(); + return out_len - 1; // discard terminating NUL + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .PERM => return syscall.fail(error.PermissionDenied), + .NOMEM => return syscall.fail(error.SystemResources), + .FAULT => |err| return syscall.errnoBug(err), + .NOENT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; }, .netbsd => { var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; @@ -8763,16 +8896,11 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .FAULT => |err| return errnoBug(err), - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), - } - }, + .PERM => return syscall.fail(error.PermissionDenied), + .NOMEM => return syscall.fail(error.SystemResources), + .FAULT => |err| return syscall.errnoBug(err), + .NOENT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), } } }, diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 0c81d0012c..b26883778d 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -335,7 +335,6 @@ const Module = struct { error.NoSpaceLeft, error.DeviceBusy, error.NoDevice, - error.SharingViolation, error.PathAlreadyExists, error.PipeBusy, error.NetworkNotFound, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 262eaef049..6f4e80f599 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1135,19 +1135,7 @@ pub const CTL_CODE = packed struct(ULONG) { _, }; -}; -pub const IOCTL = struct { - pub const KSEC = struct { - pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; - }; - pub const MOUNTMGR = struct { - pub const QUERY_POINTS: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; - pub const QUERY_DOS_VOLUME_PATH: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 12, .Method = .BUFFERED, .Access = .ANY }; - }; -}; - -pub const FSCTL = struct { pub const SET_REPARSE_POINT: CTL_CODE = .{ .DeviceType = .FILE_SYSTEM, .Function = 41, .Method = .BUFFERED, .Access = .SPECIAL }; pub const GET_REPARSE_POINT: CTL_CODE = .{ .DeviceType = .FILE_SYSTEM, .Function = 42, .Method = .BUFFERED, .Access = .ANY }; @@ -1177,6 +1165,16 @@ pub const FSCTL = struct { }; }; +pub const IOCTL = struct { + pub const KSEC = struct { + pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; + }; + pub const MOUNTMGR = struct { + pub const QUERY_POINTS: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; + pub const QUERY_DOS_VOLUME_PATH: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 12, .Method = .BUFFERED, .Access = .ANY }; + }; +}; + pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024; pub const IO_REPARSE_TAG = packed struct(ULONG) { @@ -2908,205 +2906,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const CreateSymbolicLinkError = error{ - AccessDenied, - PathAlreadyExists, - FileNotFound, - NameTooLong, - NoDevice, - NetworkNotFound, - BadPathName, - Unexpected, -}; - -/// Needs either: -/// - `SeCreateSymbolicLinkPrivilege` privilege -/// or -/// - Developer mode on Windows 10 -/// otherwise fails with `error.AccessDenied`. In which case `sym_link_path` may still -/// be created on the file system but will lack reparse processing data applied to it. -pub fn CreateSymbolicLink( - dir: ?HANDLE, - sym_link_path: []const u16, - target_path: [:0]const u16, - is_directory: bool, -) CreateSymbolicLinkError!void { - const SYMLINK_DATA = extern struct { - ReparseTag: IO_REPARSE_TAG, - ReparseDataLength: USHORT, - Reserved: USHORT, - SubstituteNameOffset: USHORT, - SubstituteNameLength: USHORT, - PrintNameOffset: USHORT, - PrintNameLength: USHORT, - Flags: ULONG, - }; - - const symlink_handle = OpenFile(sym_link_path, .{ - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .WRITE = true, .READ = true }, - }, - .dir = dir, - .creation = .CREATE, - .filter = if (is_directory) .dir_only else .non_directory_only, - }) catch |err| switch (err) { - error.IsDir => return error.PathAlreadyExists, - error.NotDir => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.NoDevice => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - else => |e| return e, - }; - defer CloseHandle(symlink_handle); - - // Relevant portions of the documentation: - // > Relative links are specified using the following conventions: - // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". - // > - Current working directory–relative—for example, if the current working directory is - // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". - // > Note: If you specify a current working directory–relative link, it is created as an absolute - // > link, due to the way the current working directory is processed based on the user and the thread. - // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw - var is_target_absolute = false; - const final_target_path = target_path: { - if (hasCommonNtPrefix(u16, target_path)) { - // Already an NT path, no need to do anything to it - break :target_path target_path; - } else { - switch (std.fs.path.getWin32PathType(u16, target_path)) { - // Rooted paths need to avoid getting put through wToPrefixedFileW - // (and they are treated as relative in this context) - // Note: It seems that rooted paths in symbolic links are relative to - // the drive that the symbolic exists on, not to the CWD's drive. - // So, if the symlink is on C:\ and the CWD is on D:\, - // it will still resolve the path relative to the root of - // the C:\ drive. - .rooted => break :target_path target_path, - // Keep relative paths relative, but anything else needs to get NT-prefixed. - else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path)) - break :target_path target_path, - } - } - var prefixed_target_path = try wToPrefixedFileW(dir, target_path); - // We do this after prefixing to ensure that drive-relative paths are treated as absolute - is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); - break :target_path prefixed_target_path.span(); - }; - - // prepare reparse data buffer - var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; - const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; - const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2; - const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); - const symlink_data: SYMLINK_DATA = .{ - .ReparseTag = .SYMLINK, - .ReparseDataLength = @intCast(buf_len - header_len), - .Reserved = 0, - .SubstituteNameOffset = @intCast(final_target_path.len * 2), - .SubstituteNameLength = @intCast(final_target_path.len * 2), - .PrintNameOffset = 0, - .PrintNameLength = @intCast(final_target_path.len * 2), - .Flags = if (!target_is_absolute) SYMLINK_FLAG_RELATIVE else 0, - }; - - @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); - @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; - @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const rc = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); - switch (rc) { - .SUCCESS => {}, - .PRIVILEGE_NOT_HELD => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem - else => return unexpectedStatus(rc), - } -} - -pub const ReadLinkError = error{ - FileNotFound, - NetworkNotFound, - AccessDenied, - Unexpected, - NameTooLong, - BadPathName, - AntivirusInterference, - UnsupportedReparsePointType, - NotLink, - OperationCanceled, -}; - -/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it -/// is safe to reuse a single buffer for both. -pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLinkError![]u16 { - const result_handle = OpenFile(sub_path_w, .{ - .access_mask = .{ - .SPECIFIC = .{ .FILE = .{ - .READ_ATTRIBUTES = true, - } }, - .STANDARD = .{ .SYNCHRONIZE = true }, - }, - .dir = dir, - .creation = .OPEN, - .follow_symlinks = false, - .filter = .any, - }) catch |err| switch (err) { - error.IsDir, error.NotDir => return error.Unexpected, // filter = .any - error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN - error.WouldBlock => return error.Unexpected, - error.NoDevice => return error.FileNotFound, - error.PipeBusy => return error.AccessDenied, - else => |e| return e, - }; - defer CloseHandle(result_handle); - - var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined; - const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] }); - switch (rc) { - .SUCCESS => {}, - .CANCELLED => return error.OperationCanceled, - .NOT_A_REPARSE_POINT => return error.NotLink, - else => return unexpectedStatus(rc), - } - - const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); - const IoReparseTagInt = @typeInfo(IO_REPARSE_TAG).@"struct".backing_integer.?; - switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) { - @as(IoReparseTagInt, @bitCast(IO_REPARSE_TAG.SYMLINK)) => { - const buf: *const SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); - const offset = buf.SubstituteNameOffset >> 1; - const len = buf.SubstituteNameLength >> 1; - const path_buf = @as([*]const u16, &buf.PathBuffer); - const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; - return parseReadLinkPath(path_buf[offset..][0..len], is_relative, out_buffer); - }, - @as(IoReparseTagInt, @bitCast(IO_REPARSE_TAG.MOUNT_POINT)) => { - const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); - const offset = buf.SubstituteNameOffset >> 1; - const len = buf.SubstituteNameLength >> 1; - const path_buf = @as([*]const u16, &buf.PathBuffer); - return parseReadLinkPath(path_buf[offset..][0..len], false, out_buffer); - }, - else => return error.UnsupportedReparsePointType, - } -} - -fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { - path: { - if (is_relative) break :path; - return ntToWin32Namespace(path, out_buffer) catch |err| switch (err) { - error.NameTooLong => |e| return e, - error.NotNtPath => break :path, - }; - } - if (out_buffer.len < path.len) return error.NameTooLong; - const dest = out_buffer[0..path.len]; - @memcpy(dest, path); - return dest; -} - pub const DeleteFileError = error{ FileNotFound, AccessDenied, diff --git a/lib/std/process.zig b/lib/std/process.zig index f7dd7e3017..a3547e2b0f 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -704,7 +704,6 @@ pub const ExecutablePathBaseError = error{ FileSystem, BadPathName, DeviceBusy, - SharingViolation, PipeBusy, NotLink, PathAlreadyExists, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 8bb1678e7d..efcf569de5 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -723,6 +723,7 @@ fn abiAndDynamicLinkerFromFile( error.UnsupportedReparsePointType => unreachable, // Windows only error.NetworkNotFound => unreachable, // Windows only error.AntivirusInterference => unreachable, // Windows only + error.FileBusy => unreachable, // Windows only error.AccessDenied, error.PermissionDenied, @@ -844,7 +845,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { error.NameTooLong => return error.Unexpected, error.BadPathName => return error.Unexpected, error.PipeBusy => return error.Unexpected, // Windows-only - error.SharingViolation => return error.Unexpected, // Windows-only error.NetworkNotFound => return error.Unexpected, // Windows-only error.AntivirusInterference => return error.Unexpected, // Windows-only error.FileLocksUnsupported => return error.Unexpected, // No lock requested. @@ -1052,7 +1052,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ error.NoSpaceLeft => return error.Unexpected, error.NameTooLong => return error.Unexpected, error.PathAlreadyExists => return error.Unexpected, - error.SharingViolation => return error.Unexpected, error.BadPathName => return error.Unexpected, error.PipeBusy => return error.Unexpected, error.FileLocksUnsupported => return error.Unexpected,