commit 651ff9f9ee7d030892bdb06fc1c7e57cdb716d2b (tree)
parent 7ce5ee2e92bf1bf1f39ccc08df19f9a1044e9f2c
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 18 Dec 2025 22:21:35 -0800
std.Io.Threaded: implement dirHardLink
Diffstat:
3 files changed, 115 insertions(+), 2 deletions(-)
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
@@ -683,6 +683,7 @@ pub const VTable = struct {
dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,
dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
+ dirHardLink: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8, Dir.HardLinkOptions) Dir.HardLinkError!void,
fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -728,6 +728,7 @@ pub fn io(t: *Threaded) Io {
.dirSetFilePermissions = dirSetFilePermissions,
.dirSetTimestamps = dirSetTimestamps,
.dirSetTimestampsNow = dirSetTimestampsNow,
+ .dirHardLink = dirHardLink,
.fileStat = fileStat,
.fileLength = fileLength,
@@ -862,6 +863,7 @@ pub fn ioBasic(t: *Threaded) Io {
.dirSetFilePermissions = dirSetFilePermissions,
.dirSetTimestamps = dirSetTimestamps,
.dirSetTimestampsNow = dirSetTimestampsNow,
+ .dirHardLink = dirHardLink,
.fileStat = fileStat,
.fileLength = fileLength,
@@ -6264,6 +6266,116 @@ fn dirOpenDirWasi(
}
}
+fn dirHardLink(
+ userdata: ?*anyopaque,
+ old_dir: Dir,
+ old_sub_path: []const u8,
+ new_dir: Dir,
+ new_sub_path: []const u8,
+ options: Dir.HardLinkOptions,
+) Dir.HardLinkError!void {
+ if (is_windows) return error.OperationUnsupported;
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const current_thread = Thread.getCurrent(t);
+
+ if (native_os == .wasi and !builtin.link_libc) {
+ const flags: std.os.wasi.lookupflags_t = .{
+ .SYMLINK_FOLLOW = options.follow_symlinks,
+ };
+ try current_thread.beginSyscall();
+ while (true) {
+ switch (std.os.wasi.path_link(
+ old_dir.handle,
+ flags,
+ old_sub_path.ptr,
+ old_sub_path.len,
+ new_dir.handle,
+ new_sub_path.ptr,
+ new_sub_path.len,
+ )) {
+ .SUCCESS => return current_thread.endSyscall(),
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .ACCES => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => |err| return errnoBug(err),
+ .IO => return error.HardwareFailure,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.NotSameFileSystem,
+ .INVAL => |err| return errnoBug(err),
+ .ILSEQ => return error.BadPathName,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ }
+ }
+
+ var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
+ var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
+
+ const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
+ const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
+
+ const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
+
+ try current_thread.beginSyscall();
+ while (true) {
+ switch (posix.errno(posix.system.linkat(
+ old_dir.handle,
+ old_sub_path_posix,
+ new_dir.handle,
+ new_sub_path_posix,
+ flags,
+ ))) {
+ .SUCCESS => return current_thread.endSyscall(),
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .ACCES => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => |err| return errnoBug(err),
+ .IO => return error.HardwareFailure,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.NotSameFileSystem,
+ .INVAL => |err| return errnoBug(err),
+ .ILSEQ => return error.BadPathName,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ }
+}
+
fn fileClose(userdata: ?*anyopaque, files: []const File) void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {
}
}
-pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize {
+pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: u32) usize {
return syscall5(
.linkat,
@as(usize, @bitCast(@as(isize, oldfd))),
@intFromPtr(oldpath),
@as(usize, @bitCast(@as(isize, newfd))),
@intFromPtr(newpath),
- @as(usize, @bitCast(@as(isize, flags))),
+ flags,
);
}