Compare commits

..

6 commits
v2.9 ... zig

Author SHA1 Message Date
Yorhel
1b3d0a670e Version 2.9.2 2025-10-24 10:00:44 +02:00
Yorhel
f452244576 Fix infinite loop when reading config file on Zig 0.15.2
Works around Zig issue https://github.com/ziglang/zig/issues/25664

Fixes #266
2025-10-23 11:27:01 +02:00
Yorhel
14bb8d0dd1 Version 2.9.1 2025-08-21 09:11:18 +02:00
Yorhel
19cfdcf543 Fix bug with drawing scan progress before calling ui.init()
This triggered an invalid integer cast that wasn't caught with Zig's
LLVM backend, but it did trigger on the native x86_64 backend.
2025-08-19 14:17:53 +02:00
Yorhel
5129de737e 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.
2025-08-19 14:02:41 +02:00
Yorhel
68671a1af1 Zig 0.15: Migrate all ArrayLists to the Unmanaged API
Managed ArrayLists are deprecated in 0.15. "ArrayList" in 0.15 is the
same as "ArrayListUnmanaged" in 0.14. The latter alias is still
available in 0.15, so let's stick with that for now. When dropping
support for 0.14, we can do s/ArrayListUnmanaged/ArrayList/.
2025-08-19 13:05:20 +02:00
13 changed files with 208 additions and 162 deletions

View file

@ -1,6 +1,14 @@
# SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl> # SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
2.9.2 - 2025-10-24
- Still requires Zig 0.14 or 0.15
- Fix hang on loading config file when compiled with Zig 0.15.2
2.9.1 - 2025-08-21
- Add support for building with Zig 0.15
- Zig 0.14 is still supported
2.9 - 2025-08-16 2.9 - 2025-08-16
- Still requires Zig 0.14 - Still requires Zig 0.14
- Add --delete-command option to replace the built-in file deletion - Add --delete-command option to replace the built-in file deletion

View file

@ -19,7 +19,7 @@ C version (1.x).
## Requirements ## Requirements
- Zig 0.14 - Zig 0.14 or 0.15
- Some sort of POSIX-like OS - Some sort of POSIX-like OS
- ncurses - ncurses
- libzstd - libzstd

View file

