Compare commits

..

3 commits

Author SHA1 Message Date
Yorhel
653c3bfe70 Version 2.8.1 2025-04-28 13:22:03 +02:00
Yorhel
beac59fb12 Use std.c.fstatat() instead of @cImport()ed version
Because translate-c can't handle struct stat as defined by musl.
(Should have done this in the first place, but wasn't aware fstatat()
had been properly wrapped in std.c)
2025-04-28 13:22:03 +02:00
Yorhel
d97a7f73dd Fix integer overflow in binary export
And allocate an appropriately-sized initial block when
--export-block-size is set.

Fixes #257.
2025-04-28 12:43:04 +02:00
6 changed files with 37 additions and 30 deletions

View file

@ -1,6 +1,12 @@
# SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
# SPDX-License-Identifier: MIT
2.8.1 - 2025-04-28
- Still requires Zig 0.14
- Fix integer overflow in binary export
- Fix crash when `fstatat()` returns EINVAL
- Minor build system improvements
2.8 - 2025-03-05
- Now requires Zig 0.14
- Add support for @-prefixed lines to ignore errors in config file

2
ncdu.1
View file

@ -1,6 +1,6 @@
.\" SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
.\" SPDX-License-Identifier: MIT
.Dd March 5, 2025
.Dd April 28, 2025
.Dt NCDU 1
.Os
.Sh NAME

View file

@ -17,8 +17,6 @@ pub const global = struct {
var root_itemref: u64 = 0;
};
const BLOCK_SIZE: usize = 64*1024;
pub const SIGNATURE = "\xbfncduEX1";
pub const ItemKey = enum(u5) {
@ -81,10 +79,15 @@ fn blockSize(num: u32) usize {
else 2048<<10; // 32768
}
// Upper bound on the return value of blockSize()
// (config.export_block_size may be larger than the sizes listed above, let's
// stick with the maximum block size supported by the file format to be safe)
const MAX_BLOCK_SIZE: usize = 1<<28;
pub const Thread = struct {
buf: []u8 = undefined,
off: usize = std.math.maxInt(usize) - (1<<10), // large number to trigger a flush() for the first write
off: usize = MAX_BLOCK_SIZE, // pretend we have a full block to trigger a flush() for the first write
block_num: u32 = std.math.maxInt(u32),
itemref: u64 = 0, // ref of item currently being written
@ -124,7 +127,7 @@ pub const Thread = struct {
global.lock.lock();
defer global.lock.unlock();
// This can only really happen when the root path exceeds BLOCK_SIZE,
// 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", .{});
@ -430,7 +433,7 @@ pub const Dir = struct {
pub fn createRoot(stat: *const sink.Stat, threads: []sink.Thread) Dir {
for (threads) |*t| {
t.sink.bin.buf = main.allocator.alloc(u8, BLOCK_SIZE) catch unreachable;
t.sink.bin.buf = main.allocator.alloc(u8, blockSize(0)) catch unreachable;
}
return .{ .stat = stat.* };

View file

@ -10,9 +10,6 @@ pub const c = @cImport({
@cInclude("locale.h"); // setlocale() and localeconv()
@cInclude("fnmatch.h"); // fnmatch()
@cInclude("unistd.h"); // getuid()
@cInclude("errno.h"); // error codes
@cInclude("fcntl.h"); // AT_SYMLINK_NOFOLLOW
@cInclude("sys/stat.h"); // fstatat()
@cInclude("sys/types.h"); // struct passwd
@cInclude("pwd.h"); // getpwnam(), getpwuid()
if (@import("builtin").os.tag == .linux) {

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
// SPDX-License-Identifier: MIT
pub const program_version = "2.8";
pub const program_version = "2.8.1";
const std = @import("std");
const model = @import("model.zig");

View file

@ -47,28 +47,29 @@ fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fiel
fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool) !sink.Stat {
// std.posix.fstatatZ() is currently unusable due to https://github.com/ziglang/zig/issues/23463
var stat: c.struct_stat = undefined;
if (c.fstatat(parent.fd, name, &stat, if (follow) 0 else c.AT_SYMLINK_NOFOLLOW) != 0)
// 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) {
return switch (std.c._errno().*) {
c.ENOENT => error.FileNotFound,
c.ENAMETOOLONG => error.NameTooLong,
c.ENOMEM => error.OutOfMemory,
c.EACCES => error.AccessDenied,
@intFromEnum(std.c.E.NOENT) => error.FileNotFound,
@intFromEnum(std.c.E.NAMETOOLONG) => error.NameTooLong,
@intFromEnum(std.c.E.NOMEM) => error.OutOfMemory,
@intFromEnum(std.c.E.ACCES) => error.AccessDenied,
else => error.Unexpected,
};
symlink.* = c.S_ISLNK(stat.st_mode);
}
symlink.* = std.c.S.ISLNK(stat.mode);
return sink.Stat{
.etype =
if (c.S_ISDIR(stat.st_mode)) .dir
else if (stat.st_nlink > 1) .link
else if (!c.S_ISREG(stat.st_mode)) .nonreg
if (std.c.S.ISDIR(stat.mode)) .dir
else if (stat.nlink > 1) .link
else if (!std.c.S.ISREG(stat.mode)) .nonreg
else .reg,
.blocks = clamp(sink.Stat, .blocks, stat.st_blocks),
.size = clamp(sink.Stat, .size, stat.st_size),
.dev = truncate(sink.Stat, .dev, stat.st_dev),
.ino = truncate(sink.Stat, .ino, stat.st_ino),
.nlink = clamp(sink.Stat, .nlink, stat.st_nlink),
.blocks = clamp(sink.Stat, .blocks, stat.blocks),
.size = clamp(sink.Stat, .size, stat.size),
.dev = truncate(sink.Stat, .dev, stat.dev),
.ino = truncate(sink.Stat, .ino, stat.ino),
.nlink = clamp(sink.Stat, .nlink, stat.nlink),
.ext = .{
.pack = .{
.hasmtime = true,
@ -76,10 +77,10 @@ fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool)
.hasgid = true,
.hasmode = true,
},
.mtime = clamp(model.Ext, .mtime, stat.st_mtim.tv_sec),
.uid = truncate(model.Ext, .uid, stat.st_uid),
.gid = truncate(model.Ext, .gid, stat.st_gid),
.mode = truncate(model.Ext, .mode, stat.st_mode),
.mtime = clamp(model.Ext, .mtime, stat.mtim.sec),
.uid = truncate(model.Ext, .uid, stat.uid),
.gid = truncate(model.Ext, .gid, stat.gid),
.mode = truncate(model.Ext, .mode, stat.mode),
},
};
}