diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-10-08 20:35:34 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2025-10-29 06:20:48 -0700 |
| commit | 89412fda775aecdedf4047355f2c45b48334a285 (patch) | |
| tree | 1aa3818600ec8bdead689f0a60bf507b4c259370 | |
| parent | 69b54b0cd12fb90f8ff101bc66e433a81d4b8409 (diff) | |
| download | zig-89412fda775aecdedf4047355f2c45b48334a285.tar.gz zig-89412fda775aecdedf4047355f2c45b48334a285.zip | |
std.Io: implement fileStat
| -rw-r--r-- | lib/std/Io/File.zig | 94 | ||||
| -rw-r--r-- | lib/std/Io/Threaded.zig | 155 | ||||
| -rw-r--r-- | lib/std/debug.zig | 4 | ||||
| -rw-r--r-- | lib/std/debug/ElfFile.zig | 3 | ||||
| -rw-r--r-- | lib/std/debug/SelfInfo/Elf.zig | 1 | ||||
| -rw-r--r-- | lib/std/fs/File.zig | 92 | ||||
| -rw-r--r-- | lib/std/posix.zig | 9 |
7 files changed, 202 insertions, 156 deletions
diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index dddbf3e66e..018825164e 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -47,90 +47,14 @@ pub const Stat = struct { kind: Kind, /// Last access time in nanoseconds, relative to UTC 1970-01-01. + /// TODO change this to Io.Timestamp except don't waste storage on clock atime: i128, /// Last modification time in nanoseconds, relative to UTC 1970-01-01. + /// TODO change this to Io.Timestamp except don't waste storage on clock mtime: i128, /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01. + /// TODO change this to Io.Timestamp except don't waste storage on clock ctime: i128, - - pub fn fromPosix(st: std.posix.Stat) Stat { - const atime = st.atime(); - const mtime = st.mtime(); - const ctime = st.ctime(); - return .{ - .inode = st.ino, - .size = @bitCast(st.size), - .mode = st.mode, - .kind = k: { - const m = st.mode & std.posix.S.IFMT; - switch (m) { - std.posix.S.IFBLK => break :k .block_device, - std.posix.S.IFCHR => break :k .character_device, - std.posix.S.IFDIR => break :k .directory, - std.posix.S.IFIFO => break :k .named_pipe, - std.posix.S.IFLNK => break :k .sym_link, - std.posix.S.IFREG => break :k .file, - std.posix.S.IFSOCK => break :k .unix_domain_socket, - else => {}, - } - if (builtin.os.tag == .illumos) switch (m) { - std.posix.S.IFDOOR => break :k .door, - std.posix.S.IFPORT => break :k .event_port, - else => {}, - }; - - break :k .unknown; - }, - .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec, - .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec, - .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec, - }; - } - - pub fn fromLinux(stx: std.os.linux.Statx) Stat { - const atime = stx.atime; - const mtime = stx.mtime; - const ctime = stx.ctime; - - return .{ - .inode = stx.ino, - .size = stx.size, - .mode = stx.mode, - .kind = switch (stx.mode & std.os.linux.S.IFMT) { - std.os.linux.S.IFDIR => .directory, - std.os.linux.S.IFCHR => .character_device, - std.os.linux.S.IFBLK => .block_device, - std.os.linux.S.IFREG => .file, - std.os.linux.S.IFIFO => .named_pipe, - std.os.linux.S.IFLNK => .sym_link, - std.os.linux.S.IFSOCK => .unix_domain_socket, - else => .unknown, - }, - .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec, - .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec, - .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec, - }; - } - - pub fn fromWasi(st: std.os.wasi.filestat_t) Stat { - return .{ - .inode = st.ino, - .size = @bitCast(st.size), - .mode = 0, - .kind = switch (st.filetype) { - .BLOCK_DEVICE => .block_device, - .CHARACTER_DEVICE => .character_device, - .DIRECTORY => .directory, - .SYMBOLIC_LINK => .sym_link, - .REGULAR_FILE => .file, - .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, - else => .unknown, - }, - .atime = st.atim, - .mtime = st.mtim, - .ctime = st.ctim, - }; - } }; pub fn stdout() File { @@ -145,13 +69,17 @@ pub fn stdin() File { return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdInput else std.posix.STDIN_FILENO }; } -pub const StatError = std.posix.FStatError || Io.Cancelable; +pub const StatError = error{ + SystemResources, + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to get its filestat information. + AccessDenied, + PermissionDenied, +} || Io.Cancelable || Io.UnexpectedError; /// Returns `Stat` containing basic information about the `File`. pub fn stat(file: File, io: Io) StatError!Stat { - _ = file; - _ = io; - @panic("TODO"); + return io.vtable.fileStat(io.userdata, file); } pub const OpenFlags = std.fs.File.OpenFlags; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0f217c7f80..16e134251c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -163,7 +163,12 @@ pub fn io(pool: *Pool) Io { .dirMake = dirMake, .dirStat = dirStat, .dirStatPath = dirStatPath, - .fileStat = fileStat, + .fileStat = switch (builtin.os.tag) { + .linux => fileStatLinux, + .windows => fileStatWindows, + .wasi => fileStatWasi, + else => fileStatPosix, + }, .createFile = createFile, .fileOpen = fileOpen, .fileClose = fileClose, @@ -781,14 +786,80 @@ fn dirStatPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir. @panic("TODO"); } -fn fileStat(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { +fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const pool: *Pool = @ptrCast(@alignCast(userdata)); - try pool.checkCancel(); + const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; + while (true) { + try pool.checkCancel(); + var stat = std.mem.zeroes(posix.Stat); + switch (posix.errno(fstat_sym(file.handle, &stat))) { + .SUCCESS => return statFromPosix(&stat), + .INTR => continue, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { + const pool: *Pool = @ptrCast(@alignCast(userdata)); + const linux = std.os.linux; + while (true) { + try pool.checkCancel(); + var statx = std.mem.zeroes(linux.Statx); + const rc = linux.statx( + file.handle, + "", + linux.AT.EMPTY_PATH, + linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME, + &statx, + ); + switch (linux.E.init(rc)) { + .SUCCESS => return statFromLinux(&statx), + .INTR => continue, + .ACCES => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .LOOP => |err| return errnoBug(err), + .NAMETOOLONG => |err| return errnoBug(err), + .NOENT => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + .NOTDIR => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + } +} +fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { + const pool: *Pool = @ptrCast(@alignCast(userdata)); + try pool.checkCancel(); _ = file; @panic("TODO"); } +fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { + if (builtin.link_libc) return fileStatPosix(userdata, file); + const pool: *Pool = @ptrCast(@alignCast(userdata)); + while (true) { + try pool.checkCancel(); + var stat: std.os.wasi.filestat_t = undefined; + switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) { + .SUCCESS => return statFromWasi(&stat), + .INTR => continue, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + } +} + fn createFile( userdata: ?*anyopaque, dir: Io.Dir, @@ -2114,3 +2185,81 @@ fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t { .cpu_thread => .THREAD_CPUTIME_ID, }; } + +fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat { + const atime = stx.atime; + const mtime = stx.mtime; + const ctime = stx.ctime; + return .{ + .inode = stx.ino, + .size = stx.size, + .mode = stx.mode, + .kind = switch (stx.mode & std.os.linux.S.IFMT) { + std.os.linux.S.IFDIR => .directory, + std.os.linux.S.IFCHR => .character_device, + std.os.linux.S.IFBLK => .block_device, + std.os.linux.S.IFREG => .file, + std.os.linux.S.IFIFO => .named_pipe, + std.os.linux.S.IFLNK => .sym_link, + std.os.linux.S.IFSOCK => .unix_domain_socket, + else => .unknown, + }, + .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec, + .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec, + .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec, + }; +} + +fn statFromPosix(st: *const std.posix.Stat) Io.File.Stat { + const atime = st.atime(); + const mtime = st.mtime(); + const ctime = st.ctime(); + return .{ + .inode = st.ino, + .size = @bitCast(st.size), + .mode = st.mode, + .kind = k: { + const m = st.mode & std.posix.S.IFMT; + switch (m) { + std.posix.S.IFBLK => break :k .block_device, + std.posix.S.IFCHR => break :k .character_device, + std.posix.S.IFDIR => break :k .directory, + std.posix.S.IFIFO => break :k .named_pipe, + std.posix.S.IFLNK => break :k .sym_link, + std.posix.S.IFREG => break :k .file, + std.posix.S.IFSOCK => break :k .unix_domain_socket, + else => {}, + } + if (builtin.os.tag == .illumos) switch (m) { + std.posix.S.IFDOOR => break :k .door, + std.posix.S.IFPORT => break :k .event_port, + else => {}, + }; + + break :k .unknown; + }, + .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec, + .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec, + .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec, + }; +} + +fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat { + return .{ + .inode = st.ino, + .size = @bitCast(st.size), + .mode = 0, + .kind = switch (st.filetype) { + .BLOCK_DEVICE => .block_device, + .CHARACTER_DEVICE => .character_device, + .DIRECTORY => .directory, + .SYMBOLIC_LINK => .sym_link, + .REGULAR_FILE => .file, + .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, + else => .unknown, + }, + .atime = st.atim, + .mtime = st.mtim, + .ctime = st.ctim, + }; +} diff --git a/lib/std/debug.zig b/lib/std/debug.zig index f6287135e5..7e09bfec8a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -82,6 +82,7 @@ pub const SelfInfoError = error{ /// The required debug info could not be read from disk due to some IO error. ReadFailed, OutOfMemory, + Canceled, Unexpected, }; @@ -691,6 +692,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri error.UnsupportedDebugInfo => "unwind info unsupported", error.ReadFailed => "filesystem error", error.OutOfMemory => "out of memory", + error.Canceled => "operation canceled", error.Unexpected => "unexpected error", }; if (it.stratOk(options.allow_unsafe_unwind)) { @@ -1079,7 +1081,7 @@ fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, error.UnsupportedDebugInfo, error.InvalidDebugInfo, => .unknown, - error.ReadFailed, error.Unexpected => s: { + error.ReadFailed, error.Unexpected, error.Canceled => s: { tty_config.setColor(writer, .dim) catch {}; try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); tty_config.setColor(writer, .reset) catch {}; diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index 5be5ee55c5..2062772533 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -108,6 +108,7 @@ pub const LoadError = error{ LockedMemoryLimitExceeded, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, + Canceled, Unexpected, }; @@ -408,7 +409,7 @@ fn loadInner( arena: Allocator, elf_file: std.fs.File, opt_crc: ?u32, -) (LoadError || error{CrcMismatch})!LoadInnerResult { +) (LoadError || error{ CrcMismatch, Canceled })!LoadInnerResult { const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { const file_len = std.math.cast( usize, diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 0f3c46e980..035ed584b2 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -336,6 +336,7 @@ const Module = struct { var elf_file = load_result catch |err| switch (err) { error.OutOfMemory, error.Unexpected, + error.Canceled, => |e| return e, error.Overflow, diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 3de4d9bcb4..191920dc83 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1,10 +1,12 @@ +const File = @This(); + const builtin = @import("builtin"); -const Os = std.builtin.Os; const native_os = builtin.os.tag; const is_windows = native_os == .windows; -const File = @This(); const std = @import("../std.zig"); +const Io = std.Io; +const Os = std.builtin.Os; const Allocator = std.mem.Allocator; const posix = std.posix; const math = std.math; @@ -17,12 +19,12 @@ const Alignment = std.mem.Alignment; /// The OS-specific file descriptor or file handle. handle: Handle, -pub const Handle = std.Io.File.Handle; -pub const Mode = std.Io.File.Mode; -pub const INode = std.Io.File.INode; +pub const Handle = Io.File.Handle; +pub const Mode = Io.File.Mode; +pub const INode = Io.File.INode; pub const Uid = posix.uid_t; pub const Gid = posix.gid_t; -pub const Kind = std.Io.File.Kind; +pub const Kind = Io.File.Kind; /// This is the default mode given to POSIX operating systems for creating /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first, @@ -386,7 +388,7 @@ pub fn mode(self: File) ModeError!Mode { return (try self.stat()).mode; } -pub const Stat = std.Io.File.Stat; +pub const Stat = Io.File.Stat; pub const StatError = posix.FStatError; @@ -436,39 +438,9 @@ pub fn stat(self: File) StatError!Stat { }; } - if (builtin.os.tag == .wasi and !builtin.link_libc) { - const st = try std.os.fstat_wasi(self.handle); - return Stat.fromWasi(st); - } - - if (builtin.os.tag == .linux) { - var stx = std.mem.zeroes(linux.Statx); - - const rc = linux.statx( - self.handle, - "", - linux.AT.EMPTY_PATH, - linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME, - &stx, - ); - - return switch (linux.E.init(rc)) { - .SUCCESS => Stat.fromLinux(stx), - .ACCES => unreachable, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .LOOP => unreachable, - .NAMETOOLONG => unreachable, - .NOENT => unreachable, - .NOMEM => error.SystemResources, - .NOTDIR => unreachable, - else => |err| posix.unexpectedErrno(err), - }; - } - - const st = try posix.fstat(self.handle); - return Stat.fromPosix(st); + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.io(); + return Io.File.stat(.{ .handle = self.handle }, io); } pub const ChmodError = posix.FChmodError; @@ -785,8 +757,8 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError return posix.pwritev(self.handle, iovecs, offset); } -/// Deprecated in favor of `std.Io.File.Reader`. -pub const Reader = std.Io.File.Reader; +/// Deprecated in favor of `Io.File.Reader`. +pub const Reader = Io.File.Reader; pub const Writer = struct { file: File, @@ -799,7 +771,7 @@ pub const Writer = struct { copy_file_range_err: ?CopyFileRangeError = null, fcopyfile_err: ?FcopyfileError = null, seek_err: ?Writer.SeekError = null, - interface: std.Io.Writer, + interface: Io.Writer, pub const Mode = Reader.Mode; @@ -845,13 +817,13 @@ pub const Writer = struct { }; } - pub fn initInterface(buffer: []u8) std.Io.Writer { + pub fn initInterface(buffer: []u8) Io.Writer { return .{ .vtable = &.{ .drain = drain, .sendFile = switch (builtin.zig_backend) { else => sendFile, - .stage2_aarch64 => std.Io.Writer.unimplementedSendFile, + .stage2_aarch64 => Io.Writer.unimplementedSendFile, }, }, .buffer = buffer, @@ -859,7 +831,7 @@ pub const Writer = struct { } /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted - pub fn moveToReader(w: *Writer, io: std.Io) Reader { + pub fn moveToReader(w: *Writer, io: Io) Reader { defer w.* = undefined; return .{ .io = io, @@ -871,7 +843,7 @@ pub const Writer = struct { }; } - pub fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); const handle = w.file.handle; const buffered = io_w.buffered(); @@ -1021,10 +993,10 @@ pub const Writer = struct { } pub fn sendFile( - io_w: *std.Io.Writer, - file_reader: *std.Io.File.Reader, - limit: std.Io.Limit, - ) std.Io.Writer.FileError!usize { + io_w: *Io.Writer, + file_reader: *Io.File.Reader, + limit: Io.Limit, + ) Io.Writer.FileError!usize { const reader_buffered = file_reader.interface.buffered(); if (reader_buffered.len >= @intFromEnum(limit)) return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); @@ -1288,16 +1260,16 @@ pub const Writer = struct { } fn sendFileBuffered( - io_w: *std.Io.Writer, - file_reader: *std.Io.File.Reader, + io_w: *Io.Writer, + file_reader: *Io.File.Reader, reader_buffered: []const u8, - ) std.Io.Writer.FileError!usize { + ) Io.Writer.FileError!usize { const n = try drain(io_w, &.{reader_buffered}, 1); file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; return n; } - pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || std.Io.Writer.Error)!void { + pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void { try w.interface.flush(); try seekToUnbuffered(w, offset); } @@ -1321,7 +1293,7 @@ pub const Writer = struct { } } - pub const EndError = SetEndPosError || std.Io.Writer.Error; + pub const EndError = SetEndPosError || Io.Writer.Error; /// Flushes any buffered data and sets the end position of the file. /// @@ -1352,14 +1324,14 @@ pub const Writer = struct { /// /// Positional is more threadsafe, since the global seek position is not /// affected. -pub fn reader(file: File, io: std.Io, buffer: []u8) Reader { +pub fn reader(file: File, io: Io, buffer: []u8) Reader { return .init(.{ .handle = file.handle }, io, buffer); } /// Positional is more threadsafe, since the global seek position is not /// affected, but when such syscalls are not available, preemptively /// initializing in streaming mode skips a failed syscall. -pub fn readerStreaming(file: File, io: std.Io, buffer: []u8) Reader { +pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader { return .initStreaming(.{ .handle = file.handle }, io, buffer); } @@ -1541,10 +1513,10 @@ pub fn downgradeLock(file: File) LockError!void { } } -pub fn adaptToNewApi(file: File) std.Io.File { +pub fn adaptToNewApi(file: File) Io.File { return .{ .handle = file.handle }; } -pub fn adaptFromNewApi(file: std.Io.File) File { +pub fn adaptFromNewApi(file: Io.File) File { return .{ .handle = file.handle }; } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index c5e61749b2..e4cd040703 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -4458,14 +4458,7 @@ pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult { } } -pub const FStatError = error{ - SystemResources, - - /// In WASI, this error may occur when the file descriptor does - /// not hold the required rights to get its filestat information. - AccessDenied, - PermissionDenied, -} || UnexpectedError; +pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. pub fn fstat(fd: fd_t) FStatError!Stat { |
