diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 0bdaf369c1403e161f0fec767cbd5f02d8898ba6..31cb59ae29da703e5f14089b84f613c9ad1e353f 100644 --- 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 index 3658252499ec3ecabe3f9a2e2b4a0bd863995488..e5bf5849e9ea64fa0a990645a7b6a7b3164b80d9 100644 --- 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 index 10f6c2d507674c4a821bca2bc40c511b6b9bc4c3..da15a70903b0250f8f056c7e48b72409f20bdeaa 100644 --- 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 index 30a3ac132c44a258e8544afd5fa8ef9dde552270..0627882e3c37a5d4d7f7c85de1753931ce315eff 100644 --- 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) {