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.
This commit is contained in:
Yorhel 2024-08-09 18:24:59 +02:00
parent 6b7983b2f5
commit c30699f93b
9 changed files with 104 additions and 49 deletions

View file

@ -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 {

View file

@ -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;

View file

@ -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

View file

@ -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);
}
}
}
};

View file

@ -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;
}
},

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}
};

View file

@ -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),