feature. See also
. The project being documented here (as the example) is the Zig library itself.
Threaded.dirSymLinkWindows
fn dirSymLinkWindows(
userdata: ?*anyopaque,
dir: Dir,
target_path: []const u8,
sym_link_path: []const u8,
flags: Dir.SymLinkFlags,
) Dir.SymLinkError!void
File
Code
fn dirSymLinkWindows(
userdata: ?*anyopaque,
dir: Dir,
target_path: []const u8,
sym_link_path: []const u8,
flags: Dir.SymLinkFlags,
) Dir.SymLinkError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
const w = windows;
// are handled differently when creating a symlink than they would be
// when converting to an NT namespaced path.
var target_path_w: WindowsPathSpace = undefined;
target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path);
target_path_w.data[target_path_w.len] = 0;
// the target path is relative, then it must use `\` as the path separator.
std.mem.replaceScalar(
u16,
target_path_w.data[0..target_path_w.len],
std.mem.nativeToLittle(u16, '/'),
std.mem.nativeToLittle(u16, '\\'),
);
const sym_link_path_w = try sliceToPrefixedFileW(dir.handle, sym_link_path, .{});
const SYMLINK_DATA = extern struct {
ReparseTag: w.IO_REPARSE_TAG,
ReparseDataLength: w.USHORT,
Reserved: w.USHORT,
SubstituteNameOffset: w.USHORT,
SubstituteNameLength: w.USHORT,
PrintNameOffset: w.USHORT,
PrintNameLength: w.USHORT,
Flags: w.ULONG,
};
const symlink_handle = handle: {
if (OpenFile(sym_link_path_w.span(), .{
.access_mask = .{
.GENERIC = .{ .READ = true, .WRITE = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.dir = dir.handle,
.creation = .CREATE,
.filter = if (flags.is_directory) .dir_only else .non_directory_only,
})) |handle| {
break :handle handle;
} else |err| switch (err) {
error.IsDir => return error.PathAlreadyExists,
error.NotDir => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.PipeBusy => return error.Unexpected,
error.FileBusy => return error.Unexpected,
error.NoDevice => return error.Unexpected,
error.AntivirusInterference => return error.Unexpected,
else => |e| return e,
}
};
defer w.CloseHandle(symlink_handle);
// > 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 (w.hasCommonNtPrefix(u16, target_path_w.span())) {
break :target_path target_path_w.span();
} else {
switch (Dir.path.getWin32PathType(u16, target_path_w.span())) {
// (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_w.span(),
else => if (!Dir.path.isAbsoluteWindowsWtf16(target_path_w.span()))
break :target_path target_path_w.span(),
}
}
var prefixed_target_path = try wToPrefixedFileW(dir.handle, target_path_w.span(), .{});
is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
break :target_path prefixed_target_path.span();
};
var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;
const target_is_absolute = Dir.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) w.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)));
switch ((try deviceIoControl(&.{
.file = .{ .handle = symlink_handle, .flags = .{ .nonblocking = false } },
.code = .SET_REPARSE_POINT,
.in = buffer[0..buf_len],
})).u.Status) {
.SUCCESS => {},
.CANCELLED => unreachable,
.INSUFFICIENT_RESOURCES => return error.SystemResources,
.PRIVILEGE_NOT_HELD => return error.PermissionDenied,
.ACCESS_DENIED => return error.AccessDenied,
.INVALID_DEVICE_REQUEST => return error.FileSystem,
else => |status| return w.unexpectedStatus(status),
}
}