From c30699f93b7fdf331b2cf5d30efe7471a0edfec8 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Fri, 9 Aug 2024 18:24:59 +0200 Subject: [PATCH] Track which extended mode fields we have + bugfixes This prevents displaying invalid zero values or writing such values out in JSON/bin exports. Very old issue, actually, but with the new binfmt experiments it's finally started annoying me. --- src/bin_export.zig | 26 ++++++++++++++++-------- src/bin_reader.zig | 24 +++++++++++----------- src/browser.zig | 49 ++++++++++++++++++++++++++++----------------- src/json_export.zig | 24 ++++++++++++++-------- src/json_import.zig | 4 ++++ src/main.zig | 2 +- src/mem_sink.zig | 5 +++-- src/model.zig | 13 ++++++++++++ src/scan.zig | 6 ++++++ 9 files changed, 104 insertions(+), 49 deletions(-) diff --git a/src/bin_export.zig b/src/bin_export.zig index ee2d69d..8fb433b 100644 --- a/src/bin_export.zig +++ b/src/bin_export.zig @@ -111,6 +111,7 @@ pub const Thread = struct { if (expected_len > t.buf.len) ui.die("Error writing data: path too long.\n", .{}); if (block.len > 0) { + 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.len); global.file_off += block.len; global.fd.writeAll(block) catch |e| @@ -120,6 +121,7 @@ pub const Thread = struct { t.off = 0; t.block_num = @intCast((global.index.items.len - 4) / 8); global.index.appendSlice(&[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", .{}); } fn cborHead(t: *Thread, major: CborMajor, arg: u64) void { @@ -184,14 +186,22 @@ pub const Thread = struct { fn itemExt(t: *Thread, stat: *const sink.Stat) void { if (!main.config.extended) return; - t.itemKey(.uid); - t.cborHead(.pos, stat.ext.uid); - t.itemKey(.gid); - t.cborHead(.pos, stat.ext.gid); - t.itemKey(.mode); - t.cborHead(.pos, stat.ext.mode); - t.itemKey(.mtime); - t.cborHead(.pos, stat.ext.mtime); + if (stat.ext.pack.hasuid) { + t.itemKey(.uid); + t.cborHead(.pos, stat.ext.uid); + } + if (stat.ext.pack.hasgid) { + t.itemKey(.gid); + t.cborHead(.pos, stat.ext.gid); + } + if (stat.ext.pack.hasmode) { + t.itemKey(.mode); + t.cborHead(.pos, stat.ext.mode); + } + if (stat.ext.pack.hasmtime) { + t.itemKey(.mtime); + t.cborHead(.pos, stat.ext.mtime); + } } fn itemEnd(t: *Thread) void { diff --git a/src/bin_reader.zig b/src/bin_reader.zig index 599a50f..06a792a 100644 --- a/src/bin_reader.zig +++ b/src/bin_reader.zig @@ -387,10 +387,10 @@ const Import = struct { .sub => ctx.fields.sub = kv.val.itemref(ref), .ino => ctx.stat.ino = kv.val.int(u64), .nlink => ctx.stat.nlink = kv.val.int(u31), - .uid => ctx.stat.ext.uid = kv.val.int(u32), - .gid => ctx.stat.ext.gid = kv.val.int(u32), - .mode => ctx.stat.ext.mode = kv.val.int(u16), - .mtime => ctx.stat.ext.mtime = kv.val.int(u64), + .uid => { ctx.stat.ext.uid = kv.val.int(u32); ctx.stat.ext.pack.hasuid = true; }, + .gid => { ctx.stat.ext.gid = kv.val.int(u32); ctx.stat.ext.pack.hasgid = true; }, + .mode => { ctx.stat.ext.mode = kv.val.int(u16); ctx.stat.ext.pack.hasmode = true; }, + .mtime => { ctx.stat.ext.mtime = kv.val.int(u64); ctx.stat.ext.pack.hasmtime = true; }, else => kv.val.skip(), }; @@ -439,20 +439,25 @@ pub fn get(ref: u64, alloc: std.mem.Allocator) *model.Entry { var etype: ?model.EType = null; var name: []const u8 = ""; var p = parser; + var ext = model.Ext{}; while (p.next()) |kv| { switch (kv.key) { .type => etype = kv.val.etype(), .name => name = kv.val.bytes(), + .uid => { ext.uid = kv.val.int(u32); ext.pack.hasuid = true; }, + .gid => { ext.gid = kv.val.int(u32); ext.pack.hasgid = true; }, + .mode => { ext.mode = kv.val.int(u16); ext.pack.hasmode = true; }, + .mtime => { ext.mtime = kv.val.int(u64); ext.pack.hasmtime = true; }, else => kv.val.skip(), } - if (etype != null and name.len != 0) break; } if (etype == null or name.len == 0) die(); - // XXX: 'extended' should really depend on whether the info is in the file. - var entry = model.Entry.create(alloc, etype.?, main.config.extended, name); + var entry = model.Entry.create(alloc, etype.?, main.config.extended and !ext.isEmpty(), name); entry.next = .{ .ref = std.math.maxInt(u64) }; + if (entry.ext()) |e| e.* = ext; if (entry.dir()) |d| d.sub = .{ .ref = std.math.maxInt(u64) }; + p = parser; while (p.next()) |kv| switch (kv.key) { .prev => entry.next = .{ .ref = kv.val.itemref(ref) }, .asize => { if (entry.pack.etype != .dir) entry.size = kv.val.int(u64); }, @@ -472,11 +477,6 @@ pub fn get(ref: u64, alloc: std.mem.Allocator) *model.Entry { .ino => { if (entry.link()) |l| l.ino = kv.val.int(u64); }, .nlink => { if (entry.link()) |l| l.pack.nlink = kv.val.int(u31); }, - - .uid => { if (entry.ext()) |e| e.uid = kv.val.int(u32); }, - .gid => { if (entry.ext()) |e| e.gid = kv.val.int(u32); }, - .mode => { if (entry.ext()) |e| e.mode = kv.val.int(u16); }, - .mtime => { if (entry.ext()) |e| e.mtime = kv.val.int(u64); }, else => kv.val.skip(), }; return entry; diff --git a/src/browser.zig b/src/browser.zig index 8257cba..2e0cb58 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -363,8 +363,13 @@ const Row = struct { defer self.col += 27; ui.move(self.row, self.col+1); const ext = if (self.item) |e| e.ext() else dir_parent.entry.ext(); - if (ext) |e| ui.addts(self.bg, e.mtime) - else ui.addstr(" no mtime"); + if (ext) |e| { + if (e.pack.hasmtime) { + ui.addts(self.bg, e.mtime); + return; + } + } + ui.addstr(" no mtime"); } fn name(self: *Self) void { @@ -526,18 +531,24 @@ const info = struct { box.move(row.*, 3); ui.style(.bold); if (e.ext()) |ext| { - ui.addstr("Mode: "); - ui.style(.default); - ui.addmode(ext.mode); var buf: [32]u8 = undefined; - ui.style(.bold); - ui.addstr(" UID: "); - ui.style(.default); - ui.addstr(std.fmt.bufPrintZ(&buf, "{d:<6}", .{ ext.uid }) catch unreachable); - ui.style(.bold); - ui.addstr(" GID: "); - ui.style(.default); - ui.addstr(std.fmt.bufPrintZ(&buf, "{d:<6}", .{ ext.gid }) catch unreachable); + if (ext.pack.hasmode) { + ui.addstr("Mode: "); + ui.style(.default); + ui.addmode(ext.mode); + ui.style(.bold); + } + if (ext.pack.hasuid) { + ui.addstr(" UID: "); + ui.style(.default); + ui.addstr(std.fmt.bufPrintZ(&buf, "{d:<6}", .{ ext.uid }) catch unreachable); + ui.style(.bold); + } + if (ext.pack.hasgid) { + ui.addstr(" GID: "); + ui.style(.default); + ui.addstr(std.fmt.bufPrintZ(&buf, "{d:<6}", .{ ext.gid }) catch unreachable); + } } else { ui.addstr("Type: "); ui.style(.default); @@ -552,11 +563,13 @@ const info = struct { // Last modified if (e.ext()) |ext| { - box.move(row.*, 3); - ui.style(.bold); - ui.addstr("Last modified: "); - ui.addts(.default, ext.mtime); - row.* += 1; + if (ext.pack.hasmtime) { + box.move(row.*, 3); + ui.style(.bold); + ui.addstr("Last modified: "); + ui.addts(.default, ext.mtime); + row.* += 1; + } } // Disk usage & Apparent size diff --git a/src/json_export.zig b/src/json_export.zig index fd476a1..fe5f255 100644 --- a/src/json_export.zig +++ b/src/json_export.zig @@ -151,14 +151,22 @@ pub const Writer = struct { } if (stat.etype == .nonreg) ctx.write(",\"notreg\":true"); if (main.config.extended) { - ctx.write(",\"uid\":"); - ctx.writeUint(stat.ext.uid); - ctx.write(",\"gid\":"); - ctx.writeUint(stat.ext.gid); - ctx.write(",\"mode\":"); - ctx.writeUint(stat.ext.mode); - ctx.write(",\"mtime\":"); - ctx.writeUint(stat.ext.mtime); + if (stat.ext.pack.hasuid) { + ctx.write(",\"uid\":"); + ctx.writeUint(stat.ext.uid); + } + if (stat.ext.pack.hasgid) { + ctx.write(",\"gid\":"); + ctx.writeUint(stat.ext.gid); + } + if (stat.ext.pack.hasmode) { + ctx.write(",\"mode\":"); + ctx.writeUint(stat.ext.mode); + } + if (stat.ext.pack.hasmtime) { + ctx.write(",\"mtime\":"); + ctx.writeUint(stat.ext.mtime); + } } } }; diff --git a/src/json_import.zig b/src/json_import.zig index aeee46b..f4a1283 100644 --- a/src/json_import.zig +++ b/src/json_import.zig @@ -347,6 +347,7 @@ fn itemkey(ctx: *Ctx, key: []const u8) void { 'g' => { if (eq(u8, key, "gid")) { ctx.stat.ext.gid = ctx.p.uint(u32); + ctx.stat.ext.pack.hasgid = true; return; } }, @@ -365,10 +366,12 @@ fn itemkey(ctx: *Ctx, key: []const u8) void { 'm' => { if (eq(u8, key, "mode")) { ctx.stat.ext.mode = ctx.p.uint(u16); + ctx.stat.ext.pack.hasmode = true; return; } if (eq(u8, key, "mtime")) { ctx.stat.ext.mtime = ctx.p.uint(u64); + ctx.stat.ext.pack.hasmtime = true; // Accept decimal numbers, but discard the fractional part because our data model doesn't support it. switch (ctx.p.nextByte()) { '.' => @@ -410,6 +413,7 @@ fn itemkey(ctx: *Ctx, key: []const u8) void { 'u' => { if (eq(u8, key, "uid")) { ctx.stat.ext.uid = ctx.p.uint(u32); + ctx.stat.ext.pack.hasuid = true; return; } }, diff --git a/src/main.zig b/src/main.zig index d6b97f8..7c4b44c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -578,7 +578,7 @@ pub fn main() void { if (import_file) |f| { readImport(f) catch |e| ui.die("Error reading file '{s}': {s}.\n", .{f, ui.errorString(e)}); config.imported = true; - if (config.binreader and export_json != null or export_bin != null) + if (config.binreader and (export_json != null or export_bin != null)) bin_reader.import(); } else { var buf = [_]u8{0} ** (std.fs.MAX_PATH_BYTES+1); diff --git a/src/mem_sink.zig b/src/mem_sink.zig index f751f9d..b930364 100644 --- a/src/mem_sink.zig +++ b/src/mem_sink.zig @@ -106,7 +106,7 @@ pub const Dir = struct { } } - const e = self.getEntry(t, stat.etype, main.config.extended, name); + const e = self.getEntry(t, stat.etype, main.config.extended and !stat.ext.isEmpty(), name); e.pack.blocks = stat.blocks; e.size = stat.size; if (e.dir()) |d| { @@ -172,7 +172,7 @@ pub const Dir = struct { pub fn createRoot(path: []const u8, stat: *const sink.Stat) Dir { const p = global.root orelse blk: { - model.root = model.Entry.create(main.allocator, .dir, main.config.extended, path).dir().?; + model.root = model.Entry.create(main.allocator, .dir, main.config.extended and !stat.ext.isEmpty(), path).dir().?; break :blk model.root; }; sink.global.state = .zeroing; @@ -185,6 +185,7 @@ pub fn createRoot(path: []const u8, stat: *const sink.Stat) Dir { p.entry.pack.blocks = stat.blocks; p.entry.size = stat.size; p.pack.dev = model.devices.getId(stat.dev); + if (p.entry.ext()) |e| e.* = stat.ext; return Dir.init(p); } diff --git a/src/model.zig b/src/model.zig index 9ab7cd0..6bbd433 100644 --- a/src/model.zig +++ b/src/model.zig @@ -325,10 +325,23 @@ pub const File = extern struct { }; pub const Ext = extern struct { + pack: Pack = .{}, mtime: u64 align(1) = 0, uid: u32 align(1) = 0, gid: u32 align(1) = 0, mode: u16 align(1) = 0, + + pub const Pack = packed struct(u8) { + hasmtime: bool = false, + hasuid: bool = false, + hasgid: bool = false, + hasmode: bool = false, + _pad: u4 = 0, + }; + + pub fn isEmpty(e: *const Ext) bool { + return !e.pack.hasmtime and !e.pack.hasuid and !e.pack.hasgid and !e.pack.hasmode; + } }; diff --git a/src/scan.zig b/src/scan.zig index d8e435e..a76368f 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -69,6 +69,12 @@ fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool) .ino = truncate(sink.Stat, .ino, stat.ino), .nlink = clamp(sink.Stat, .nlink, stat.nlink), .ext = .{ + .pack = .{ + .hasmtime = true, + .hasuid = true, + .hasgid = true, + .hasmode = true, + }, .mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec), .uid = truncate(model.Ext, .uid, stat.uid), .gid = truncate(model.Ext, .gid, stat.gid),