zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit f3723b42e1f05d14479d6f5507943cd1905e0fcf (tree)
parent 446c145ca86b014d6743f5666e9ee1d671b56045
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Thu, 18 Dec 2025 13:49:56 -0800

std.Io: add unimplemented hard link API to File and Dir

Diffstat:
Mlib/std/Io/Dir.zig | 33+++++++++++++++++++++++++++++++++
Mlib/std/Io/File.zig | 6+++++-
Mlib/std/Io/Threaded.zig | 46++++++++++++++++++++++++++++++----------------
Mlib/std/c.zig | 2+-
4 files changed, 69 insertions(+), 18 deletions(-)

diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig @@ -989,6 +989,39 @@ pub fn renameAbsolute(io: Io, old_path: []const u8, new_path: []const u8) Rename return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path); } +pub const HardLinkOptions = struct { + follow_symlinks: bool = true, +}; + +pub const HardLinkError = error{ + AccessDenied, + PermissionDenied, + DiskQuota, + PathAlreadyExists, + HardwareFailure, + /// Either the OS or the filesystem does not support hard links. + OperationUnsupported, + SymLinkLoop, + LinkQuotaExceeded, + FileNotFound, + SystemResources, + NoSpaceLeft, + ReadOnlyFileSystem, + NotSameFileSystem, + NotDir, +} || Io.Cancelable || PathNameError || Io.UnexpectedError; + +pub fn hardLink( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, + io: Io, + options: HardLinkOptions, +) HardLinkError!void { + return io.vtable.dirHardLink(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path, options); +} + /// Use with `symLink`, `symLinkAtomic`, and `symLinkAbsolute` to /// specify whether the symlink will point to a file or a directory. This value /// is ignored on all hosts except Windows where creating symlinks to different diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig @@ -17,6 +17,7 @@ pub const Atomic = @import("File/Atomic.zig"); pub const Handle = std.posix.fd_t; pub const INode = std.posix.ino_t; +pub const NLink = std.posix.nlink_t; pub const Uid = std.posix.uid_t; pub const Gid = std.posix.gid_t; @@ -47,6 +48,7 @@ pub const Stat = struct { /// The FileIndex on Windows is similar. It is a number for a file that /// is unique to each filesystem. inode: INode, + nlink: NLink, size: u64, permissions: Permissions, kind: Kind, @@ -101,9 +103,11 @@ pub const OpenFlags = struct { mode: OpenMode = .read_only, /// Determines the behavior when opening a path that refers to a directory. + /// /// If set to true, directories may be opened, but `error.IsDir` is still /// possible in certain scenarios, e.g. attempting to open a directory with /// write permissions. + /// /// If set to false, `error.IsDir` will always be returned when opening a directory. /// /// When set to false: @@ -289,7 +293,7 @@ pub fn sync(file: File, io: Io) SyncError!void { return io.vtable.fileSync(io.userdata, file); } -/// Test whether the file refers to a terminal. +/// Test whether the file refers to a terminal (similar to libc "isatty"). /// /// See also: /// * `enableAnsiEscapeCodes` diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig @@ -1829,19 +1829,21 @@ fn dirStatFileLinux( dir.handle, sub_path_posix, flags, - .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, + .{ + .TYPE = true, + .MODE = true, + .ATIME = true, + .MTIME = true, + .CTIME = true, + .INO = true, + .SIZE = true, + .NLINK = true, + }, &statx, ); switch (sys.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); - assert(statx.mask.TYPE); - assert(statx.mask.MODE); - assert(statx.mask.ATIME); - assert(statx.mask.MTIME); - assert(statx.mask.CTIME); - assert(statx.mask.INO); - assert(statx.mask.SIZE); return statFromLinux(&statx); }, .INTR => { @@ -2082,19 +2084,21 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { file.handle, "", linux.AT.EMPTY_PATH, - .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, + .{ + .TYPE = true, + .MODE = true, + .ATIME = true, + .MTIME = true, + .CTIME = true, + .INO = true, + .SIZE = true, + .NLINK = true, + }, &statx, ); switch (sys.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); - assert(statx.mask.TYPE); - assert(statx.mask.MODE); - assert(statx.mask.ATIME); - assert(statx.mask.MTIME); - assert(statx.mask.CTIME); - assert(statx.mask.INO); - assert(statx.mask.SIZE); return statFromLinux(&statx); }, .INTR => { @@ -10499,11 +10503,20 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { } fn statFromLinux(stx: *const std.os.linux.Statx) File.Stat { + assert(stx.mask.TYPE); + assert(stx.mask.MODE); + assert(stx.mask.ATIME); + assert(stx.mask.MTIME); + assert(stx.mask.CTIME); + assert(stx.mask.INO); + assert(stx.mask.SIZE); + assert(stx.mask.NLINK); const atime = stx.atime; const mtime = stx.mtime; const ctime = stx.ctime; return .{ .inode = stx.ino, + .nlink = stx.nlink, .size = stx.size, .permissions = .fromMode(stx.mode), .kind = switch (stx.mode & std.os.linux.S.IFMT) { @@ -10528,6 +10541,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat { const ctime = st.ctime(); return .{ .inode = st.ino, + .nlink = st.nlink, .size = @bitCast(st.size), .permissions = .fromMode(st.mode), .kind = k: { diff --git a/lib/std/c.zig b/lib/std/c.zig @@ -162,7 +162,7 @@ pub const nlink_t = switch (native_os) { .freebsd, .serenity => u64, .openbsd, .netbsd, .illumos => u32, .haiku => i32, - else => void, + else => u0, }; pub const uid_t = switch (native_os) {