| ... | ... | @@ -2908,123 +2908,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { |
| 2908 | 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 | 2911 | pub const ReadLinkError = error{ |
| 3029 | 2912 | FileNotFound, |
| 3030 | 2913 | NetworkNotFound, |