From 5129de737ec8ec0afe532f2ac712157333eeb364 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Tue, 19 Aug 2025 14:02:39 +0200 Subject: [PATCH] Zig 0.15: Fix support for new IO interface I've managed to get a single codebase to build with both 0.14 and 0.15 now, but compiling with Zig's native x86_64 backend seems buggy. Need to investigate what's going on there. This is a lazy "just get it to work" migration and avoids the use of 0.15-exclusive APIs. We can probably clean up some code when dropping support for 0.14. --- README.md | 2 +- src/main.zig | 41 ++++++++++++----------------------------- src/model.zig | 3 ++- src/scan.zig | 2 +- src/sink.zig | 2 +- src/ui.zig | 41 +++++++++++++++++++++-------------------- src/util.zig | 41 ++++++++++++++++++++++++++++++++++++++++- 7 files changed, 78 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index b0282ff..8954745 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ C version (1.x). ## Requirements -- Zig 0.14 +- Zig 0.14 or 0.15 - Some sort of POSIX-like OS - ncurses - libzstd diff --git a/src/main.zig b/src/main.zig index 53d4832..d8e3fdc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -118,6 +118,9 @@ 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(); + // Simple generic argument parser, supports getopt_long() style arguments. const Args = struct { lst: []const [:0]const u8, @@ -327,19 +330,13 @@ fn tryReadArgsFile(path: [:0]const u8) void { }; defer f.close(); - var rd_ = std.io.bufferedReader(f.reader()); - const rd = rd_.reader(); - var line_buf: [4096]u8 = undefined; - var line_fbs = std.io.fixedBufferStream(&line_buf); - const line_writer = line_fbs.writer(); + var line_rd = util.LineReader.init(f, &line_buf); - while (true) : (line_fbs.reset()) { - rd.streamUntilDelimiter(line_writer, '\n', line_buf.len) catch |err| switch (err) { - error.EndOfStream => if (line_fbs.getPos() catch unreachable == 0) break, - else => |e| ui.die("Error reading from {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) }), - }; - const line_ = line_fbs.getWritten(); + while (true) { + const line_ = (line_rd.read() catch |e| + ui.die("Error reading from {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) }) + ) orelse break; var argc: usize = 0; var ignerror = false; @@ -374,13 +371,11 @@ fn tryReadArgsFile(path: [:0]const u8) void { } fn version() noreturn { - const stdout = std.io.getStdOut(); stdout.writeAll("ncdu " ++ program_version ++ "\n") catch {}; std.process.exit(0); } fn help() noreturn { - const stdout = std.io.getStdOut(); stdout.writeAll( \\ncdu \\ @@ -443,19 +438,9 @@ fn readExcludeFile(path: [:0]const u8) !void { const f = try std.fs.cwd().openFileZ(path, .{}); defer f.close(); - var rd_ = std.io.bufferedReader(f.reader()); - const rd = rd_.reader(); - var line_buf: [4096]u8 = undefined; - var line_fbs = std.io.fixedBufferStream(&line_buf); - const line_writer = line_fbs.writer(); - - while (true) : (line_fbs.reset()) { - rd.streamUntilDelimiter(line_writer, '\n', line_buf.len) catch |err| switch (err) { - error.EndOfStream => if (line_fbs.getPos() catch unreachable == 0) break, - else => |e| return e, - }; - const line = line_fbs.getWritten(); + var line_rd = util.LineReader.init(f, &line_buf); + while (try line_rd.read()) |line| { if (line.len > 0) exclude.addPattern(line); } @@ -463,12 +448,12 @@ fn readExcludeFile(path: [:0]const u8) !void { fn readImport(path: [:0]const u8) !void { const fd = - if (std.mem.eql(u8, "-", path)) std.io.getStdIn() + if (std.mem.eql(u8, "-", path)) stdin else try std.fs.cwd().openFileZ(path, .{}); errdefer fd.close(); var buf: [8]u8 = undefined; - try fd.reader().readNoEof(&buf); + if (8 != try fd.readAll(&buf)) return error.EndOfStream; if (std.mem.eql(u8, &buf, bin_export.SIGNATURE)) { try bin_reader.open(fd); config.binreader = true; @@ -550,8 +535,6 @@ 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 stdin = std.io.getStdIn(); - const stdout = std.io.getStdOut(); const out_tty = stdout.isTty(); const in_tty = stdin.isTty(); if (config.scan_ui == null) { diff --git a/src/model.zig b/src/model.zig index 834dee5..b428d24 100644 --- a/src/model.zig +++ b/src/model.zig @@ -109,7 +109,8 @@ pub const Entry = extern struct { fn alloc(comptime T: type, allocator: std.mem.Allocator, etype: EType, isext: bool, ename: []const u8) *Entry { const size = (if (isext) @as(usize, @sizeOf(Ext)) else 0) + @sizeOf(T) + ename.len + 1; var ptr = blk: while (true) { - if (allocator.allocWithOptions(u8, size, 1, null)) |p| break :blk p + const alignment = if (@typeInfo(@TypeOf(std.mem.Allocator.allocWithOptions)).@"fn".params[3].type == ?u29) 1 else std.mem.Alignment.@"1"; + if (allocator.allocWithOptions(u8, size, alignment, null)) |p| break :blk p else |_| {} ui.oom(); }; diff --git a/src/scan.zig b/src/scan.zig index 476dbf6..d98bf73 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -91,7 +91,7 @@ fn isCacheDir(dir: std.fs.Dir) bool { const f = dir.openFileZ("CACHEDIR.TAG", .{}) catch return false; defer f.close(); var buf: [sig.len]u8 = undefined; - const len = f.reader().readAll(&buf) catch return false; + const len = f.readAll(&buf) catch return false; return len == sig.len and std.mem.eql(u8, &buf, sig); } diff --git a/src/sink.zig b/src/sink.zig index 50a5ecf..3dd81ca 100644 --- a/src/sink.zig +++ b/src/sink.zig @@ -292,7 +292,7 @@ fn drawConsole() void { var ansi: ?bool = null; var lines_written: usize = 0; }; - const stderr = std.io.getStdErr(); + const stderr = if (@hasDecl(std.io, "getStdErr")) std.io.getStdErr() else std.fs.File.stderr(); const ansi = st.ansi orelse blk: { const t = stderr.supportsAnsiEscapeCodes(); st.ansi = t; diff --git a/src/ui.zig b/src/ui.zig index 07713b2..1436b6e 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -17,8 +17,7 @@ pub var cols: u32 = undefined; pub fn die(comptime fmt: []const u8, args: anytype) noreturn { deinit(); - const stderr = std.io.getStdErr(); - stderr.writer().print(fmt, args) catch {}; + std.debug.print(fmt, args); std.process.exit(1); } @@ -27,6 +26,8 @@ pub fn quit() noreturn { std.process.exit(0); } +const sleep = if (@hasDecl(std.time, "sleep")) std.time.sleep else std.Thread.sleep; + // Should be called when malloc fails. Will show a message to the user, wait // for a second and return to give it another try. // Glitch: this function may be called while we're in the process of drawing @@ -41,14 +42,13 @@ pub fn oom() void { if (main_thread == std.Thread.getCurrentId()) { const haveui = inited; deinit(); - const stderr = std.io.getStdErr(); - stderr.writeAll("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8") catch {}; - std.time.sleep(std.time.ns_per_s); + std.debug.print("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8", .{}); + sleep(std.time.ns_per_s); if (haveui) init(); } else { _ = oom_threads.fetchAdd(1, .monotonic); - std.time.sleep(std.time.ns_per_s); + sleep(std.time.ns_per_s); _ = oom_threads.fetchSub(1, .monotonic); } } @@ -335,8 +335,7 @@ fn updateSize() void { fn clearScr() void { // Send a "clear from cursor to end of screen" instruction, to clear a // potential line left behind from scanning in -1 mode. - const stderr = std.io.getStdErr(); - stderr.writeAll("\x1b[J") catch {}; + std.debug.print("\x1b[J", .{}); } pub fn init() void { @@ -634,7 +633,7 @@ pub fn getch(block: bool) i32 { } if (ch == c.ERR) { if (!block) return 0; - std.time.sleep(10*std.time.ns_per_ms); + sleep(10*std.time.ns_per_ms); continue; } return ch; @@ -643,6 +642,15 @@ pub fn getch(block: bool) i32 { .{ c.strerror(@intFromEnum(std.posix.errno(-1))) }); } +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; + } +} pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMap, reporterr: bool) void { deinit(); @@ -662,14 +670,9 @@ pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMa child.cwd = cwd; child.env_map = env; - const stdin = std.io.getStdIn(); - const stderr = std.io.getStdErr(); const term = child.spawnAndWait() catch |e| blk: { - stderr.writer().print( - "Error running command: {s}\n\nPress enter to continue.\n", - .{ ui.errorString(e) } - ) catch {}; - stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable; + 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 }; }; @@ -681,9 +684,7 @@ pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMa }; const v = switch (term) { inline else => |v| v }; if (term != .Exited or (reporterr and v != 0)) { - stderr.writer().print( - "\nCommand returned with {s} code {}.\nPress enter to continue.\n", .{ n, v } - ) catch {}; - stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable; + 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 96b0512..e2090fb 100644 --- a/src/util.zig +++ b/src/util.zig @@ -196,5 +196,44 @@ pub fn expanduser(path: []const u8, alloc: std.mem.Allocator) ![:0]u8 { const home = std.mem.trimRight(u8, home_raw, "/"); if (home.len == 0 and path.len == len) return alloc.dupeZ(u8, "/"); - return try std.fmt.allocPrintZ(alloc, "{s}{s}", .{ home, path[len..] }); + return try std.mem.concatWithSentinel(alloc, u8, &.{ home, path[len..] }, 0); } + + +// 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(); + } + +} else struct { + rd: std.fs.File.Reader, + + pub fn init(f: std.fs.File, buf: []u8) @This() { + return .{ .rd = f.readerStreaming(buf) }; + } + + pub fn read(s: *@This()) !?[]u8 { + return s.rd.interface.takeDelimiterExclusive('\n') catch |err| switch (err) { + error.EndOfStream => null, + else => |e| return e, + }; + } +};