authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 21:35:41-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-05 20:28:58-08:00
log4365b0df88a300a48abdfcd5534acdb048b465ba
treea42e9306d7ea229d2ceda7445d03b00e2459c5b0
parent81bfd289745c661ff5b96b42470f47492091c389

std.Io.Threaded: add File.hardLink


2 files changed, 66 insertions(+), 45 deletions(-)

lib/std/Io/Threaded.zig+62-40
...@@ -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,
14491450
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,
15961598
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}
51465148
5149fn 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
5171fn 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
5147const dirDeleteFile = switch (native_os) {5208const 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);
75617622
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;
75637624 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}
76047626
7605fn fileClose(userdata: ?*anyopaque, files: []const File) void {7627fn fileClose(userdata: ?*anyopaque, files: []const File) void {
lib/std/fs/test.zig+4-5
...@@ -1652,11 +1652,10 @@ test "AtomicFile" {...@@ -1652,11 +1652,10 @@ test "AtomicFile" {
1652 ;1652 ;
16531653
1654 {1654 {
1655 var buffer: [100]u8 = undefined;1655 var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
1656 var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer });1656 defer af.deinit(io);
1657 defer af.deinit();1657 try af.file.writeStreamingAll(io, test_content);
1658 try af.file_writer.interface.writeAll(test_content);1658 try af.replace(io);
1659 try af.finish();
1660 }1659 }
1661 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));1660 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));
1662 try expectEqualStrings(test_content, content);1661 try expectEqualStrings(test_content, content);