From 840331ee48e54b1ce4a3c8196f90eec73ce6deea Mon Sep 17 00:00:00 2001 From: Tau Date: Mon, 25 Jan 2021 07:23:03 +0100 Subject: [PATCH] Rebase link(at) properly --- lib/std/c.zig | 2 ++ lib/std/os.zig | 86 ++++++++++++++++++++++++++++++++++++++++++++ lib/std/os/linux.zig | 31 ++++++++++++++++ lib/std/os/test.zig | 69 +++++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index b5ee8cd893..1688824dd9 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -100,6 +100,8 @@ pub extern "c" fn pwrite(fd: fd_t, buf: [*]const u8, nbyte: usize, offset: u64) pub extern "c" fn mmap(addr: ?*align(page_size) c_void, len: usize, prot: c_uint, flags: c_uint, fd: fd_t, offset: u64) *c_void; pub extern "c" fn munmap(addr: *align(page_size) c_void, len: usize) c_int; pub extern "c" fn mprotect(addr: *align(page_size) c_void, len: usize, prot: c_uint) c_int; +pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: c_int) c_int; +pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_int) c_int; pub extern "c" fn unlink(path: [*:0]const u8) c_int; pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int; pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8; diff --git a/lib/std/os.zig b/lib/std/os.zig index 02eec2de71..c2431ff12c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1634,6 +1634,92 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*: } } +pub const LinkError = UnexpectedError || error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + FileSystem, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotSameFileSystem, +}; + +pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { + switch (errno(system.link(oldpath, newpath, flags))) { + 0 => return, + EACCES => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EFAULT => unreachable, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + EPERM => return error.AccessDenied, + EROFS => return error.ReadOnlyFileSystem, + EXDEV => return error.NotSameFileSystem, + EINVAL => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { + const old = try toPosixPath(oldpath); + const new = try toPosixPath(newpath); + return try linkZ(&old, &new, flags); +} + +pub const LinkatError = LinkError || error{NotDir}; + +pub fn linkatZ( + olddir: fd_t, + oldpath: [*:0]const u8, + newdir: fd_t, + newpath: [*:0]const u8, + flags: i32, +) LinkatError!void { + switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { + 0 => return, + EACCES => return error.AccessDenied, + EDQUOT => return error.DiskQuota, + EEXIST => return error.PathAlreadyExists, + EFAULT => unreachable, + EIO => return error.FileSystem, + ELOOP => return error.SymLinkLoop, + EMLINK => return error.LinkQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + ENOTDIR => return error.NotDir, + EPERM => return error.AccessDenied, + EROFS => return error.ReadOnlyFileSystem, + EXDEV => return error.NotSameFileSystem, + EINVAL => unreachable, + else => |err| return unexpectedErrno(err), + } +} + +pub fn linkat( + olddir: fd_t, + oldpath: []const u8, + newdir: fd_t, + newpath: []const u8, + flags: i32, +) LinkatError!void { + const old = try toPosixPath(oldpath); + const new = try toPosixPath(newpath); + return try linkatZ(olddir, &old, newdir, &new, flags); +} + pub const UnlinkError = error{ FileNotFound, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index b7534db191..035cdabe63 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -634,6 +634,37 @@ pub fn tgkill(tgid: pid_t, tid: pid_t, sig: i32) usize { return syscall2(.tgkill, @bitCast(usize, @as(isize, tgid)), @bitCast(usize, @as(isize, tid)), @bitCast(usize, @as(isize, sig))); } +pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) usize { + if (@hasField(SYS, "link")) { + return syscall3( + .link, + @ptrToInt(oldpath), + @ptrToInt(newpath), + @bitCast(usize, @as(isize, flags)), + ); + } else { + return syscall5( + .linkat, + @bitCast(usize, @as(isize, AT_FDCWD)), + @ptrToInt(oldpath), + @bitCast(usize, @as(isize, AT_FDCWD)), + @ptrToInt(newpath), + @bitCast(usize, @as(isize, flags)), + ); + } +} + +pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize { + return syscall5( + .linkat, + @bitCast(usize, @as(isize, oldfd)), + @ptrToInt(oldpath), + @bitCast(usize, @as(isize, newfd)), + @ptrToInt(newpath), + @bitCast(usize, @as(isize, flags)), + ); +} + pub fn unlink(path: [*:0]const u8) usize { if (@hasField(SYS, "unlink")) { return syscall1(.unlink, @ptrToInt(path)); diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 0904a8585f..f08d4d58fa 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -189,6 +189,75 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { expect(mem.eql(u8, target_path, given)); } +test "link with relative paths" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + var cwd = fs.cwd(); + + cwd.deleteFile("example.txt") catch {}; + cwd.deleteFile("new.txt") catch {}; + + try cwd.writeFile("example.txt", "example"); + try os.link("example.txt", "new.txt", 0); + + const efd = try cwd.openFile("example.txt", .{}); + defer efd.close(); + + const nfd = try cwd.openFile("new.txt", .{}); + defer nfd.close(); + + { + const estat = try os.fstat(efd.handle); + const nstat = try os.fstat(nfd.handle); + + testing.expectEqual(estat.ino, nstat.ino); + testing.expectEqual(@as(usize, 2), nstat.nlink); + } + + try os.unlink("new.txt"); + + { + const estat = try os.fstat(efd.handle); + testing.expectEqual(@as(usize, 1), estat.nlink); + } + + try cwd.deleteFile("example.txt"); +} + +test "linkat with different directories" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + var cwd = fs.cwd(); + var tmp = tmpDir(.{}); + + cwd.deleteFile("example.txt") catch {}; + tmp.dir.deleteFile("new.txt") catch {}; + + try cwd.writeFile("example.txt", "example"); + try os.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0); + + const efd = try cwd.openFile("example.txt", .{}); + defer efd.close(); + + const nfd = try tmp.dir.openFile("new.txt", .{}); + + { + defer nfd.close(); + const estat = try os.fstat(efd.handle); + const nstat = try os.fstat(nfd.handle); + + testing.expectEqual(estat.ino, nstat.ino); + testing.expectEqual(@as(usize, 2), nstat.nlink); + } + + try os.unlinkat(tmp.dir.fd, "new.txt", 0); + + { + const estat = try os.fstat(efd.handle); + testing.expectEqual(@as(usize, 1), estat.nlink); + } + + try cwd.deleteFile("example.txt"); +} + test "fstatat" { // enable when `fstat` and `fstatat` are implemented on Windows if (builtin.os.tag == .windows) return error.SkipZigTest;