commit 37a9ca71634ea256ddc9466b7773feb394e02a8d (tree)
parent 757ec185f0eb91a15c4bdbe0201f0d998f30258c
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 29 Jan 2026 08:40:37 +0100
Merge pull request 'std: finish moving os.windows.ReadLink logic to Io.Threaded' (#31044) from windows-OpenFile into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31044
Diffstat:
7 files changed, 219 insertions(+), 292 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,