Function CreateSymbolicLink [src]
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.
Prototype
pub fn CreateSymbolicLink( dir: ?HANDLE, sym_link_path: []const u16, target_path: [:0]const u16, is_directory: bool, ) CreateSymbolicLinkError!void
Parameters
dir: ?HANDLE
sym_link_path: []const u16
target_path: [:0]const u16
is_directory: bool
Possible Errors
The volume does not contain a recognized file system. File system drivers might not be loaded, or the volume may be corrupt.
Source
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: ULONG,
ReparseDataLength: USHORT,
Reserved: USHORT,
SubstituteNameOffset: USHORT,
SubstituteNameLength: USHORT,
PrintNameOffset: USHORT,
PrintNameLength: USHORT,
Flags: ULONG,
};
const symlink_handle = OpenFile(sym_link_path, .{
.access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
.dir = dir,
.creation = FILE_CREATE,
.filter = if (is_directory) .dir_only else .file_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: {
switch (getNamespacePrefix(u16, target_path)) {
.none => switch (getUnprefixedPathType(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,
},
// Already an NT path, no need to do anything to it
.nt => break :target_path target_path,
else => {},
}
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 = IO_REPARSE_TAG_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)));
_ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
}