Skip to content
Snippets Groups Projects
Commit 36bc405a authored by Yorhel's avatar Yorhel
Browse files

Add parent node pointers to Dir struct + remove Parents abstraction

While this simplifies the code a bit, it's a regression in the sense
that it increases memory use.

This commit is yak shaving for another hard link counting approach I'd
like to try out, which should be a *LOT* less memory hungry compared to
the current approach. Even though it does, indeed, add an extra cost of
these parent node pointers.
parent b94db184
No related branches found
No related tags found
No related merge requests found
...@@ -10,8 +10,8 @@ const ui = @import("ui.zig"); ...@@ -10,8 +10,8 @@ const ui = @import("ui.zig");
const c = @cImport(@cInclude("time.h")); const c = @cImport(@cInclude("time.h"));
usingnamespace @import("util.zig"); usingnamespace @import("util.zig");
// Currently opened directory and its parents. // Currently opened directory.
pub var dir_parents = model.Parents{}; pub var dir_parent: *model.Dir = undefined;
// Sorted list of all items in the currently opened directory. // Sorted list of all items in the currently opened directory.
// (first item may be null to indicate the "parent directory" item) // (first item may be null to indicate the "parent directory" item)
...@@ -44,12 +44,12 @@ const View = struct { ...@@ -44,12 +44,12 @@ const View = struct {
fn save(self: *@This()) void { fn save(self: *@This()) void {
self.cursor_hash = if (dir_items.items.len == 0) 0 self.cursor_hash = if (dir_items.items.len == 0) 0
else hashEntry(dir_items.items[cursor_idx]); else hashEntry(dir_items.items[cursor_idx]);
opened_dir_views.put(@ptrToInt(dir_parents.top()), self.*) catch {}; opened_dir_views.put(@ptrToInt(dir_parent), self.*) catch {};
} }
// Should be called after dir_parents or dir_items has changed, will load the last saved view and find the proper cursor_idx. // Should be called after dir_parent or dir_items has changed, will load the last saved view and find the proper cursor_idx.
fn load(self: *@This(), sel: ?*const model.Entry) void { fn load(self: *@This(), sel: ?*const model.Entry) void {
if (opened_dir_views.get(@ptrToInt(dir_parents.top()))) |v| self.* = v if (opened_dir_views.get(@ptrToInt(dir_parent))) |v| self.* = v
else self.* = @This(){}; else self.* = @This(){};
cursor_idx = 0; cursor_idx = 0;
for (dir_items.items) |e, i| { for (dir_items.items) |e, i| {
...@@ -123,7 +123,7 @@ fn sortDir(next_sel: ?*const model.Entry) void { ...@@ -123,7 +123,7 @@ fn sortDir(next_sel: ?*const model.Entry) void {
} }
// Must be called when: // Must be called when:
// - dir_parents changes (i.e. we change directory) // - dir_parent changes (i.e. we change directory)
// - config.show_hidden changes // - config.show_hidden changes
// - files in this dir have been added or removed // - files in this dir have been added or removed
pub fn loadDir(next_sel: ?*const model.Entry) void { pub fn loadDir(next_sel: ?*const model.Entry) void {
...@@ -132,9 +132,9 @@ pub fn loadDir(next_sel: ?*const model.Entry) void { ...@@ -132,9 +132,9 @@ pub fn loadDir(next_sel: ?*const model.Entry) void {
dir_max_blocks = 1; dir_max_blocks = 1;
dir_has_shared = false; dir_has_shared = false;
if (!dir_parents.isRoot()) if (dir_parent != model.root)
dir_items.append(null) catch unreachable; dir_items.append(null) catch unreachable;
var it = dir_parents.top().sub; var it = dir_parent.sub;
while (it) |e| { while (it) |e| {
if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks; if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks;
if (e.size > dir_max_size) dir_max_size = e.size; if (e.size > dir_max_size) dir_max_size = e.size;
...@@ -219,8 +219,8 @@ const Row = struct { ...@@ -219,8 +219,8 @@ const Row = struct {
if (main.config.show_graph == .both or main.config.show_graph == .percent) { if (main.config.show_graph == .both or main.config.show_graph == .percent) {
self.bg.fg(.num); self.bg.fg(.num);
ui.addprint("{d:>5.1}", .{ 100* ui.addprint("{d:>5.1}", .{ 100*
if (main.config.show_blocks) @intToFloat(f32, item.blocks) / @intToFloat(f32, std.math.max(1, dir_parents.top().entry.blocks)) 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_parents.top().entry.size)) else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parent.entry.size))
}); });
self.bg.fg(.default); self.bg.fg(.default);
ui.addch('%'); ui.addch('%');
...@@ -276,7 +276,7 @@ const Row = struct { ...@@ -276,7 +276,7 @@ const Row = struct {
if (!main.config.show_mtime or self.col + 37 > ui.cols) return; if (!main.config.show_mtime or self.col + 37 > ui.cols) return;
defer self.col += 27; defer self.col += 27;
ui.move(self.row, self.col+1); ui.move(self.row, self.col+1);
const ext = (if (self.item) |e| e.ext() else @as(?*model.Ext, null)) orelse dir_parents.top().entry.ext(); const ext = (if (self.item) |e| e.ext() else @as(?*model.Ext, null)) orelse dir_parent.entry.ext();
if (ext) |e| ui.addts(self.bg, e.mtime) if (ext) |e| ui.addts(self.bg, e.mtime)
else ui.addstr(" no mtime"); else ui.addstr(" no mtime");
} }
...@@ -359,7 +359,7 @@ const info = struct { ...@@ -359,7 +359,7 @@ const info = struct {
state = .info; state = .info;
tab = t; tab = t;
if (tab == .links and links == null) { if (tab == .links and links == null) {
links = model.LinkPaths.find(&dir_parents, e.?.link().?); links = model.LinkPaths.find(dir_parent, e.?.link().?);
for (links.?.paths.items) |n,i| { for (links.?.paths.items) |n,i| {
if (&n.node.entry == e) { if (&n.node.entry == e) {
links_idx = i; links_idx = i;
...@@ -536,8 +536,7 @@ const info = struct { ...@@ -536,8 +536,7 @@ const info = struct {
return true; return true;
if (ch == 10) { // Enter - go to selected entry if (ch == 10) { // Enter - go to selected entry
const p = links.?.paths.items[links_idx]; const p = links.?.paths.items[links_idx];
dir_parents.stack.shrinkRetainingCapacity(0); dir_parent = p.path;
dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable;
loadDir(&p.node.entry); loadDir(&p.node.entry);
set(null, .info); set(null, .info);
} }
...@@ -730,7 +729,7 @@ pub fn draw() void { ...@@ -730,7 +729,7 @@ pub fn draw() void {
ui.style(.dir); ui.style(.dir);
var pathbuf = std.ArrayList(u8).init(main.allocator); var pathbuf = std.ArrayList(u8).init(main.allocator);
dir_parents.fmtPath(true, &pathbuf); dir_parent.fmtPath(true, &pathbuf);
ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5))); ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
pathbuf.deinit(); pathbuf.deinit();
...@@ -760,12 +759,12 @@ pub fn draw() void { ...@@ -760,12 +759,12 @@ pub fn draw() void {
ui.move(ui.rows-1, 1); ui.move(ui.rows-1, 1);
ui.style(if (main.config.show_blocks) .bold_hd else .hd); ui.style(if (main.config.show_blocks) .bold_hd else .hd);
ui.addstr("Total disk usage: "); ui.addstr("Total disk usage: ");
ui.addsize(.hd, blocksToSize(dir_parents.top().entry.blocks)); ui.addsize(.hd, blocksToSize(dir_parent.entry.blocks));
ui.style(if (main.config.show_blocks) .hd else .bold_hd); ui.style(if (main.config.show_blocks) .hd else .bold_hd);
ui.addstr(" Apparent size: "); ui.addstr(" Apparent size: ");
ui.addsize(.hd, dir_parents.top().entry.size); ui.addsize(.hd, dir_parent.entry.size);
ui.addstr(" Items: "); ui.addstr(" Items: ");
ui.addnum(.hd, dir_parents.top().items); ui.addnum(.hd, dir_parent.items);
switch (state) { switch (state) {
.main => {}, .main => {},
...@@ -832,7 +831,7 @@ pub fn keyInput(ch: i32) void { ...@@ -832,7 +831,7 @@ pub fn keyInput(ch: i32) void {
message = "Directory imported from file, refreshing is disabled." message = "Directory imported from file, refreshing is disabled."
else { else {
main.state = .refresh; main.state = .refresh;
scan.setupRefresh(dir_parents.copy()); scan.setupRefresh(dir_parent);
} }
}, },
'b' => { 'b' => {
...@@ -855,7 +854,7 @@ pub fn keyInput(ch: i32) void { ...@@ -855,7 +854,7 @@ pub fn keyInput(ch: i32) void {
if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1] if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1]
else if (cursor_idx == 0) null else if (cursor_idx == 0) null
else dir_items.items[cursor_idx-1]; else dir_items.items[cursor_idx-1];
delete.setup(dir_parents.copy(), e, next); delete.setup(dir_parent, e, next);
} }
}, },
...@@ -890,20 +889,20 @@ pub fn keyInput(ch: i32) void { ...@@ -890,20 +889,20 @@ pub fn keyInput(ch: i32) void {
if (dir_items.items.len == 0) { if (dir_items.items.len == 0) {
} else if (dir_items.items[cursor_idx]) |e| { } else if (dir_items.items[cursor_idx]) |e| {
if (e.dir()) |d| { if (e.dir()) |d| {
dir_parents.push(d); dir_parent = d;
loadDir(null); loadDir(null);
state = .main; state = .main;
} }
} else if (!dir_parents.isRoot()) { } else if (dir_parent.parent) |p| {
dir_parents.pop(); dir_parent = p;
loadDir(null); loadDir(null);
state = .main; state = .main;
} }
}, },
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => { 'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
if (!dir_parents.isRoot()) { if (dir_parent.parent) |p| {
const e = dir_parents.top(); const e = dir_parent;
dir_parents.pop(); dir_parent = p;
loadDir(&e.entry); loadDir(&e.entry);
state = .main; state = .main;
} }
......
...@@ -8,7 +8,7 @@ const ui = @import("ui.zig"); ...@@ -8,7 +8,7 @@ const ui = @import("ui.zig");
const browser = @import("browser.zig"); const browser = @import("browser.zig");
usingnamespace @import("util.zig"); usingnamespace @import("util.zig");
var parents: model.Parents = .{}; var parent: *model.Dir = undefined;
var entry: *model.Entry = undefined; var entry: *model.Entry = undefined;
var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds
var state: enum { confirm, busy, err } = .confirm; var state: enum { confirm, busy, err } = .confirm;
...@@ -16,9 +16,8 @@ var confirm: enum { yes, no, ignore } = .no; ...@@ -16,9 +16,8 @@ var confirm: enum { yes, no, ignore } = .no;
var error_option: enum { abort, ignore, all } = .abort; var error_option: enum { abort, ignore, all } = .abort;
var error_code: anyerror = undefined; var error_code: anyerror = undefined;
// ownership of p is passed to this function pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void {
pub fn setup(p: model.Parents, e: *model.Entry, n: ?*model.Entry) void { parent = p;
parents = p;
entry = e; entry = e;
next_sel = n; next_sel = n;
state = if (main.config.confirm_delete) .confirm else .busy; state = if (main.config.confirm_delete) .confirm else .busy;
...@@ -49,8 +48,8 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) ...@@ -49,8 +48,8 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry)
var fd = dir.openDirZ(path, .{ .access_sub_paths = true, .iterate = false }) var fd = dir.openDirZ(path, .{ .access_sub_paths = true, .iterate = false })
catch |e| return err(e); catch |e| return err(e);
var it = &d.sub; var it = &d.sub;
parents.push(d); parent = d;
defer parents.pop(); defer parent = parent.parent.?;
while (it.*) |n| { while (it.*) |n| {
if (deleteItem(fd, n.name(), it)) { if (deleteItem(fd, n.name(), it)) {
fd.close(); fd.close();
...@@ -64,14 +63,13 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) ...@@ -64,14 +63,13 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry)
return if (e != error.DirNotEmpty or d.sub == null) err(e) else false; return if (e != error.DirNotEmpty or d.sub == null) err(e) else false;
} else } else
dir.deleteFileZ(path) catch |e| return err(e); dir.deleteFileZ(path) catch |e| return err(e);
ptr.*.?.delStats(&parents); ptr.*.?.delStats(parent);
ptr.* = ptr.*.?.next; ptr.* = ptr.*.?.next;
return false; return false;
} }
// Returns the item that should be selected in the browser. // Returns the item that should be selected in the browser.
pub fn delete() ?*model.Entry { pub fn delete() ?*model.Entry {
defer parents.deinit();
while (main.state == .delete and state == .confirm) while (main.state == .delete and state == .confirm)
main.handleEvent(true, false); main.handleEvent(true, false);
if (main.state != .delete) if (main.state != .delete)
...@@ -79,14 +77,14 @@ pub fn delete() ?*model.Entry { ...@@ -79,14 +77,14 @@ pub fn delete() ?*model.Entry {
// Find the pointer to this entry // Find the pointer to this entry
const e = entry; const e = entry;
var it = &parents.top().sub; var it = &parent.sub;
while (it.*) |n| : (it = &n.next) while (it.*) |n| : (it = &n.next)
if (it.* == entry) if (it.* == entry)
break; break;
var path = std.ArrayList(u8).init(main.allocator); var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit(); defer path.deinit();
parents.fmtPath(true, &path); parent.fmtPath(true, &path);
if (path.items.len == 0 or path.items[path.items.len-1] != '/') if (path.items.len == 0 or path.items[path.items.len-1] != '/')
path.append('/') catch unreachable; path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable; path.appendSlice(entry.name()) catch unreachable;
...@@ -125,7 +123,7 @@ fn drawConfirm() void { ...@@ -125,7 +123,7 @@ fn drawConfirm() void {
fn drawProgress() void { fn drawProgress() void {
var path = std.ArrayList(u8).init(main.allocator); var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit(); defer path.deinit();
parents.fmtPath(false, &path); parent.fmtPath(false, &path);
path.append('/') catch unreachable; path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable; path.appendSlice(entry.name()) catch unreachable;
...@@ -145,7 +143,7 @@ fn drawProgress() void { ...@@ -145,7 +143,7 @@ fn drawProgress() void {
fn drawErr() void { fn drawErr() void {
var path = std.ArrayList(u8).init(main.allocator); var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit(); defer path.deinit();
parents.fmtPath(false, &path); parent.fmtPath(false, &path);
path.append('/') catch unreachable; path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable; path.appendSlice(entry.name()) catch unreachable;
......
...@@ -187,7 +187,7 @@ fn spawnShell() void { ...@@ -187,7 +187,7 @@ fn spawnShell() void {
var path = std.ArrayList(u8).init(allocator); var path = std.ArrayList(u8).init(allocator);
defer path.deinit(); defer path.deinit();
browser.dir_parents.fmtPath(true, &path); browser.dir_parent.fmtPath(true, &path);
var env = std.process.getEnvMap(allocator) catch unreachable; var env = std.process.getEnvMap(allocator) catch unreachable;
defer env.deinit(); defer env.deinit();
...@@ -338,6 +338,7 @@ pub fn main() void { ...@@ -338,6 +338,7 @@ pub fn main() void {
config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode. config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode.
ui.init(); ui.init();
state = .browse; state = .browse;
browser.dir_parent = model.root;
browser.loadDir(null); browser.loadDir(null);
while (true) { while (true) {
......
...@@ -99,29 +99,27 @@ pub const Entry = packed struct { ...@@ -99,29 +99,27 @@ pub const Entry = packed struct {
} }
// Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents. // Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents.
pub fn set_err(self: *Self, parents: *const Parents) void { pub fn setErr(self: *Self, parent: *Dir) void {
if (self.dir()) |d| d.err = true if (self.dir()) |d| d.err = true
else if (self.file()) |f| f.err = true else if (self.file()) |f| f.err = true
else unreachable; else unreachable;
var it = parents.iter(); var it: ?*Dir = if (&parent.entry == self) parent.parent else parent;
if (&parents.top().entry == self) _ = it.next(); while (it) |p| : (it = p.parent) {
while (it.next()) |p| {
if (p.suberr) break; if (p.suberr) break;
p.suberr = true; p.suberr = true;
} }
} }
pub fn addStats(self: *Entry, parents: *const Parents) void { pub fn addStats(self: *Entry, parent: *Dir) void {
if (self.counted) return; if (self.counted) return;
self.counted = true; self.counted = true;
const dev = parents.top().dev;
// Set if this is the first time we've found this hardlink in the bottom-most directory of the given dev. // Set if this is the first time we've found this hardlink in the bottom-most directory of the given dev.
// Means we should count it for other-dev parent dirs, too. // Means we should count it for other-dev parent dirs, too.
var new_hl = false; var new_hl = false;
var it = parents.iter(); var it: ?*Dir = parent;
while(it.next()) |p| { while(it) |p| : (it = p.parent) {
var add_total = false; var add_total = false;
if (self.ext()) |e| if (self.ext()) |e|
...@@ -130,12 +128,12 @@ pub const Entry = packed struct { ...@@ -130,12 +128,12 @@ pub const Entry = packed struct {
p.items = saturateAdd(p.items, 1); p.items = saturateAdd(p.items, 1);
// Hardlink in a subdirectory with a different device, only count it the first time. // Hardlink in a subdirectory with a different device, only count it the first time.
if (self.etype == .link and dev != p.dev) { if (self.etype == .link and parent.dev != p.dev) {
add_total = new_hl; add_total = new_hl;
} else if (self.link()) |l| { } else if (self.link()) |l| {
const n = devices.HardlinkNode{ .ino = l.ino, .dir = p }; const n = devices.HardlinkNode{ .ino = l.ino, .dir = p };
var d = devices.list.items[dev].hardlinks.getOrPut(n) catch unreachable; var d = devices.list.items[parent.dev].hardlinks.getOrPut(n) catch unreachable;
new_hl = !d.found_existing; new_hl = !d.found_existing;
// First time we encounter this file in this dir, count it. // First time we encounter this file in this dir, count it.
if (!d.found_existing) { if (!d.found_existing) {
...@@ -175,29 +173,28 @@ pub const Entry = packed struct { ...@@ -175,29 +173,28 @@ pub const Entry = packed struct {
// //
// This function assumes that, for directories, all sub-entries have // This function assumes that, for directories, all sub-entries have
// already been un-counted. // already been un-counted.
pub fn delStats(self: *Entry, parents: *const Parents) void { pub fn delStats(self: *Entry, parent: *Dir) void {
if (!self.counted) return; if (!self.counted) return;
self.counted = false; self.counted = false;
const dev = parents.top().dev;
var del_hl = false; var del_hl = false;
var it = parents.iter(); var it: ?*Dir = parent;
while(it.next()) |p| { while(it) |p| : (it = p.parent) {
var del_total = false; var del_total = false;
p.items = saturateSub(p.items, 1); p.items = saturateSub(p.items, 1);
if (self.etype == .link and dev != p.dev) { if (self.etype == .link and parent.dev != p.dev) {
del_total = del_hl; del_total = del_hl;
} else if (self.link()) |l| { } else if (self.link()) |l| {
const n = devices.HardlinkNode{ .ino = l.ino, .dir = p }; const n = devices.HardlinkNode{ .ino = l.ino, .dir = p };
var dp = devices.list.items[dev].hardlinks.getEntry(n); var dp = devices.list.items[parent.dev].hardlinks.getEntry(n);
if (dp) |d| { if (dp) |d| {
d.value_ptr.* -= 1; d.value_ptr.* -= 1;
del_total = d.value_ptr.* == 0; del_total = d.value_ptr.* == 0;
del_hl = del_total; del_hl = del_total;
if (del_total) if (del_total)
_ = devices.list.items[dev].hardlinks.remove(n); _ = devices.list.items[parent.dev].hardlinks.remove(n);
} }
} else } else
del_total = true; del_total = true;
...@@ -208,28 +205,13 @@ pub const Entry = packed struct { ...@@ -208,28 +205,13 @@ pub const Entry = packed struct {
} }
} }
pub fn delStatsRec(self: *Entry, parents: *Parents) void { pub fn delStatsRec(self: *Entry, parent: *Dir) void {
if (self.dir()) |d| { if (self.dir()) |d| {
parents.push(d);
var it = d.sub; var it = d.sub;
while (it) |e| : (it = e.next) while (it) |e| : (it = e.next)
e.delStatsRec(parents); e.delStatsRec(d);
parents.pop();
} }
self.delStats(parents); self.delStats(parent);
}
// Insert this entry into the tree at the given directory, updating parent sizes and item counts.
pub fn insert(self: *Entry, parents: *const Parents) void {
self.next = parents.top().sub;
parents.top().sub = self;
if (self.dir()) |d| std.debug.assert(d.sub == null);
// Links with nlink == 0 are counted after we're done scanning.
if (if (self.link()) |l| l.nlink == 0 else false)
link_count.add(parents.top().dev, self.link().?.ino)
else
self.addStats(parents);
} }
}; };
...@@ -239,6 +221,7 @@ pub const Dir = packed struct { ...@@ -239,6 +221,7 @@ pub const Dir = packed struct {
entry: Entry, entry: Entry,
sub: ?*Entry, sub: ?*Entry,
parent: ?*Dir,
// entry.{blocks,size}: Total size of all unique files + dirs. Non-shared hardlinks are counted only once. // 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) // (i.e. the space you'll need if you created a filesystem with only this dir)
...@@ -258,6 +241,23 @@ pub const Dir = packed struct { ...@@ -258,6 +241,23 @@ pub const Dir = packed struct {
// Only used to find the @byteOffsetOff, the name is written at this point as a 0-terminated string. // Only used to find the @byteOffsetOff, the name is written at this point as a 0-terminated string.
// (Old C habits die hard) // (Old C habits die hard)
name: u8, name: u8,
pub fn fmtPath(self: *const @This(), withRoot: bool, out: *std.ArrayList(u8)) void {
var components = std.ArrayList([:0]const u8).init(main.allocator);
defer components.deinit();
var it: ?*const @This() = self;
while (it) |e| : (it = e.parent)
if (withRoot or e != root)
components.append(e.entry.name()) catch unreachable;
var i: usize = components.items.len-1;
while (true) {
if (i != components.items.len-1) out.append('/') catch unreachable;
out.appendSlice(components.items[i]) catch unreachable;
if (i == 0) break;
i -= 1;
}
}
}; };
// File that's been hardlinked (i.e. nlink > 1) // File that's been hardlinked (i.e. nlink > 1)
...@@ -420,23 +420,16 @@ pub const link_count = struct { ...@@ -420,23 +420,16 @@ pub const link_count = struct {
if (d.found_existing) d.key_ptr.*.count += 1; if (d.found_existing) d.key_ptr.*.count += 1;
} }
var final_dir: Parents = undefined; fn finalRec(parent: *Dir) void {
var it = parent.sub;
fn final_rec() void {
var it = final_dir.top().sub;
while (it) |e| : (it = e.next) { while (it) |e| : (it = e.next) {
if (e.dir()) |d| { if (e.dir()) |d| finalRec(d);
final_dir.push(d);
final_rec();
final_dir.pop();
continue;
}
const l = e.link() orelse continue; const l = e.link() orelse continue;
if (l.nlink > 0) continue; if (l.nlink > 0) continue;
const s = Node{ .dev = final_dir.top().dev, .ino = l.ino, .count = 0 }; const s = Node{ .dev = parent.dev, .ino = l.ino, .count = 0 };
if (nodes.getEntry(s)) |n| { if (nodes.getEntry(s)) |n| {
l.nlink = n.key_ptr.*.count; l.nlink = n.key_ptr.*.count;
e.addStats(&final_dir); e.addStats(parent);
} }
} }
} }
...@@ -445,96 +438,30 @@ pub const link_count = struct { ...@@ -445,96 +438,30 @@ pub const link_count = struct {
// find all links, update their nlink count and parent sizes. // find all links, update their nlink count and parent sizes.
pub fn final() void { pub fn final() void {
if (nodes.count() == 0) return; if (nodes.count() == 0) return;
final_dir = Parents{}; finalRec(root);
final_rec();
nodes.clearAndFree(); nodes.clearAndFree();
final_dir.deinit();
} }
}; };
pub var root: *Dir = undefined; pub var root: *Dir = undefined;
// Stack of parent directories, convenient helper when constructing and traversing the tree.
// The 'root' node is always implicitely at the bottom of the stack.
pub const Parents = struct {
stack: std.ArrayList(*Dir) = std.ArrayList(*Dir).init(main.allocator),
const Self = @This();
pub fn push(self: *Self, dir: *Dir) void {
return self.stack.append(dir) catch unreachable;
}
pub fn isRoot(self: *Self) bool {
return self.stack.items.len == 0;
}
// Attempting to remove the root node is considered a bug.
pub fn pop(self: *Self) void {
_ = self.stack.pop();
}
pub fn top(self: *const Self) *Dir {
return if (self.stack.items.len == 0) root else self.stack.items[self.stack.items.len-1];
}
pub const Iterator = struct {
lst: *const Self,
index: usize = 0, // 0 = top of the stack, counts upwards to go down
pub fn next(it: *Iterator) ?*Dir {
const len = it.lst.stack.items.len;
if (it.index > len) return null;
it.index += 1;
return if (it.index > len) root else it.lst.stack.items[len-it.index];
}
};
// Iterate from top to bottom of the stack.
pub fn iter(self: *const Self) Iterator {
return .{ .lst = self };
}
// Append the path to the given arraylist. The list is assumed to use main.allocator, so it can't fail.
pub fn fmtPath(self: *const Self, withRoot: bool, out: *std.ArrayList(u8)) void {
const r = root.entry.name();
if (withRoot) out.appendSlice(r) catch unreachable;
var i: usize = 0;
while (i < self.stack.items.len) {
if (i != 0 or (withRoot and r[r.len-1] != '/')) out.append('/') catch unreachable;
out.appendSlice(self.stack.items[i].entry.name()) catch unreachable;
i += 1;
}
}
pub fn copy(self: *const Self) Self {
var c = Self{};
c.stack.appendSlice(self.stack.items) catch unreachable;
return c;
}
pub fn deinit(self: *Self) void {
self.stack.deinit();
}
};
// List of paths for the same inode. // List of paths for the same inode.
pub const LinkPaths = struct { pub const LinkPaths = struct {
paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator), paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator),
pub const Path = struct { pub const Path = struct {
path: Parents, path: *Dir,
node: *Link, node: *Link,
fn lt(_: void, a: Path, b: Path) bool { fn lt(_: void, a: Path, b: Path) bool {
var i: usize = 0; var pa = std.ArrayList(u8).init(main.allocator);
while (i < a.path.stack.items.len and i < b.path.stack.items.len) : (i += 1) var pb = std.ArrayList(u8).init(main.allocator);
if (a.path.stack.items[i] != b.path.stack.items[i]) defer pa.deinit();
return std.mem.lessThan(u8, a.path.stack.items[i].entry.name(), b.path.stack.items[i].entry.name()); defer pb.deinit();
if (a.path.stack.items.len != b.path.stack.items.len) a.fmtPath(false, &pa);
return a.path.stack.items.len < b.path.stack.items.len; b.fmtPath(false, &pb);
return std.mem.lessThan(u8, a.node.entry.name(), b.node.entry.name()); return std.mem.lessThan(u8, pa.items, pb.items);
} }
pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void { pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void {
...@@ -546,42 +473,36 @@ pub const LinkPaths = struct { ...@@ -546,42 +473,36 @@ pub const LinkPaths = struct {
const Self = @This(); const Self = @This();
fn findRec(self: *Self, parent: *Parents, node: *const Link) void { fn findRec(self: *Self, parent: *Dir, node: *const Link) void {
var entry = parent.top().sub; var entry = parent.sub;
while (entry) |e| : (entry = e.next) { while (entry) |e| : (entry = e.next) {
if (e.link()) |l| { if (e.link()) |l| {
if (l.ino == node.ino) if (l.ino == node.ino)
self.paths.append(Path{ .path = parent.copy(), .node = l }) catch unreachable; self.paths.append(Path{ .path = parent, .node = l }) catch unreachable;
}
if (e.dir()) |d| {
if (d.dev == parent.top().dev) {
parent.push(d);
self.findRec(parent, node);
parent.pop();
}
} }
if (e.dir()) |d|
if (d.dev == parent.dev)
self.findRec(d, node);
} }
} }
// Find all paths for the given link // Find all paths for the given link
pub fn find(parents_: *const Parents, node: *const Link) Self { pub fn find(parent_: *Dir, node: *const Link) Self {
var parents = parents_.copy(); var parent = parent_;
var self = Self{}; var self = Self{};
// First find the bottom-most parent that has no shared_size, // First find the bottom-most parent that has no shared_size,
// all links are guaranteed to be inside that directory. // all links are guaranteed to be inside that directory.
while (!parents.isRoot() and parents.top().shared_size > 0) while (parent.parent != null and parent.shared_size > 0)
parents.pop(); parent = parent.parent.?;
self.findRec(&parents, node); self.findRec(parent, node);
// TODO: Zig's sort() implementation is type-generic and not very // TODO: Zig's sort() implementation is type-generic and not very
// small. I suspect we can get a good save on our binary size by using // small. I suspect we can get a good save on our binary size by using
// a smaller or non-generic sort. This doesn't have to be very fast. // a smaller or non-generic sort. This doesn't have to be very fast.
std.sort.sort(Path, self.paths.items, @as(void, undefined), Path.lt); std.sort.sort(Path, self.paths.items, @as(void, undefined), Path.lt);
parents.deinit();
return self; return self;
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
for (self.paths.items) |*p| p.path.deinit();
self.paths.deinit(); self.paths.deinit();
} }
}; };
......
...@@ -107,6 +107,8 @@ fn writeJsonString(wr: anytype, s: []const u8) !void { ...@@ -107,6 +107,8 @@ fn writeJsonString(wr: anytype, s: []const u8) !void {
// entries read from disk can be merged into, without doing an O(1) lookup for // entries read from disk can be merged into, without doing an O(1) lookup for
// each entry. // each entry.
const ScanDir = struct { const ScanDir = struct {
dir: *model.Dir,
// Lookup table for name -> *entry. // Lookup table for name -> *entry.
// null is never stored in the table, but instead used pass a name string // null is never stored in the table, but instead used pass a name string
// as out-of-band argument for lookups. // as out-of-band argument for lookups.
...@@ -130,21 +132,24 @@ const ScanDir = struct { ...@@ -130,21 +132,24 @@ const ScanDir = struct {
const Self = @This(); const Self = @This();
fn init(parents: *const model.Parents) Self { fn init(dir: *model.Dir) Self {
var self = Self{ .entries = Map.initContext(main.allocator, HashContext{}) }; var self = Self{
.dir = dir,
.entries = Map.initContext(main.allocator, HashContext{}),
};
var count: Map.Size = 0; var count: Map.Size = 0;
var it = parents.top().sub; var it = dir.sub;
while (it) |e| : (it = e.next) count += 1; while (it) |e| : (it = e.next) count += 1;
self.entries.ensureCapacity(count) catch unreachable; self.entries.ensureCapacity(count) catch unreachable;
it = parents.top().sub; it = dir.sub;
while (it) |e| : (it = e.next) while (it) |e| : (it = e.next)
self.entries.putAssumeCapacity(e, @as(void,undefined)); self.entries.putAssumeCapacity(e, @as(void,undefined));
return self; return self;
} }
fn addSpecial(self: *Self, parents: *model.Parents, name: []const u8, t: Context.Special) void { fn addSpecial(self: *Self, name: []const u8, t: Context.Special) void {
var e = blk: { var e = blk: {
if (self.entries.getEntryAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name })) |entry| { if (self.entries.getEntryAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name })) |entry| {
// XXX: If the type doesn't match, we could always do an // XXX: If the type doesn't match, we could always do an
...@@ -153,32 +158,32 @@ const ScanDir = struct { ...@@ -153,32 +158,32 @@ const ScanDir = struct {
var e = entry.key_ptr.*.?; var e = entry.key_ptr.*.?;
if (e.etype == .file) { if (e.etype == .file) {
if (e.size > 0 or e.blocks > 0) { if (e.size > 0 or e.blocks > 0) {
e.delStats(parents); e.delStats(self.dir);
e.size = 0; e.size = 0;
e.blocks = 0; e.blocks = 0;
e.addStats(parents); e.addStats(self.dir);
} }
e.file().?.resetFlags(); e.file().?.resetFlags();
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name }); _ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
break :blk e; break :blk e;
} else e.delStatsRec(parents); } else e.delStatsRec(self.dir);
} }
var e = model.Entry.create(.file, false, name); var e = model.Entry.create(.file, false, name);
e.next = parents.top().sub; e.next = self.dir.sub;
parents.top().sub = e; self.dir.sub = e;
e.addStats(parents); e.addStats(self.dir);
break :blk e; break :blk e;
}; };
var f = e.file().?; var f = e.file().?;
switch (t) { switch (t) {
.err => e.set_err(parents), .err => e.setErr(self.dir),
.other_fs => f.other_fs = true, .other_fs => f.other_fs = true,
.kernfs => f.kernfs = true, .kernfs => f.kernfs = true,
.excluded => f.excluded = true, .excluded => f.excluded = true,
} }
} }
fn addStat(self: *Self, parents: *model.Parents, name: []const u8, stat: *Stat) *model.Entry { fn addStat(self: *Self, name: []const u8, stat: *Stat) *model.Entry {
const etype = if (stat.dir) model.EType.dir const etype = if (stat.dir) model.EType.dir
else if (stat.hlinkc) model.EType.link else if (stat.hlinkc) model.EType.link
else model.EType.file; else model.EType.file;
...@@ -192,11 +197,11 @@ const ScanDir = struct { ...@@ -192,11 +197,11 @@ const ScanDir = struct {
if (e.etype == etype and samedev and sameino) { if (e.etype == etype and samedev and sameino) {
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name }); _ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
break :blk e; break :blk e;
} else e.delStatsRec(parents); } else e.delStatsRec(self.dir);
} }
var e = model.Entry.create(etype, main.config.extended, name); var e = model.Entry.create(etype, main.config.extended, name);
e.next = parents.top().sub; e.next = self.dir.sub;
parents.top().sub = e; self.dir.sub = e;
break :blk e; break :blk e;
}; };
// Ignore the new size/blocks field for directories, as we don't know // Ignore the new size/blocks field for directories, as we don't know
...@@ -205,11 +210,14 @@ const ScanDir = struct { ...@@ -205,11 +210,14 @@ const ScanDir = struct {
// sizes. The current approach may result in incorrect sizes after // sizes. The current approach may result in incorrect sizes after
// refresh, but I expect the difference to be fairly minor. // 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.etype == .dir and e.counted) and (e.blocks != stat.blocks or e.size != stat.size)) {
e.delStats(parents); e.delStats(self.dir);
e.blocks = stat.blocks; e.blocks = stat.blocks;
e.size = stat.size; e.size = stat.size;
} }
if (e.dir()) |d| d.dev = model.devices.getId(stat.dev); if (e.dir()) |d| {
d.parent = self.dir;
d.dev = model.devices.getId(stat.dev);
}
if (e.file()) |f| { if (e.file()) |f| {
f.resetFlags(); f.resetFlags();
f.notreg = !stat.dir and !stat.reg; f.notreg = !stat.dir and !stat.reg;
...@@ -228,19 +236,19 @@ const ScanDir = struct { ...@@ -228,19 +236,19 @@ const ScanDir = struct {
// Assumption: l.link == 0 only happens on import, not refresh. // Assumption: l.link == 0 only happens on import, not refresh.
if (if (e.link()) |l| l.nlink == 0 else false) if (if (e.link()) |l| l.nlink == 0 else false)
model.link_count.add(parents.top().dev, e.link().?.ino) model.link_count.add(self.dir.dev, e.link().?.ino)
else else
e.addStats(parents); e.addStats(self.dir);
return e; return e;
} }
fn final(self: *Self, parents: *model.Parents) void { fn final(self: *Self) void {
if (self.entries.count() == 0) // optimization for the common case if (self.entries.count() == 0) // optimization for the common case
return; return;
var it = &parents.top().sub; var it = &self.dir.sub;
while (it.*) |e| { while (it.*) |e| {
if (self.entries.contains(e)) { if (self.entries.contains(e)) {
e.delStatsRec(parents); e.delStatsRec(self.dir);
it.* = e.next; it.* = e.next;
} else } else
it = &e.next; it = &e.next;
...@@ -264,8 +272,7 @@ const ScanDir = struct { ...@@ -264,8 +272,7 @@ const ScanDir = struct {
// //
const Context = struct { const Context = struct {
// When scanning to RAM // When scanning to RAM
parents: ?model.Parents = null, parents: ?std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator),
parent_entries: std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator),
// When scanning to a file // When scanning to a file
wr: ?*Writer = null, wr: ?*Writer = null,
...@@ -303,10 +310,10 @@ const Context = struct { ...@@ -303,10 +310,10 @@ const Context = struct {
return self; return self;
} }
// Ownership of p is passed to the object, it will be deallocated on deinit(). fn initMem(dir: ?*model.Dir) *Self {
fn initMem(p: model.Parents) *Self {
var self = main.allocator.create(Self) catch unreachable; var self = main.allocator.create(Self) catch unreachable;
self.* = .{ .parents = p }; self.* = .{ .parents = std.ArrayList(ScanDir).init(main.allocator) };
if (dir) |d| self.parents.?.append(ScanDir.init(d)) catch unreachable;
return self; return self;
} }
...@@ -335,10 +342,11 @@ const Context = struct { ...@@ -335,10 +342,11 @@ const Context = struct {
if (self.stat.dir) { if (self.stat.dir) {
if (self.parents) |*p| { if (self.parents) |*p| {
var d = self.parent_entries.pop(); if (p.items.len > 0) {
d.final(p); var d = p.pop();
d.final();
d.deinit(); d.deinit();
if (!p.isRoot()) p.pop(); }
} }
if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e); if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e);
} else } else
...@@ -352,7 +360,7 @@ const Context = struct { ...@@ -352,7 +360,7 @@ const Context = struct {
// Set a flag to indicate that there was an error listing file entries in the current directory. // Set a flag to indicate that there was an error listing file entries in the current directory.
// (Such errors are silently ignored when exporting to a file, as the directory metadata has already been written) // (Such errors are silently ignored when exporting to a file, as the directory metadata has already been written)
fn setDirlistError(self: *Self) void { fn setDirlistError(self: *Self) void {
if (self.parents) |*p| p.top().entry.set_err(p); if (self.parents) |*p| p.items[p.items.len-1].dir.entry.setErr(p.items[p.items.len-1].dir);
} }
const Special = enum { err, other_fs, kernfs, excluded }; const Special = enum { err, other_fs, kernfs, excluded };
...@@ -383,7 +391,7 @@ const Context = struct { ...@@ -383,7 +391,7 @@ const Context = struct {
} }
if (self.parents) |*p| if (self.parents) |*p|
self.parent_entries.items[self.parent_entries.items.len-1].addSpecial(p, self.name, t) p.items[p.items.len-1].addSpecial(self.name, t)
else if (self.wr) |wr| else if (self.wr) |wr|
self.writeSpecial(wr.writer(), t) catch |e| writeErr(e); self.writeSpecial(wr.writer(), t) catch |e| writeErr(e);
...@@ -410,7 +418,7 @@ const Context = struct { ...@@ -410,7 +418,7 @@ const Context = struct {
// Insert current path as a counted file/dir/hardlink, with information from self.stat // Insert current path as a counted file/dir/hardlink, with information from self.stat
fn addStat(self: *Self, dir_dev: u64) void { fn addStat(self: *Self, dir_dev: u64) void {
if (self.parents) |*p| { if (self.parents) |*p| {
var e = if (self.items_seen == 0) blk: { var e = if (p.items.len == 0) blk: {
// Root entry // Root entry
var e = model.Entry.create(.dir, main.config.extended, self.name); var e = model.Entry.create(.dir, main.config.extended, self.name);
e.blocks = self.stat.blocks; e.blocks = self.stat.blocks;
...@@ -420,12 +428,10 @@ const Context = struct { ...@@ -420,12 +428,10 @@ const Context = struct {
model.root.dev = model.devices.getId(self.stat.dev); model.root.dev = model.devices.getId(self.stat.dev);
break :blk e; break :blk e;
} else } else
self.parent_entries.items[self.parent_entries.items.len-1].addStat(p, self.name, &self.stat); p.items[p.items.len-1].addStat(self.name, &self.stat);
if (e.dir()) |d| { // Enter the directory if (e.dir()) |d| // Enter the directory
if (self.items_seen != 0) p.push(d); p.append(ScanDir.init(d)) catch unreachable;
self.parent_entries.append(ScanDir.init(p)) catch unreachable;
}
} else if (self.wr) |wr| } else if (self.wr) |wr|
self.writeStat(wr.writer(), dir_dev) catch |e| writeErr(e); self.writeStat(wr.writer(), dir_dev) catch |e| writeErr(e);
...@@ -435,11 +441,13 @@ const Context = struct { ...@@ -435,11 +441,13 @@ const Context = struct {
fn deinit(self: *Self) void { fn deinit(self: *Self) void {
if (self.last_error) |p| main.allocator.free(p); if (self.last_error) |p| main.allocator.free(p);
if (self.parents) |*p| p.deinit(); if (self.parents) |*p| {
for (p.items) |*i| i.deinit();
p.deinit();
}
if (self.wr) |p| main.allocator.destroy(p); if (self.wr) |p| main.allocator.destroy(p);
self.path.deinit(); self.path.deinit();
self.path_indices.deinit(); self.path_indices.deinit();
self.parent_entries.deinit();
main.allocator.destroy(self); main.allocator.destroy(self);
} }
}; };
...@@ -533,7 +541,7 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) void { ...@@ -533,7 +541,7 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) void {
} }
pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void { pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
active_context = if (out) |f| Context.initFile(f) else Context.initMem(.{}); active_context = if (out) |f| Context.initFile(f) else Context.initMem(null);
const full_path = std.fs.realpathAlloc(main.allocator, path) catch null; const full_path = std.fs.realpathAlloc(main.allocator, path) catch null;
defer if (full_path) |p| main.allocator.free(p); defer if (full_path) |p| main.allocator.free(p);
...@@ -545,16 +553,14 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void { ...@@ -545,16 +553,14 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
scan(); scan();
} }
pub fn setupRefresh(parents: model.Parents) void { pub fn setupRefresh(parent: *model.Dir) void {
active_context = Context.initMem(parents); active_context = Context.initMem(parent);
var full_path = std.ArrayList(u8).init(main.allocator); var full_path = std.ArrayList(u8).init(main.allocator);
defer full_path.deinit(); defer full_path.deinit();
parents.fmtPath(true, &full_path); parent.fmtPath(true, &full_path);
active_context.pushPath(full_path.items); active_context.pushPath(full_path.items);
active_context.parent_entries.append(ScanDir.init(&parents)) catch unreachable;
active_context.stat.dir = true; active_context.stat.dir = true;
active_context.stat.dev = model.devices.getDev(parents.top().dev); active_context.stat.dev = model.devices.getDev(parent.dev);
active_context.items_seen = 1; // The "root" item has already been added.
} }
// To be called after setupRefresh() (or from scanRoot()) // To be called after setupRefresh() (or from scanRoot())
...@@ -969,7 +975,7 @@ pub fn importRoot(path: [:0]const u8, out: ?std.fs.File) void { ...@@ -969,7 +975,7 @@ pub fn importRoot(path: [:0]const u8, out: ?std.fs.File) void {
catch |e| ui.die("Error reading file: {s}.\n", .{ui.errorString(e)}); catch |e| ui.die("Error reading file: {s}.\n", .{ui.errorString(e)});
defer fd.close(); defer fd.close();
active_context = if (out) |f| Context.initFile(f) else Context.initMem(.{}); active_context = if (out) |f| Context.initFile(f) else Context.initMem(null);
var imp = Import{ .ctx = active_context, .rd = fd }; var imp = Import{ .ctx = active_context, .rd = fd };
defer imp.ctx.deinit(); defer imp.ctx.deinit();
imp.root(); imp.root();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment