| ... | ... | @@ -1446,6 +1446,7 @@ pub fn io(t: *Threaded) Io { |
| 1446 | 1446 | .fileUnlock = fileUnlock, |
| 1447 | 1447 | .fileDowngradeLock = fileDowngradeLock, |
| 1448 | 1448 | .fileRealPath = fileRealPath, |
| 1449 | .fileHardLink = fileHardLink, |
| 1449 | 1450 | |
| 1450 | 1451 | .processExecutableOpen = processExecutableOpen, |
| 1451 | 1452 | .processExecutablePath = processExecutablePath, |
| ... | ... | @@ -1593,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1593 | 1594 | .fileUnlock = fileUnlock, |
| 1594 | 1595 | .fileDowngradeLock = fileDowngradeLock, |
| 1595 | 1596 | .fileRealPath = fileRealPath, |
| 1597 | .fileHardLink = fileHardLink, |
| 1596 | 1598 | |
| 1597 | 1599 | .processExecutableOpen = processExecutableOpen, |
| 1598 | 1600 | .processExecutablePath = processExecutablePath, |
| ... | ... | @@ -5144,6 +5146,65 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { |
| 5144 | 5146 | comptime unreachable; |
| 5145 | 5147 | } |
| 5146 | 5148 | |
| 5149 | fn fileHardLink( |
| 5150 | userdata: ?*anyopaque, |
| 5151 | file: File, |
| 5152 | new_dir: Dir, |
| 5153 | new_sub_path: []const u8, |
| 5154 | options: File.HardLinkOptions, |
| 5155 | ) File.HardLinkError!void { |
| 5156 | _ = userdata; |
| 5157 | if (is_windows) return error.OperationUnsupported; |
| 5158 | if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); |
| 5159 | |
| 5160 | var new_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 5161 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 5162 | |
| 5163 | const flags: u32 = if (!options.follow_symlinks) |
| 5164 | posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH |
| 5165 | else |
| 5166 | posix.AT.EMPTY_PATH; |
| 5167 | |
| 5168 | return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags); |
| 5169 | } |
| 5170 | |
| 5171 | fn linkat( |
| 5172 | old_dir: posix.fd_t, |
| 5173 | old_path: [*:0]const u8, |
| 5174 | new_dir: posix.fd_t, |
| 5175 | new_path: [*:0]const u8, |
| 5176 | flags: u32, |
| 5177 | ) File.HardLinkError!void { |
| 5178 | const syscall: Syscall = try .start(); |
| 5179 | while (true) { |
| 5180 | switch (posix.errno(posix.system.linkat(old_dir, old_path, new_dir, new_path, flags))) { |
| 5181 | .SUCCESS => return syscall.finish(), |
| 5182 | .INTR => { |
| 5183 | try syscall.checkCancel(); |
| 5184 | continue; |
| 5185 | }, |
| 5186 | .ACCES => return syscall.fail(error.AccessDenied), |
| 5187 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 5188 | .EXIST => return syscall.fail(error.PathAlreadyExists), |
| 5189 | .IO => return syscall.fail(error.HardwareFailure), |
| 5190 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 5191 | .MLINK => return syscall.fail(error.LinkQuotaExceeded), |
| 5192 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 5193 | .NOENT => return syscall.fail(error.FileNotFound), |
| 5194 | .NOMEM => return syscall.fail(error.SystemResources), |
| 5195 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 5196 | .NOTDIR => return syscall.fail(error.NotDir), |
| 5197 | .PERM => return syscall.fail(error.PermissionDenied), |
| 5198 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 5199 | .XDEV => return syscall.fail(error.NotSameFileSystem), |
| 5200 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 5201 | .FAULT => |err| return syscall.errnoBug(err), |
| 5202 | .INVAL => |err| return syscall.errnoBug(err), |
| 5203 | else => |err| return syscall.unexpectedErrno(err), |
| 5204 | } |
| 5205 | } |
| 5206 | } |
| 5207 | |
| 5147 | 5208 | const dirDeleteFile = switch (native_os) { |
| 5148 | 5209 | .windows => dirDeleteFileWindows, |
| 5149 | 5210 | .wasi => dirDeleteFileWasi, |
| ... | ... | @@ -7560,46 +7621,7 @@ fn dirHardLink( |
| 7560 | 7621 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7561 | 7622 | |
| 7562 | 7623 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 7563 | | |
| 7564 | | const syscall: Syscall = try .start(); |
| 7565 | | while (true) { |
| 7566 | | switch (posix.errno(posix.system.linkat( |
| 7567 | | old_dir.handle, |
| 7568 | | old_sub_path_posix, |
| 7569 | | new_dir.handle, |
| 7570 | | new_sub_path_posix, |
| 7571 | | flags, |
| 7572 | | ))) { |
| 7573 | | .SUCCESS => return syscall.finish(), |
| 7574 | | .INTR => { |
| 7575 | | try syscall.checkCancel(); |
| 7576 | | continue; |
| 7577 | | }, |
| 7578 | | else => |e| { |
| 7579 | | syscall.finish(); |
| 7580 | | switch (e) { |
| 7581 | | .ACCES => return error.AccessDenied, |
| 7582 | | .DQUOT => return error.DiskQuota, |
| 7583 | | .EXIST => return error.PathAlreadyExists, |
| 7584 | | .FAULT => |err| return errnoBug(err), |
| 7585 | | .IO => return error.HardwareFailure, |
| 7586 | | .LOOP => return error.SymLinkLoop, |
| 7587 | | .MLINK => return error.LinkQuotaExceeded, |
| 7588 | | .NAMETOOLONG => return error.NameTooLong, |
| 7589 | | .NOENT => return error.FileNotFound, |
| 7590 | | .NOMEM => return error.SystemResources, |
| 7591 | | .NOSPC => return error.NoSpaceLeft, |
| 7592 | | .NOTDIR => return error.NotDir, |
| 7593 | | .PERM => return error.PermissionDenied, |
| 7594 | | .ROFS => return error.ReadOnlyFileSystem, |
| 7595 | | .XDEV => return error.NotSameFileSystem, |
| 7596 | | .INVAL => |err| return errnoBug(err), |
| 7597 | | .ILSEQ => return error.BadPathName, |
| 7598 | | else => |err| return posix.unexpectedErrno(err), |
| 7599 | | } |
| 7600 | | }, |
| 7601 | | } |
| 7602 | | } |
| 7624 | return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags); |
| 7603 | 7625 | } |
| 7604 | 7626 | |
| 7605 | 7627 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { |