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: ?HANDLEsym_link_path: []const u16target_path: [:0]const u16is_directory: bool

Possible Errors

AccessDenied
BadPathName
FileNotFound
NameTooLong
NetworkNotFound
NoDevice
PathAlreadyExists
Unexpected
UnrecognizedVolume

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); }