diff --git a/src/bin_export.zig b/src/bin_export.zig index a10128b..0972ddd 100644 --- a/src/bin_export.zig +++ b/src/bin_export.zig @@ -10,10 +10,10 @@ const ui = @import("ui.zig"); const c = @import("c.zig").c; pub const global = struct { - var fd: std.fs.File = undefined; + var fd: std.Io.File = undefined; var index: std.ArrayListUnmanaged(u8) = .empty; var file_off: u64 = 0; - var lock: std.Thread.Mutex = .{}; + var lock: std.Io.Mutex = .init; var root_itemref: u64 = 0; }; @@ -125,8 +125,8 @@ pub const Thread = struct { var block = createBlock(t); defer block.deinit(main.allocator); - global.lock.lock(); - defer global.lock.unlock(); + global.lock.lockUncancelable(main.io); + defer global.lock.unlock(main.io); // This can only really happen when the root path exceeds our block size, // in which case we would probably have error'ed out earlier anyway. if (expected_len > t.buf.len) ui.die("Error writing data: path too long.\n", .{}); @@ -135,7 +135,7 @@ pub const Thread = struct { if (global.file_off >= (1<<40)) ui.die("Export data file has grown too large, please report a bug.\n", .{}); global.index.items[4..][t.block_num*8..][0..8].* = bigu64((global.file_off << 24) + block.items.len); global.file_off += block.items.len; - global.fd.writeAll(block.items) catch |e| + global.fd.writeStreamingAll(main.io, block.items) catch |e| ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) }); } @@ -246,7 +246,7 @@ pub const Dir = struct { // I'm not expecting much lock contention, but it's possible to turn // last_item into an atomic integer and other fields could be split up for // subdir use. - lock: std.Thread.Mutex = .{}, + lock: std.Io.Mutex = .init, last_sub: ?u64 = null, stat: sink.Stat, items: u64 = 0, @@ -268,8 +268,8 @@ pub const Dir = struct { pub fn addSpecial(d: *Dir, t: *Thread, name: []const u8, sp: model.EType) void { - d.lock.lock(); - defer d.lock.unlock(); + d.lock.lockUncancelable(main.io); + defer d.lock.unlock(main.io); d.items += 1; if (sp == .err) d.suberr = true; d.last_sub = t.itemStart(sp, d.last_sub, name); @@ -277,8 +277,8 @@ pub const Dir = struct { } pub fn addStat(d: *Dir, t: *Thread, name: []const u8, stat: *const sink.Stat) void { - d.lock.lock(); - defer d.lock.unlock(); + d.lock.lockUncancelable(main.io); + defer d.lock.unlock(main.io); d.items += 1; if (stat.etype != .link) { d.size +|= stat.size; @@ -309,8 +309,8 @@ pub const Dir = struct { } pub fn addDir(d: *Dir, stat: *const sink.Stat) Dir { - d.lock.lock(); - defer d.lock.unlock(); + d.lock.lockUncancelable(main.io); + defer d.lock.unlock(main.io); d.items += 1; d.size +|= stat.size; d.blocks +|= stat.blocks; @@ -318,8 +318,8 @@ pub const Dir = struct { } pub fn setReadError(d: *Dir) void { - d.lock.lock(); - defer d.lock.unlock(); + d.lock.lockUncancelable(main.io); + defer d.lock.unlock(main.io); d.err = true; } @@ -375,8 +375,8 @@ pub const Dir = struct { } pub fn final(d: *Dir, t: *Thread, name: []const u8, parent: ?*Dir) void { - if (parent) |p| p.lock.lock(); - defer if (parent) |p| p.lock.unlock(); + if (parent) |p| p.lock.lockUncancelable(main.io); + defer if (parent) |p| p.lock.unlock(main.io); if (parent) |p| { // Different dev? Don't merge the 'inodes' sets, just count the @@ -450,16 +450,16 @@ pub fn done(threads: []sink.Thread) void { global.index.appendSlice(main.allocator, &bigu64(global.root_itemref)) catch unreachable; global.index.appendSlice(main.allocator, &blockHeader(1, @intCast(global.index.items.len + 4))) catch unreachable; global.index.items[0..4].* = blockHeader(1, @intCast(global.index.items.len)); - global.fd.writeAll(global.index.items) catch |e| + global.fd.writeStreamingAll(main.io, global.index.items) catch |e| ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) }); global.index.clearAndFree(main.allocator); - global.fd.close(); + global.fd.close(main.io); } -pub fn setupOutput(fd: std.fs.File) void { +pub fn setupOutput(fd: std.Io.File) void { global.fd = fd; - fd.writeAll(SIGNATURE) catch |e| + fd.writeStreamingAll(main.io, SIGNATURE) catch |e| ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) }); global.file_off = 8; diff --git a/src/bin_reader.zig b/src/bin_reader.zig index cdbe117..31b989a 100644 --- a/src/bin_reader.zig +++ b/src/bin_reader.zig @@ -39,7 +39,7 @@ const ItemKey = bin_export.ItemKey; // This file only implements (2) at the moment. pub const global = struct { - var fd: std.fs.File = undefined; + var fd: std.Io.File = undefined; var index: []u8 = undefined; var blocks: [8]Block = [1]Block{.{}}**8; var counter: u64 = 0; @@ -97,7 +97,7 @@ fn readBlock(num: u32) []const u8 { // Only read the compressed data part, assume block header, number and footer are correct. const buf = main.allocator.alloc(u8, @intCast(len - 12)) catch unreachable; defer main.allocator.free(buf); - const rdlen = global.fd.preadAll(buf, off + 8) + const rdlen = global.fd.readPositionalAll(main.io, buf, off + 8) catch |e| ui.die("Error reading from file: {s}\n", .{ui.errorString(e)}); if (rdlen != buf.len) die(); @@ -233,8 +233,8 @@ const CborVal = struct { fn etype(v: *const CborVal) model.EType { const n = v.int(i32); - return std.meta.intToEnum(model.EType, n) - catch if (n < 0) .pattern else .nonreg; + return std.enums.fromInt(model.EType, n) + orelse if (n < 0) .pattern else .nonreg; } fn itemref(v: *const CborVal, cur: u64) u64 { @@ -501,21 +501,19 @@ pub fn import() void { } // Assumes that the file signature has already been read and validated. -pub fn open(fd: std.fs.File) !void { +pub fn open(fd: std.Io.File) !void { global.fd = fd; - // Do not use fd.getEndPos() because that requires newer kernels supporting statx() #261. - try fd.seekFromEnd(0); - const size = try fd.getPos(); + const size = try fd.length(main.io); if (size < 16) return error.EndOfStream; // Read index block var buf: [4]u8 = undefined; - if (try fd.preadAll(&buf, size - 4) != 4) return error.EndOfStream; + if (try fd.readPositionalAll(main.io, &buf, size - 4) != 4) return error.EndOfStream; const index_header = bigu32(buf); if ((index_header >> 28) != 1 or (index_header & 7) != 0) die(); const len = (index_header & 0x0fffffff) - 8; // excluding block header & footer if (len >= size) die(); global.index = main.allocator.alloc(u8, len) catch unreachable; - if (try fd.preadAll(global.index, size - len - 4) != global.index.len) return error.EndOfStream; + if (try fd.readPositionalAll(main.io, global.index, size - len - 4) != global.index.len) return error.EndOfStream; } diff --git a/src/browser.zig b/src/browser.zig index c664baf..e380296 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -137,7 +137,7 @@ pub fn loadDir(next_sel: u64) void { // XXX: The current dir listing is wiped before loading the new one, which // causes the screen to flicker a bit when the loading indicator is drawn. // Should we keep the old listing around? - main.event_delay_timer.reset(); + main.event_delay_timer = std.Io.Timestamp.now(main.io, .awake); _ = dir_alloc.reset(.free_all); dir_items.shrinkRetainingCapacity(0); dir_refs.shrinkRetainingCapacity(0); diff --git a/src/delete.zig b/src/delete.zig index 5d4d4af..595503c 100644 --- a/src/delete.zig +++ b/src/delete.zig @@ -42,30 +42,30 @@ fn err(e: anyerror) bool { return main.state != .delete; } -fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool { +fn deleteItem(dir: std.Io.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool { entry = ptr.*.?; main.handleEvent(false, false); if (main.state != .delete) return true; if (entry.dir()) |d| { - var fd = dir.openDirZ(path, .{ .no_follow = true, .iterate = false }) catch |e| return err(e); + var fd = dir.openDir(main.io, path, .{ .follow_symlinks = false, .iterate = false }) catch |e| return err(e); var it = &d.sub.ptr; parent = d; defer parent = parent.parent.?; while (it.*) |n| { if (deleteItem(fd, n.name(), it)) { - fd.close(); + fd.close(main.io); return true; } if (it.* == n) // item deletion failed, make sure to still advance to next it = &n.next.ptr; } - fd.close(); - dir.deleteDirZ(path) catch |e| + fd.close(main.io); + dir.deleteDir(main.io, path) catch |e| return if (e != error.DirNotEmpty or d.sub.ptr == null) err(e) else false; } else - dir.deleteFileZ(path) catch |e| return err(e); + dir.deleteFile(main.io, path) catch |e| return err(e); ptr.*.?.zeroStats(parent); ptr.* = ptr.*.?.next.ptr; return false; @@ -74,7 +74,7 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) // Returns true if the item has been deleted successfully. fn deleteCmd(path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool { { - var env = std.process.getEnvMap(main.allocator) catch unreachable; + var env = main.environ.createMap(main.allocator) catch unreachable; defer env.deinit(); env.put("NCDU_DELETE_PATH", path) catch unreachable; @@ -86,7 +86,7 @@ fn deleteCmd(path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool { ui.runCmd(&.{"/bin/sh", "-c", cmd}, null, &env, true); } - const stat = scan.statAt(std.fs.cwd(), path, false, null) catch { + const stat = scan.statAt(std.Io.Dir.cwd(), path, false, null) catch { // Stat failed. Would be nice to display an error if it's not // 'FileNotFound', but w/e, let's just assume the item has been // deleted as expected. @@ -144,7 +144,7 @@ pub fn delete() ?*model.Entry { path.appendSlice(main.allocator, entry.name()) catch unreachable; if (main.config.delete_command.len == 0) { - _ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path, main.allocator), it); + _ = deleteItem(std.Io.Dir.cwd(), util.arrayListBufZ(&path, main.allocator), it); model.inodes.addAllStats(); return if (it.* == e) e else next_sel; } else { diff --git a/src/exclude.zig b/src/exclude.zig index 2376640..f3b094f 100644 --- a/src/exclude.zig +++ b/src/exclude.zig @@ -55,7 +55,7 @@ const Pattern = struct { } fn parse(pat_: []const u8) *const Pattern { - var pat = std.mem.trimLeft(u8, pat_, "/"); + var pat = std.mem.trimStart(u8, pat_, "/"); const top = main.allocator.create(Pattern) catch unreachable; var tail = top; tail.sub = null; @@ -150,7 +150,7 @@ fn PatternList(comptime withsub: bool) type { const e = self.literals.getOrPut(main.allocator, pat) catch unreachable; if (!e.found_existing) { e.key_ptr.* = pat; - e.value_ptr.* = if (withsub) .{} else {}; + e.value_ptr.* = if (withsub) .empty else {}; } if (!withsub and !pat.isdir and e.key_ptr.*.isdir) e.key_ptr.* = pat; if (withsub) { diff --git a/src/json_export.zig b/src/json_export.zig index 3937b3f..8d2a7e9 100644 --- a/src/json_export.zig +++ b/src/json_export.zig @@ -42,7 +42,7 @@ const ZstdWriter = struct { main.allocator.destroy(w); } - fn write(w: *ZstdWriter, f: std.fs.File, in: []const u8, flush: bool) !void { + fn write(w: *ZstdWriter, f: std.Io.File, in: []const u8, flush: bool) !void { var arg = c.ZSTD_inBuffer{ .src = in.ptr, .size = in.len, @@ -52,7 +52,7 @@ const ZstdWriter = struct { const v = c.ZSTD_compressStream2(w.ctx, &w.out, &arg, if (flush) c.ZSTD_e_end else c.ZSTD_e_continue); if (c.ZSTD_isError(v) != 0) return error.ZstdCompressError; if (flush or w.out.pos > w.outbuf.len / 2) { - try f.writeAll(w.outbuf[0..w.out.pos]); + try f.writeStreamingAll(main.io, w.outbuf[0..w.out.pos]); w.out.pos = 0; } if (!flush and arg.pos == arg.size) break; @@ -62,7 +62,7 @@ const ZstdWriter = struct { }; pub const Writer = struct { - fd: std.fs.File, + fd: std.Io.File, zstd: ?*ZstdWriter = null, // Must be large enough to hold PATH_MAX*6 plus some overhead. // (The 6 is because, in the worst case, every byte expands to a "\u####" @@ -78,7 +78,7 @@ pub const Writer = struct { // in which case we would probably have error'ed out earlier anyway. if (bytes > ctx.buf.len) ui.die("Error writing JSON export: path too long.\n", .{}); const buf = ctx.buf[0..ctx.off]; - (if (ctx.zstd) |z| z.write(ctx.fd, buf, bytes == 0) else ctx.fd.writeAll(buf)) catch |e| + (if (ctx.zstd) |z| z.write(ctx.fd, buf, bytes == 0) else ctx.fd.writeStreamingAll(main.io, buf)) catch |e| ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) }); ctx.off = 0; } @@ -138,12 +138,13 @@ pub const Writer = struct { ctx.write(buf[index..]); } - fn init(out: std.fs.File) *Writer { + fn init(out: std.Io.File) *Writer { var ctx = main.allocator.create(Writer) catch unreachable; ctx.* = .{ .fd = out }; if (main.config.compress) ctx.zstd = ZstdWriter.create(); ctx.write("[1,2,{\"progname\":\"ncdu\",\"progver\":\"" ++ main.program_version ++ "\",\"timestamp\":"); - ctx.writeUint(@intCast(@max(0, std.time.timestamp()))); + const now = std.Io.Timestamp.now(main.io, .real).toSeconds(); + ctx.writeUint(@intCast(@max(0, now))); ctx.writeByte('}'); return ctx; } @@ -261,10 +262,10 @@ pub fn done() void { global.writer.write("]\n"); global.writer.flush(0); if (global.writer.zstd) |z| z.destroy(); - global.writer.fd.close(); + global.writer.fd.close(main.io); main.allocator.destroy(global.writer); } -pub fn setupOutput(out: std.fs.File) void { +pub fn setupOutput(out: std.Io.File) void { global.writer = Writer.init(out); } diff --git a/src/json_import.zig b/src/json_import.zig index 28224e6..d496ac9 100644 --- a/src/json_import.zig +++ b/src/json_import.zig @@ -37,11 +37,11 @@ const ZstdReader = struct { main.allocator.destroy(r); } - fn read(r: *ZstdReader, f: std.fs.File, out: []u8) !usize { + fn read(r: *ZstdReader, f: std.Io.File, out: []u8) !usize { while (true) { if (r.in.size == r.in.pos) { r.in.pos = 0; - r.in.size = try f.read(&r.inbuf); + r.in.size = try f.readStreaming(main.io, &.{&r.inbuf}); if (r.in.size == 0) { if (r.lastret == 0) return 0; return error.ZstdDecompressError; // Early EOF @@ -63,7 +63,7 @@ const ZstdReader = struct { // strings. const Parser = struct { - rd: std.fs.File, + rd: std.Io.File, zstd: ?*ZstdReader = null, rdoff: usize = 0, rdsize: usize = 0, @@ -84,7 +84,7 @@ const Parser = struct { fn fill(p: *Parser) void { p.rdoff = 0; - p.rdsize = (if (p.zstd) |z| z.read(p.rd, &p.buf) else p.rd.read(&p.buf)) catch |e| switch (e) { + p.rdsize = (if (p.zstd) |z| z.read(p.rd, &p.buf) else p.rd.readStreaming(main.io, &.{&p.buf})) catch |e| switch (e) { error.IsDir => p.die("not a file"), // should be detected at open() time, but no flag for that... error.SystemResources => p.die("out of memory"), error.ZstdDecompressError => p.die("decompression error"), @@ -528,7 +528,7 @@ fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void { } -pub fn import(fd: std.fs.File, head: []const u8) void { +pub fn import(fd: std.Io.File, head: []const u8) void { const sink_threads = sink.createThreads(1); defer sink.done(); diff --git a/src/main.zig b/src/main.zig index 7301586..067570e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -118,8 +118,14 @@ pub const config = struct { pub var state: enum { scan, browse, refresh, shell, delete } = .scan; -const stdin = if (@hasDecl(std.io, "getStdIn")) std.io.getStdIn() else std.fs.File.stdin(); -const stdout = if (@hasDecl(std.io, "getStdOut")) std.io.getStdOut() else std.fs.File.stdout(); +const stdin = std.Io.File.stdin(); +const stdout = std.Io.File.stdout(); +pub var io: std.Io = undefined; +pub var environ: std.process.Environ = .empty; + +pub fn getenvZ(key: []const u8) ?[:0]const u8 { + return environ.getPosix(key); +} // Simple generic argument parser, supports getopt_long() style arguments. const Args = struct { @@ -323,15 +329,15 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) !void { } fn tryReadArgsFile(path: [:0]const u8) void { - var f = std.fs.cwd().openFileZ(path, .{}) catch |e| switch (e) { + var f = std.Io.Dir.cwd().openFile(io, path, .{}) catch |e| switch (e) { error.FileNotFound => return, error.NotDir => return, else => ui.die("Error opening {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) }), }; - defer f.close(); + defer f.close(io); var line_buf: [4096]u8 = undefined; - var line_rd = util.LineReader.init(f, &line_buf); + var line_rd = util.LineReader.init(f, io, &line_buf); while (true) { const line_ = (line_rd.read() catch |e| @@ -351,7 +357,7 @@ fn tryReadArgsFile(path: [:0]const u8) void { if (std.mem.indexOfAny(u8, line, " \t=")) |i| { arglist[argc] = allocator.dupeZ(u8, line[0..i]) catch unreachable; argc += 1; - line = std.mem.trimLeft(u8, line[i+1..], &std.ascii.whitespace); + line = std.mem.trimStart(u8, line[i+1..], &std.ascii.whitespace); } arglist[argc] = allocator.dupeZ(u8, line) catch unreachable; argc += 1; @@ -371,12 +377,12 @@ fn tryReadArgsFile(path: [:0]const u8) void { } fn version() noreturn { - stdout.writeAll("ncdu " ++ program_version ++ "\n") catch {}; + stdout.writeStreamingAll(io, "ncdu " ++ program_version ++ "\n") catch {}; std.process.exit(0); } fn help() noreturn { - stdout.writeAll( + stdout.writeStreamingAll(io, \\ncdu \\ \\Mode selection: @@ -435,11 +441,11 @@ fn help() noreturn { fn readExcludeFile(path: [:0]const u8) !void { - const f = try std.fs.cwd().openFileZ(path, .{}); - defer f.close(); + const f = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f.close(io); var line_buf: [4096]u8 = undefined; - var line_rd = util.LineReader.init(f, &line_buf); + var line_rd = util.LineReader.init(f, io, &line_buf); while (try line_rd.read()) |line| { if (line.len > 0) exclude.addPattern(line); @@ -449,21 +455,24 @@ fn readExcludeFile(path: [:0]const u8) !void { fn readImport(path: [:0]const u8) !void { const fd = if (std.mem.eql(u8, "-", path)) stdin - else try std.fs.cwd().openFileZ(path, .{}); - errdefer fd.close(); + else try std.Io.Dir.cwd().openFile(io, path, .{}); + errdefer fd.close(io); var buf: [8]u8 = undefined; - if (8 != try fd.readAll(&buf)) return error.EndOfStream; + if (8 != try fd.readStreaming(io, &.{&buf})) return error.EndOfStream; if (std.mem.eql(u8, &buf, bin_export.SIGNATURE)) { try bin_reader.open(fd); config.binreader = true; } else { json_import.import(fd, &buf); - fd.close(); + fd.close(io); } } -pub fn main() void { +pub fn main(init: std.process.Init) void { + io = init.io; + environ = init.minimal.environ; + ui.main_thread = std.Thread.getCurrentId(); // Grab thousands_sep from the current C locale. @@ -477,7 +486,7 @@ pub fn main() void { } const loadConf = blk: { - var args = std.process.ArgIteratorPosix.init(); + var args = std.process.Args.Iterator.init(init.minimal.args); while (args.next()) |a| if (std.mem.eql(u8, a, "--ignore-config")) break :blk false; @@ -487,11 +496,11 @@ pub fn main() void { if (loadConf) { tryReadArgsFile("/etc/ncdu.conf"); - if (std.posix.getenvZ("XDG_CONFIG_HOME")) |p| { + if (getenvZ("XDG_CONFIG_HOME")) |p| { const path = std.fs.path.joinZ(allocator, &.{p, "ncdu", "config"}) catch unreachable; defer allocator.free(path); tryReadArgsFile(path); - } else if (std.posix.getenvZ("HOME")) |p| { + } else if (getenvZ("HOME")) |p| { const path = std.fs.path.joinZ(allocator, &.{p, ".config", "ncdu", "config"}) catch unreachable; defer allocator.free(path); tryReadArgsFile(path); @@ -504,8 +513,10 @@ pub fn main() void { var export_bin: ?[:0]const u8 = null; var quit_after_scan = false; { - const arglist = std.process.argsAlloc(allocator) catch unreachable; - defer std.process.argsFree(allocator, arglist); + const raw_args = init.minimal.args.vector; + const arglist = allocator.alloc([:0]const u8, raw_args.len) catch unreachable; + defer allocator.free(arglist); + for (arglist, raw_args) |*arg, raw_arg| arg.* = std.mem.sliceTo(raw_arg, 0); var args = Args.init(arglist); _ = args.next() catch unreachable; // program name while (args.next() catch unreachable) |opt| { @@ -535,8 +546,8 @@ pub fn main() void { if (@import("builtin").os.tag != .linux and config.exclude_kernfs) ui.die("The --exclude-kernfs flag is currently only supported on Linux.\n", .{}); - const out_tty = stdout.isTty(); - const in_tty = stdin.isTty(); + const out_tty = stdout.isTty(io) catch false; + const in_tty = stdin.isTty(io) catch false; if (config.scan_ui == null) { if (export_json orelse export_bin) |f| { if (!out_tty or std.mem.eql(u8, f, "-")) config.scan_ui = .none @@ -547,20 +558,20 @@ pub fn main() void { ui.die("Standard input is not a TTY. Did you mean to import a file using '-f -'?\n", .{}); config.nc_tty = !in_tty or (if (export_json orelse export_bin) |f| std.mem.eql(u8, f, "-") else false); - event_delay_timer = std.time.Timer.start() catch unreachable; + event_delay_timer = std.Io.Timestamp.now(io, .awake); defer ui.deinit(); if (export_json) |f| { const file = if (std.mem.eql(u8, f, "-")) stdout - else std.fs.cwd().createFileZ(f, .{}) + else std.Io.Dir.cwd().createFile(io, f, .{}) catch |e| ui.die("Error opening export file: {s}.\n", .{ui.errorString(e)}); json_export.setupOutput(file); sink.global.sink = .json; } else if (export_bin) |f| { const file = if (std.mem.eql(u8, f, "-")) stdout - else std.fs.cwd().createFileZ(f, .{}) + else std.Io.Dir.cwd().createFile(io, f, .{}) catch |e| ui.die("Error opening export file: {s}.\n", .{ui.errorString(e)}); bin_export.setupOutput(file); sink.global.sink = .bin; @@ -572,10 +583,13 @@ pub fn main() void { if (config.binreader and (export_json != null or export_bin != null)) bin_reader.import(); } else { - var buf: [std.fs.max_path_bytes+1]u8 = @splat(0); - const path = - if (std.posix.realpathZ(scan_dir orelse ".", buf[0..buf.len-1])) |p| buf[0..p.len:0] - else |_| (scan_dir orelse "."); + var buf: [std.Io.Dir.max_path_bytes+1]u8 = @splat(0); + const path = blk: { + const len = std.Io.Dir.cwd().realPathFile(io, scan_dir orelse ".", buf[0..buf.len-1]) catch + break :blk scan_dir orelse "."; + buf[len] = 0; + break :blk buf[0..len:0]; + }; scan.scan(path) catch |e| ui.die("Error opening directory: {s}.\n", .{ui.errorString(e)}); } if (quit_after_scan or export_json != null or export_bin != null) return; @@ -604,8 +618,8 @@ pub fn main() void { browser.loadDir(0); }, .shell => { - const shell = std.posix.getenvZ("NCDU_SHELL") orelse std.posix.getenvZ("SHELL") orelse "/bin/sh"; - var env = std.process.getEnvMap(allocator) catch unreachable; + const shell = getenvZ("NCDU_SHELL") orelse getenvZ("SHELL") orelse "/bin/sh"; + var env = environ.createMap(allocator) catch unreachable; defer env.deinit(); ui.runCmd(&.{shell}, browser.dir_path, &env, false); state = .browse; @@ -622,14 +636,15 @@ pub fn main() void { } } -pub var event_delay_timer: std.time.Timer = undefined; +pub var event_delay_timer: std.Io.Timestamp = .zero; // Draw the screen and handle the next input event. // In non-blocking mode, screen drawing is rate-limited to keep this function fast. pub fn handleEvent(block: bool, force_draw: bool) void { while (ui.oom_threads.load(.monotonic) > 0) ui.oom(); - if (block or force_draw or event_delay_timer.read() > config.update_delay) { + const elapsed = event_delay_timer.durationTo(std.Io.Timestamp.now(io, .awake)).toNanoseconds(); + if (block or force_draw or elapsed > config.update_delay) { if (ui.inited) _ = c.erase(); switch (state) { .scan, .refresh => sink.draw(), @@ -638,7 +653,7 @@ pub fn handleEvent(block: bool, force_draw: bool) void { .shell => unreachable, } if (ui.inited) _ = c.refresh(); - event_delay_timer.reset(); + event_delay_timer = std.Io.Timestamp.now(io, .awake); } if (!ui.inited) { std.debug.assert(!block); diff --git a/src/mem_sink.zig b/src/mem_sink.zig index 37b0ece..9973fb7 100644 --- a/src/mem_sink.zig +++ b/src/mem_sink.zig @@ -28,8 +28,8 @@ pub fn statToEntry(stat: *const sink.Stat, e: *model.Entry, parent: *model.Dir) l.parent = parent; l.ino = stat.ino; l.pack.nlink = stat.nlink; - model.inodes.lock.lock(); - defer model.inodes.lock.unlock(); + model.inodes.lock.lockUncancelable(main.io); + defer model.inodes.lock.unlock(main.io); l.addLink(); } if (e.ext()) |ext| ext.* = stat.ext; @@ -49,7 +49,7 @@ pub const Dir = struct { items: u32 = 0, mtime: u64 = 0, suberr: bool = false, - lock: std.Thread.Mutex = .{}, + lock: std.Io.Mutex = .init, const Map = std.HashMap(*model.Entry, void, HashContext, 80); @@ -161,8 +161,8 @@ pub const Dir = struct { // Add own counts to parent if (parent) |p| { - p.lock.lock(); - defer p.lock.unlock(); + p.lock.lockUncancelable(main.io); + defer p.lock.unlock(main.io); p.blocks +|= self.dir.entry.pack.blocks - self.own_blocks; p.bytes +|= self.dir.entry.size - self.own_bytes; p.items +|= self.dir.items; diff --git a/src/model.zig b/src/model.zig index b428d24..87c94a1 100644 --- a/src/model.zig +++ b/src/model.zig @@ -211,7 +211,7 @@ pub const Dir = extern struct { // (Old C habits die hard) name: [0]u8 = undefined, - pub const Packed = packed struct { + pub const Packed = packed struct(u32) { // Indexes into the global 'devices.list' array dev: DevId = 0, err: bool = false, @@ -350,15 +350,15 @@ pub const Ext = extern struct { // List of st_dev entries. Those are typically 64bits, but that's quite a waste // of space when a typical scan won't cover many unique devices. pub const devices = struct { - var lock = std.Thread.Mutex{}; + var lock = std.Io.Mutex.init; // id -> dev pub var list: std.ArrayListUnmanaged(u64) = .empty; // dev -> id var lookup = std.AutoHashMap(u64, DevId).init(main.allocator); pub fn getId(dev: u64) DevId { - lock.lock(); - defer lock.unlock(); + lock.lockUncancelable(main.io); + defer lock.unlock(main.io); const d = lookup.getOrPut(dev) catch unreachable; if (!d.found_existing) { if (list.items.len >= std.math.maxInt(DevId)) ui.die("Maximum number of device identifiers exceeded.\n", .{}); @@ -386,7 +386,7 @@ pub const inodes = struct { var uncounted = std.HashMap(*Link, void, HashContext, 80).init(main.allocator); var uncounted_full = true; // start with true for the initial scan - pub var lock = std.Thread.Mutex{}; + pub var lock = std.Io.Mutex.init; const HashContext = struct { pub fn hash(_: @This(), l: *Link) u64 { diff --git a/src/scan.zig b/src/scan.zig index d98bf73..f61d4bf 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -12,9 +12,9 @@ const c = @import("c.zig").c; // This function only works on Linux -fn isKernfs(dir: std.fs.Dir) bool { +fn isKernfs(dir: std.Io.Dir) bool { var buf: c.struct_statfs = undefined; - if (c.fstatfs(dir.fd, &buf) != 0) return false; // silently ignoring errors isn't too nice. + if (c.fstatfs(dir.handle, &buf) != 0) return false; // silently ignoring errors isn't too nice. const iskern = switch (util.castTruncate(u32, buf.f_type)) { // These numbers are documented in the Linux 'statfs(2)' man page, so I assume they're stable. 0x42494e4d, // BINFMTFS_MAGIC @@ -46,10 +46,10 @@ fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fiel } -pub fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: ?*bool) !sink.Stat { +pub fn statAt(parent: std.Io.Dir, name: [:0]const u8, follow: bool, symlink: ?*bool) !sink.Stat { // std.posix.fstatatZ() in Zig 0.14 is not suitable due to https://github.com/ziglang/zig/issues/23463 var stat: std.c.Stat = undefined; - if (std.c.fstatat(parent.fd, name, &stat, if (follow) 0 else std.c.AT.SYMLINK_NOFOLLOW) != 0) { + if (std.c.fstatat(parent.handle, name, &stat, if (follow) 0 else std.c.AT.SYMLINK_NOFOLLOW) != 0) { return switch (std.c._errno().*) { @intFromEnum(std.c.E.NOENT) => error.FileNotFound, @intFromEnum(std.c.E.NAMETOOLONG) => error.NameTooLong, @@ -86,12 +86,12 @@ pub fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: ?*b } -fn isCacheDir(dir: std.fs.Dir) bool { +fn isCacheDir(dir: std.Io.Dir) bool { const sig = "Signature: 8a477f597d28d172789f06886806bc55"; - const f = dir.openFileZ("CACHEDIR.TAG", .{}) catch return false; - defer f.close(); + const f = dir.openFile(main.io, "CACHEDIR.TAG", .{}) catch return false; + defer f.close(main.io); var buf: [sig.len]u8 = undefined; - const len = f.readAll(&buf) catch return false; + const len = f.readStreaming(main.io, &.{&buf}) catch return false; return len == sig.len and std.mem.eql(u8, &buf, sig); } @@ -107,8 +107,8 @@ const State = struct { // impossible for me to predict how that ends up affecting performance. queue: [QUEUE_SIZE]*Dir = undefined, queue_len: std.atomic.Value(usize) = std.atomic.Value(usize).init(0), - queue_lock: std.Thread.Mutex = .{}, - queue_cond: std.Thread.Condition = .{}, + queue_lock: std.Io.Mutex = .init, + queue_cond: std.Io.Condition = .init, threads: []Thread, waiting: usize = 0, @@ -121,28 +121,28 @@ const State = struct { fn tryPush(self: *State, d: *Dir) bool { if (self.queue_len.load(.acquire) == QUEUE_SIZE) return false; { - self.queue_lock.lock(); - defer self.queue_lock.unlock(); + self.queue_lock.lockUncancelable(main.io); + defer self.queue_lock.unlock(main.io); if (self.queue_len.load(.monotonic) == QUEUE_SIZE) return false; const slot = self.queue_len.fetchAdd(1, .monotonic); self.queue[slot] = d; } - self.queue_cond.signal(); + self.queue_cond.signal(main.io); return true; } // Blocks while the queue is empty, returns null when all threads are blocking. fn waitPop(self: *State) ?*Dir { - self.queue_lock.lock(); - defer self.queue_lock.unlock(); + self.queue_lock.lockUncancelable(main.io); + defer self.queue_lock.unlock(main.io); self.waiting += 1; while (self.queue_len.load(.monotonic) == 0) { if (self.waiting == self.threads.len) { - self.queue_cond.broadcast(); + self.queue_cond.broadcast(main.io); return null; } - self.queue_cond.wait(&self.queue_lock); + self.queue_cond.waitUncancelable(main.io, &self.queue_lock); } self.waiting -= 1; @@ -154,13 +154,13 @@ const State = struct { const Dir = struct { - fd: std.fs.Dir, + fd: std.Io.Dir, dev: u64, pat: exclude.Patterns, - it: std.fs.Dir.Iterator, + it: std.Io.Dir.Iterator, sink: *sink.Dir, - fn create(fd: std.fs.Dir, dev: u64, pat: exclude.Patterns, s: *sink.Dir) *Dir { + fn create(fd: std.Io.Dir, dev: u64, pat: exclude.Patterns, s: *sink.Dir) *Dir { const d = main.allocator.create(Dir) catch unreachable; d.* = .{ .fd = fd, @@ -174,7 +174,7 @@ const Dir = struct { fn destroy(d: *Dir, t: *Thread) void { d.pat.deinit(); - d.fd.close(); + d.fd.close(main.io); d.sink.unref(t.sink); main.allocator.destroy(d); } @@ -239,7 +239,7 @@ const Thread = struct { return; } - var edir = dir.fd.openDirZ(name, .{ .no_follow = true, .iterate = true }) catch { + var edir = dir.fd.openDir(main.io, name, .{ .follow_symlinks = false, .iterate = true }) catch { const s = dir.sink.addDir(t.sink, name, &stat); s.setReadError(t.sink); s.unref(t.sink); @@ -251,14 +251,14 @@ const Thread = struct { and stat.dev != dir.dev and isKernfs(edir) ) { - edir.close(); + edir.close(main.io); dir.sink.addSpecial(t.sink, name, .kernfs); return; } if (main.config.exclude_caches and isCacheDir(edir)) { dir.sink.addSpecial(t.sink, name, .pattern); - edir.close(); + edir.close(main.io); return; } @@ -279,7 +279,7 @@ const Thread = struct { t.sink.setDir(d.sink); if (t.thread_num == 0) main.handleEvent(false, false); - const entry = d.it.next() catch blk: { + const entry = d.it.next(main.io) catch blk: { dir.sink.setReadError(t.sink); break :blk null; }; @@ -299,8 +299,8 @@ pub fn scan(path: [:0]const u8) !void { defer sink.done(); var symlink: bool = undefined; - const stat = try statAt(std.fs.cwd(), path, true, &symlink); - const fd = try std.fs.cwd().openDirZ(path, .{ .iterate = true }); + const stat = try statAt(std.Io.Dir.cwd(), path, true, &symlink); + const fd = try std.Io.Dir.cwd().openDir(main.io, path, .{ .iterate = true }); var state = State{ .threads = main.allocator.alloc(Thread, main.config.threads) catch unreachable, diff --git a/src/sink.zig b/src/sink.zig index 71e95cd..ca0f595 100644 --- a/src/sink.zig +++ b/src/sink.zig @@ -86,8 +86,8 @@ pub const Dir = struct { .bin => |*b| b.addSpecial(&t.sink.bin, name, sp), } if (sp == .err) { - global.last_error_lock.lock(); - defer global.last_error_lock.unlock(); + global.last_error_lock.lockUncancelable(main.io); + defer global.last_error_lock.unlock(main.io); if (global.last_error) |p| main.allocator.free(p); const p = d.path(); global.last_error = std.fs.path.joinZ(main.allocator, &.{ p, name }) catch unreachable; @@ -133,8 +133,8 @@ pub const Dir = struct { .json => |*j| j.setReadError(), .bin => |*b| b.setReadError(), } - global.last_error_lock.lock(); - defer global.last_error_lock.unlock(); + global.last_error_lock.lockUncancelable(main.io); + defer global.last_error_lock.unlock(main.io); if (global.last_error) |p| main.allocator.free(p); global.last_error = d.path(); } @@ -180,7 +180,7 @@ pub const Dir = struct { pub const Thread = struct { current_dir: ?*Dir = null, - lock: std.Thread.Mutex = .{}, + lock: std.Io.Mutex = .init, // On 32-bit architectures, bytes_seen is protected by the above mutex instead. bytes_seen: std.atomic.Value(u64) = std.atomic.Value(u64).init(0), files_seen: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), @@ -194,8 +194,8 @@ pub const Thread = struct { fn addBytes(t: *Thread, bytes: u64) void { if (@bitSizeOf(usize) >= 64) _ = t.bytes_seen.fetchAdd(bytes, .monotonic) else { - t.lock.lock(); - defer t.lock.unlock(); + t.lock.lockUncancelable(main.io); + defer t.lock.unlock(main.io); t.bytes_seen.raw += bytes; } } @@ -203,15 +203,15 @@ pub const Thread = struct { fn getBytes(t: *Thread) u64 { if (@bitSizeOf(usize) >= 64) return t.bytes_seen.load(.monotonic) else { - t.lock.lock(); - defer t.lock.unlock(); + t.lock.lockUncancelable(main.io); + defer t.lock.unlock(main.io); return t.bytes_seen.raw; } } pub fn setDir(t: *Thread, d: ?*Dir) void { - t.lock.lock(); - defer t.lock.unlock(); + t.lock.lockUncancelable(main.io); + defer t.lock.unlock(main.io); t.current_dir = d; } }; @@ -223,7 +223,7 @@ pub const global = struct { pub var sink: enum { json, mem, bin } = .mem; pub var last_error: ?[:0]u8 = null; - var last_error_lock = std.Thread.Mutex{}; + var last_error_lock = std.Io.Mutex.init; var need_confirm_quit = false; }; @@ -292,16 +292,15 @@ fn drawConsole() void { var ansi: ?bool = null; var lines_written: usize = 0; }; - const stderr = if (@hasDecl(std.io, "getStdErr")) std.io.getStdErr() else std.fs.File.stderr(); + const stderr = std.Io.File.stderr(); const ansi = st.ansi orelse blk: { - const t = stderr.supportsAnsiEscapeCodes(); + const t = stderr.supportsAnsiEscapeCodes(main.io) catch false; st.ansi = t; break :blk t; }; var buf: [4096]u8 = undefined; - var strm = std.io.fixedBufferStream(buf[0..]); - var wr = strm.writer(); + var wr = std.Io.Writer.fixed(&buf); while (ansi and st.lines_written > 0) { wr.writeAll("\x1b[1F\x1b[2K") catch {}; st.lines_written -= 1; @@ -327,8 +326,8 @@ fn drawConsole() void { for (global.threads, 0..) |*t, i| { const dir = blk: { - t.lock.lock(); - defer t.lock.unlock(); + t.lock.lockUncancelable(main.io); + defer t.lock.unlock(main.io); break :blk if (t.current_dir) |d| d.path() else null; }; wr.print(" #{}: {s}\n", .{i+1, ui.shorten(ui.toUtf8(dir orelse "(waiting)"), 73)}) catch {}; @@ -337,7 +336,7 @@ fn drawConsole() void { } } - stderr.writeAll(strm.getWritten()) catch {}; + stderr.writeStreamingAll(main.io, wr.buffered()) catch {}; } @@ -369,8 +368,8 @@ fn drawProgress() void { box.move(3+@as(u32, @intCast(i)), 4); const dir = blk: { const t = &global.threads[i]; - t.lock.lock(); - defer t.lock.unlock(); + t.lock.lockUncancelable(main.io); + defer t.lock.unlock(main.io); break :blk if (t.current_dir) |d| d.path() else null; }; ui.addstr(ui.shorten(ui.toUtf8(dir orelse "(waiting)"), width -| 6)); @@ -378,8 +377,8 @@ fn drawProgress() void { } blk: { - global.last_error_lock.lock(); - defer global.last_error_lock.unlock(); + global.last_error_lock.lockUncancelable(main.io); + defer global.last_error_lock.unlock(main.io); const err = global.last_error orelse break :blk; box.move(4 + numthreads, 2); ui.style(.bold); diff --git a/src/ui.zig b/src/ui.zig index 1436b6e..c7e5e30 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -26,7 +26,9 @@ pub fn quit() noreturn { std.process.exit(0); } -const sleep = if (@hasDecl(std.time, "sleep")) std.time.sleep else std.Thread.sleep; +fn sleep(ns: u64) void { + main.io.sleep(.fromNanoseconds(@intCast(ns)), .awake) catch {}; +} // Should be called when malloc fails. Will show a message to the user, wait // for a second and return to give it another try. @@ -113,7 +115,8 @@ pub fn toUtf8(in: [:0]const u8) [:0]const u8 { } else |_| {} } } else |_| {} - to_utf8_buf.writer(main.allocator).print("\\x{X:0>2}", .{in[i]}) catch unreachable; + var buf: [4]u8 = undefined; + to_utf8_buf.appendSlice(main.allocator, std.fmt.bufPrint(&buf, "\\x{X:0>2}", .{in[i]}) catch unreachable) catch unreachable; i += 1; } return util.arrayListBufZ(&to_utf8_buf, main.allocator); @@ -280,21 +283,13 @@ const styles = [_]StyleDef{ }; pub const Style = lbl: { - var fields: [styles.len]std.builtin.Type.EnumField = undefined; - for (&fields, styles, 0..) |*field, s, i| { - field.* = .{ - .name = s.name, - .value = i, - }; + var field_names: [styles.len][]const u8 = undefined; + var field_values: [styles.len]u8 = undefined; + for (&field_names, &field_values, styles, 0..) |*name, *value, s, i| { + name.* = s.name; + value.* = i; } - break :lbl @Type(.{ - .@"enum" = .{ - .tag_type = u8, - .fields = &fields, - .decls = &[_]std.builtin.Type.Declaration{}, - .is_exhaustive = true, - } - }); + break :lbl @Enum(u8, .exhaustive, &field_names, &field_values); }; const ui = @This(); @@ -643,16 +638,12 @@ pub fn getch(block: bool) i32 { } fn waitInput() void { - if (@hasDecl(std.io, "getStdIn")) { - std.io.getStdIn().reader().skipUntilDelimiterOrEof('\n') catch unreachable; - } else { - var buf: [512]u8 = undefined; - var rd = std.fs.File.stdin().reader(&buf); - _ = rd.interface.discardDelimiterExclusive('\n') catch unreachable; - } + var buf: [512]u8 = undefined; + var rd = std.Io.File.stdin().reader(main.io, &buf); + _ = rd.interface.discardDelimiterExclusive('\n') catch unreachable; } -pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMap, reporterr: bool) void { +pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.Environ.Map, reporterr: bool) void { deinit(); defer init(); @@ -666,24 +657,33 @@ pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMa else env.put("NCDU_LEVEL", "1") catch unreachable; - var child = std.process.Child.init(cmd, main.allocator); - child.cwd = cwd; - child.env_map = env; - - const term = child.spawnAndWait() catch |e| blk: { + var child = std.process.spawn(main.io, .{ + .argv = cmd, + .cwd = if (cwd) |p| .{ .path = p } else .inherit, + .environ_map = env, + }) catch |e| blk: { std.debug.print("Error running command: {s}\n\nPress enter to continue.\n", .{ ui.errorString(e) }); waitInput(); - break :blk std.process.Child.Term{ .Exited = 0 }; + break :blk null; }; + const term = if (child) |*c_| c_.wait(main.io) catch |e| blk: { + std.debug.print("Error running command: {s}\n\nPress enter to continue.\n", .{ ui.errorString(e) }); + waitInput(); + break :blk std.process.Child.Term{ .exited = 0 }; + } else std.process.Child.Term{ .exited = 0 }; const n = switch (term) { - .Exited => "error", - .Signal => "signal", - .Stopped => "stopped", - .Unknown => "unknown", + .exited => "error", + .signal => "signal", + .stopped => "stopped", + .unknown => "unknown", + }; + const v: u32 = switch (term) { + .exited => |v_| v_, + .signal, .stopped => |v_| @intFromEnum(v_), + .unknown => |v_| v_, }; - const v = switch (term) { inline else => |v| v }; - if (term != .Exited or (reporterr and v != 0)) { + if (term != .exited or (reporterr and v != 0)) { std.debug.print("\nCommand returned with {s} code {}.\nPress enter to continue.\n", .{ n, v }); waitInput(); } diff --git a/src/util.zig b/src/util.zig index 607dfb5..1d41b05 100644 --- a/src/util.zig +++ b/src/util.zig @@ -180,7 +180,7 @@ pub fn expanduser(path: []const u8, alloc: std.mem.Allocator) ![:0]u8 { const home_raw = blk: { const pwd = pwd: { if (len == 1) { - if (std.posix.getenvZ("HOME")) |p| break :blk p; + if (std.c.getenv("HOME")) |p| break :blk std.mem.sliceTo(p, 0); break :pwd c.getpwuid(c.getuid()); } else { const name = try alloc.dupeZ(u8, path[1..len]); @@ -193,7 +193,7 @@ pub fn expanduser(path: []const u8, alloc: std.mem.Allocator) ![:0]u8 { break :blk std.mem.span(p); return alloc.dupeZ(u8, path); }; - const home = std.mem.trimRight(u8, home_raw, "/"); + const home = std.mem.trimEnd(u8, home_raw, "/"); if (home.len == 0 and path.len == len) return alloc.dupeZ(u8, "/"); return try std.mem.concatWithSentinel(alloc, u8, &.{ home, path[len..] }, 0); @@ -203,47 +203,14 @@ pub fn expanduser(path: []const u8, alloc: std.mem.Allocator) ![:0]u8 { // Silly abstraction to read a file one line at a time. Only exists to help // with supporting both Zig 0.14 and 0.15, can be removed once 0.14 support is // dropped. -pub const LineReader = if (@hasDecl(std.io, "bufferedReader")) struct { - rd: std.io.BufferedReader(4096, std.fs.File.Reader), - fbs: std.io.FixedBufferStream([]u8), - - pub fn init(f: std.fs.File, buf: []u8) @This() { - return .{ - .rd = std.io.bufferedReader(f.reader()), - .fbs = std.io.fixedBufferStream(buf), - }; - } - - pub fn read(s: *@This()) !?[]u8 { - s.fbs.reset(); - s.rd.reader().streamUntilDelimiter(s.fbs.writer(), '\n', s.fbs.buffer.len) catch |err| switch (err) { - error.EndOfStream => if (s.fbs.getPos() catch unreachable == 0) return null, - else => |e| return e, - }; - return s.fbs.getWritten(); - } +pub const LineReader = struct { + rd: std.Io.File.Reader, -} else struct { - rd: std.fs.File.Reader, - - pub fn init(f: std.fs.File, buf: []u8) @This() { - return .{ .rd = f.readerStreaming(buf) }; + pub fn init(f: std.Io.File, io: std.Io, buf: []u8) @This() { + return .{ .rd = f.readerStreaming(io, buf) }; } pub fn read(s: *@This()) !?[]u8 { - // Can't use takeDelimiter() because that's not available in 0.15.1, - // Can't use takeDelimiterExclusive() because that changed behavior in 0.15.2. - const r = &s.rd.interface; - const result = r.peekDelimiterInclusive('\n') catch |err| switch (err) { - error.EndOfStream => { - const remaining = r.buffer[r.seek..r.end]; - if (remaining.len == 0) return null; - r.toss(remaining.len); - return remaining; - }, - else => |e| return e, - }; - r.toss(result.len); - return result[0 .. result.len - 1]; + return s.rd.interface.takeDelimiter('\n'); } };