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 main = @import("main.zig");
const model = @import("model.zig");
const sink = @import("sink.zig");
const util = @import("util.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 ItemType = enum(i3) {
dir = 0,
reg = 1,
nonreg = 2,
link = 3,
err = -1,
pattern = -2,
otherfs = -3,
kernfs = -4
};
const ItemKey = enum(u5) {
// all items
type = 0, // ItemType
type = 0, // EType
name = 1, // bytes
prev = 2, // itemref
// 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.
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;
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();
defer d.lock.unlock();
d.items += 1;
if (sp == .err) d.suberr = true;
const it: ItemType = switch (sp) {
.err => .err,
.other_fs => .otherfs,
.kernfs => .kernfs,
.excluded => .pattern,
};
d.last_sub = t.itemStart(it, d.last_sub, name);
d.last_sub = t.itemStart(sp, d.last_sub, name);
t.itemEnd();
}
@ -280,18 +264,17 @@ pub const Dir = struct {
d.lock.lock();
defer d.lock.unlock();
d.items += 1;
if (!stat.hlinkc) {
if (stat.etype != .link) {
d.size +|= stat.size;
d.blocks +|= stat.blocks;
}
const it: ItemType = if (stat.hlinkc) .link else if (stat.reg) .reg else .nonreg;
d.last_sub = t.itemStart(it, d.last_sub, name);
d.last_sub = t.itemStart(stat.etype, d.last_sub, name);
t.itemKey(.asize);
t.cborHead(.pos, stat.size);
t.itemKey(.dsize);
t.cborHead(.pos, util.blocksToSize(stat.blocks));
if (stat.hlinkc) {
if (stat.etype == .link) {
const lnk = d.inodes.getOrPut(stat.ino) catch unreachable;
if (!lnk.found_existing) lnk.value_ptr.* = .{
.size = stat.size,

View file

@ -78,8 +78,8 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
const a = ap.?;
const b = bp.?;
if (main.config.sort_dirsfirst and a.isDirectory() != b.isDirectory())
return a.isDirectory();
if (main.config.sort_dirsfirst and a.pack.etype.isDirectory() != b.pack.etype.isDirectory())
return a.pack.etype.isDirectory();
switch (main.config.sort_col) {
.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.size > dir_max_size) dir_max_size = e.size;
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();
break :blk !excl and name[0] != '.' and name[name.len-1] != '~';
};
@ -162,19 +165,17 @@ const Row = struct {
fn flag(self: *Self) void {
defer self.col += 2;
const item = self.item orelse return;
const ch: u7 = ch: {
if (item.file()) |f| {
if (f.pack.err) break :ch '!';
if (f.pack.excluded) break :ch '<';
if (f.pack.other_fs) break :ch '>';
if (f.pack.kernfs) break :ch '^';
if (f.pack.notreg) break :ch '@';
} else if (item.dir()) |d| {
if (d.pack.err) break :ch '!';
if (d.pack.suberr) break :ch '.';
if (d.sub == null) break :ch 'e';
} else if (item.link()) |_| break :ch 'H';
return;
const ch: u7 = switch (item.pack.etype) {
.dir => if (item.dir().?.pack.err) '!'
else if (item.dir().?.pack.suberr) '.'
else if (item.dir().?.sub == null) 'e'
else return,
.link => 'H',
.pattern => '<',
.otherfs => '>',
.kernfs => '^',
.nonreg => '@',
else => return,
};
ui.move(self.row, self.col);
self.bg.fg(.flag);
@ -291,7 +292,7 @@ const Row = struct {
ui.move(self.row, self.col);
if (self.item) |i| {
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));
} else {
self.bg.fg(.dir);
@ -456,7 +457,12 @@ const info = struct {
} else {
ui.addstr("Type: ");
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;

View file

@ -3,6 +3,7 @@
const std = @import("std");
const main = @import("main.zig");
const model = @import("model.zig");
const sink = @import("sink.zig");
const util = @import("util.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.ensureSpace(name.len*6 + 1000);
// not necessarily correct, but mimics model.Entry.isDirectory()
const isdir = switch (t) {
.other_fs, .kernfs => true,
.err, .excluded => false,
};
ctx.write(if (isdir) ",\n[{\"name\":\"" else ",\n{\"name\":\"");
ctx.write(if (t.isDirectory()) ",\n[{\"name\":\"" else ",\n{\"name\":\"");
ctx.writeStr(name);
ctx.write(switch (t) {
.err => "\",\"read_error\":true}",
.other_fs => "\",\"excluded\":\"otherfs\"}",
.otherfs => "\",\"excluded\":\"otherfs\"}",
.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 {
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.writeByte('"');
if (stat.size > 0) {
@ -142,17 +139,17 @@ pub const Writer = struct {
ctx.write(",\"dsize\":");
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.writeUint(stat.dev);
}
if (stat.hlinkc) {
if (stat.etype == .link) {
ctx.write(",\"ino\":");
ctx.writeUint(stat.ino);
ctx.write(",\"hlnkc\":true,\"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) {
ctx.write(",\"uid\":");
ctx.writeUint(stat.ext.uid);
@ -169,7 +166,7 @@ pub const Writer = struct {
pub const Dir = struct {
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);
}

View file

@ -307,7 +307,7 @@ const Ctx = struct {
p: *Parser,
sink: *sink.Thread,
stat: sink.Stat = .{},
special: ?sink.Special = null,
rderr: bool = false,
namelen: usize = 0,
namebuf: [32*1024]u8 = undefined,
};
@ -337,9 +337,10 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
var buf: [32]u8 = undefined;
const typ = ctx.p.string(&buf);
// "frmlnk" is also possible, but currently considered equivalent to "pattern".
if (eq(u8, typ, "otherfs") or eq(u8, typ, "othfs")) ctx.special = .other_fs
else if (eq(u8, typ, "kernfs")) ctx.special = .kernfs
else ctx.special = .excluded;
ctx.stat.etype =
if (eq(u8, typ, "otherfs") or eq(u8, typ, "othfs")) .otherfs
else if (eq(u8, typ, "kernfs")) .kernfs
else .pattern;
return;
}
},
@ -351,7 +352,7 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
},
'h' => {
if (eq(u8, key, "hlnkc")) {
ctx.stat.hlinkc = ctx.p.boolean();
if (ctx.p.boolean()) ctx.stat.etype = .link;
return;
}
},
@ -388,19 +389,21 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
}
if (eq(u8, key, "nlink")) {
ctx.stat.nlink = ctx.p.uint(u31);
if (!ctx.stat.dir and ctx.stat.nlink > 1)
ctx.stat.hlinkc = true;
if (ctx.stat.etype != .dir and ctx.stat.nlink > 1)
ctx.stat.etype = .link;
return;
}
if (eq(u8, key, "notreg")) {
ctx.stat.reg = !ctx.p.boolean();
if (ctx.p.boolean()) ctx.stat.etype = .nonreg;
return;
}
},
'r' => {
if (eq(u8, key, "read_error")) {
if (ctx.p.boolean())
ctx.special = .err;
if (ctx.p.boolean()) {
if (ctx.stat.etype == .dir) ctx.rderr = true
else ctx.stat.etype = .err;
}
return;
}
},
@ -419,8 +422,8 @@ fn itemkey(ctx: *Ctx, key: []const u8) void {
fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void {
ctx.stat = .{ .dev = dev };
ctx.namelen = 0;
ctx.special = null;
ctx.stat.dir = switch (ctx.p.nextChr()) {
ctx.rderr = false;
const isdir = switch (ctx.p.nextChr()) {
'[' => blk: {
ctx.p.obj();
break :blk true;
@ -428,7 +431,8 @@ fn item(ctx: *Ctx, parent: ?*sink.Dir, dev: u64) void {
'{' => false,
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 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");
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 dir =
if (parent) |d| d.addDir(ctx.sink, name, &ctx.stat)
else sink.createRoot(name, &ctx.stat);
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);
ctx.sink.setDir(parent);
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 {
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)

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 {
if (self.entries.getKeyAdapted(name, HashContextAdapted{})) |e| {
// 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;
_ = self.entries.removeAdapted(name, HashContextAdapted{});
return e;
@ -87,23 +88,16 @@ pub const Dir = struct {
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;
if (st == .err) self.dir.pack.suberr = true;
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 },
};
_ = self.getEntry(t, st, false, name);
}
pub fn addStat(self: *Dir, t: *Thread, name: []const u8, stat: *const sink.Stat) *model.Entry {
if (global.stats) {
self.dir.items +|= 1;
if (!stat.hlinkc) {
if (stat.etype != .link) {
self.dir.entry.pack.blocks +|= stat.blocks;
self.dir.entry.size +|= stat.size;
}
@ -112,17 +106,13 @@ pub const Dir = struct {
}
}
const etype = if (stat.dir) model.EType.dir
else if (stat.hlinkc) model.EType.link
else model.EType.file;
const e = self.getEntry(t, etype, main.config.extended, name);
const e = self.getEntry(t, stat.etype, main.config.extended, name);
e.pack.blocks = stat.blocks;
e.size = stat.size;
if (e.dir()) |d| {
d.parent = self.dir;
d.pack.dev = model.devices.getId(stat.dev);
}
if (e.file()) |f| f.pack = .{ .notreg = !stat.dir and !stat.reg };
if (e.link()) |l| {
l.parent = self.dir;
l.ino = stat.ino;

View file

@ -12,6 +12,7 @@ const sink = @import("sink.zig");
fn toStat(e: *model.Entry) sink.Stat {
const el = e.link();
return sink.Stat{
.etype = e.pack.etype,
.blocks = e.pack.blocks,
.size = e.size,
.dev =
@ -20,10 +21,6 @@ fn toStat(e: *model.Entry) sink.Stat {
else undefined,
.ino = if (el) |l| l.ino else undefined,
.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 .{},
};
}
@ -39,7 +36,9 @@ fn rec(ctx: *Ctx, dir: *sink.Dir, entry: *model.Entry) void {
main.handleEvent(false, false);
ctx.stat = toStat(entry);
if (entry.dir()) |d| {
switch (entry.pack.etype) {
.dir => {
const d = entry.dir().?;
var ndir = dir.addDir(ctx.sink, entry.name(), &ctx.stat);
ctx.sink.setDir(ndir);
if (d.pack.err) ndir.setReadError(ctx.sink);
@ -47,15 +46,10 @@ fn rec(ctx: *Ctx, dir: *sink.Dir, entry: *model.Entry) void {
while (it) |e| : (it = e.next) rec(ctx, ndir, e);
ctx.sink.setDir(dir);
ndir.unref(ctx.sink);
return;
},
.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 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.
pub const Blocks = u61;
pub const Blocks = u60;
// Memory layout:
// (Ext +) Dir + name
@ -44,20 +70,14 @@ pub const Entry = extern struct {
}
pub fn file(self: *Self) ?*File {
return if (self.pack.etype == .file) @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;
return if (self.pack.etype != .dir and self.pack.etype != .link) @ptrCast(self) else null;
}
pub fn name(self: *const Self) [:0]const u8 {
const self_name = switch (self.pack.etype) {
.dir => &@as(*const Dir, @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);
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 {
return switch (etype) {
.dir => alloc(Dir, allocator, etype, isext, ename),
.file => alloc(File, allocator, etype, isext, ename),
.link => alloc(Link, allocator, etype, isext, ename),
else => alloc(File, allocator, etype, isext, ename),
};
}
fn hasErr(self: *Self) bool {
return
if (self.file()) |f| f.pack.err
else if (self.dir()) |d| d.pack.err or d.pack.suberr
else false;
if(self.dir()) |d| d.pack.err or d.pack.suberr
else self.pack.etype == .err;
}
fn removeLinks(self: *Entry) void {
@ -113,7 +132,6 @@ pub const Entry = extern struct {
fn zeroStatsRec(self: *Entry) void {
self.pack.blocks = 0;
self.size = 0;
if (self.file()) |f| f.pack = .{};
if (self.dir()) |d| {
d.items = 0;
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".
pub const File = extern struct {
entry: Entry,
pack: Packed = .{},
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 {

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);
symlink.* = std.posix.S.ISLNK(stat.mode);
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),
.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),
.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 = .{
.mtime = clamp(model.Ext, .mtime, stat.mtime().tv_sec),
.uid = truncate(model.Ext, .uid, stat.uid),
@ -190,23 +192,24 @@ const Thread = struct {
const excluded = dir.pat.match(name);
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;
}
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);
return;
};
if (main.config.follow_symlinks and stat.symlink) {
if (statAt(dir.fd, name, true)) |nstat| {
if (!nstat.dir) {
if (main.config.follow_symlinks and symlink) {
if (statAt(dir.fd, name, true, &symlink)) |nstat| {
if (nstat.etype != .dir) {
stat = nstat;
// Symlink targets may reside on different filesystems,
// this will break hardlink detection and counting so let's disable it.
if (stat.hlinkc and stat.dev != dir.dev) {
stat.hlinkc = false;
if (stat.etype == .link and stat.dev != dir.dev) {
stat.etype = .reg;
stat.nlink = 1;
}
}
@ -214,17 +217,17 @@ const Thread = struct {
}
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;
}
if (!stat.dir) {
if (stat.etype != .dir) {
dir.sink.addStat(t.sink, name, &stat);
return;
}
if (excluded == true) {
dir.sink.addSpecial(t.sink, name, .excluded);
dir.sink.addSpecial(t.sink, name, .pattern);
return;
}
@ -246,7 +249,7 @@ const Thread = struct {
}
if (main.config.exclude_caches and isCacheDir(edir)) {
dir.sink.addSpecial(t.sink, name, .excluded);
dir.sink.addSpecial(t.sink, name, .pattern);
edir.close();
return;
}
@ -287,7 +290,8 @@ pub fn scan(path: [:0]const u8) !void {
const sink_threads = sink.createThreads(main.config.threads);
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 });
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.
pub const Stat = struct {
etype: model.EType = .reg,
blocks: model.Blocks = 0,
size: u64 = 0,
dev: u64 = 0,
ino: u64 = 0,
nlink: u31 = 0,
hlinkc: bool = false,
dir: bool = false,
reg: bool = true,
symlink: bool = false,
ext: model.Ext = .{},
};
pub const Special = enum { err, other_fs, kernfs, excluded };
pub const Dir = struct {
refcnt: std.atomic.Value(usize) = std.atomic.Value(usize).init(1),
@ -82,7 +77,8 @@ pub const Dir = struct {
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);
switch (d.out) {
.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 {
_ = t.files_seen.fetchAdd(1, .monotonic);
_ = t.addBytes((stat.blocks *| 512) / @max(1, stat.nlink));
std.debug.assert(!stat.dir);
std.debug.assert(stat.etype != .dir);
switch (d.out) {
.mem => |*m| _ = m.addStat(&t.sink.mem, 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 {
_ = t.files_seen.fetchAdd(1, .monotonic);
_ = 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);
const s = main.allocator.create(Dir) catch unreachable;