| ... | @@ -1446,6 +1446,7 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1446,6 +1446,7 @@ pub fn io(t: *Threaded) Io { |
| 1446 | .fileUnlock = fileUnlock, | 1446 | .fileUnlock = fileUnlock, |
| 1447 | .fileDowngradeLock = fileDowngradeLock, | 1447 | .fileDowngradeLock = fileDowngradeLock, |
| 1448 | .fileRealPath = fileRealPath, | 1448 | .fileRealPath = fileRealPath, |
| | 1449 | .fileHardLink = fileHardLink, |
| 1449 | | 1450 | |
| 1450 | .processExecutableOpen = processExecutableOpen, | 1451 | .processExecutableOpen = processExecutableOpen, |
| 1451 | .processExecutablePath = processExecutablePath, | 1452 | .processExecutablePath = processExecutablePath, |
| ... | @@ -1593,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -1593,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1593 | .fileUnlock = fileUnlock, | 1594 | .fileUnlock = fileUnlock, |
| 1594 | .fileDowngradeLock = fileDowngradeLock, | 1595 | .fileDowngradeLock = fileDowngradeLock, |
| 1595 | .fileRealPath = fileRealPath, | 1596 | .fileRealPath = fileRealPath, |
| | 1597 | .fileHardLink = fileHardLink, |
| 1596 | | 1598 | |
| 1597 | .processExecutableOpen = processExecutableOpen, | 1599 | .processExecutableOpen = processExecutableOpen, |
| 1598 | .processExecutablePath = processExecutablePath, | 1600 | .processExecutablePath = processExecutablePath, |
| ... | @@ -5144,6 +5146,65 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { | ... | @@ -5144,6 +5146,65 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { |
| 5144 | comptime unreachable; | 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 | const dirDeleteFile = switch (native_os) { | 5208 | const dirDeleteFile = switch (native_os) { |
| 5148 | .windows => dirDeleteFileWindows, | 5209 | .windows => dirDeleteFileWindows, |
| 5149 | .wasi => dirDeleteFileWasi, | 5210 | .wasi => dirDeleteFileWasi, |
| ... | @@ -7560,46 +7621,7 @@ fn dirHardLink( | ... | @@ -7560,46 +7621,7 @@ fn dirHardLink( |
| 7560 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); | 7621 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7561 | | 7622 | |
| 7562 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; | 7623 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 7563 | | 7624 | return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags); |
| 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 | } | | |
| 7603 | } | 7625 | } |
| 7604 | | 7626 | |
| 7605 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { | 7627 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { |