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:
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) {