authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-18 22:21:35-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:10-08:00
log651ff9f9ee7d030892bdb06fc1c7e57cdb716d2b
tree13aae290aac345a27220788aa2778d4c27bf7f28
parent7ce5ee2e92bf1bf1f39ccc08df19f9a1044e9f2c

std.Io.Threaded: implement dirHardLink


3 files changed, 115 insertions(+), 2 deletions(-)

lib/std/Io.zig+1
...@@ -683,6 +683,7 @@ pub const VTable = struct {...@@ -683,6 +683,7 @@ pub const VTable = struct {
683 dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,683 dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,
684 dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,684 dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
685 dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,685 dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
686 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,
686687
687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,688 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
688 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,689 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
lib/std/Io/Threaded.zig+112
...@@ -728,6 +728,7 @@ pub fn io(t: *Threaded) Io {...@@ -728,6 +728,7 @@ pub fn io(t: *Threaded) Io {
728 .dirSetFilePermissions = dirSetFilePermissions,728 .dirSetFilePermissions = dirSetFilePermissions,
729 .dirSetTimestamps = dirSetTimestamps,729 .dirSetTimestamps = dirSetTimestamps,
730 .dirSetTimestampsNow = dirSetTimestampsNow,730 .dirSetTimestampsNow = dirSetTimestampsNow,
731 .dirHardLink = dirHardLink,
731732
732 .fileStat = fileStat,733 .fileStat = fileStat,
733 .fileLength = fileLength,734 .fileLength = fileLength,
...@@ -862,6 +863,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -862,6 +863,7 @@ pub fn ioBasic(t: *Threaded) Io {
862 .dirSetFilePermissions = dirSetFilePermissions,863 .dirSetFilePermissions = dirSetFilePermissions,
863 .dirSetTimestamps = dirSetTimestamps,864 .dirSetTimestamps = dirSetTimestamps,
864 .dirSetTimestampsNow = dirSetTimestampsNow,865 .dirSetTimestampsNow = dirSetTimestampsNow,
866 .dirHardLink = dirHardLink,
865867
866 .fileStat = fileStat,868 .fileStat = fileStat,
867 .fileLength = fileLength,869 .fileLength = fileLength,
...@@ -6264,6 +6266,116 @@ fn dirOpenDirWasi(...@@ -6264,6 +6266,116 @@ fn dirOpenDirWasi(
6264 }6266 }
6265}6267}
62666268
6269fn dirHardLink(
6270 userdata: ?*anyopaque,
6271 old_dir: Dir,
6272 old_sub_path: []const u8,
6273 new_dir: Dir,
6274 new_sub_path: []const u8,
6275 options: Dir.HardLinkOptions,
6276) Dir.HardLinkError!void {
6277 if (is_windows) return error.OperationUnsupported;
6278 const t: *Threaded = @ptrCast(@alignCast(userdata));
6279 const current_thread = Thread.getCurrent(t);
6280
6281 if (native_os == .wasi and !builtin.link_libc) {
6282 const flags: std.os.wasi.lookupflags_t = .{
6283 .SYMLINK_FOLLOW = options.follow_symlinks,
6284 };
6285 try current_thread.beginSyscall();
6286 while (true) {
6287 switch (std.os.wasi.path_link(
6288 old_dir.handle,
6289 flags,
6290 old_sub_path.ptr,
6291 old_sub_path.len,
6292 new_dir.handle,
6293 new_sub_path.ptr,
6294 new_sub_path.len,
6295 )) {
6296 .SUCCESS => return current_thread.endSyscall(),
6297 .INTR => {
6298 try current_thread.checkCancel();
6299 continue;
6300 },
6301 .CANCELED => return current_thread.endSyscallCanceled(),
6302 else => |e| {
6303 current_thread.endSyscall();
6304 switch (e) {
6305 .ACCES => return error.AccessDenied,
6306 .DQUOT => return error.DiskQuota,
6307 .EXIST => return error.PathAlreadyExists,
6308 .FAULT => |err| return errnoBug(err),
6309 .IO => return error.HardwareFailure,
6310 .LOOP => return error.SymLinkLoop,
6311 .MLINK => return error.LinkQuotaExceeded,
6312 .NAMETOOLONG => return error.NameTooLong,
6313 .NOENT => return error.FileNotFound,
6314 .NOMEM => return error.SystemResources,
6315 .NOSPC => return error.NoSpaceLeft,
6316 .NOTDIR => return error.NotDir,
6317 .PERM => return error.PermissionDenied,
6318 .ROFS => return error.ReadOnlyFileSystem,
6319 .XDEV => return error.NotSameFileSystem,
6320 .INVAL => |err| return errnoBug(err),
6321 .ILSEQ => return error.BadPathName,
6322 else => |err| return posix.unexpectedErrno(err),
6323 }
6324 },
6325 }
6326 }
6327 }
6328
6329 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
6330 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
6331
6332 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
6333 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
6334
6335 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
6336
6337 try current_thread.beginSyscall();
6338 while (true) {
6339 switch (posix.errno(posix.system.linkat(
6340 old_dir.handle,
6341 old_sub_path_posix,
6342 new_dir.handle,
6343 new_sub_path_posix,
6344 flags,
6345 ))) {
6346 .SUCCESS => return current_thread.endSyscall(),
6347 .INTR => {
6348 try current_thread.checkCancel();
6349 continue;
6350 },
6351 .CANCELED => return current_thread.endSyscallCanceled(),
6352 else => |e| {
6353 current_thread.endSyscall();
6354 switch (e) {
6355 .ACCES => return error.AccessDenied,
6356 .DQUOT => return error.DiskQuota,
6357 .EXIST => return error.PathAlreadyExists,
6358 .FAULT => |err| return errnoBug(err),
6359 .IO => return error.HardwareFailure,
6360 .LOOP => return error.SymLinkLoop,
6361 .MLINK => return error.LinkQuotaExceeded,
6362 .NAMETOOLONG => return error.NameTooLong,
6363 .NOENT => return error.FileNotFound,
6364 .NOMEM => return error.SystemResources,
6365 .NOSPC => return error.NoSpaceLeft,
6366 .NOTDIR => return error.NotDir,
6367 .PERM => return error.PermissionDenied,
6368 .ROFS => return error.ReadOnlyFileSystem,
6369 .XDEV => return error.NotSameFileSystem,
6370 .INVAL => |err| return errnoBug(err),
6371 .ILSEQ => return error.BadPathName,
6372 else => |err| return posix.unexpectedErrno(err),
6373 }
6374 },
6375 }
6376 }
6377}
6378
6267fn fileClose(userdata: ?*anyopaque, files: []const File) void {6379fn fileClose(userdata: ?*anyopaque, files: []const File) void {
6268 const t: *Threaded = @ptrCast(@alignCast(userdata));6380 const t: *Threaded = @ptrCast(@alignCast(userdata));
6269 _ = t;6381 _ = t;
lib/std/os/linux.zig+2-2
...@@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {...@@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {
1561 }1561 }
1562}1562}
15631563
1564pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize {1564pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: u32) usize {
1565 return syscall5(1565 return syscall5(
1566 .linkat,1566 .linkat,
1567 @as(usize, @bitCast(@as(isize, oldfd))),1567 @as(usize, @bitCast(@as(isize, oldfd))),
1568 @intFromPtr(oldpath),1568 @intFromPtr(oldpath),
1569 @as(usize, @bitCast(@as(isize, newfd))),1569 @as(usize, @bitCast(@as(isize, newfd))),
1570 @intFromPtr(newpath),1570 @intFromPtr(newpath),
1571 @as(usize, @bitCast(@as(isize, flags))),1571 flags,
1572 );1572 );
1573}1573}
15741574