refactor: Merge sink.Special and bin_export.ItemType into model.EType

Simplifies code a little bit and saves one whole byte off of file
entries.
This commit is contained in:
Yorhel 2024-08-01 14:20:34 +02:00
parent 5a0c8c6175
commit cd00ae50d1
9 changed files with 148 additions and 164 deletions

View file

@ -3,6 +3,7 @@
const std = @import("std"); const std = @import("std");
const main = @import("main.zig"); const main = @import("main.zig");
const model = @import("model.zig");
const sink = @import("sink.zig"); const sink = @import("sink.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const ui = @import("ui.zig"); const ui = @import("ui.zig");
@ -22,20 +23,9 @@ pub const global = struct {
const BLOCK_SIZE: usize = 512*1024; // XXX: Current maximum for benchmarking, should just stick with a fixed block size. const BLOCK_SIZE: usize = 512*1024; // XXX: Current maximum for benchmarking, should just stick with a fixed block size.
const ItemType = enum(i3) {
dir = 0,
reg = 1,
nonreg = 2,
link = 3,
err = -1,
pattern = -2,
otherfs = -3,
kernfs = -4
};
const ItemKey = enum(u5) { const ItemKey = enum(u5) {
// all items // all items
type = 0, // ItemType type = 0, // EType
name = 1, // bytes name = 1, // bytes
prev = 2, // itemref prev = 2, // itemref
// Only for non-specials // Only for non-specials
@ -193,7 +183,7 @@ pub const Thread = struct {
} }
// Reserve space for a new item, write out the type, prev and name fields and return the itemref. // Reserve space for a new item, write out the type, prev and name fields and return the itemref.
fn itemStart(t: *Thread, itype: ItemType, prev_item: u64, name: []const u8) u64 { fn itemStart(t: *Thread, itype: model.EType, prev_item: u64, name: []const u8) u64 {
const min_len = name.len + MAX_ITEM_LEN; const min_len = name.len + MAX_ITEM_LEN;
if (t.off + min_len > main.config.blocksize) t.flush(min_len); if (t.off + min_len > main.config.blocksize) t.flush(min_len);
@ -261,18 +251,12 @@ pub const Dir = struct {
}; };
pub fn addSpecial(d: *Dir, t: *Thread, name: []const u8, sp: sink.Special) void { pub fn addSpecial(d: *Dir, t: *Thread, name: []const u8, sp: model.EType) void {
d.lock.lock(); d.lock.lock();
defer d.lock.unlock(); defer d.lock.unlock();
d.items += 1; d.items += 1;
if (sp == .err) d.suberr = true; if (sp == .err) d.suberr = true;
const it: ItemType = switch (sp) { d.last_sub = t.itemStart(sp, d.last_sub, name);
.err => .err,
.other_fs => .otherfs,
.kernfs => .kernfs,
.excluded => .pattern,
};
d.last_sub = t.itemStart(it, d.last_sub, name);
t.itemEnd(); t.itemEnd();
} }
@ -280,18 +264,17 @@ pub const Dir = struct {
d.lock.lock(); d.lock.lock();
defer d.lock.unlock(); defer d.lock.unlock();
d.items += 1; d.items += 1;
if (!stat.hlinkc) { if (stat.etype != .link) {
d.size +|= stat.size; d.size +|= stat.size;
d.blocks +|= stat.blocks; d.blocks +|= stat.blocks;
} }
const it: ItemType = if (stat.hlinkc) .link else if (stat.reg) .reg else .nonreg; d.last_sub = t.itemStart(stat.etype, d.last_sub, name);
d.last_sub = t.itemStart(it, d.last_sub, name);
t.itemKey(.asize); t.itemKey(.asize);
t.cborHead(.pos, stat.size); t.cborHead(.pos, stat.size);
t.itemKey(.dsize); t.itemKey(.dsize);
t.cborHead(.pos, util.blocksToSize(stat.blocks)); t.cborHead(.pos, util.blocksToSize(stat.blocks));
if (stat.hlinkc) { if (stat.etype == .link) {
const lnk = d.inodes.getOrPut(stat.ino) catch unreachable; const lnk = d.inodes.getOrPut(stat.ino) catch unreachable;
if (!lnk.found_existing) lnk.value_ptr.* = .{ if (!lnk.found_existing) lnk.value_ptr.* = .{
.size = stat.size, .size = stat.size,

View file

@ -78,8 +78,8 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
const a = ap.?; const a = ap.?;
const b = bp.?; const b = bp.?;
if (main.config.sort_dirsfirst and a.isDirectory() != b.isDirectory()) if (main.config.sort_dirsfirst and a.pack.etype.isDirectory() != b.pack.etype.isDirectory())
return a.isDirectory(); return a.pack.etype.isDirectory();
switch (main.config.sort_col) { switch (main.config.sort_col) {
.name => {}, // name sorting is the fallback .name => {}, // name sorting is the fallback
@ -139,7 +139,10 @@ pub fn loadDir(next_sel: ?*const model.Entry) void {
if (e.pack.blocks > dir_max_blocks) dir_max_blocks = e.pack.blocks; if (e.pack.blocks > dir_max_blocks) dir_max_blocks = e.pack.blocks;
if (e.size > dir_max_size) dir_max_size = e.size; if (e.size > dir_max_size) dir_max_size = e.size;
const shown = main.config.show_hidden or blk: { const shown = main.config.show_hidden or blk: {
const excl = if (e.file()) |f| f.pack.excluded else false; const excl = switch (e.pack.etype) {
.pattern, .otherfs, .kernfs => true,
else => false,
};
const name = e.name(); const name = e.name();
break :blk !excl and name[0] != '.' and name[name.len-1] != '~'; break :blk !excl and name[0] != '.' and name[name.len-1] != '~';
}; };
@ -162,19 +165,17 @@ const Row = struct {
fn flag(self: *Self) void { fn flag(self: *Self) void {
defer self.col += 2; defer self.col += 2;
const item = self.item orelse return; const item = self.item orelse return;
const ch: u7 = ch: { const ch: u7 = switch (item.pack.etype) {
if (item.file()) |f| { .dir => if (item.dir().?.pack.err) '!'
if (f.pack.err) break :ch '!'; else if (item.dir().?.pack.suberr) '.'
if (f.pack.excluded) break :ch '<'; else if (item.dir().?.sub == null) 'e'
if (f.pack.other_fs) break :ch '>'; else return,
if (f.pack.kernfs) break :ch '^'; .link => 'H',
if (f.pack.notreg) break :ch '@'; .pattern => '<',
} else if (item.dir()) |d| { .otherfs => '>',
if (d.pack.err) break :ch '!'; .kernfs => '^',
if (d.pack.suberr) break :ch '.'; .nonreg => '@',
if (d.sub == null) break :ch 'e'; else => return,
} else if (item.link()) |_| break :ch 'H';
return;
}; };
ui.move(self.row, self.col); ui.move(self.row, self.col);
self.bg.fg(.flag); self.bg.fg(.flag);
@ -291,7 +292,7 @@ const Row = struct {
ui.move(self.row, self.col); ui.move(self.row, self.col);
if (self.item) |i| { if (self.item) |i| {
self.bg.fg(if (i.pack.etype == .dir) .dir else .default); self.bg.fg(if (i.pack.etype == .dir) .dir else .default);
ui.addch(if (i.isDirectory()) '/' else ' '); ui.addch(if (i.pack.etype.isDirectory()) '/' else ' ');
ui.addstr(ui.shorten(ui.toUtf8(i.name()), ui.cols -| self.col -| 1)); ui.addstr(ui.shorten(ui.toUtf8(i.name()), ui.cols -| self.col -| 1));
} else { } else {
self.bg.fg(.dir); self.bg.fg(.dir);
@ -456,7 +457,12 @@ const info = struct {
} else { } else {
ui.addstr("Type: "); ui.addstr("Type: ");
ui.style(.default); ui.style(.default);
ui.addstr(if (e.isDirectory()) "Directory" else if (if (e.file()) |f| f.pack.notreg else false) "Other" else "File"); ui.addstr(switch (e.pack.etype) {
.dir => "Directory",
.nonreg => "Other",
.reg, .link => "File",
else => "Excluded",
});
} }
row.* += 1; row.* += 1;

View file

@ -3,6 +3,7 @@
const std = @import("std"); const std = @import("std");
const main = @import("main.zig"); const main = @import("main.zig");
const model = @import("model.zig");
const sink = @import("sink.zig"); const sink = @import("sink.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const ui = @import("ui.zig"); const ui = @import("ui.zig");
@ -110,28 +111,24 @@ pub const Writer = struct {
} }
} }
fn writeSpecial(ctx: *Writer, name: []const u8, t: sink.Special) void { fn writeSpecial(ctx: *Writer, name: []const u8, t: model.EType) void {
ctx.closeDirEntry(false); ctx.closeDirEntry(false);
ctx.ensureSpace(name.len*6 + 1000); ctx.ensureSpace(name.len*6 + 1000);
// not necessarily correct, but mimics model.Entry.isDirectory() ctx.write(if (t.isDirectory()) ",\n[{\"name\":\"" else ",\n{\"name\":\"");
const isdir = switch (t) {
.other_fs, .kernfs => true,
.err, .excluded => false,
};
ctx.write(if (isdir) ",\n[{\"name\":\"" else ",\n{\"name\":\"");
ctx.writeStr(name); ctx.writeStr(name);
ctx.write(switch (t) { ctx.write(switch (t) {
.err => "\",\"read_error\":true}", .err => "\",\"read_error\":true}",
.other_fs => "\",\"excluded\":\"otherfs\"}", .otherfs => "\",\"excluded\":\"otherfs\"}",
.kernfs => "\",\"excluded\":\"kernfs\"}", .kernfs => "\",\"excluded\":\"kernfs\"}",
.excluded => "\",\"excluded\":\"pattern\"}", .pattern => "\",\"excluded\":\"pattern\"}",
else => unreachable,
}); });
if (isdir) ctx.writeByte(']'); if (t.isDirectory()) ctx.writeByte(']');
} }
fn writeStat(ctx: *Writer, name: []const u8, stat: *const sink.Stat, parent_dev: u64) void { fn writeStat(ctx: *Writer, name: []const u8, stat: *const sink.Stat, parent_dev: u64) void {
ctx.ensureSpace(name.len*6 + 1000); ctx.ensureSpace(name.len*6 + 1000);
ctx.write(if (stat.dir) ",\n[{\"name\":\"" else ",\n{\"name\":\""); ctx.write(if (stat.etype == .dir) ",\n[{\"name\":\"" else ",\n{\"name\":\"");
ctx.writeStr(name); ctx.writeStr(name);
ctx.writeByte('"'); ctx.writeByte('"');
if (stat.size > 0) { if (stat.size > 0) {
@ -142,17 +139,17 @@ pub const Writer = struct {
ctx.write(",\"dsize\":"); ctx.write(",\"dsize\":");
ctx.writeUint(util.blocksToSize(stat.blocks)); ctx.writeUint(util.blocksToSize(stat.blocks));
} }
if (stat.dir and stat.dev != parent_dev) { if (stat.etype == .dir and stat.dev != parent_dev) {
ctx.write(",\"dev\":"); ctx.write(",\"dev\":");
ctx.writeUint(stat.dev); ctx.writeUint(stat.dev);
} }
if (stat.hlinkc) { if (stat.etype == .link) {
ctx.write(",\"ino\":"); ctx.write(",\"ino\":");
ctx.writeUint(stat.ino); ctx.writeUint(stat.ino);
ctx.write(",\"hlnkc\":true,\"nlink\":"); ctx.write(",\"hlnkc\":true,\"nlink\":");
ctx.writeUint(stat.nlink); ctx.writeUint(stat.nlink);
} }
if (!stat.dir and !stat.reg) ctx.write(",\"notreg\":true"); if (stat.etype == .nonreg) ctx.write(",\"notreg\":true");
if (main.config.extended) { if (main.config.extended) {
ctx.write(",\"uid\":"); ctx.write(",\"uid\":");
ctx.writeUint(stat.ext.uid); ctx.writeUint(stat.ext.uid);
@ -169,7 +166,7 @@ pub const Writer = struct {
pub const Dir = struct { pub const Dir = struct {
dev: u64, dev: u64,
pub fn addSpecial(_: *Dir, name: []const u8, sp: sink.Special) void { pub fn addSpecial(_: *Dir, name: []const u8, sp: model.EType) void {
global.writer.writeSpecial(name, sp); global.writer.writeSpecial(name, sp);
} }

View file

@ -307,7 +307,7 @@ const Ctx = struct {
p: *Parser, p: *Parser,
sink: *sink.Thread, sink: *sink.Thread,
stat: sink.Stat = .{}, stat: sink.Stat = .{},
special: ?sink.Special = null, rderr: bool = false,
namelen: usize = 0, namelen: usize = 0,
namebuf: [32*1024]u8 = undefined, namebuf: [32*1024]u8 = undefined,
}; };
@ -337,9 +337,10 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
var buf: [32]u8 = undefined; var buf: [32]u8 = undefined;
const typ = ctx.p.string(&buf); const typ = ctx.p.string(&buf);
// "frmlnk" is also possible, but currently considered equivalent to "pattern". // "frmlnk" is also possible, but currently considered equivalent to "pattern".
if (eq(u8, typ, "otherfs") or eq(u8, typ, "othfs")) ctx.special = .other_fs ctx.stat.etype =
else if (eq(u8, typ, "kernfs")) ctx.special = .kernfs if (eq(u8, typ, "otherfs") or eq(u8, typ, "othfs")) .otherfs
else ctx.special = .excluded; else if (eq(u8, typ, "kernfs")) .kernfs
else .pattern;
return; return;
} }
}, },
@ -351,7 +352,7 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
}, },
'h' => { 'h' => {
if (eq(u8, key, "hlnkc")) { if (eq(u8, key, "hlnkc")) {
ctx.stat.hlinkc = ctx.p.boolean(); if (ctx.p.boolean()) ctx.stat.etype = .link;
return; return;
} }
}, },
@ -388,19 +389,21 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
} }
if (eq(u8, key, "nlink")) { if (eq(u8, key, "nlink")) {
ctx.stat.nlink = ctx.p.uint(u31); ctx.stat.nlink = ctx.p.uint(u31);
if (!ctx.stat.dir and ctx.stat.nlink > 1) if (ctx.stat.etype != .dir and ctx.stat.nlink > 1)
ctx.stat.hlinkc = true; ctx.stat.etype = .link;
return; return;
} }
if (eq(u8, key, "notreg")) { if (eq(u8, key, "notreg")) {
ctx.stat.reg = !ctx.p.boolean(); if (ctx.p.boolean()) ctx.stat.etype = .nonreg;
return; return;
} }
}, },
'r' => { 'r' => {
if (eq(u8, key, "read_error")) { if (eq(u8, key, "read_error")) {
if (ctx.p.boolean()) if (ctx.p.boolean()) {
ctx.special = .err; if (ctx.stat.etype == .dir) ctx.rderr = true
else ctx.stat.etype = .err;
}
return; return;
} }
}, },
@ -419,8 +422,8 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void { fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void {
ctx.stat = .{ .dev = dev }; ctx.stat = .{ .dev = dev };
ctx.namelen = 0; ctx.namelen = 0;
ctx.special = null; ctx.rderr = false;
ctx.stat.dir = switch (ctx.p.nextChr()) { const isdir = switch (ctx.p.nextChr()) {
'[' => blk: { '[' => blk: {
ctx.p.obj(); ctx.p.obj();
break :blk true; break :blk true;
@ -428,7 +431,8 @@ fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void {
'{' => false, '{' => false,
else => ctx.p.die("expected object or array"), else => ctx.p.die("expected object or array"),
}; };
if (parent == null and !ctx.stat.dir) ctx.p.die("parent item must be a directory"); if (parent == null and !isdir) ctx.p.die("parent item must be a directory");
ctx.stat.etype = if (isdir) .dir else .reg;
var keybuf: [32]u8 = undefined; var keybuf: [32]u8 = undefined;
var first = true; var first = true;
@ -439,21 +443,23 @@ fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void {
if (ctx.namelen == 0) ctx.p.die("missing \"name\" field"); if (ctx.namelen == 0) ctx.p.die("missing \"name\" field");
const name = (&ctx.namebuf)[0..ctx.namelen]; const name = (&ctx.namebuf)[0..ctx.namelen];
if (ctx.stat.dir and (ctx.special == null or ctx.special == .err)) { if (ctx.stat.etype == .dir) {
const ndev = ctx.stat.dev; const ndev = ctx.stat.dev;
const dir = const dir =
if (parent) |d| d.addDir(ctx.sink, name, &ctx.stat) if (parent) |d| d.addDir(ctx.sink, name, &ctx.stat)
else sink.createRoot(name, &ctx.stat); else sink.createRoot(name, &ctx.stat);
ctx.sink.setDir(dir); ctx.sink.setDir(dir);
if (ctx.special == .err) dir.setReadError(ctx.sink); if (ctx.rderr) dir.setReadError(ctx.sink);
while (ctx.p.elem(false)) item(ctx, dir, ndev); while (ctx.p.elem(false)) item(ctx, dir, ndev);
ctx.sink.setDir(parent); ctx.sink.setDir(parent);
dir.unref(ctx.sink); dir.unref(ctx.sink);
} else if (ctx.special) |s| {
parent.?.addSpecial(ctx.sink, name, s);
if (ctx.stat.dir and ctx.p.elem(false)) ctx.p.die("unexpected contents in an excluded directory");
} else { } else {
parent.?.addStat(ctx.sink, name, &ctx.stat); if (@intFromEnum(ctx.stat.etype) < 0)
parent.?.addSpecial(ctx.sink, name, ctx.stat.etype)
else
parent.?.addStat(ctx.sink, name, &ctx.stat);
if (isdir and ctx.p.elem(false)) ctx.p.die("unexpected contents in an excluded directory");
} }
if ((ctx.sink.files_seen.load(.monotonic) & 65) == 0) if ((ctx.sink.files_seen.load(.monotonic) & 65) == 0)

View file

@ -75,7 +75,8 @@ pub const Dir = struct {
fn getEntry(self: *Dir, t: *Thread, etype: model.EType, isext: bool, name: []const u8) *model.Entry { fn getEntry(self: *Dir, t: *Thread, etype: model.EType, isext: bool, name: []const u8) *model.Entry {
if (self.entries.getKeyAdapted(name, HashContextAdapted{})) |e| { if (self.entries.getKeyAdapted(name, HashContextAdapted{})) |e| {
// XXX: In-place conversion may be possible in some cases. // XXX: In-place conversion may be possible in some cases.
if (e.pack.etype == etype and (!isext or e.pack.isext)) { if (e.pack.etype.base() == etype.base() and (!isext or e.pack.isext)) {
e.pack.etype = etype;
e.pack.isext = isext; e.pack.isext = isext;
_ = self.entries.removeAdapted(name, HashContextAdapted{}); _ = self.entries.removeAdapted(name, HashContextAdapted{});
return e; return e;
@ -87,23 +88,16 @@ pub const Dir = struct {
return e; return e;
} }
pub fn addSpecial(self: *Dir, t: *Thread, name: []const u8, st: sink.Special) void { pub fn addSpecial(self: *Dir, t: *Thread, name: []const u8, st: model.EType) void {
self.dir.items += 1; self.dir.items += 1;
if (st == .err) self.dir.pack.suberr = true; if (st == .err) self.dir.pack.suberr = true;
_ = self.getEntry(t, st, false, name);
const e = self.getEntry(t, .file, false, name);
e.file().?.pack = switch (st) {
.err => .{ .err = true },
.other_fs => .{ .other_fs = true },
.kernfs => .{ .kernfs = true },
.excluded => .{ .excluded = true },
};
} }
pub fn addStat(self: *Dir, t: *Thread, name: []const u8, stat: *const sink.Stat) *model.Entry { pub fn addStat(self: *Dir, t: *Thread, name: []const u8, stat: *const sink.Stat) *model.Entry {
if (global.stats) { if (global.stats) {
self.dir.items +|= 1; self.dir.items +|= 1;
if (!stat.hlinkc) { if (stat.etype != .link) {
self.dir.entry.pack.blocks +|= stat.blocks; self.dir.entry.pack.blocks +|= stat.blocks;
self.dir.entry.size +|= stat.size; self.dir.entry.size +|= stat.size;
} }
@ -112,17 +106,13 @@ pub const Dir = struct {
} }
} }
const etype = if (stat.dir) model.EType.dir const e = self.getEntry(t, stat.etype, main.config.extended, name);
else if (stat.hlinkc) model.EType.link
else model.EType.file;
const e = self.getEntry(t, etype, main.config.extended, name);
e.pack.blocks = stat.blocks; e.pack.blocks = stat.blocks;
e.size = stat.size; e.size = stat.size;
if (e.dir()) |d| { if (e.dir()) |d| {
d.parent = self.dir; d.parent = self.dir;
d.pack.dev = model.devices.getId(stat.dev); d.pack.dev = model.devices.getId(stat.dev);
} }
if (e.file()) |f| f.pack = .{ .notreg = !stat.dir and !stat.reg };
if (e.link()) |l| { if (e.link()) |l| {
l.parent = self.dir; l.parent = self.dir;
l.ino = stat.ino; l.ino = stat.ino;

View file

@ -12,6 +12,7 @@ const sink = @import("sink.zig");
fn toStat(e: *model.Entry) sink.Stat { fn toStat(e: *model.Entry) sink.Stat {
const el = e.link(); const el = e.link();
return sink.Stat{ return sink.Stat{
.etype = e.pack.etype,
.blocks = e.pack.blocks, .blocks = e.pack.blocks,
.size = e.size, .size = e.size,
.dev = .dev =
@ -20,10 +21,6 @@ fn toStat(e: *model.Entry) sink.Stat {
else undefined, else undefined,
.ino = if (el) |l| l.ino else undefined, .ino = if (el) |l| l.ino else undefined,
.nlink = if (el) |l| l.pack.nlink else 1, .nlink = if (el) |l| l.pack.nlink else 1,
.hlinkc = el != null,
.dir = e.pack.etype == .dir,
.reg = if (e.file()) |f| !f.pack.notreg else e.pack.etype != .dir,
.symlink = undefined,
.ext = if (e.ext()) |x| x.* else .{}, .ext = if (e.ext()) |x| x.* else .{},
}; };
} }
@ -39,23 +36,20 @@ fn rec(ctx: *Ctx, dir: *sink.Dir, entry: *model.Entry) void {
main.handleEvent(false, false); main.handleEvent(false, false);
ctx.stat = toStat(entry); ctx.stat = toStat(entry);
if (entry.dir()) |d| { switch (entry.pack.etype) {
var ndir = dir.addDir(ctx.sink, entry.name(), &ctx.stat); .dir => {
ctx.sink.setDir(ndir); const d = entry.dir().?;
if (d.pack.err) ndir.setReadError(ctx.sink); var ndir = dir.addDir(ctx.sink, entry.name(), &ctx.stat);
var it = d.sub; ctx.sink.setDir(ndir);
while (it) |e| : (it = e.next) rec(ctx, ndir, e); if (d.pack.err) ndir.setReadError(ctx.sink);
ctx.sink.setDir(dir); var it = d.sub;
ndir.unref(ctx.sink); while (it) |e| : (it = e.next) rec(ctx, ndir, e);
return; ctx.sink.setDir(dir);
ndir.unref(ctx.sink);
},
.reg, .nonreg, .link => dir.addStat(ctx.sink, entry.name(), &ctx.stat),
else => dir.addSpecial(ctx.sink, entry.name(), entry.pack.etype),
} }
if (entry.file()) |f| {
if (f.pack.err) return dir.addSpecial(ctx.sink, entry.name(), .err);
if (f.pack.excluded) return dir.addSpecial(ctx.sink, entry.name(), .excluded);
if (f.pack.other_fs) return dir.addSpecial(ctx.sink, entry.name(), .other_fs);
if (f.pack.kernfs) return dir.addSpecial(ctx.sink, entry.name(), .kernfs);
}
dir.addStat(ctx.sink, entry.name(), &ctx.stat);
} }

View file

@ -6,10 +6,36 @@ const main = @import("main.zig");
const ui = @import("ui.zig"); const ui = @import("ui.zig");
const util = @import("util.zig"); const util = @import("util.zig");
pub const EType = enum(u2) { dir, link, file }; // Numbers are used in the binfmt export, so must be stable.
pub const EType = enum(i3) {
dir = 0,
reg = 1,
nonreg = 2,
link = 3,
err = -1,
pattern = -2,
otherfs = -3,
kernfs = -4,
pub fn base(t: EType) EType {
return switch (t) {
.dir, .link => t,
else => .reg,
};
}
// Whether this entry should be displayed as a "directory".
// Some dirs are actually represented in this data model as a File for efficiency.
pub fn isDirectory(t: EType) bool {
return switch (t) {
.dir, .otherfs, .kernfs => true,
else => false,
};
}
};
// Type for the Entry.Packed.blocks field. Smaller than a u64 to make room for flags. // Type for the Entry.Packed.blocks field. Smaller than a u64 to make room for flags.
pub const Blocks = u61; pub const Blocks = u60;
// Memory layout: // Memory layout:
// (Ext +) Dir + name // (Ext +) Dir + name
@ -44,20 +70,14 @@ pub const Entry = extern struct {
} }
pub fn file(self: *Self) ?*File { pub fn file(self: *Self) ?*File {
return if (self.pack.etype == .file) @ptrCast(self) else null; return if (self.pack.etype != .dir and self.pack.etype != .link) @ptrCast(self) else null;
}
// Whether this entry should be displayed as a "directory".
// Some dirs are actually represented in this data model as a File for efficiency.
pub fn isDirectory(self: *Self) bool {
return if (self.file()) |f| f.pack.other_fs or f.pack.kernfs else self.pack.etype == .dir;
} }
pub fn name(self: *const Self) [:0]const u8 { pub fn name(self: *const Self) [:0]const u8 {
const self_name = switch (self.pack.etype) { const self_name = switch (self.pack.etype) {
.dir => &@as(*const Dir, @ptrCast(self)).name, .dir => &@as(*const Dir, @ptrCast(self)).name,
.link => &@as(*const Link, @ptrCast(self)).name, .link => &@as(*const Link, @ptrCast(self)).name,
.file => &@as(*const File, @ptrCast(self)).name, else => &@as(*const File, @ptrCast(self)).name,
}; };
const name_ptr: [*:0]const u8 = @ptrCast(self_name); const name_ptr: [*:0]const u8 = @ptrCast(self_name);
return std.mem.sliceTo(name_ptr, 0); return std.mem.sliceTo(name_ptr, 0);
@ -90,16 +110,15 @@ pub const Entry = extern struct {
pub fn create(allocator: std.mem.Allocator, etype: EType, isext: bool, ename: []const u8) *Entry { pub fn create(allocator: std.mem.Allocator, etype: EType, isext: bool, ename: []const u8) *Entry {
return switch (etype) { return switch (etype) {
.dir => alloc(Dir, allocator, etype, isext, ename), .dir => alloc(Dir, allocator, etype, isext, ename),
.file => alloc(File, allocator, etype, isext, ename),
.link => alloc(Link, allocator, etype, isext, ename), .link => alloc(Link, allocator, etype, isext, ename),
else => alloc(File, allocator, etype, isext, ename),
}; };
} }
fn hasErr(self: *Self) bool { fn hasErr(self: *Self) bool {
return return
if (self.file()) |f| f.pack.err if(self.dir()) |d| d.pack.err or d.pack.suberr
else if (self.dir()) |d| d.pack.err or d.pack.suberr else self.pack.etype == .err;
else false;
} }
fn removeLinks(self: *Entry) void { fn removeLinks(self: *Entry) void {
@ -113,7 +132,6 @@ pub const Entry = extern struct {
fn zeroStatsRec(self: *Entry) void { fn zeroStatsRec(self: *Entry) void {
self.pack.blocks = 0; self.pack.blocks = 0;
self.size = 0; self.size = 0;
if (self.file()) |f| f.pack = .{};
if (self.dir()) |d| { if (self.dir()) |d| {
d.items = 0; d.items = 0;
d.pack.err = false; d.pack.err = false;
@ -274,17 +292,7 @@ pub const Link = extern struct {
// Anything that's not an (indexed) directory or hardlink. Excluded directories are also "Files". // Anything that's not an (indexed) directory or hardlink. Excluded directories are also "Files".
pub const File = extern struct { pub const File = extern struct {
entry: Entry, entry: Entry,
pack: Packed = .{},
name: [0]u8 = undefined, name: [0]u8 = undefined,
pub const Packed = packed struct(u8) {
err: bool = false,
excluded: bool = false,
other_fs: bool = false,
kernfs: bool = false,
notreg: bool = false,
_pad: u3 = 0, // Make this struct "ABI sized" to allow inclusion in an extern struct
};
}; };
pub const Ext = extern struct { pub const Ext = extern struct {

View file

@ -54,18 +54,20 @@ 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) !sink.Stat { fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: *bool) !sink.Stat {
const stat = try std.posix.fstatatZ(parent.fd, name, if (follow) 0 else std.posix.AT.SYMLINK_NOFOLLOW); const stat = try std.posix.fstatatZ(parent.fd, name, if (follow) 0 else std.posix.AT.SYMLINK_NOFOLLOW);
symlink.* = std.posix.S.ISLNK(stat.mode);
return sink.Stat{ return sink.Stat{
.etype =
if (std.posix.S.ISDIR(stat.mode)) .dir
else if (stat.nlink > 1) .link
else if (!std.posix.S.ISREG(stat.mode)) .nonreg
else .reg,
.blocks = clamp(sink.Stat, .blocks, stat.blocks), .blocks = clamp(sink.Stat, .blocks, stat.blocks),
.size = clamp(sink.Stat, .size, stat.size), .size = clamp(sink.Stat, .size, stat.size),
.dev = truncate(sink.Stat, .dev, stat.dev), .dev = truncate(sink.Stat, .dev, stat.dev),
.ino = truncate(sink.Stat, .ino, stat.ino), .ino = truncate(sink.Stat, .ino, stat.ino),
.nlink = clamp(sink.Stat, .nlink, stat.nlink), .nlink = clamp(sink.Stat, .nlink, stat.nlink),
.hlinkc = stat.nlink > 1 and !std.posix.S.ISDIR(stat.mode),
.dir = std.posix.S.ISDIR(stat.mode),
.reg = std.posix.S.ISREG(stat.mode),
.symlink = std.posix.S.ISLNK(stat.mode),
.ext = .{ .ext = .{
.mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec), .mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec),
.uid = truncate(model.Ext, .uid, stat.uid), .uid = truncate(model.Ext, .uid, stat.uid),
@ -190,23 +192,24 @@ const Thread = struct {
const excluded = dir.pat.match(name); const excluded = dir.pat.match(name);
if (excluded == false) { // matched either a file or directory, so we can exclude this before stat()ing. if (excluded == false) { // matched either a file or directory, so we can exclude this before stat()ing.
dir.sink.addSpecial(t.sink, name, .excluded); dir.sink.addSpecial(t.sink, name, .pattern);
return; return;
} }
var stat = statAt(dir.fd, name, false) catch { var symlink: bool = undefined;
var stat = statAt(dir.fd, name, false, &symlink) catch {
dir.sink.addSpecial(t.sink, name, .err); dir.sink.addSpecial(t.sink, name, .err);
return; return;
}; };
if (main.config.follow_symlinks and stat.symlink) { if (main.config.follow_symlinks and symlink) {
if (statAt(dir.fd, name, true)) |nstat| { if (statAt(dir.fd, name, true, &symlink)) |nstat| {
if (!nstat.dir) { if (nstat.etype != .dir) {
stat = nstat; stat = nstat;
// Symlink targets may reside on different filesystems, // Symlink targets may reside on different filesystems,
// this will break hardlink detection and counting so let's disable it. // this will break hardlink detection and counting so let's disable it.
if (stat.hlinkc and stat.dev != dir.dev) { if (stat.etype == .link and stat.dev != dir.dev) {
stat.hlinkc = false; stat.etype = .reg;
stat.nlink = 1; stat.nlink = 1;
} }
} }
@ -214,17 +217,17 @@ const Thread = struct {
} }
if (main.config.same_fs and stat.dev != dir.dev) { if (main.config.same_fs and stat.dev != dir.dev) {
dir.sink.addSpecial(t.sink, name, .other_fs); dir.sink.addSpecial(t.sink, name, .otherfs);
return; return;
} }
if (!stat.dir) { if (stat.etype != .dir) {
dir.sink.addStat(t.sink, name, &stat); dir.sink.addStat(t.sink, name, &stat);
return; return;
} }
if (excluded == true) { if (excluded == true) {
dir.sink.addSpecial(t.sink, name, .excluded); dir.sink.addSpecial(t.sink, name, .pattern);
return; return;
} }
@ -246,7 +249,7 @@ const Thread = struct {
} }
if (main.config.exclude_caches and isCacheDir(edir)) { if (main.config.exclude_caches and isCacheDir(edir)) {
dir.sink.addSpecial(t.sink, name, .excluded); dir.sink.addSpecial(t.sink, name, .pattern);
edir.close(); edir.close();
return; return;
} }
@ -287,7 +290,8 @@ pub fn scan(path: [:0]const u8) !void {
const sink_threads = sink.createThreads(main.config.threads); const sink_threads = sink.createThreads(main.config.threads);
defer sink.done(); defer sink.done();
const stat = try statAt(std.fs.cwd(), path, true); var symlink: bool = undefined;
const stat = try statAt(std.fs.cwd(), path, true, &symlink);
const fd = try std.fs.cwd().openDirZ(path, .{ .iterate = true }); const fd = try std.fs.cwd().openDirZ(path, .{ .iterate = true });
var state = State{ var state = State{

View file

@ -55,20 +55,15 @@ const util = @import("util.zig");
// Concise stat struct for fields we're interested in, with the types used by the model. // Concise stat struct for fields we're interested in, with the types used by the model.
pub const Stat = struct { pub const Stat = struct {
etype: model.EType = .reg,
blocks: model.Blocks = 0, blocks: model.Blocks = 0,
size: u64 = 0, size: u64 = 0,
dev: u64 = 0, dev: u64 = 0,
ino: u64 = 0, ino: u64 = 0,
nlink: u31 = 0, nlink: u31 = 0,
hlinkc: bool = false,
dir: bool = false,
reg: bool = true,
symlink: bool = false,
ext: model.Ext = .{}, ext: model.Ext = .{},
}; };
pub const Special = enum { err, other_fs, kernfs, excluded };
pub const Dir = struct { pub const Dir = struct {
refcnt: std.atomic.Value(usize) = std.atomic.Value(usize).init(1), refcnt: std.atomic.Value(usize) = std.atomic.Value(usize).init(1),
@ -82,7 +77,8 @@ pub const Dir = struct {
bin: bin_export.Dir, bin: bin_export.Dir,
}; };
pub fn addSpecial(d: *Dir, t: *Thread, name: []const u8, sp: Special) void { pub fn addSpecial(d: *Dir, t: *Thread, name: []const u8, sp: model.EType) void {
std.debug.assert(@intFromEnum(sp) < 0); // >=0 aren't "special"
_ = t.files_seen.fetchAdd(1, .monotonic); _ = t.files_seen.fetchAdd(1, .monotonic);
switch (d.out) { switch (d.out) {
.mem => |*m| m.addSpecial(&t.sink.mem, name, sp), .mem => |*m| m.addSpecial(&t.sink.mem, name, sp),
@ -102,7 +98,7 @@ pub const Dir = struct {
pub fn addStat(d: *Dir, t: *Thread, name: []const u8, stat: *const Stat) void { pub fn addStat(d: *Dir, t: *Thread, name: []const u8, stat: *const Stat) void {
_ = t.files_seen.fetchAdd(1, .monotonic); _ = t.files_seen.fetchAdd(1, .monotonic);
_ = t.addBytes((stat.blocks *| 512) / @max(1, stat.nlink)); _ = t.addBytes((stat.blocks *| 512) / @max(1, stat.nlink));
std.debug.assert(!stat.dir); std.debug.assert(stat.etype != .dir);
switch (d.out) { switch (d.out) {
.mem => |*m| _ = m.addStat(&t.sink.mem, name, stat), .mem => |*m| _ = m.addStat(&t.sink.mem, name, stat),
.json => |*j| j.addStat(name, stat), .json => |*j| j.addStat(name, stat),
@ -113,7 +109,7 @@ pub const Dir = struct {
pub fn addDir(d: *Dir, t: *Thread, name: []const u8, stat: *const Stat) *Dir { pub fn addDir(d: *Dir, t: *Thread, name: []const u8, stat: *const Stat) *Dir {
_ = t.files_seen.fetchAdd(1, .monotonic); _ = t.files_seen.fetchAdd(1, .monotonic);
_ = t.addBytes(stat.blocks *| 512); _ = t.addBytes(stat.blocks *| 512);
std.debug.assert(stat.dir); std.debug.assert(stat.etype == .dir);
std.debug.assert(d.out != .json or d.refcnt.load(.monotonic) == 1); std.debug.assert(d.out != .json or d.refcnt.load(.monotonic) == 1);
const s = main.allocator.create(Dir) catch unreachable; const s = main.allocator.create(Dir) catch unreachable;