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 {
14461446 .fileUnlock = fileUnlock,
14471447 .fileDowngradeLock = fileDowngradeLock,
14481448 .fileRealPath = fileRealPath,
1449 .fileHardLink = fileHardLink,
14491450
14501451 .processExecutableOpen = processExecutableOpen,
14511452 .processExecutablePath = processExecutablePath,
......@@ -1593,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
15931594 .fileUnlock = fileUnlock,
15941595 .fileDowngradeLock = fileDowngradeLock,
15951596 .fileRealPath = fileRealPath,
1597 .fileHardLink = fileHardLink,
15961598
15971599 .processExecutableOpen = processExecutableOpen,
15981600 .processExecutablePath = processExecutablePath,
......@@ -5144,6 +5146,65 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
51445146 comptime unreachable;
51455147}
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
51475208const dirDeleteFile = switch (native_os) {
51485209 .windows => dirDeleteFileWindows,
51495210 .wasi => dirDeleteFileWasi,
......@@ -7560,46 +7621,7 @@ fn dirHardLink(
75607621 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
75617622
75627623 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);
76037625}
76047626
76057627fn fileClose(userdata: ?*anyopaque, files: []const File) void {
lib/std/fs/test.zig+4-5
......@@ -1652,11 +1652,10 @@ test "AtomicFile" {
16521652 ;
16531653
16541654 {
1655 var buffer: [100]u8 = undefined;
1656 var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer });
1657 defer af.deinit();
1658 try af.file_writer.interface.writeAll(test_content);
1659 try af.finish();
1655 var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
1656 defer af.deinit(io);
1657 try af.file.writeStreamingAll(io, test_content);
1658 try af.replace(io);
16601659 }
16611660 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));
16621661 try expectEqualStrings(test_content, content);