@ -11,7 +11,7 @@ const c = @import("c.zig").c;
pub const global = struct { pub const global = struct {
var fd: std.fs.File = undefined; var fd: std.fs.File = undefined;
var index = std.ArrayList(u8).init(main.allocator); var index: std.ArrayListUnmanaged(u8) = .empty;
var file_off: u64 = 0; var file_off: u64 = 0;
var lock: std.Thread.Mutex = .{}; var lock: std.Thread.Mutex = .{};
var root_itemref: u64 = 0; var root_itemref: u64 = 0;
@ -105,11 +105,11 @@ pub const Thread = struct {
} }
} }
fn createBlock(t: *Thread) std.ArrayList(u8) { fn createBlock(t: *Thread) std.ArrayListUnmanaged(u8) {
var out = std.ArrayList(u8).init(main.allocator); var out: std.ArrayListUnmanaged(u8) = .empty;
if (t.block_num == std.math.maxInt(u32) or t.off == 0) return out; if (t.block_num == std.math.maxInt(u32) or t.off == 0) return out;
out.ensureTotalCapacityPrecise(12 + @as(usize, @intCast(c.ZSTD_COMPRESSBOUND(@as(c_int, @intCast(t.off)))))) catch unreachable; out.ensureTotalCapacityPrecise(main.allocator, 12 + @as(usize, @intCast(c.ZSTD_COMPRESSBOUND(@as(c_int, @intCast(t.off)))))) catch unreachable;
out.items.len = out.capacity; out.items.len = out.capacity;
const bodylen = compressZstd(t.buf[0..t.off], out.items[8..]); const bodylen = compressZstd(t.buf[0..t.off], out.items[8..]);
out.items.len = 12 + bodylen; out.items.len = 12 + bodylen;
@ -122,8 +122,8 @@ pub const Thread = struct {
fn flush(t: *Thread, expected_len: usize) void { fn flush(t: *Thread, expected_len: usize) void {
@branchHint(.unlikely); @branchHint(.unlikely);
const block = createBlock(t); var block = createBlock(t);
defer block.deinit(); defer block.deinit(main.allocator);
global.lock.lock(); global.lock.lock();
defer global.lock.unlock(); defer global.lock.unlock();
@ -141,7 +141,7 @@ pub const Thread = struct {
t.off = 0; t.off = 0;
t.block_num = @intCast((global.index.items.len - 4) / 8); t.block_num = @intCast((global.index.items.len - 4) / 8);
global.index.appendSlice(&[1]u8{0}**8) catch unreachable; global.index.appendSlice(main.allocator, &[1]u8{0}**8) catch unreachable;
if (global.index.items.len + 12 >= (1<<28)) ui.die("Too many data blocks, please report a bug.\n", .{}); if (global.index.items.len + 12 >= (1<<28)) ui.die("Too many data blocks, please report a bug.\n", .{});
const newsize = blockSize(t.block_num); const newsize = blockSize(t.block_num);
@ -447,12 +447,12 @@ pub fn done(threads: []sink.Thread) void {
while (std.mem.endsWith(u8, global.index.items, &[1]u8{0}**8)) while (std.mem.endsWith(u8, global.index.items, &[1]u8{0}**8))
global.index.shrinkRetainingCapacity(global.index.items.len - 8); global.index.shrinkRetainingCapacity(global.index.items.len - 8);
global.index.appendSlice(&bigu64(global.root_itemref)) catch unreachable; global.index.appendSlice(main.allocator, &bigu64(global.root_itemref)) catch unreachable;
global.index.appendSlice(&blockHeader(1, @intCast(global.index.items.len + 4))) 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.index.items[0..4].* = blockHeader(1, @intCast(global.index.items.len));
global.fd.writeAll(global.index.items) catch |e| global.fd.writeAll(global.index.items) catch |e|
ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) }); ui.die("Error writing to file: {s}.\n", .{ ui.errorString(e) });
global.index.clearAndFree(); global.index.clearAndFree(main.allocator);
global.fd.close(); global.fd.close();
} }
@ -464,5 +464,5 @@ pub fn setupOutput(fd: std.fs.File) void {
global.file_off = 8; global.file_off = 8;
// Placeholder for the index block header. // Placeholder for the index block header.
global.index.appendSlice("aaaa") catch unreachable; global.index.appendSlice(main.allocator, "aaaa") catch unreachable;
} }

View file

@ -15,16 +15,16 @@ const util = @import("util.zig");
// Currently opened directory. // Currently opened directory.
pub var dir_parent: *model.Dir = undefined; pub var dir_parent: *model.Dir = undefined;
pub var dir_path: [:0]u8 = undefined; pub var dir_path: [:0]u8 = undefined;
var dir_parents = std.ArrayList(model.Ref).init(main.allocator); var dir_parents: std.ArrayListUnmanaged(model.Ref) = .empty;
var dir_alloc = std.heap.ArenaAllocator.init(main.allocator); var dir_alloc = std.heap.ArenaAllocator.init(main.allocator);
// Used to keep track of which dir is which ref, so we can enter it. // Used to keep track of which dir is which ref, so we can enter it.
// Only used for binreader browsing. // Only used for binreader browsing.
var dir_refs = std.ArrayList(struct { ptr: *model.Dir, ref: u64 }).init(main.allocator); var dir_refs: std.ArrayListUnmanaged(struct { ptr: *model.Dir, ref: u64 }) = .empty;
// Sorted list of all items in the currently opened directory. // Sorted list of all items in the currently opened directory.
// (first item may be null to indicate the "parent directory" item) // (first item may be null to indicate the "parent directory" item)
var dir_items = std.ArrayList(?*model.Entry).init(main.allocator); var dir_items: std.ArrayListUnmanaged(?*model.Entry) = .empty;
var dir_max_blocks: u64 = 0; var dir_max_blocks: u64 = 0;
var dir_max_size: u64 = 0; var dir_max_size: u64 = 0;
@ -146,7 +146,7 @@ pub fn loadDir(next_sel: u64) void {
dir_has_shared = false; dir_has_shared = false;
if (dir_parents.items.len > 1) if (dir_parents.items.len > 1)
dir_items.append(null) catch unreachable; dir_items.append(main.allocator, null) catch unreachable;
var ref = dir_parent.sub; var ref = dir_parent.sub;
while (!ref.isNull()) { while (!ref.isNull()) {
const e = const e =
@ -164,10 +164,10 @@ pub fn loadDir(next_sel: u64) void {
break :blk !excl and name[0] != '.' and name[name.len-1] != '~'; break :blk !excl and name[0] != '.' and name[name.len-1] != '~';
}; };
if (shown) { if (shown) {
dir_items.append(e) catch unreachable; dir_items.append(main.allocator, e) catch unreachable;
if (e.dir()) |d| { if (e.dir()) |d| {
if (d.shared_blocks > 0 or d.shared_size > 0) dir_has_shared = true; if (d.shared_blocks > 0 or d.shared_size > 0) dir_has_shared = true;
if (main.config.binreader) dir_refs.append(.{ .ptr = d, .ref = ref.ref }) catch unreachable; if (main.config.binreader) dir_refs.append(main.allocator, .{ .ptr = d, .ref = ref.ref }) catch unreachable;
} }
} }
@ -185,10 +185,10 @@ pub fn initRoot() void {
if (main.config.binreader) { if (main.config.binreader) {
const ref = bin_reader.getRoot(); const ref = bin_reader.getRoot();
dir_parent = bin_reader.get(ref, main.allocator).dir() orelse ui.die("Invalid import\n", .{}); dir_parent = bin_reader.get(ref, main.allocator).dir() orelse ui.die("Invalid import\n", .{});
dir_parents.append(.{ .ref = ref }) catch unreachable; dir_parents.append(main.allocator, .{ .ref = ref }) catch unreachable;
} else { } else {
dir_parent = model.root; dir_parent = model.root;
dir_parents.append(.{ .ptr = &dir_parent.entry }) catch unreachable; dir_parents.append(main.allocator, .{ .ptr = &dir_parent.entry }) catch unreachable;
} }
dir_path = main.allocator.dupeZ(u8, dir_parent.entry.name()) catch unreachable; dir_path = main.allocator.dupeZ(u8, dir_parent.entry.name()) catch unreachable;
loadDir(0); loadDir(0);
@ -202,10 +202,10 @@ fn enterSub(e: *model.Dir) void {
}; };
dir_parent.entry.destroy(main.allocator); dir_parent.entry.destroy(main.allocator);
dir_parent = bin_reader.get(ref, main.allocator).dir() orelse unreachable; dir_parent = bin_reader.get(ref, main.allocator).dir() orelse unreachable;
dir_parents.append(.{ .ref = ref }) catch unreachable; dir_parents.append(main.allocator, .{ .ref = ref }) catch unreachable;
} else { } else {
dir_parent = e; dir_parent = e;
dir_parents.append(.{ .ptr = &e.entry }) catch unreachable; dir_parents.append(main.allocator, .{ .ptr = &e.entry }) catch unreachable;
} }
const newpath = std.fs.path.joinZ(main.allocator, &[_][]const u8{ dir_path, e.entry.name() }) catch unreachable; const newpath = std.fs.path.joinZ(main.allocator, &[_][]const u8{ dir_path, e.entry.name() }) catch unreachable;
@ -430,7 +430,7 @@ const info = struct {
var tab: Tab = .info; var tab: Tab = .info;
var entry: ?*model.Entry = null; var entry: ?*model.Entry = null;
var links: ?std.ArrayList(*model.Link) = null; var links: ?std.ArrayListUnmanaged(*model.Link) = null;
var links_top: usize = 0; var links_top: usize = 0;
var links_idx: usize = 0; var links_idx: usize = 0;
@ -445,7 +445,7 @@ const info = struct {
// Set the displayed entry to the currently selected item and open the tab. // Set the displayed entry to the currently selected item and open the tab.
fn set(e: ?*model.Entry, t: Tab) void { fn set(e: ?*model.Entry, t: Tab) void {
if (e != entry) { if (e != entry) {
if (links) |*l| l.deinit(); if (links) |*l| l.deinit(main.allocator);
links = null; links = null;
links_top = 0; links_top = 0;
links_idx = 0; links_idx = 0;
@ -458,10 +458,10 @@ const info = struct {
state = .info; state = .info;
tab = t; tab = t;
if (tab == .links and links == null and !main.config.binreader) { if (tab == .links and links == null and !main.config.binreader) {
var list = std.ArrayList(*model.Link).init(main.allocator); var list: std.ArrayListUnmanaged(*model.Link) = .empty;
var l = e.?.link().?; var l = e.?.link().?;
while (true) { while (true) {
list.append(l) catch unreachable; list.append(main.allocator, l) catch unreachable;
l = l.next; l = l.next;
if (&l.entry == e) if (&l.entry == e)
break; break;

View file

@ -136,19 +136,19 @@ pub fn delete() ?*model.Entry {
if (it.* == entry) if (it.* == entry)
break; break;
var path = std.ArrayList(u8).init(main.allocator); var path: std.ArrayListUnmanaged(u8) = .empty;
defer path.deinit(); defer path.deinit(main.allocator);
parent.fmtPath(true, &path); parent.fmtPath(main.allocator, true, &path);
if (path.items.len == 0 or path.items[path.items.len-1] != '/') if (path.items.len == 0 or path.items[path.items.len-1] != '/')
path.append('/') catch unreachable; path.append(main.allocator, '/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable; path.appendSlice(main.allocator, entry.name()) catch unreachable;
if (main.config.delete_command.len == 0) { if (main.config.delete_command.len == 0) {
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it); _ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path, main.allocator), it);
model.inodes.addAllStats(); model.inodes.addAllStats();
return if (it.* == e) e else next_sel; return if (it.* == e) e else next_sel;
} else { } else {
const isdel = deleteCmd(util.arrayListBufZ(&path), it); const isdel = deleteCmd(util.arrayListBufZ(&path, main.allocator), it);
model.inodes.addAllStats(); model.inodes.addAllStats();
return if (isdel) next_sel else it.*; return if (isdel) next_sel else it.*;
} }
@ -197,17 +197,17 @@ fn drawConfirm() void {
} }
fn drawProgress() void { fn drawProgress() void {
var path = std.ArrayList(u8).init(main.allocator); var path: std.ArrayListUnmanaged(u8) = .empty;
defer path.deinit(); defer path.deinit(main.allocator);
parent.fmtPath(false, &path); parent.fmtPath(main.allocator, false, &path);
path.append('/') catch unreachable; path.append(main.allocator, '/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable; path.appendSlice(main.allocator, entry.name()) catch unreachable;
// TODO: Item counts and progress bar would be nice. // TODO: Item counts and progress bar would be nice.
const box = ui.Box.create(6, 60, "Deleting..."); const box = ui.Box.create(6, 60, "Deleting...");
box.move(2, 2); box.move(2, 2);
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56)); ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path, main.allocator)), 56));
box.move(4, 41); box.move(4, 41);
ui.addstr("Press "); ui.addstr("Press ");
ui.style(.key); ui.style(.key);
@ -217,16 +217,16 @@ fn drawProgress() void {
} }
fn drawErr() void { fn drawErr() void {
var path = std.ArrayList(u8).init(main.allocator); var path: std.ArrayListUnmanaged(u8) = .empty;
defer path.deinit(); defer path.deinit(main.allocator);
parent.fmtPath(false, &path); parent.fmtPath(main.allocator, false, &path);
path.append('/') catch unreachable; path.append(main.allocator, '/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable; path.appendSlice(main.allocator, entry.name()) catch unreachable;
const box = ui.Box.create(6, 60, "Error"); const box = ui.Box.create(6, 60, "Error");
box.move(1, 2); box.move(1, 2);
ui.addstr("Error deleting "); ui.addstr("Error deleting ");
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 41)); ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path, main.allocator)), 41));
box.move(2, 4); box.move(2, 4);
ui.addstr(ui.errorString(error_code)); ui.addstr(ui.errorString(error_code));

View file

@ -123,7 +123,7 @@ test "parse" {
fn PatternList(comptime withsub: bool) type { fn PatternList(comptime withsub: bool) type {
return struct { return struct {
literals: std.HashMapUnmanaged(*const Pattern, Val, Ctx, 80) = .{}, literals: std.HashMapUnmanaged(*const Pattern, Val, Ctx, 80) = .{},
wild: std.ArrayListUnmanaged(*const Pattern) = .{}, wild: std.ArrayListUnmanaged(*const Pattern) = .empty,
// Not a fan of the map-of-arrays approach in the 'withsub' case, it // Not a fan of the map-of-arrays approach in the 'withsub' case, it
// has a lot of extra allocations. Linking the Patterns together in a // has a lot of extra allocations. Linking the Patterns together in a

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl> // SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pub const program_version = "2.9"; pub const program_version = "2.9.2";
const std = @import("std"); const std = @import("std");
const model = @import("model.zig"); const model = @import("model.zig");
@ -80,7 +80,6 @@ pub const config = struct {
pub var follow_symlinks: bool = false; pub var follow_symlinks: bool = false;
pub var exclude_caches: bool = false; pub var exclude_caches: bool = false;
pub var exclude_kernfs: bool = false; pub var exclude_kernfs: bool = false;
pub var exclude_patterns: std.ArrayList([:0]const u8) = std.ArrayList([:0]const u8).init(allocator);
pub var threads: usize = 1; pub var threads: usize = 1;
pub var complevel: u8 = 4; pub var complevel: u8 = 4;
pub var compress: bool = false; pub var compress: bool = false;
@ -119,6 +118,9 @@ pub const config = struct {
pub var state: enum { scan, browse, refresh, shell, delete } = .scan; 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. // Simple generic argument parser, supports getopt_long() style arguments.
const Args = struct { const Args = struct {
lst: []const [:0]const u8, lst: []const [:0]const u8,
@ -328,19 +330,13 @@ fn tryReadArgsFile(path: [:0]const u8) void {
}; };
defer f.close(); defer f.close();
var rd_ = std.io.bufferedReader(f.reader());
const rd = rd_.reader();
var line_buf: [4096]u8 = undefined; var line_buf: [4096]u8 = undefined;
var line_fbs = std.io.fixedBufferStream(&line_buf); var line_rd = util.LineReader.init(f, &line_buf);
const line_writer = line_fbs.writer();
while (true) : (line_fbs.reset()) { while (true) {
rd.streamUntilDelimiter(line_writer, '\n', line_buf.len) catch |err| switch (err) { const line_ = (line_rd.read() catch |e|
error.EndOfStream => if (line_fbs.getPos() catch unreachable == 0) break, ui.die("Error reading from {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) })
else => |e| ui.die("Error reading from {s}: {s}\nRun with --ignore-config to skip reading config files.\n", .{ path, ui.errorString(e) }), ) orelse break;
};
const line_ = line_fbs.getWritten();
var argc: usize = 0; var argc: usize = 0;
var ignerror = false; var ignerror = false;
@ -375,13 +371,11 @@ fn tryReadArgsFile(path: [:0]const u8) void {
} }
fn version() noreturn { fn version() noreturn {
const stdout = std.io.getStdOut();
stdout.writeAll("ncdu " ++ program_version ++ "\n") catch {}; stdout.writeAll("ncdu " ++ program_version ++ "\n") catch {};
std.process.exit(0); std.process.exit(0);
} }
fn help() noreturn { fn help() noreturn {
const stdout = std.io.getStdOut();
stdout.writeAll( stdout.writeAll(
\\ncdu <options> <directory> \\ncdu <options> <directory>
\\ \\
@ -444,19 +438,9 @@ fn readExcludeFile(path: [:0]const u8) !void {
const f = try std.fs.cwd().openFileZ(path, .{}); const f = try std.fs.cwd().openFileZ(path, .{});
defer f.close(); defer f.close();
var rd_ = std.io.bufferedReader(f.reader());
const rd = rd_.reader();
var line_buf: [4096]u8 = undefined; var line_buf: [4096]u8 = undefined;
var line_fbs = std.io.fixedBufferStream(&line_buf); var line_rd = util.LineReader.init(f, &line_buf);
const line_writer = line_fbs.writer(); while (try line_rd.read()) |line| {
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();
if (line.len > 0) if (line.len > 0)
exclude.addPattern(line); exclude.addPattern(line);
} }
@ -464,12 +448,12 @@ fn readExcludeFile(path: [:0]const u8) !void {
fn readImport(path: [:0]const u8) !void { fn readImport(path: [:0]const u8) !void {
const fd = 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, .{}); else try std.fs.cwd().openFileZ(path, .{});
errdefer fd.close(); errdefer fd.close();
var buf: [8]u8 = undefined; 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)) { if (std.mem.eql(u8, &buf, bin_export.SIGNATURE)) {
try bin_reader.open(fd); try bin_reader.open(fd);
config.binreader = true; config.binreader = true;
@ -551,8 +535,6 @@ pub fn main() void {
if (@import("builtin").os.tag != .linux and config.exclude_kernfs) if (@import("builtin").os.tag != .linux and config.exclude_kernfs)
ui.die("The --exclude-kernfs flag is currently only supported on Linux.\n", .{}); 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 out_tty = stdout.isTty();
const in_tty = stdin.isTty(); const in_tty = stdin.isTty();
if (config.scan_ui == null) { if (config.scan_ui == null) {
@ -610,10 +592,10 @@ pub fn main() void {
while (true) { while (true) {
switch (state) { switch (state) {
.refresh => { .refresh => {
var full_path = std.ArrayList(u8).init(allocator); var full_path: std.ArrayListUnmanaged(u8) = .empty;
defer full_path.deinit(); defer full_path.deinit(allocator);
mem_sink.global.root.?.fmtPath(true, &full_path); mem_sink.global.root.?.fmtPath(allocator, true, &full_path);
scan.scan(util.arrayListBufZ(&full_path)) catch { scan.scan(util.arrayListBufZ(&full_path, allocator)) catch {
sink.global.last_error = allocator.dupeZ(u8, full_path.items) catch unreachable; sink.global.last_error = allocator.dupeZ(u8, full_path.items) catch unreachable;
sink.global.state = .err; sink.global.state = .err;
while (state == .refresh) handleEvent(true, true); while (state == .refresh) handleEvent(true, true);

View file

@ -60,10 +60,10 @@ pub fn run(d: *model.Dir) void {
.sink = &sink_threads[0], .sink = &sink_threads[0],
.stat = toStat(&d.entry), .stat = toStat(&d.entry),
}; };
var buf = std.ArrayList(u8).init(main.allocator); var buf: std.ArrayListUnmanaged(u8) = .empty;
d.fmtPath(true, &buf); d.fmtPath(main.allocator, true, &buf);
const root = sink.createRoot(buf.items, &ctx.stat); const root = sink.createRoot(buf.items, &ctx.stat);
buf.deinit(); buf.deinit(main.allocator);
var it = d.sub.ptr; var it = d.sub.ptr;
while (it) |e| : (it = e.next.ptr) rec(&ctx, root, e); while (it) |e| : (it = e.next.ptr) rec(&ctx, root, e);

View file

@ -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 { 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; const size = (if (isext) @as(usize, @sizeOf(Ext)) else 0) + @sizeOf(T) + ename.len + 1;
var ptr = blk: while (true) { 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 |_| {} else |_| {}
ui.oom(); ui.oom();
}; };
@ -217,19 +218,20 @@ pub const Dir = extern struct {
suberr: bool = false, suberr: bool = false,
}; };
pub fn fmtPath(self: *const @This(), withRoot: bool, out: *std.ArrayList(u8)) void { pub fn fmtPath(self: *const @This(), alloc: std.mem.Allocator, withRoot: bool, out: *std.ArrayListUnmanaged(u8)) void {
if (!withRoot and self.parent == null) return; if (!withRoot and self.parent == null) return;
var components = std.ArrayList([:0]const u8).init(main.allocator); var components: std.ArrayListUnmanaged([:0]const u8) = .empty;
defer components.deinit(); defer components.deinit(main.allocator);
var it: ?*const @This() = self; var it: ?*const @This() = self;
while (it) |e| : (it = e.parent) while (it) |e| : (it = e.parent)
if (withRoot or e.parent != null) if (withRoot or e.parent != null)
components.append(e.entry.name()) catch unreachable; components.append(main.allocator, e.entry.name()) catch unreachable;
var i: usize = components.items.len-1; var i: usize = components.items.len-1;
while (true) { while (true) {
if (i != components.items.len-1 and !(out.items.len != 0 and out.items[out.items.len-1] == '/')) out.append('/') catch unreachable; if (i != components.items.len-1 and !(out.items.len != 0 and out.items[out.items.len-1] == '/'))
out.appendSlice(components.items[i]) catch unreachable; out.append(main.allocator, '/') catch unreachable;
out.appendSlice(alloc, components.items[i]) catch unreachable;
if (i == 0) break; if (i == 0) break;
i -= 1; i -= 1;
} }
@ -271,11 +273,11 @@ pub const Link = extern struct {
// Return value should be freed with main.allocator. // Return value should be freed with main.allocator.
pub fn path(self: *const @This(), withRoot: bool) [:0]const u8 { pub fn path(self: *const @This(), withRoot: bool) [:0]const u8 {
var out = std.ArrayList(u8).init(main.allocator); var out: std.ArrayListUnmanaged(u8) = .empty;
self.parent.fmtPath(withRoot, &out); self.parent.fmtPath(main.allocator, withRoot, &out);
out.append('/') catch unreachable; out.append(main.allocator, '/') catch unreachable;
out.appendSlice(self.entry.name()) catch unreachable; out.appendSlice(main.allocator, self.entry.name()) catch unreachable;
return out.toOwnedSliceSentinel(0) catch unreachable; return out.toOwnedSliceSentinel(main.allocator, 0) catch unreachable;
} }
// Add this link to the inodes map and mark it as 'uncounted'. // Add this link to the inodes map and mark it as 'uncounted'.
@ -350,7 +352,7 @@ pub const Ext = extern struct {
pub const devices = struct { pub const devices = struct {
var lock = std.Thread.Mutex{}; var lock = std.Thread.Mutex{};
// id -> dev // id -> dev
pub var list = std.ArrayList(u64).init(main.allocator); pub var list: std.ArrayListUnmanaged(u64) = .empty;
// dev -> id // dev -> id
var lookup = std.AutoHashMap(u64, DevId).init(main.allocator); var lookup = std.AutoHashMap(u64, DevId).init(main.allocator);
@ -361,7 +363,7 @@ pub const devices = struct {
if (!d.found_existing) { if (!d.found_existing) {
if (list.items.len >= std.math.maxInt(DevId)) ui.die("Maximum number of device identifiers exceeded.\n", .{}); if (list.items.len >= std.math.maxInt(DevId)) ui.die("Maximum number of device identifiers exceeded.\n", .{});
d.value_ptr.* = @as(DevId, @intCast(list.items.len)); d.value_ptr.* = @as(DevId, @intCast(list.items.len));
list.append(dev) catch unreachable; list.append(main.allocator, dev) catch unreachable;
} }
return d.value_ptr.*; return d.value_ptr.*;
} }

View file

@ -91,7 +91,7 @@ fn isCacheDir(dir: std.fs.Dir) bool {
const f = dir.openFileZ("CACHEDIR.TAG", .{}) catch return false; const f = dir.openFileZ("CACHEDIR.TAG", .{}) catch return false;
defer f.close(); defer f.close();
var buf: [sig.len]u8 = undefined; 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); return len == sig.len and std.mem.eql(u8, &buf, sig);
} }
@ -184,7 +184,7 @@ const Thread = struct {
thread_num: usize, thread_num: usize,
sink: *sink.Thread, sink: *sink.Thread,
state: *State, state: *State,
stack: std.ArrayList(*Dir) = std.ArrayList(*Dir).init(main.allocator), stack: std.ArrayListUnmanaged(*Dir) = .empty,
thread: std.Thread = undefined, thread: std.Thread = undefined,
namebuf: [4096]u8 = undefined, namebuf: [4096]u8 = undefined,
@ -265,13 +265,13 @@ const Thread = struct {
const s = dir.sink.addDir(t.sink, name, &stat); const s = dir.sink.addDir(t.sink, name, &stat);
const ndir = Dir.create(edir, stat.dev, dir.pat.enter(name), s); const ndir = Dir.create(edir, stat.dev, dir.pat.enter(name), s);
if (main.config.threads == 1 or !t.state.tryPush(ndir)) if (main.config.threads == 1 or !t.state.tryPush(ndir))
t.stack.append(ndir) catch unreachable; t.stack.append(main.allocator, ndir) catch unreachable;
} }
fn run(t: *Thread) void { fn run(t: *Thread) void {
defer t.stack.deinit(); defer t.stack.deinit(main.allocator);
while (t.state.waitPop()) |dir| { while (t.state.waitPop()) |dir| {
t.stack.append(dir) catch unreachable; t.stack.append(main.allocator, dir) catch unreachable;
while (t.stack.items.len > 0) { while (t.stack.items.len > 0) {
const d = t.stack.items[t.stack.items.len - 1]; const d = t.stack.items[t.stack.items.len - 1];

View file

@ -140,20 +140,21 @@ pub const Dir = struct {
} }
fn path(d: *Dir) [:0]u8 { fn path(d: *Dir) [:0]u8 {
var components = std.ArrayList([]const u8).init(main.allocator); var components: std.ArrayListUnmanaged([]const u8) = .empty;
defer components.deinit(); defer components.deinit(main.allocator);
var it: ?*Dir = d; var it: ?*Dir = d;
while (it) |e| : (it = e.parent) components.append(e.name) catch unreachable; while (it) |e| : (it = e.parent) components.append(main.allocator, e.name) catch unreachable;
var out = std.ArrayList(u8).init(main.allocator); var out: std.ArrayListUnmanaged(u8) = .empty;
var i: usize = components.items.len-1; var i: usize = components.items.len-1;
while (true) { while (true) {
if (i != components.items.len-1 and !(out.items.len != 0 and out.items[out.items.len-1] == '/')) out.append('/') catch unreachable; if (i != components.items.len-1 and !(out.items.len != 0 and out.items[out.items.len-1] == '/'))
out.appendSlice(components.items[i]) catch unreachable; out.append(main.allocator, '/') catch unreachable;
out.appendSlice(main.allocator, components.items[i]) catch unreachable;
if (i == 0) break; if (i == 0) break;
i -= 1; i -= 1;
} }
return out.toOwnedSliceSentinel(0) catch unreachable; return out.toOwnedSliceSentinel(main.allocator, 0) catch unreachable;
} }
fn ref(d: *Dir) void { fn ref(d: *Dir) void {
@ -291,7 +292,7 @@ fn drawConsole() void {
var ansi: ?bool = null; var ansi: ?bool = null;
var lines_written: usize = 0; 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 ansi = st.ansi orelse blk: {
const t = stderr.supportsAnsiEscapeCodes(); const t = stderr.supportsAnsiEscapeCodes();
st.ansi = t; st.ansi = t;
@ -450,25 +451,28 @@ pub fn draw() void {
switch (main.config.scan_ui.?) { switch (main.config.scan_ui.?) {
.none => {}, .none => {},
.line => drawConsole(), .line => drawConsole(),
.full => switch (global.state) { .full => {
.done => {}, ui.init();
.err => drawError(), switch (global.state) {
.zeroing => { .done => {},
const box = ui.Box.create(4, ui.cols -| 5, "Initializing"); .err => drawError(),
box.move(2, 2); .zeroing => {
ui.addstr("Clearing directory counts..."); const box = ui.Box.create(4, ui.cols -| 5, "Initializing");
}, box.move(2, 2);
.hlcnt => { ui.addstr("Clearing directory counts...");
const box = ui.Box.create(4, ui.cols -| 5, "Finalizing"); },
box.move(2, 2); .hlcnt => {
ui.addstr("Counting hardlinks... "); const box = ui.Box.create(4, ui.cols -| 5, "Finalizing");
if (model.inodes.add_total > 0) { box.move(2, 2);
ui.addnum(.default, model.inodes.add_done); ui.addstr("Counting hardlinks... ");
ui.addstr(" / "); if (model.inodes.add_total > 0) {
ui.addnum(.default, model.inodes.add_total); ui.addnum(.default, model.inodes.add_done);
} ui.addstr(" / ");
}, ui.addnum(.default, model.inodes.add_total);
.running => drawProgress(), }
},
.running => drawProgress(),
}
}, },
} }
} }

View file

@ -17,8 +17,7 @@ pub var cols: u32 = undefined;
pub fn die(comptime fmt: []const u8, args: anytype) noreturn { pub fn die(comptime fmt: []const u8, args: anytype) noreturn {
deinit(); deinit();
const stderr = std.io.getStdErr(); std.debug.print(fmt, args);
stderr.writer().print(fmt, args) catch {};
std.process.exit(1); std.process.exit(1);
} }
@ -27,6 +26,8 @@ pub fn quit() noreturn {
std.process.exit(0); 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 // Should be called when malloc fails. Will show a message to the user, wait
// for a second and return to give it another try. // for a second and return to give it another try.
// Glitch: this function may be called while we're in the process of drawing // 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()) { if (main_thread == std.Thread.getCurrentId()) {
const haveui = inited; const haveui = inited;
deinit(); deinit();
const stderr = std.io.getStdErr(); std.debug.print("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8", .{});
stderr.writeAll("\x1b7\x1b[JOut of memory, trying again in 1 second. Hit Ctrl-C to abort.\x1b8") catch {}; sleep(std.time.ns_per_s);
std.time.sleep(std.time.ns_per_s);
if (haveui) if (haveui)
init(); init();
} else { } else {
_ = oom_threads.fetchAdd(1, .monotonic); _ = oom_threads.fetchAdd(1, .monotonic);
std.time.sleep(std.time.ns_per_s); sleep(std.time.ns_per_s);
_ = oom_threads.fetchSub(1, .monotonic); _ = oom_threads.fetchSub(1, .monotonic);
} }
} }
@ -80,7 +80,7 @@ pub fn errorString(e: anyerror) [:0]const u8 {
}; };
} }
var to_utf8_buf = std.ArrayList(u8).init(main.allocator); var to_utf8_buf: std.ArrayListUnmanaged(u8) = .empty;
fn toUtf8BadChar(ch: u8) bool { fn toUtf8BadChar(ch: u8) bool {
return switch (ch) { return switch (ch) {
@ -107,19 +107,19 @@ pub fn toUtf8(in: [:0]const u8) [:0]const u8 {
if (std.unicode.utf8ByteSequenceLength(in[i])) |cp_len| { if (std.unicode.utf8ByteSequenceLength(in[i])) |cp_len| {
if (!toUtf8BadChar(in[i]) and i + cp_len <= in.len) { if (!toUtf8BadChar(in[i]) and i + cp_len <= in.len) {
if (std.unicode.utf8Decode(in[i .. i + cp_len])) |_| { if (std.unicode.utf8Decode(in[i .. i + cp_len])) |_| {
to_utf8_buf.appendSlice(in[i .. i + cp_len]) catch unreachable; to_utf8_buf.appendSlice(main.allocator, in[i .. i + cp_len]) catch unreachable;
i += cp_len; i += cp_len;
continue; continue;
} else |_| {} } else |_| {}
} }
} else |_| {} } else |_| {}
to_utf8_buf.writer().print("\\x{X:0>2}", .{in[i]}) catch unreachable; to_utf8_buf.writer(main.allocator).print("\\x{X:0>2}", .{in[i]}) catch unreachable;
i += 1; i += 1;
} }
return util.arrayListBufZ(&to_utf8_buf); return util.arrayListBufZ(&to_utf8_buf, main.allocator);
} }
var shorten_buf = std.ArrayList(u8).init(main.allocator); var shorten_buf: std.ArrayListUnmanaged(u8) = .empty;
// Shorten the given string to fit in the given number of columns. // Shorten the given string to fit in the given number of columns.
// If the string is too long, only the prefix and suffix will be printed, with '...' in between. // If the string is too long, only the prefix and suffix will be printed, with '...' in between.
@ -150,8 +150,8 @@ pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 {
if (total_width <= max_width) return in; if (total_width <= max_width) return in;
shorten_buf.shrinkRetainingCapacity(0); shorten_buf.shrinkRetainingCapacity(0);
shorten_buf.appendSlice(in[0..prefix_end]) catch unreachable; shorten_buf.appendSlice(main.allocator, in[0..prefix_end]) catch unreachable;
shorten_buf.appendSlice("...") catch unreachable; shorten_buf.appendSlice(main.allocator, "...") catch unreachable;
var start_width: u32 = prefix_width; var start_width: u32 = prefix_width;
var start_len: u32 = prefix_end; var start_len: u32 = prefix_end;
@ -163,11 +163,11 @@ pub fn shorten(in: [:0]const u8, max_width: u32) [:0] const u8 {
start_width += cp_width; start_width += cp_width;
start_len += cp_len; start_len += cp_len;
if (total_width - start_width <= max_width - prefix_width - 3) { if (total_width - start_width <= max_width - prefix_width - 3) {
shorten_buf.appendSlice(in[start_len..]) catch unreachable; shorten_buf.appendSlice(main.allocator, in[start_len..]) catch unreachable;
break; break;
} }
} }
return util.arrayListBufZ(&shorten_buf); return util.arrayListBufZ(&shorten_buf, main.allocator);
} }
fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void { fn shortenTest(in: [:0]const u8, max_width: u32, out: [:0]const u8) !void {
@ -335,8 +335,7 @@ fn updateSize() void {
fn clearScr() void { fn clearScr() void {
// Send a "clear from cursor to end of screen" instruction, to clear a // Send a "clear from cursor to end of screen" instruction, to clear a
// potential line left behind from scanning in -1 mode. // potential line left behind from scanning in -1 mode.
const stderr = std.io.getStdErr(); std.debug.print("\x1b[J", .{});
stderr.writeAll("\x1b[J") catch {};
} }
pub fn init() void { pub fn init() void {
@ -634,7 +633,7 @@ pub fn getch(block: bool) i32 {
} }
if (ch == c.ERR) { if (ch == c.ERR) {
if (!block) return 0; if (!block) return 0;
std.time.sleep(10*std.time.ns_per_ms); sleep(10*std.time.ns_per_ms);
continue; continue;
} }
return ch; return ch;
@ -643,6 +642,15 @@ pub fn getch(block: bool) i32 {
.{ c.strerror(@intFromEnum(std.posix.errno(-1))) }); .{ 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 { pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMap, reporterr: bool) void {
deinit(); deinit();
@ -662,14 +670,9 @@ pub fn runCmd(cmd: []const []const u8, cwd: ?[]const u8, env: *std.process.EnvMa
child.cwd = cwd; child.cwd = cwd;
child.env_map = env; child.env_map = env;
const stdin = std.io.getStdIn();
const stderr = std.io.getStdErr();
const term = child.spawnAndWait() catch |e| blk: { const term = child.spawnAndWait() catch |e| blk: {
stderr.writer().print( std.debug.print("Error running command: {s}\n\nPress enter to continue.\n", .{ ui.errorString(e) });
"Error running command: {s}\n\nPress enter to continue.\n", waitInput();
.{ ui.errorString(e) }
) catch {};
stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable;
break :blk std.process.Child.Term{ .Exited = 0 }; 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 }; const v = switch (term) { inline else => |v| v };
if (term != .Exited or (reporterr and v != 0)) { if (term != .Exited or (reporterr and v != 0)) {
stderr.writer().print( std.debug.print("\nCommand returned with {s} code {}.\nPress enter to continue.\n", .{ n, v });
"\nCommand returned with {s} code {}.\nPress enter to continue.\n", .{ n, v } waitInput();
) catch {};
stdin.reader().skipUntilDelimiterOrEof('\n') catch unreachable;
} }
} }

View file

@ -32,8 +32,8 @@ pub fn blocksToSize(b: u64) u64 {
// Ensure the given arraylist buffer gets zero-terminated and returns a slice // Ensure the given arraylist buffer gets zero-terminated and returns a slice
// into the buffer. The returned buffer is invalidated whenever the arraylist // into the buffer. The returned buffer is invalidated whenever the arraylist
// is freed or written to. // is freed or written to.
pub fn arrayListBufZ(buf: *std.ArrayList(u8)) [:0]const u8 { pub fn arrayListBufZ(buf: *std.ArrayListUnmanaged(u8), alloc: std.mem.Allocator) [:0]const u8 {
buf.append(0) catch unreachable; buf.append(alloc, 0) catch unreachable;
defer buf.items.len -= 1; defer buf.items.len -= 1;
return buf.items[0..buf.items.len-1:0]; return buf.items[0..buf.items.len-1:0];
} }
@ -196,5 +196,54 @@ pub fn expanduser(path: []const u8, alloc: std.mem.Allocator) ![:0]u8 {
const home = std.mem.trimRight(u8, home_raw, "/"); const home = std.mem.trimRight(u8, home_raw, "/");
if (home.len == 0 and path.len == len) return alloc.dupeZ(u8, "/"); 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 {
// 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];
}
};