Upstream https://code.blicky.net/yorhel/ncdu/issues/213

[Zig 0.10.0](https://ziglang.org/download/0.10.0/release-notes.html) is out.
I've already got ncdu working with that version - with the self-hosted compiler - on my [zig-0.10](https://code.blicky.net/yorhel/ncdu/commits/branch/zig-0.10) branch.
Users are encouraged to test it out and report bugs.
If you for some reason have to upgrade to the new Zig, feel free to use those patches as well.

The completely rewritten compiler and the changes to packed structs in combination with [#13308](https://github.com/ziglang/zig/issues/13308) means that there's likely a few regressions.
The Zig release notes mention "Waiting until 0.10.1 to upgrade is recommended for users who desire a more stable experience",
so that's what I'll do with respect to the stable ncdu releases.

This archive contains all patches from "zig-0.10" branch.

From f7e774ee6eb2066f79c11ae52416d2880f117f5f Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Mon, 22 Aug 2022 13:59:33 +0200
Subject: [PATCH] Fixes for stdlib changes

---
 src/delete.zig |  3 +--
 src/main.zig   |  3 +--
 src/scan.zig   | 21 ++++++++++++---------
 src/ui.zig     |  4 ++--
 4 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/src/delete.zig b/src/delete.zig
index 22c2706..1f6575c 100644
--- a/src/delete.zig
+++ b/src/delete.zig
@@ -45,8 +45,7 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry)
         return true;
 
     if (entry.dir()) |d| {
-        var fd = dir.openDirZ(path, .{ .access_sub_paths = true, .iterate = false })
-            catch |e| return err(e);
+        var fd = dir.openDirZ(path, .{.no_follow = true}, false) catch |e| return err(e);
         var it = &d.sub;
         parent = d;
         defer parent = parent.parent.?;
diff --git a/src/main.zig b/src/main.zig
index a04d96f..7e2d220 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -343,8 +343,7 @@ fn spawnShell() void {
         env.put("NCDU_LEVEL", "1") catch unreachable;
 
     const shell = std.os.getenvZ("NCDU_SHELL") orelse std.os.getenvZ("SHELL") orelse "/bin/sh";
-    var child = std.ChildProcess.init(&.{shell}, allocator) catch unreachable;
-    defer child.deinit();
+    var child = std.ChildProcess.init(&.{shell}, allocator);
     child.cwd = path.items;
     child.env_map = &env;
 
diff --git a/src/scan.zig b/src/scan.zig
index b6a411b..74c2ae5 100644
--- a/src/scan.zig
+++ b/src/scan.zig
@@ -450,8 +450,8 @@ const Context = struct {
 var active_context: *Context = undefined;
 
 // Read and index entries of the given dir.
-fn scanDir(ctx: *Context, pat: *const exclude.Patterns, dir: std.fs.Dir, dir_dev: u64) void {
-    var it = main.allocator.create(std.fs.Dir.Iterator) catch unreachable;
+fn scanDir(ctx: *Context, pat: *const exclude.Patterns, dir: std.fs.IterableDir, dir_dev: u64) void {
+    var it = main.allocator.create(std.fs.IterableDir.Iterator) catch unreachable;
     defer main.allocator.destroy(it);
     it.* = dir.iterate();
     while(true) {
@@ -471,7 +471,7 @@ fn scanDir(ctx: *Context, pat: *const exclude.Patterns, dir: std.fs.Dir, dir_dev
             continue;
         }
 
-        ctx.stat = Stat.read(dir, ctx.name, false) catch {
+        ctx.stat = Stat.read(dir.dir, ctx.name, false) catch {
             ctx.addSpecial(.err);
             continue;
         };
@@ -481,7 +481,7 @@ fn scanDir(ctx: *Context, pat: *const exclude.Patterns, dir: std.fs.Dir, dir_dev
         }
 
         if (main.config.follow_symlinks and ctx.stat.symlink) {
-            if (Stat.read(dir, ctx.name, true)) |nstat| {
+            if (Stat.read(dir.dir, ctx.name, true)) |nstat| {
                 if (!nstat.dir) {
                     ctx.stat = nstat;
                     // Symlink targets may reside on different filesystems,
@@ -497,19 +497,21 @@ fn scanDir(ctx: *Context, pat: *const exclude.Patterns, dir: std.fs.Dir, dir_dev
         };
 
         var edir =
-            if (ctx.stat.dir) dir.openDirZ(ctx.name, .{ .access_sub_paths = true, .iterate = true, .no_follow = true }) catch {
+            if (!ctx.stat.dir) null
+            else if (dir.dir.openDirZ(ctx.name, .{ .no_follow = true }, true)) |d| std.fs.IterableDir{.dir = d}
+            else |_| {
                 ctx.addSpecial(.err);
                 continue;
-            } else null;
+            };
         defer if (edir != null) edir.?.close();
 
-        if (@import("builtin").os.tag == .linux and main.config.exclude_kernfs and ctx.stat.dir and isKernfs(edir.?, ctx.stat.dev)) {
+        if (@import("builtin").os.tag == .linux and main.config.exclude_kernfs and ctx.stat.dir and isKernfs(edir.?.dir, ctx.stat.dev)) {
             ctx.addSpecial(.kernfs);
             continue;
         }
 
         if (main.config.exclude_caches and ctx.stat.dir) {
-            if (edir.?.openFileZ("CACHEDIR.TAG", .{})) |f| {
+            if (edir.?.dir.openFileZ("CACHEDIR.TAG", .{})) |f| {
                 const sig = "Signature: 8a477f597d28d172789f06886806bc55";
                 var buf: [sig.len]u8 = undefined;
                 if (f.reader().readAll(&buf)) |len| {
@@ -556,13 +558,14 @@ pub fn setupRefresh(parent: *model.Dir) void {
 // To be called after setupRefresh() (or from scanRoot())
 pub fn scan() void {
     defer active_context.deinit();
-    var dir = std.fs.cwd().openDirZ(active_context.pathZ(), .{ .access_sub_paths = true, .iterate = true }) catch |e| {
+    var dir_ = std.fs.cwd().openDirZ(active_context.pathZ(), .{}, true) catch |e| {
         active_context.last_error = main.allocator.dupeZ(u8, active_context.path.items) catch unreachable;
         active_context.fatal_error = e;
         while (main.state == .refresh or main.state == .scan)
             main.handleEvent(true, true);
         return;
     };
+    var dir = std.fs.IterableDir{.dir = dir_};
     defer dir.close();
     var pat = exclude.getPatterns(active_context.pathZ());
     defer pat.deinit();
diff --git a/src/ui.zig b/src/ui.zig
index e0f66c8..4e00548 100644
--- a/src/ui.zig
+++ b/src/ui.zig
@@ -286,8 +286,8 @@ const styles = [_]StyleDef{
 };
 
 pub const Style = lbl: {
-    var fields: [styles.len]std.builtin.TypeInfo.EnumField = undefined;
-    var decls = [_]std.builtin.TypeInfo.Declaration{};
+    var fields: [styles.len]std.builtin.Type.EnumField = undefined;
+    var decls = [_]std.builtin.Type.Declaration{};
     inline for (styles) |s, i| {
         fields[i] = .{
             .name = s.name,
From 1452b91032cc4f11a9171c452b7f9fa3cdf3c910 Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Mon, 22 Aug 2022 14:21:52 +0200
Subject: [PATCH] Some fixes for building with Zig stage2

Building is currently broken on packed struct alignment issues. :/
---
 src/exclude.zig | 8 ++++----
 src/main.zig    | 6 ++++--
 src/ui.zig      | 2 +-
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/exclude.zig b/src/exclude.zig
index 8e2b793..0528f30 100644
--- a/src/exclude.zig
+++ b/src/exclude.zig
@@ -120,7 +120,7 @@ test "parse" {
 // the match result is only used to construct the PatternList of the
 // subdirectory) and patterns without a sub-pointer (where the match result
 // determines whether the file/dir at this level should be included or not).
-fn PatternList(withsub: bool) type {
+fn PatternList(comptime withsub: bool) type {
     return struct {
         literals: std.HashMapUnmanaged(*const Pattern, Val, Ctx, 80) = .{},
         wild: std.ArrayListUnmanaged(*const Pattern) = .{},
@@ -150,7 +150,7 @@ fn PatternList(withsub: bool) type {
                 var e = self.literals.getOrPut(main.allocator, pat) catch unreachable;
                 if (!e.found_existing) {
                     e.key_ptr.* = pat;
-                    e.value_ptr.* = .{};
+                    e.value_ptr.* = if (withsub) .{} else {};
                 }
                 if (!withsub and !pat.isdir and e.key_ptr.*.isdir) e.key_ptr.* = pat;
                 if (withsub) {
@@ -165,14 +165,14 @@ fn PatternList(withsub: bool) type {
             if (self.literals.getKey(&.{ .pattern = name })) |p| ret = p.isdir;
             for (self.wild.items) |p| {
                 if (ret == false) return ret;
-                if (c.fnmatch(p.pattern, name, 0) == 0) ret = p.isdir;
+                if (c.fnmatch(p.pattern.ptr, name.ptr, 0) == 0) ret = p.isdir;
             }
             return ret;
         }
 
         fn enter(self: *const Self, out: *Patterns, name: [:0]const u8) void {
             if (self.literals.get(&.{ .pattern = name })) |lst| for (lst.items) |sub| out.append(sub);
-            for (self.wild.items) |p| if (c.fnmatch(p.pattern, name, 0) == 0) out.append(p.sub.?);
+            for (self.wild.items) |p| if (c.fnmatch(p.pattern.ptr, name.ptr, 0) == 0) out.append(p.sub.?);
         }
 
         fn deinit(self: *Self) void {
diff --git a/src/main.zig b/src/main.zig
index 7e2d220..c2dd268 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -261,7 +261,8 @@ fn tryReadArgsFile(path: [:0]const u8) void {
     defer f.close();
 
     var arglist = std.ArrayList([:0]const u8).init(allocator);
-    var rd = std.io.bufferedReader(f.reader()).reader();
+    var rd_ = std.io.bufferedReader(f.reader());
+    var rd = rd_.reader();
     var linebuf: [4096]u8 = undefined;
 
     while (
@@ -379,7 +380,8 @@ fn spawnShell() void {
 fn readExcludeFile(path: [:0]const u8) !void {
     const f = try std.fs.cwd().openFileZ(path, .{});
     defer f.close();
-    var rd = std.io.bufferedReader(f.reader()).reader();
+    var rd_ = std.io.bufferedReader(f.reader());
+    var rd = rd_.reader();
     var buf = std.ArrayList(u8).init(allocator);
     defer buf.deinit();
     while (true) {
diff --git a/src/ui.zig b/src/ui.zig
index 4e00548..161546d 100644
--- a/src/ui.zig
+++ b/src/ui.zig
@@ -395,7 +395,7 @@ pub fn move(y: u32, x: u32) void {
 // (Well, addchstr() does that, but not entirely sure I want to go that way.
 // Does that even work with UTF-8? Or do I really need to go wchar madness?)
 pub fn addstr(s: [:0]const u8) void {
-    _ = c.addstr(s);
+    _ = c.addstr(s.ptr);
 }
 
 // Not to be used for strings that may end up >256 bytes.
From 91281ef11f4a826e5e369844dc222924e580d16f Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Wed, 2 Nov 2022 11:28:43 +0100
Subject: [PATCH] Use extern instead of packed structs for the data model

Still using a few embedded packed structs for those fields that benefit
from bit packing. This isn't much cleaner than using packed structs for
everything, but it does have better semantics. In particular, all fields
(except those inside nested packed structs) are now guaranteed to be
byte-aligned and I don't have to worry about the memory representation
of integers when pointer-casting between the different Entry types.
---
 src/browser.zig |  46 ++++++------
 src/delete.zig  |   2 +-
 src/model.zig   | 181 ++++++++++++++++++++++++------------------------
 src/scan.zig    |  34 ++++-----
 4 files changed, 130 insertions(+), 133 deletions(-)

diff --git a/src/browser.zig b/src/browser.zig
index 13e09eb..8ec4765 100644
--- a/src/browser.zig
+++ b/src/browser.zig
@@ -83,22 +83,22 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
     switch (main.config.sort_col) {
         .name => {}, // name sorting is the fallback
         .blocks => {
-            if (sortIntLt(a.blocks, b.blocks)) |r| return r;
+            if (sortIntLt(a.pack.blocks, b.pack.blocks)) |r| return r;
             if (sortIntLt(a.size, b.size)) |r| return r;
         },
         .size => {
             if (sortIntLt(a.size, b.size)) |r| return r;
-            if (sortIntLt(a.blocks, b.blocks)) |r| return r;
+            if (sortIntLt(a.pack.blocks, b.pack.blocks)) |r| return r;
         },
         .items => {
             const ai = if (a.dir()) |d| d.items else 0;
             const bi = if (b.dir()) |d| d.items else 0;
             if (sortIntLt(ai, bi)) |r| return r;
-            if (sortIntLt(a.blocks, b.blocks)) |r| return r;
+            if (sortIntLt(a.pack.blocks, b.pack.blocks)) |r| return r;
             if (sortIntLt(a.size, b.size)) |r| return r;
         },
         .mtime => {
-            if (!a.isext or !b.isext) return a.isext;
+            if (!a.pack.isext or !b.pack.isext) return a.pack.isext;
             if (sortIntLt(a.ext().?.mtime, b.ext().?.mtime)) |r| return r;
         },
     }
@@ -135,10 +135,10 @@ pub fn loadDir(next_sel: ?*const model.Entry) void {
         dir_items.append(null) catch unreachable;
     var it = dir_parent.sub;
     while (it) |e| {
-        if (e.blocks > dir_max_blocks) dir_max_blocks = e.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;
         const shown = main.config.show_hidden or blk: {
-            const excl = if (e.file()) |f| f.excluded else false;
+            const excl = if (e.file()) |f| f.pack.excluded else false;
             const name = e.name();
             break :blk !excl and name[0] != '.' and name[name.len-1] != '~';
         };
@@ -164,14 +164,14 @@ const Row = struct {
         const item = self.item orelse return;
         const ch: u7 = ch: {
             if (item.file()) |f| {
-                if (f.err) break :ch '!';
-                if (f.excluded) break :ch '<';
-                if (f.other_fs) break :ch '>';
-                if (f.kernfs) break :ch '^';
-                if (f.notreg) break :ch '@';
+                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.err) break :ch '!';
-                if (d.suberr) break :ch '.';
+                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;
@@ -187,7 +187,7 @@ const Row = struct {
             width += 2 + width;
         defer self.col += width;
         const item = self.item orelse return;
-        const siz = if (main.config.show_blocks) util.blocksToSize(item.blocks) else item.size;
+        const siz = if (main.config.show_blocks) util.blocksToSize(item.pack.blocks) else item.size;
         var shr = if (item.dir()) |d| (if (main.config.show_blocks) util.blocksToSize(d.shared_blocks) else d.shared_size) else 0;
         if (main.config.show_shared == .unique) shr = siz -| shr;
 
@@ -216,8 +216,8 @@ const Row = struct {
         if (main.config.show_percent) {
             self.bg.fg(.num);
             ui.addprint("{d:>5.1}", .{ 100 *
-                if (main.config.show_blocks) @intToFloat(f32, item.blocks) / @intToFloat(f32, std.math.max(1, dir_parent.entry.blocks))
-                else                         @intToFloat(f32, item.size)   / @intToFloat(f32, std.math.max(1, dir_parent.entry.size))
+                if (main.config.show_blocks) @intToFloat(f32, item.pack.blocks) / @intToFloat(f32, std.math.max(1, dir_parent.entry.pack.blocks))
+                else                         @intToFloat(f32, item.size)        / @intToFloat(f32, std.math.max(1, dir_parent.entry.size))
             });
             self.bg.fg(.default);
             ui.addch('%');
@@ -225,7 +225,7 @@ const Row = struct {
         if (main.config.show_graph and main.config.show_percent) ui.addch(' ');
         if (main.config.show_graph) {
             var max = if (main.config.show_blocks) dir_max_blocks else dir_max_size;
-            var num = if (main.config.show_blocks) item.blocks else item.size;
+            var num = if (main.config.show_blocks) item.pack.blocks else item.size;
             if (max < bar_size) {
                 max *= bar_size;
                 num *= bar_size;
@@ -290,7 +290,7 @@ const Row = struct {
     fn name(self: *Self) void {
         ui.move(self.row, self.col);
         if (self.item) |i| {
-            self.bg.fg(if (i.etype == .dir) .dir else .default);
+            self.bg.fg(if (i.pack.etype == .dir) .dir else .default);
             ui.addch(if (i.isDirectory()) '/' else ' ');
             ui.addstr(ui.shorten(ui.toUtf8(i.name()), ui.cols -| self.col -| 1));
         } else {
@@ -460,7 +460,7 @@ const info = struct {
         } else {
             ui.addstr("Type: ");
             ui.style(.default);
-            ui.addstr(if (e.isDirectory()) "Directory" else if (if (e.file()) |f| f.notreg else false) "Other" else "File");
+            ui.addstr(if (e.isDirectory()) "Directory" else if (if (e.file()) |f| f.pack.notreg else false) "Other" else "File");
         }
         row.* += 1;
 
@@ -474,7 +474,7 @@ const info = struct {
         }
 
         // Disk usage & Apparent size
-        drawSize(box, row, "   Disk usage: ", util.blocksToSize(e.blocks), if (e.dir()) |d| util.blocksToSize(d.shared_blocks) else 0);
+        drawSize(box, row, "   Disk usage: ", util.blocksToSize(e.pack.blocks), if (e.dir()) |d| util.blocksToSize(d.shared_blocks) else 0);
         drawSize(box, row, "Apparent size: ", e.size, if (e.dir()) |d| d.shared_size else 0);
 
         // Number of items
@@ -522,7 +522,7 @@ const info = struct {
         var row: u32 = 2;
 
         // Tabs
-        if (e.etype == .link) {
+        if (e.pack.etype == .link) {
             box.tab(cols-19, tab == .info, 1, "Info");
             box.tab(cols-10, tab == .links, 2, "Links");
         }
@@ -543,7 +543,7 @@ const info = struct {
     }
 
     fn keyInput(ch: i32) bool {
-        if (entry.?.etype == .link) {
+        if (entry.?.pack.etype == .link) {
             switch (ch) {
                 '1', 'h', ui.c.KEY_LEFT => { set(entry, .info); return true; },
                 '2', 'l', ui.c.KEY_RIGHT => { set(entry, .links); return true; },
@@ -778,7 +778,7 @@ pub fn draw() void {
     ui.move(ui.rows-1, 1);
     ui.style(if (main.config.show_blocks) .bold_hd else .hd);
     ui.addstr("Total disk usage: ");
-    ui.addsize(.hd, util.blocksToSize(dir_parent.entry.blocks));
+    ui.addsize(.hd, util.blocksToSize(dir_parent.entry.pack.blocks));
     ui.style(if (main.config.show_blocks) .hd else .bold_hd);
     ui.addstr("  Apparent size: ");
     ui.addsize(.hd, dir_parent.entry.size);
diff --git a/src/delete.zig b/src/delete.zig
index 1f6575c..d98a2bd 100644
--- a/src/delete.zig
+++ b/src/delete.zig
@@ -100,7 +100,7 @@ fn drawConfirm() void {
     ui.addstr("Are you sure you want to delete \"");
     ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
     ui.addch('"');
-    if (entry.etype != .dir)
+    if (entry.pack.etype != .dir)
         ui.addch('?')
     else {
         box.move(2, 18);
diff --git a/src/model.zig b/src/model.zig
index 75cfe3d..cfd67b5 100644
--- a/src/model.zig
+++ b/src/model.zig
@@ -17,7 +17,7 @@ const allocator = allocator_state.allocator();
 
 pub const EType = enum(u2) { dir, link, file };
 
-// Type for the Entry.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 = u60;
 
 // Memory layout:
@@ -31,38 +31,40 @@ pub const Blocks = u60;
 // These are all packed structs and hence do not have any alignment, which is
 // great for saving memory but perhaps not very great for code size or
 // performance.
-// (TODO: What are the aliassing rules for Zig? There is a 'noalias' keyword,
-// but does that mean all unmarked pointers are allowed to alias?)
-pub const Entry = packed struct {
-    etype: EType,
-    isext: bool,
-    // Whether or not this entry's size has been counted in its parents.
-    // Counting of Link entries is deferred until the scan/delete operation has
-    // completed, so for those entries this flag indicates an intention to be
-    // counted.
-    counted: bool,
-    blocks: Blocks, // 512-byte blocks
-    size: u64,
-    next: ?*Entry,
+pub const Entry = extern struct {
+    pack: Packed align(1),
+    size: u64 align(1),
+    next: ?*Entry align(1),
+
+    pub const Packed = packed struct(u64) {
+        etype: EType,
+        isext: bool,
+        // Whether or not this entry's size has been counted in its parents.
+        // Counting of Link entries is deferred until the scan/delete operation has
+        // completed, so for those entries this flag indicates an intention to be
+        // counted.
+        counted: bool,
+        blocks: Blocks, // 512-byte blocks
+    };
 
     const Self = @This();
 
     pub fn dir(self: *Self) ?*Dir {
-        return if (self.etype == .dir) @ptrCast(*Dir, self) else null;
+        return if (self.pack.etype == .dir) @ptrCast(*Dir, self) else null;
     }
 
     pub fn link(self: *Self) ?*Link {
-        return if (self.etype == .link) @ptrCast(*Link, self) else null;
+        return if (self.pack.etype == .link) @ptrCast(*Link, self) else null;
     }
 
     pub fn file(self: *Self) ?*File {
-        return if (self.etype == .file) @ptrCast(*File, self) else null;
+        return if (self.pack.etype == .file) @ptrCast(*File, 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.other_fs or f.kernfs else self.etype == .dir;
+        return if (self.file()) |f| f.pack.other_fs or f.pack.kernfs else self.pack.etype == .dir;
     }
 
     fn nameOffset(etype: EType) usize {
@@ -74,12 +76,12 @@ pub const Entry = packed struct {
     }
 
     pub fn name(self: *const Self) [:0]const u8 {
-        const ptr = @ptrCast([*:0]const u8, self) + nameOffset(self.etype);
+        const ptr = @ptrCast([*:0]const u8, self) + nameOffset(self.pack.etype); // TODO: ptrCast the 'name' field instead.
         return std.mem.sliceTo(ptr, 0);
     }
 
     pub fn ext(self: *Self) ?*Ext {
-        if (!self.isext) return null;
+        if (!self.pack.isext) return null;
         return @ptrCast(*Ext, @ptrCast([*]Ext, self) - 1);
     }
 
@@ -88,7 +90,7 @@ pub const Entry = packed struct {
         const size = nameOffset(etype) + ename.len + 1 + extsize;
         var ptr = blk: {
             while (true) {
-                if (allocator.allocWithOptions(u8, size, std.math.max(@alignOf(Ext), @alignOf(Entry)), null)) |p|
+                if (allocator.allocWithOptions(u8, size, 1, null)) |p|
                     break :blk p
                 else |_| {}
                 ui.oom();
@@ -96,8 +98,8 @@ pub const Entry = packed struct {
         };
         std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
         var e = @ptrCast(*Entry, ptr.ptr + extsize);
-        e.etype = etype;
-        e.isext = isext;
+        e.pack.etype = etype;
+        e.pack.isext = isext;
         var name_ptr = @ptrCast([*]u8, e) + nameOffset(etype);
         std.mem.copy(u8, name_ptr[0..ename.len], ename);
         return e;
@@ -105,19 +107,19 @@ pub const Entry = packed struct {
 
     // Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents.
     pub fn setErr(self: *Self, parent: *Dir) void {
-        if (self.dir()) |d| d.err = true
-        else if (self.file()) |f| f.err = true
+        if (self.dir()) |d| d.pack.err = true
+        else if (self.file()) |f| f.pack.err = true
         else unreachable;
         var it: ?*Dir = if (&parent.entry == self) parent.parent else parent;
         while (it) |p| : (it = p.parent) {
-            if (p.suberr) break;
-            p.suberr = true;
+            if (p.pack.suberr) break;
+            p.pack.suberr = true;
         }
     }
 
     pub fn addStats(self: *Entry, parent: *Dir, nlink: u31) void {
-        if (self.counted) return;
-        self.counted = true;
+        if (self.pack.counted) return;
+        self.pack.counted = true;
 
         // Add link to the inode map, but don't count its size (yet).
         if (self.link()) |l| {
@@ -125,7 +127,7 @@ pub const Entry = packed struct {
             var d = inodes.map.getOrPut(l) catch unreachable;
             if (!d.found_existing) {
                 d.value_ptr.* = .{ .counted = false, .nlink = nlink };
-                inodes.total_blocks +|= self.blocks;
+                inodes.total_blocks +|= self.pack.blocks;
                 l.next = l;
             } else {
                 inodes.setStats(.{ .key_ptr = d.key_ptr, .value_ptr = d.value_ptr }, false);
@@ -144,9 +146,9 @@ pub const Entry = packed struct {
                 if (p.entry.ext()) |pe|
                     if (e.mtime > pe.mtime) { pe.mtime = e.mtime; };
             p.items +|= 1;
-            if (self.etype != .link) {
+            if (self.pack.etype != .link) {
                 p.entry.size +|= self.size;
-                p.entry.blocks +|= self.blocks;
+                p.entry.pack.blocks +|= self.pack.blocks;
             }
         }
     }
@@ -165,8 +167,8 @@ pub const Entry = packed struct {
     // anymore, meaning that delStats() followed by addStats() with the same
     // data may cause information to be lost.
     pub fn delStats(self: *Entry, parent: *Dir) void {
-        if (!self.counted) return;
-        defer self.counted = false; // defer, to make sure inodes.setStats() still sees it as counted.
+        if (!self.pack.counted) return;
+        defer self.pack.counted = false; // defer, to make sure inodes.setStats() still sees it as counted.
 
         if (self.link()) |l| {
             var d = inodes.map.getEntry(l).?;
@@ -175,7 +177,7 @@ pub const Entry = packed struct {
             if (l.next == l) {
                 _ = inodes.map.remove(l);
                 _ = inodes.uncounted.remove(l);
-                inodes.total_blocks -|= self.blocks;
+                inodes.total_blocks -|= self.pack.blocks;
             } else {
                 if (d.key_ptr.* == l)
                     d.key_ptr.* = l.next;
@@ -195,9 +197,9 @@ pub const Entry = packed struct {
         var it: ?*Dir = parent;
         while(it) |p| : (it = p.parent) {
             p.items -|= 1;
-            if (self.etype != .link) {
+            if (self.pack.etype != .link) {
                 p.entry.size -|= self.size;
-                p.entry.blocks -|= self.blocks;
+                p.entry.pack.blocks -|= self.pack.blocks;
             }
         }
     }
@@ -212,32 +214,35 @@ pub const Entry = packed struct {
     }
 };
 
-const DevId = u30; // Can be reduced to make room for more flags in Dir.
+const DevId = u30; // Can be reduced to make room for more flags in Dir.Packed.
 
-pub const Dir = packed struct {
+pub const Dir = extern struct {
     entry: Entry,
 
-    sub: ?*Entry,
-    parent: ?*Dir,
+    sub: ?*Entry align(1),
+    parent: ?*Dir align(1),
 
     // entry.{blocks,size}: Total size of all unique files + dirs. Non-shared hardlinks are counted only once.
     //   (i.e. the space you'll need if you created a filesystem with only this dir)
     // shared_*: Unique hardlinks that still have references outside of this directory.
     //   (i.e. the space you won't reclaim by deleting this dir)
     // (space reclaimed by deleting a dir =~ entry. - shared_)
-    shared_blocks: u64,
-    shared_size: u64,
-    items: u32,
-
-    // Indexes into the global 'devices.list' array
-    dev: DevId,
+    shared_blocks: u64 align(1),
+    shared_size: u64 align(1),
+    items: u32 align(1),
 
-    err: bool,
-    suberr: bool,
+    pack: Packed align(1),
 
     // Only used to find the @offsetOff, the name is written at this point as a 0-terminated string.
     // (Old C habits die hard)
-    name: u8,
+    name: [0]u8,
+
+    pub const Packed = packed struct {
+        // Indexes into the global 'devices.list' array
+        dev: DevId,
+        err: bool,
+        suberr: bool,
+    };
 
     pub fn fmtPath(self: *const @This(), withRoot: bool, out: *std.ArrayList(u8)) void {
         if (!withRoot and self.parent == null) return;
@@ -259,13 +264,13 @@ pub const Dir = packed struct {
 };
 
 // File that's been hardlinked (i.e. nlink > 1)
-pub const Link = packed struct {
+pub const Link = extern struct {
     entry: Entry,
-    parent: *Dir,
-    next: *Link, // Singly circular linked list of all *Link nodes with the same dev,ino.
+    parent: *Dir align(1),
+    next: *Link align(1), // Singly circular linked list of all *Link nodes with the same dev,ino.
     // dev is inherited from the parent Dir
-    ino: u64,
-    name: u8,
+    ino: u64 align(1),
+    name: [0]u8,
 
     // Return value should be freed with main.allocator.
     pub fn path(self: @This(), withRoot: bool) [:0]const u8 {
@@ -278,40 +283,32 @@ pub const Link = packed struct {
 };
 
 // Anything that's not an (indexed) directory or hardlink. Excluded directories are also "Files".
-pub const File = packed struct {
+pub const File = extern struct {
     entry: Entry,
-
-    err: bool,
-    excluded: bool,
-    other_fs: bool,
-    kernfs: bool,
-    notreg: bool,
-    _pad: u3,
-
-    name: u8,
+    pack: Packed,
+    name: [0]u8,
+
+    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 fn resetFlags(f: *@This()) void {
-        f.err = false;
-        f.excluded = false;
-        f.other_fs = false;
-        f.kernfs = false;
-        f.notreg = false;
+        f.pack = .{};
     }
 };
 
-pub const Ext = packed struct {
-    mtime: u64 = 0,
-    uid: u32 = 0,
-    gid: u32 = 0,
-    mode: u16 = 0,
+pub const Ext = extern struct {
+    mtime: u64 align(1) = 0,
+    uid: u32 align(1) = 0,
+    gid: u32 align(1) = 0,
+    mode: u16 align(1) = 0,
 };
 
-comptime {
-    std.debug.assert(@bitOffsetOf(Dir, "name") % 8 == 0);
-    std.debug.assert(@bitOffsetOf(Link, "name") % 8 == 0);
-    std.debug.assert(@bitOffsetOf(File, "name") % 8 == 0);
-}
-
 
 // List of st_dev entries. Those are typically 64bits, but that's quite a waste
 // of space when a typical scan won't cover many unique devices.
@@ -367,13 +364,13 @@ pub const inodes = struct {
     const HashContext = struct {
         pub fn hash(_: @This(), l: *Link) u64 {
             var h = std.hash.Wyhash.init(0);
-            h.update(std.mem.asBytes(&@as(u32, l.parent.dev)));
+            h.update(std.mem.asBytes(&@as(u32, l.parent.pack.dev)));
             h.update(std.mem.asBytes(&l.ino));
             return h.final();
         }
 
         pub fn eql(_: @This(), a: *Link, b: *Link) bool {
-            return a.ino == b.ino and a.parent.dev == b.parent.dev;
+            return a.ino == b.ino and a.parent.pack.dev == b.parent.pack.dev;
         }
     };
 
@@ -399,7 +396,7 @@ pub const inodes = struct {
         defer dirs.deinit();
         var it = entry.key_ptr.*;
         while (true) {
-            if (it.entry.counted) {
+            if (it.entry.pack.counted) {
                 nlink += 1;
                 var parent: ?*Dir = it.parent;
                 while (parent) |p| : (parent = p.parent) {
@@ -419,19 +416,19 @@ pub const inodes = struct {
         var dir_iter = dirs.iterator();
         if (add) {
             while (dir_iter.next()) |de| {
-                de.key_ptr.*.entry.blocks +|= entry.key_ptr.*.entry.blocks;
-                de.key_ptr.*.entry.size   +|= entry.key_ptr.*.entry.size;
+                de.key_ptr.*.entry.pack.blocks +|= entry.key_ptr.*.entry.pack.blocks;
+                de.key_ptr.*.entry.size        +|= entry.key_ptr.*.entry.size;
                 if (de.value_ptr.* < nlink) {
-                    de.key_ptr.*.shared_blocks +|= entry.key_ptr.*.entry.blocks;
+                    de.key_ptr.*.shared_blocks +|= entry.key_ptr.*.entry.pack.blocks;
                     de.key_ptr.*.shared_size   +|= entry.key_ptr.*.entry.size;
                 }
             }
         } else {
             while (dir_iter.next()) |de| {
-                de.key_ptr.*.entry.blocks -|= entry.key_ptr.*.entry.blocks;
-                de.key_ptr.*.entry.size   -|= entry.key_ptr.*.entry.size;
+                de.key_ptr.*.entry.pack.blocks -|= entry.key_ptr.*.entry.pack.blocks;
+                de.key_ptr.*.entry.size        -|= entry.key_ptr.*.entry.size;
                 if (de.value_ptr.* < nlink) {
-                    de.key_ptr.*.shared_blocks -|= entry.key_ptr.*.entry.blocks;
+                    de.key_ptr.*.shared_blocks -|= entry.key_ptr.*.entry.pack.blocks;
                     de.key_ptr.*.shared_size   -|= entry.key_ptr.*.entry.size;
                 }
             }
@@ -458,7 +455,7 @@ pub var root: *Dir = undefined;
 
 test "entry" {
     var e = Entry.create(.file, false, "hello");
-    try std.testing.expectEqual(e.etype, .file);
-    try std.testing.expect(!e.isext);
+    try std.testing.expectEqual(e.pack.etype, .file);
+    try std.testing.expect(!e.pack.isext);
     try std.testing.expectEqualStrings(e.name(), "hello");
 }
diff --git a/src/scan.zig b/src/scan.zig
index 74c2ae5..142857c 100644
--- a/src/scan.zig
+++ b/src/scan.zig
@@ -156,11 +156,11 @@ const ScanDir = struct {
                 // in-place conversion to a File entry. That's more efficient,
                 // but also more code. I don't expect this to happen often.
                 var e = entry.key_ptr.*.?;
-                if (e.etype == .file) {
-                    if (e.size > 0 or e.blocks > 0) {
+                if (e.pack.etype == .file) {
+                    if (e.size > 0 or e.pack.blocks > 0) {
                         e.delStats(self.dir);
                         e.size = 0;
-                        e.blocks = 0;
+                        e.pack.blocks = 0;
                         e.addStats(self.dir, 0);
                     }
                     e.file().?.resetFlags();
@@ -177,9 +177,9 @@ const ScanDir = struct {
         var f = e.file().?;
         switch (t) {
             .err => e.setErr(self.dir),
-            .other_fs => f.other_fs = true,
-            .kernfs => f.kernfs = true,
-            .excluded => f.excluded = true,
+            .other_fs => f.pack.other_fs = true,
+            .kernfs => f.pack.kernfs = true,
+            .excluded => f.pack.excluded = true,
         }
     }
 
@@ -192,9 +192,9 @@ const ScanDir = struct {
                 // XXX: In-place conversion may also be possible here.
                 var e = entry.key_ptr.*.?;
                 // changes of dev/ino affect hard link counting in a way we can't simply merge.
-                const samedev = if (e.dir()) |d| d.dev == model.devices.getId(stat.dev) else true;
+                const samedev = if (e.dir()) |d| d.pack.dev == model.devices.getId(stat.dev) else true;
                 const sameino = if (e.link()) |l| l.ino == stat.ino else true;
-                if (e.etype == etype and samedev and sameino) {
+                if (e.pack.etype == etype and samedev and sameino) {
                     _ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
                     break :blk e;
                 } else e.delStatsRec(self.dir);
@@ -209,18 +209,18 @@ const ScanDir = struct {
         // entire subtree, which, in turn, would break all shared hardlink
         // sizes. The current approach may result in incorrect sizes after
         // refresh, but I expect the difference to be fairly minor.
-        if (!(e.etype == .dir and e.counted) and (e.blocks != stat.blocks or e.size != stat.size)) {
+        if (!(e.pack.etype == .dir and e.pack.counted) and (e.pack.blocks != stat.blocks or e.size != stat.size)) {
             e.delStats(self.dir);
-            e.blocks = stat.blocks;
+            e.pack.blocks = stat.blocks;
             e.size = stat.size;
         }
         if (e.dir()) |d| {
             d.parent = self.dir;
-            d.dev = model.devices.getId(stat.dev);
+            d.pack.dev = model.devices.getId(stat.dev);
         }
         if (e.file()) |f| {
             f.resetFlags();
-            f.notreg = !stat.dir and !stat.reg;
+            f.pack.notreg = !stat.dir and !stat.reg;
         }
         if (e.link()) |l| l.ino = stat.ino;
         if (e.ext()) |ext| {
@@ -415,11 +415,11 @@ const Context = struct {
             var e = if (p.items.len == 0) blk: {
                 // Root entry
                 var e = model.Entry.create(.dir, main.config.extended, self.name);
-                e.blocks = self.stat.blocks;
+                e.pack.blocks = self.stat.blocks;
                 e.size = self.stat.size;
                 if (e.ext()) |ext| ext.* = self.stat.ext;
                 model.root = e.dir().?;
-                model.root.dev = model.devices.getId(self.stat.dev);
+                model.root.pack.dev = model.devices.getId(self.stat.dev);
                 break :blk e;
             } else
                 p.items[p.items.len-1].addStat(self.name, &self.stat);
@@ -552,7 +552,7 @@ pub fn setupRefresh(parent: *model.Dir) void {
     parent.fmtPath(true, &full_path);
     active_context.pushPath(full_path.items);
     active_context.stat.dir = true;
-    active_context.stat.dev = model.devices.list.items[parent.dev];
+    active_context.stat.dev = model.devices.list.items[parent.pack.dev];
 }
 
 // To be called after setupRefresh() (or from scanRoot())
@@ -1021,7 +1021,7 @@ fn drawBox() void {
         box.move(2, 30);
         ui.addstr("size: ");
         // TODO: Should display the size of the dir-to-be-refreshed on refreshing, not the root.
-        ui.addsize(.default, util.blocksToSize(model.root.entry.blocks +| model.inodes.total_blocks));
+        ui.addsize(.default, util.blocksToSize(model.root.entry.pack.blocks +| model.inodes.total_blocks));
     }
 
     box.move(3, 2);
@@ -1088,7 +1088,7 @@ pub fn draw() void {
                     .{ ui.shorten(active_context.pathZ(), 63), active_context.items_seen }
                 ) catch return;
             } else {
-                const r = ui.FmtSize.fmt(util.blocksToSize(model.root.entry.blocks));
+                const r = ui.FmtSize.fmt(util.blocksToSize(model.root.entry.pack.blocks));
                 line = std.fmt.bufPrint(&buf, "\x1b7\x1b[J{s: <51} {d:>9} files / {s}{s}\x1b8",
                     .{ ui.shorten(active_context.pathZ(), 51), active_context.items_seen, r.num(), r.unit }
                 ) catch return;
From 890e5a4af7d0d1afe6ec523749744cf2bcaa4d84 Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Wed, 2 Nov 2022 14:39:05 +0100
Subject: [PATCH] Slightly less hacky Entry struct allocation and
 initialization

---
 src/model.zig | 99 +++++++++++++++++++++++++--------------------------
 src/scan.zig  |  7 ++--
 2 files changed, 51 insertions(+), 55 deletions(-)

diff --git a/src/model.zig b/src/model.zig
index cfd67b5..34e9a1c 100644
--- a/src/model.zig
+++ b/src/model.zig
@@ -33,8 +33,8 @@ pub const Blocks = u60;
 // performance.
 pub const Entry = extern struct {
     pack: Packed align(1),
-    size: u64 align(1),
-    next: ?*Entry align(1),
+    size: u64 align(1) = 0,
+    next: ?*Entry align(1) = null,
 
     pub const Packed = packed struct(u64) {
         etype: EType,
@@ -43,8 +43,8 @@ pub const Entry = extern struct {
         // Counting of Link entries is deferred until the scan/delete operation has
         // completed, so for those entries this flag indicates an intention to be
         // counted.
-        counted: bool,
-        blocks: Blocks, // 512-byte blocks
+        counted: bool = false,
+        blocks: Blocks = 0, // 512-byte blocks
     };
 
     const Self = @This();
@@ -67,17 +67,13 @@ pub const Entry = extern struct {
         return if (self.file()) |f| f.pack.other_fs or f.pack.kernfs else self.pack.etype == .dir;
     }
 
-    fn nameOffset(etype: EType) usize {
-        return switch (etype) {
-            .dir => @offsetOf(Dir, "name"),
-            .link => @offsetOf(Link, "name"),
-            .file => @offsetOf(File, "name"),
-        };
-    }
-
     pub fn name(self: *const Self) [:0]const u8 {
-        const ptr = @ptrCast([*:0]const u8, self) + nameOffset(self.pack.etype); // TODO: ptrCast the 'name' field instead.
-        return std.mem.sliceTo(ptr, 0);
+        const ptr = switch (self.pack.etype) {
+            .dir => &@ptrCast(*const Dir, self).name,
+            .link => &@ptrCast(*const Link, self).name,
+            .file => &@ptrCast(*const File, self).name,
+        };
+        return std.mem.sliceTo(@ptrCast([*:0]const u8, ptr), 0);
     }
 
     pub fn ext(self: *Self) ?*Ext {
@@ -85,24 +81,31 @@ pub const Entry = extern struct {
         return @ptrCast(*Ext, @ptrCast([*]Ext, self) - 1);
     }
 
+    fn alloc(comptime T: type, etype: EType, isext: bool, ename: []const u8) *Entry {
+        const size = (if (isext) @as(usize, @sizeOf(Ext)) else 0) + @sizeOf(T) + ename.len + 1;
+        var ptr = blk: while (true) {
+            if (allocator.allocWithOptions(u8, size, 1, null)) |p| break :blk p
+            else |_| {}
+            ui.oom();
+        };
+        if (isext) {
+            @ptrCast(*Ext, ptr).* = .{};
+            ptr = ptr[@sizeOf(Ext)..];
+        }
+        const e = @ptrCast(*T, ptr);
+        e.* = .{ .entry = .{ .pack = .{ .etype = etype, .isext = isext } } };
+        const n = @ptrCast([*]u8, &e.name)[0..ename.len+1];
+        std.mem.copy(u8, n, ename);
+        n[ename.len] = 0;
+        return &e.entry;
+    }
+
     pub fn create(etype: EType, isext: bool, ename: []const u8) *Entry {
-        const extsize = if (isext) @as(usize, @sizeOf(Ext)) else 0;
-        const size = nameOffset(etype) + ename.len + 1 + extsize;
-        var ptr = blk: {
-            while (true) {
-                if (allocator.allocWithOptions(u8, size, 1, null)) |p|
-                    break :blk p
-                else |_| {}
-                ui.oom();
-            }
+        return switch (etype) {
+            .dir  => alloc(Dir, etype, isext, ename),
+            .file => alloc(File, etype, isext, ename),
+            .link => alloc(Link, etype, isext, ename),
         };
-        std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
-        var e = @ptrCast(*Entry, ptr.ptr + extsize);
-        e.pack.etype = etype;
-        e.pack.isext = isext;
-        var name_ptr = @ptrCast([*]u8, e) + nameOffset(etype);
-        std.mem.copy(u8, name_ptr[0..ename.len], ename);
-        return e;
     }
 
     // Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents.
@@ -219,29 +222,29 @@ const DevId = u30; // Can be reduced to make room for more flags in Dir.Packed.
 pub const Dir = extern struct {
     entry: Entry,
 
-    sub: ?*Entry align(1),
-    parent: ?*Dir align(1),
+    sub: ?*Entry align(1) = null,
+    parent: ?*Dir align(1) = null,
 
     // entry.{blocks,size}: Total size of all unique files + dirs. Non-shared hardlinks are counted only once.
     //   (i.e. the space you'll need if you created a filesystem with only this dir)
     // shared_*: Unique hardlinks that still have references outside of this directory.
     //   (i.e. the space you won't reclaim by deleting this dir)
     // (space reclaimed by deleting a dir =~ entry. - shared_)
-    shared_blocks: u64 align(1),
-    shared_size: u64 align(1),
-    items: u32 align(1),
+    shared_blocks: u64 align(1) = 0,
+    shared_size: u64 align(1) = 0,
+    items: u32 align(1) = 0,
 
-    pack: Packed align(1),
+    pack: Packed align(1) = .{},
 
     // Only used to find the @offsetOff, the name is written at this point as a 0-terminated string.
     // (Old C habits die hard)
-    name: [0]u8,
+    name: [0]u8 = undefined,
 
     pub const Packed = packed struct {
         // Indexes into the global 'devices.list' array
-        dev: DevId,
-        err: bool,
-        suberr: bool,
+        dev: DevId = 0,
+        err: bool = false,
+        suberr: bool = false,
     };
 
     pub fn fmtPath(self: *const @This(), withRoot: bool, out: *std.ArrayList(u8)) void {
@@ -266,11 +269,11 @@ pub const Dir = extern struct {
 // File that's been hardlinked (i.e. nlink > 1)
 pub const Link = extern struct {
     entry: Entry,
-    parent: *Dir align(1),
-    next: *Link align(1), // Singly circular linked list of all *Link nodes with the same dev,ino.
+    parent: *Dir align(1) = undefined,
+    next: *Link align(1) = undefined, // Singly circular linked list of all *Link nodes with the same dev,ino.
     // dev is inherited from the parent Dir
-    ino: u64 align(1),
-    name: [0]u8,
+    ino: u64 align(1) = undefined,
+    name: [0]u8 = undefined,
 
     // Return value should be freed with main.allocator.
     pub fn path(self: @This(), withRoot: bool) [:0]const u8 {
@@ -285,8 +288,8 @@ 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,
+    pack: Packed = .{},
+    name: [0]u8 = undefined,
 
     pub const Packed = packed struct(u8) {
         err: bool = false,
@@ -296,10 +299,6 @@ pub const File = extern struct {
         notreg: bool = false,
         _pad: u3 = 0, // Make this struct "ABI sized" to allow inclusion in an extern struct
     };
-
-    pub fn resetFlags(f: *@This()) void {
-        f.pack = .{};
-    }
 };
 
 pub const Ext = extern struct {
diff --git a/src/scan.zig b/src/scan.zig
index 142857c..5da6a6e 100644
--- a/src/scan.zig
+++ b/src/scan.zig
@@ -163,7 +163,7 @@ const ScanDir = struct {
                         e.pack.blocks = 0;
                         e.addStats(self.dir, 0);
                     }
-                    e.file().?.resetFlags();
+                    e.file().?.pack = .{};
                     _ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
                     break :blk e;
                 } else e.delStatsRec(self.dir);
@@ -218,10 +218,7 @@ const ScanDir = struct {
             d.parent = self.dir;
             d.pack.dev = model.devices.getId(stat.dev);
         }
-        if (e.file()) |f| {
-            f.resetFlags();
-            f.pack.notreg = !stat.dir and !stat.reg;
-        }
+        if (e.file()) |f| f.pack = .{ .notreg = !stat.dir and !stat.reg };
         if (e.link()) |l| l.ino = stat.ino;
         if (e.ext()) |ext| {
             if (ext.mtime > stat.ext.mtime)
From 4d124c7c3d8c216109f2580ab3d6a5ce6bc86c6c Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Wed, 2 Nov 2022 14:51:22 +0100
Subject: [PATCH] Fix struct copy and invalid pointer access in Link.path()

Interesting case of
https://ziglang.org/download/0.10.0/release-notes.html#Escaped-Pointer-to-Parameter
---
 src/model.zig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/model.zig b/src/model.zig
index 34e9a1c..d93d5b7 100644
--- a/src/model.zig
+++ b/src/model.zig
@@ -276,7 +276,7 @@ pub const Link = extern struct {
     name: [0]u8 = undefined,
 
     // Return value should be freed with main.allocator.
-    pub fn path(self: @This(), withRoot: bool) [:0]const u8 {
+    pub fn path(self: *const @This(), withRoot: bool) [:0]const u8 {
         var out = std.ArrayList(u8).init(main.allocator);
         self.parent.fmtPath(withRoot, &out);
         out.append('/') catch unreachable;
