| ... | ... | @@ -728,6 +728,7 @@ pub fn io(t: *Threaded) Io { |
| 728 | 728 | .dirSetFilePermissions = dirSetFilePermissions, |
| 729 | 729 | .dirSetTimestamps = dirSetTimestamps, |
| 730 | 730 | .dirSetTimestampsNow = dirSetTimestampsNow, |
| 731 | .dirHardLink = dirHardLink, |
| 731 | 732 | |
| 732 | 733 | .fileStat = fileStat, |
| 733 | 734 | .fileLength = fileLength, |
| ... | ... | @@ -862,6 +863,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 862 | 863 | .dirSetFilePermissions = dirSetFilePermissions, |
| 863 | 864 | .dirSetTimestamps = dirSetTimestamps, |
| 864 | 865 | .dirSetTimestampsNow = dirSetTimestampsNow, |
| 866 | .dirHardLink = dirHardLink, |
| 865 | 867 | |
| 866 | 868 | .fileStat = fileStat, |
| 867 | 869 | .fileLength = fileLength, |
| ... | ... | @@ -6264,6 +6266,116 @@ fn dirOpenDirWasi( |
| 6264 | 6266 | } |
| 6265 | 6267 | } |
| 6266 | 6268 | |
| 6269 | fn 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 | |
| 6267 | 6379 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { |
| 6268 | 6380 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6269 | 6381 | _ = t; |