Commit 840331ee48

Tau <tauoverpi@yandex.com>
2021-01-25 07:23:03
Rebase link(at) properly
1 parent c70832b
Changed files (4)
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));
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;
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;
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,