diff --git a/src/bin_export.zig b/src/bin_export.zig index b726ed3..b0f70dc 100644 --- a/src/bin_export.zig +++ b/src/bin_export.zig @@ -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, diff --git a/src/browser.zig b/src/browser.zig index db31330..177be49 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -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; diff --git a/src/json_export.zig b/src/json_export.zig index 575a95b..fd476a1 100644 --- a/src/json_export.zig +++ b/src/json_export.zig @@ -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); } diff --git a/src/json_import.zig b/src/json_import.zig index eff37f9..50fc893 100644 --- a/src/json_import.zig +++ b/src/json_import.zig @@ -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 { - 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) diff --git a/src/mem_sink.zig b/src/mem_sink.zig index ad19faf..5a4a2d0 100644 --- a/src/mem_sink.zig +++ b/src/mem_sink.zig @@ -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; diff --git a/src/mem_src.zig b/src/mem_src.zig index cd11660..60e869c 100644 --- a/src/mem_src.zig +++ b/src/mem_src.zig @@ -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,23 +36,20 @@ fn rec(ctx: *Ctx, dir: *sink.Dir, entry: *model.Entry) void { main.handleEvent(false, false); ctx.stat = toStat(entry); - if (entry.dir()) |d| { - var ndir = dir.addDir(ctx.sink, entry.name(), &ctx.stat); - ctx.sink.setDir(ndir); - if (d.pack.err) ndir.setReadError(ctx.sink); - var it = d.sub; - while (it) |e| : (it = e.next) rec(ctx, ndir, e); - ctx.sink.setDir(dir); - ndir.unref(ctx.sink); - return; + 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); + var it = d.sub; + while (it) |e| : (it = e.next) rec(ctx, ndir, e); + 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); } diff --git a/src/model.zig b/src/model.zig index 9028768..67817a9 100644 --- a/src/model.zig +++ b/src/model.zig @@ -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 { diff --git a/src/scan.zig b/src/scan.zig index 9e4a81c..d8e435e 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -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{ diff --git a/src/sink.zig b/src/sink.zig index bd067a1..8a51a80 100644 --- a/src/sink.zig +++ b/src/sink.zig @@ -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;