| ... | @@ -2908,123 +2908,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { | ... | @@ -2908,123 +2908,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { |
| 2908 | return buffer[0..end_index]; | 2908 | return buffer[0..end_index]; |
| 2909 | } | 2909 | } |
| 2910 | | 2910 | |
| 2911 | pub const CreateSymbolicLinkError = error{ | | |
| 2912 | AccessDenied, | | |
| 2913 | PathAlreadyExists, | | |
| 2914 | FileNotFound, | | |
| 2915 | NameTooLong, | | |
| 2916 | NoDevice, | | |
| 2917 | NetworkNotFound, | | |
| 2918 | BadPathName, | | |
| 2919 | Unexpected, | | |
| 2920 | }; | | |
| 2921 | | | |
| 2922 | /// Needs either: | | |
| 2923 | /// - `SeCreateSymbolicLinkPrivilege` privilege | | |
| 2924 | /// or | | |
| 2925 | /// - Developer mode on Windows 10 | | |
| 2926 | /// otherwise fails with `error.AccessDenied`. In which case `sym_link_path` may still | | |
| 2927 | /// be created on the file system but will lack reparse processing data applied to it. | | |
| 2928 | pub fn CreateSymbolicLink( | | |
| 2929 | dir: ?HANDLE, | | |
| 2930 | sym_link_path: []const u16, | | |
| 2931 | target_path: [:0]const u16, | | |
| 2932 | is_directory: bool, | | |
| 2933 | ) CreateSymbolicLinkError!void { | | |
| 2934 | const SYMLINK_DATA = extern struct { | | |
| 2935 | ReparseTag: IO_REPARSE_TAG, | | |
| 2936 | ReparseDataLength: USHORT, | | |
| 2937 | Reserved: USHORT, | | |
| 2938 | SubstituteNameOffset: USHORT, | | |
| 2939 | SubstituteNameLength: USHORT, | | |
| 2940 | PrintNameOffset: USHORT, | | |
| 2941 | PrintNameLength: USHORT, | | |
| 2942 | Flags: ULONG, | | |
| 2943 | }; | | |
| 2944 | | | |
| 2945 | const symlink_handle = OpenFile(sym_link_path, .{ | | |
| 2946 | .access_mask = .{ | | |
| 2947 | .STANDARD = .{ .SYNCHRONIZE = true }, | | |
| 2948 | .GENERIC = .{ .WRITE = true, .READ = true }, | | |
| 2949 | }, | | |
| 2950 | .dir = dir, | | |
| 2951 | .creation = .CREATE, | | |
| 2952 | .filter = if (is_directory) .dir_only else .non_directory_only, | | |
| 2953 | }) catch |err| switch (err) { | | |
| 2954 | error.IsDir => return error.PathAlreadyExists, | | |
| 2955 | error.NotDir => return error.Unexpected, | | |
| 2956 | error.WouldBlock => return error.Unexpected, | | |
| 2957 | error.PipeBusy => return error.Unexpected, | | |
| 2958 | error.NoDevice => return error.Unexpected, | | |
| 2959 | error.AntivirusInterference => return error.Unexpected, | | |
| 2960 | else => |e| return e, | | |
| 2961 | }; | | |
| 2962 | defer CloseHandle(symlink_handle); | | |
| 2963 | | | |
| 2964 | // Relevant portions of the documentation: | | |
| 2965 | // > Relative links are specified using the following conventions: | | |
| 2966 | // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". | | |
| 2967 | // > - Current working directory–relative—for example, if the current working directory is | | |
| 2968 | // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". | | |
| 2969 | // > Note: If you specify a current working directory–relative link, it is created as an absolute | | |
| 2970 | // > link, due to the way the current working directory is processed based on the user and the thread. | | |
| 2971 | // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw | | |
| 2972 | var is_target_absolute = false; | | |
| 2973 | const final_target_path = target_path: { | | |
| 2974 | if (hasCommonNtPrefix(u16, target_path)) { | | |
| 2975 | // Already an NT path, no need to do anything to it | | |
| 2976 | break :target_path target_path; | | |
| 2977 | } else { | | |
| 2978 | switch (std.fs.path.getWin32PathType(u16, target_path)) { | | |
| 2979 | // Rooted paths need to avoid getting put through wToPrefixedFileW | | |
| 2980 | // (and they are treated as relative in this context) | | |
| 2981 | // Note: It seems that rooted paths in symbolic links are relative to | | |
| 2982 | // the drive that the symbolic exists on, not to the CWD's drive. | | |
| 2983 | // So, if the symlink is on C:\ and the CWD is on D:\, | | |
| 2984 | // it will still resolve the path relative to the root of | | |
| 2985 | // the C:\ drive. | | |
| 2986 | .rooted => break :target_path target_path, | | |
| 2987 | // Keep relative paths relative, but anything else needs to get NT-prefixed. | | |
| 2988 | else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path)) | | |
| 2989 | break :target_path target_path, | | |
| 2990 | } | | |
| 2991 | } | | |
| 2992 | var prefixed_target_path = try wToPrefixedFileW(dir, target_path); | | |
| 2993 | // We do this after prefixing to ensure that drive-relative paths are treated as absolute | | |
| 2994 | is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); | | |
| 2995 | break :target_path prefixed_target_path.span(); | | |
| 2996 | }; | | |
| 2997 | | | |
| 2998 | // prepare reparse data buffer | | |
| 2999 | var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; | | |
| 3000 | const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; | | |
| 3001 | const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2; | | |
| 3002 | const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); | | |
| 3003 | const symlink_data: SYMLINK_DATA = .{ | | |
| 3004 | .ReparseTag = .SYMLINK, | | |
| 3005 | .ReparseDataLength = @intCast(buf_len - header_len), | | |
| 3006 | .Reserved = 0, | | |
| 3007 | .SubstituteNameOffset = @intCast(final_target_path.len * 2), | | |
| 3008 | .SubstituteNameLength = @intCast(final_target_path.len * 2), | | |
| 3009 | .PrintNameOffset = 0, | | |
| 3010 | .PrintNameLength = @intCast(final_target_path.len * 2), | | |
| 3011 | .Flags = if (!target_is_absolute) SYMLINK_FLAG_RELATIVE else 0, | | |
| 3012 | }; | | |
| 3013 | | | |
| 3014 | @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); | | |
| 3015 | @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); | | |
| 3016 | const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; | | |
| 3017 | @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); | | |
| 3018 | const rc = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); | | |
| 3019 | switch (rc) { | | |
| 3020 | .SUCCESS => {}, | | |
| 3021 | .PRIVILEGE_NOT_HELD => return error.AccessDenied, | | |
| 3022 | .ACCESS_DENIED => return error.AccessDenied, | | |
| 3023 | .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem | | |
| 3024 | else => return unexpectedStatus(rc), | | |
| 3025 | } | | |
| 3026 | } | | |
| 3027 | | | |
| 3028 | pub const ReadLinkError = error{ | 2911 | pub const ReadLinkError = error{ |
| 3029 | FileNotFound, | 2912 | FileNotFound, |
| 3030 | NetworkNotFound, | 2913 | NetworkNotFound, |