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.
This commit is contained in:
Yorhel 2021-07-26 14:03:08 +02:00
parent b94db184f4
commit 36bc405a69
5 changed files with 153 additions and 228 deletions

View file

@ -10,8 +10,8 @@ const ui = @import("ui.zig");
const c = @cImport(@cInclude("time.h"));
usingnamespace @import("util.zig");
// Currently opened directory and its parents.
pub var dir_parents = model.Parents{};
// Currently opened directory.
pub var dir_parent: *model.Dir = undefined;
// Sorted list of all items in the currently opened directory.
// (first item may be null to indicate the "parent directory" item)
@ -44,12 +44,12 @@ const View = struct {
fn save(self: *@This()) void {
self.cursor_hash = if (dir_items.items.len == 0) 0
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 {
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(){};
cursor_idx = 0;
for (dir_items.items) |e, i| {
@ -123,7 +123,7 @@ fn sortDir(next_sel: ?*const model.Entry) void {
}
// Must be called when:
// - dir_parents changes (i.e. we change directory)
// - dir_parent changes (i.e. we change directory)
// - config.show_hidden changes
// - files in this dir have been added or removed
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_has_shared = false;
if (!dir_parents.isRoot())
if (dir_parent != model.root)
dir_items.append(null) catch unreachable;
var it = dir_parents.top().sub;
var it = dir_parent.sub;
while (it) |e| {
if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks;
if (e.size > dir_max_size) dir_max_size = e.size;
@ -219,8 +219,8 @@ const Row = struct {
if (main.config.show_graph == .both or main.config.show_graph == .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_parents.top().entry.blocks))
else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parents.top().entry.size))
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))
});
self.bg.fg(.default);
ui.addch('%');
@ -276,7 +276,7 @@ const Row = struct {
if (!main.config.show_mtime or self.col + 37 > ui.cols) return;
defer self.col += 27;
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)
else ui.addstr(" no mtime");
}
@ -359,7 +359,7 @@ const info = struct {
state = .info;
tab = t;
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| {
if (&n.node.entry == e) {
links_idx = i;
@ -536,8 +536,7 @@ const info = struct {
return true;
if (ch == 10) { // Enter - go to selected entry
const p = links.?.paths.items[links_idx];
dir_parents.stack.shrinkRetainingCapacity(0);
dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable;
dir_parent = p.path;
loadDir(&p.node.entry);
set(null, .info);
}
@ -730,7 +729,7 @@ pub fn draw() void {
ui.style(.dir);
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)));
pathbuf.deinit();
@ -760,12 +759,12 @@ 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, 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.addstr(" Apparent size: ");
ui.addsize(.hd, dir_parents.top().entry.size);
ui.addsize(.hd, dir_parent.entry.size);
ui.addstr(" Items: ");
ui.addnum(.hd, dir_parents.top().items);
ui.addnum(.hd, dir_parent.items);
switch (state) {
.main => {},
@ -832,7 +831,7 @@ pub fn keyInput(ch: i32) void {
message = "Directory imported from file, refreshing is disabled."
else {
main.state = .refresh;
scan.setupRefresh(dir_parents.copy());
scan.setupRefresh(dir_parent);
}
},
'b' => {
@ -855,7 +854,7 @@ pub fn keyInput(ch: i32) void {
if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1]
else if (cursor_idx == 0) null
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 {
if (dir_items.items.len == 0) {
} else if (dir_items.items[cursor_idx]) |e| {
if (e.dir()) |d| {
dir_parents.push(d);
dir_parent = d;
loadDir(null);
state = .main;
}
} else if (!dir_parents.isRoot()) {
dir_parents.pop();
} else if (dir_parent.parent) |p| {
dir_parent = p;
loadDir(null);
state = .main;
}
},
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
if (!dir_parents.isRoot()) {
const e = dir_parents.top();
dir_parents.pop();
if (dir_parent.parent) |p| {
const e = dir_parent;
dir_parent = p;
loadDir(&e.entry);
state = .main;
}

View file

@ -8,7 +8,7 @@ const ui = @import("ui.zig");
const browser = @import("browser.zig");
usingnamespace @import("util.zig");
var parents: model.Parents = .{};
var parent: *model.Dir = undefined;
var entry: *model.Entry = undefined;
var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds
var state: enum { confirm, busy, err } = .confirm;
@ -16,9 +16,8 @@ var confirm: enum { yes, no, ignore } = .no;
var error_option: enum { abort, ignore, all } = .abort;
var error_code: anyerror = undefined;
// ownership of p is passed to this function
pub fn setup(p: model.Parents, e: *model.Entry, n: ?*model.Entry) void {
parents = p;
pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void {
parent = p;
entry = e;
next_sel = n;
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)
var fd = dir.openDirZ(path, .{ .access_sub_paths = true, .iterate = false })
catch |e| return err(e);
var it = &d.sub;
parents.push(d);
defer parents.pop();
parent = d;
defer parent = parent.parent.?;
while (it.*) |n| {
if (deleteItem(fd, n.name(), it)) {
fd.close();
@ -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;
} else
dir.deleteFileZ(path) catch |e| return err(e);
ptr.*.?.delStats(&parents);
ptr.*.?.delStats(parent);
ptr.* = ptr.*.?.next;
return false;
}
// Returns the item that should be selected in the browser.
pub fn delete() ?*model.Entry {
defer parents.deinit();
while (main.state == .delete and state == .confirm)
main.handleEvent(true, false);
if (main.state != .delete)
@ -79,14 +77,14 @@ pub fn delete() ?*model.Entry {
// Find the pointer to this entry
const e = entry;
var it = &parents.top().sub;
var it = &parent.sub;
while (it.*) |n| : (it = &n.next)
if (it.* == entry)
break;
var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit();
parents.fmtPath(true, &path);
parent.fmtPath(true, &path);
if (path.items.len == 0 or path.items[path.items.len-1] != '/')
path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable;
@ -125,7 +123,7 @@ fn drawConfirm() void {
fn drawProgress() void {
var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit();
parents.fmtPath(false, &path);
parent.fmtPath(false, &path);
path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable;
@ -145,7 +143,7 @@ fn drawProgress() void {
fn drawErr() void {
var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit();
parents.fmtPath(false, &path);
parent.fmtPath(false, &path);
path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable;

View file

@ -187,7 +187,7 @@ fn spawnShell() void {
var path = std.ArrayList(u8).init(allocator);
defer path.deinit();
browser.dir_parents.fmtPath(true, &path);
browser.dir_parent.fmtPath(true, &path);
var env = std.process.getEnvMap(allocator) catch unreachable;
defer env.deinit();
@ -338,6 +338,7 @@ pub fn main() void {
config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode.
ui.init();
state = .browse;
browser.dir_parent = model.root;
browser.loadDir(null);
while (true) {

View file

@ -99,29 +99,27 @@ pub const Entry = packed struct {
}
// 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
else if (self.file()) |f| f.err = true
else unreachable;
var it = parents.iter();
if (&parents.top().entry == self) _ = it.next();
while (it.next()) |p| {
var it: ?*Dir = if (&parent.entry == self) parent.parent else parent;
while (it) |p| : (it = p.parent) {
if (p.suberr) break;
p.suberr = true;
}
}
pub fn addStats(self: *Entry, parents: *const Parents) void {
pub fn addStats(self: *Entry, parent: *Dir) void {
if (self.counted) return;
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.
// Means we should count it for other-dev parent dirs, too.
var new_hl = false;
var it = parents.iter();
while(it.next()) |p| {
var it: ?*Dir = parent;
while(it) |p| : (it = p.parent) {
var add_total = false;
if (self.ext()) |e|
@ -130,12 +128,12 @@ pub const Entry = packed struct {
p.items = saturateAdd(p.items, 1);
// 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;
} else if (self.link()) |l| {
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;
// First time we encounter this file in this dir, count it.
if (!d.found_existing) {
@ -175,29 +173,28 @@ pub const Entry = packed struct {
//
// This function assumes that, for directories, all sub-entries have
// 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;
self.counted = false;
const dev = parents.top().dev;
var del_hl = false;
var it = parents.iter();
while(it.next()) |p| {
var it: ?*Dir = parent;
while(it) |p| : (it = p.parent) {
var del_total = false;
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;
} else if (self.link()) |l| {
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| {
d.value_ptr.* -= 1;
del_total = d.value_ptr.* == 0;
del_hl = del_total;
if (del_total)
_ = devices.list.items[dev].hardlinks.remove(n);
_ = devices.list.items[parent.dev].hardlinks.remove(n);
}
} else
del_total = true;
@ -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| {
parents.push(d);
var it = d.sub;
while (it) |e| : (it = e.next)
e.delStatsRec(parents);
parents.pop();
e.delStatsRec(d);
}
self.delStats(parents);
}
// 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);
self.delStats(parent);
}
};
@ -239,6 +221,7 @@ pub const Dir = packed struct {
entry: Entry,
sub: ?*Entry,
parent: ?*Dir,
// 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)
@ -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.
// (Old C habits die hard)
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)
@ -420,23 +420,16 @@ pub const link_count = struct {
if (d.found_existing) d.key_ptr.*.count += 1;
}
var final_dir: Parents = undefined;
fn final_rec() void {
var it = final_dir.top().sub;
fn finalRec(parent: *Dir) void {
var it = parent.sub;
while (it) |e| : (it = e.next) {
if (e.dir()) |d| {
final_dir.push(d);
final_rec();
final_dir.pop();
continue;
}
if (e.dir()) |d| finalRec(d);
const l = e.link() orelse 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| {
l.nlink = n.key_ptr.*.count;
e.addStats(&final_dir);
e.addStats(parent);
}
}
}
@ -445,96 +438,30 @@ pub const link_count = struct {
// find all links, update their nlink count and parent sizes.
pub fn final() void {
if (nodes.count() == 0) return;
final_dir = Parents{};
final_rec();
finalRec(root);
nodes.clearAndFree();
final_dir.deinit();
}
};
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.
pub const LinkPaths = struct {
paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator),
pub const Path = struct {
path: Parents,
path: *Dir,
node: *Link,
fn lt(_: void, a: Path, b: Path) bool {
var i: usize = 0;
while (i < a.path.stack.items.len and i < b.path.stack.items.len) : (i += 1)
if (a.path.stack.items[i] != b.path.stack.items[i])
return std.mem.lessThan(u8, a.path.stack.items[i].entry.name(), b.path.stack.items[i].entry.name());
if (a.path.stack.items.len != b.path.stack.items.len)
return a.path.stack.items.len < b.path.stack.items.len;
return std.mem.lessThan(u8, a.node.entry.name(), b.node.entry.name());
var pa = std.ArrayList(u8).init(main.allocator);
var pb = std.ArrayList(u8).init(main.allocator);
defer pa.deinit();
defer pb.deinit();
a.fmtPath(false, &pa);
b.fmtPath(false, &pb);
return std.mem.lessThan(u8, pa.items, pb.items);
}
pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void {
@ -546,42 +473,36 @@ pub const LinkPaths = struct {
const Self = @This();
fn findRec(self: *Self, parent: *Parents, node: *const Link) void {
var entry = parent.top().sub;
fn findRec(self: *Self, parent: *Dir, node: *const Link) void {
var entry = parent.sub;
while (entry) |e| : (entry = e.next) {
if (e.link()) |l| {
if (l.ino == node.ino)
self.paths.append(Path{ .path = parent.copy(), .node = l }) catch unreachable;
}
if (e.dir()) |d| {
if (d.dev == parent.top().dev) {
parent.push(d);
self.findRec(parent, node);
parent.pop();
}
self.paths.append(Path{ .path = parent, .node = l }) catch unreachable;
}
if (e.dir()) |d|
if (d.dev == parent.dev)
self.findRec(d, node);
}
}
// Find all paths for the given link
pub fn find(parents_: *const Parents, node: *const Link) Self {
var parents = parents_.copy();
pub fn find(parent_: *Dir, node: *const Link) Self {
var parent = parent_;
var self = Self{};
// First find the bottom-most parent that has no shared_size,
// all links are guaranteed to be inside that directory.
while (!parents.isRoot() and parents.top().shared_size > 0)
parents.pop();
self.findRec(&parents, node);
while (parent.parent != null and parent.shared_size > 0)
parent = parent.parent.?;
self.findRec(parent, node);
// 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
// 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);
parents.deinit();
return self;
}
pub fn deinit(self: *Self) void {
for (self.paths.items) |*p| p.path.deinit();
self.paths.deinit();
}
};

View file

@ -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
// each entry.
const ScanDir = struct {
dir: *model.Dir,
// Lookup table for name -> *entry.
// null is never stored in the table, but instead used pass a name string
// as out-of-band argument for lookups.
@ -130,21 +132,24 @@ const ScanDir = struct {
const Self = @This();
fn init(parents: *const model.Parents) Self {
var self = Self{ .entries = Map.initContext(main.allocator, HashContext{}) };
fn init(dir: *model.Dir) Self {
var self = Self{
.dir = dir,
.entries = Map.initContext(main.allocator, HashContext{}),
};
var count: Map.Size = 0;
var it = parents.top().sub;
var it = dir.sub;
while (it) |e| : (it = e.next) count += 1;
self.entries.ensureCapacity(count) catch unreachable;
it = parents.top().sub;
it = dir.sub;
while (it) |e| : (it = e.next)
self.entries.putAssumeCapacity(e, @as(void,undefined));
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: {
if (self.entries.getEntryAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name })) |entry| {
// XXX: If the type doesn't match, we could always do an
@ -153,32 +158,32 @@ const ScanDir = struct {
var e = entry.key_ptr.*.?;
if (e.etype == .file) {
if (e.size > 0 or e.blocks > 0) {
e.delStats(parents);
e.delStats(self.dir);
e.size = 0;
e.blocks = 0;
e.addStats(parents);
e.addStats(self.dir);
}
e.file().?.resetFlags();
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
break :blk e;
} else e.delStatsRec(parents);
} else e.delStatsRec(self.dir);
}
var e = model.Entry.create(.file, false, name);
e.next = parents.top().sub;
parents.top().sub = e;
e.addStats(parents);
e.next = self.dir.sub;
self.dir.sub = e;
e.addStats(self.dir);
break :blk e;
};
var f = e.file().?;
switch (t) {
.err => e.set_err(parents),
.err => e.setErr(self.dir),
.other_fs => f.other_fs = true,
.kernfs => f.kernfs = 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
else if (stat.hlinkc) model.EType.link
else model.EType.file;
@ -192,11 +197,11 @@ const ScanDir = struct {
if (e.etype == etype and samedev and sameino) {
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
break :blk e;
} else e.delStatsRec(parents);
} else e.delStatsRec(self.dir);
}
var e = model.Entry.create(etype, main.config.extended, name);
e.next = parents.top().sub;
parents.top().sub = e;
e.next = self.dir.sub;
self.dir.sub = e;
break :blk e;
};
// Ignore the new size/blocks field for directories, as we don't know
@ -205,11 +210,14 @@ const ScanDir = struct {
// 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)) {
e.delStats(parents);
e.delStats(self.dir);
e.blocks = stat.blocks;
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| {
f.resetFlags();
f.notreg = !stat.dir and !stat.reg;
@ -228,19 +236,19 @@ const ScanDir = struct {
// Assumption: l.link == 0 only happens on import, not refresh.
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
e.addStats(parents);
e.addStats(self.dir);
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
return;
var it = &parents.top().sub;
var it = &self.dir.sub;
while (it.*) |e| {
if (self.entries.contains(e)) {
e.delStatsRec(parents);
e.delStatsRec(self.dir);
it.* = e.next;
} else
it = &e.next;
@ -264,8 +272,7 @@ const ScanDir = struct {
//
const Context = struct {
// When scanning to RAM
parents: ?model.Parents = null,
parent_entries: std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator),
parents: ?std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator),
// When scanning to a file
wr: ?*Writer = null,
@ -303,10 +310,10 @@ const Context = struct {
return self;
}
// Ownership of p is passed to the object, it will be deallocated on deinit().
fn initMem(p: model.Parents) *Self {
fn initMem(dir: ?*model.Dir) *Self {
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;
}
@ -335,10 +342,11 @@ const Context = struct {
if (self.stat.dir) {
if (self.parents) |*p| {
var d = self.parent_entries.pop();
d.final(p);
d.deinit();
if (!p.isRoot()) p.pop();
if (p.items.len > 0) {
var d = p.pop();
d.final();
d.deinit();
}
}
if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e);
} else
@ -352,7 +360,7 @@ const Context = struct {
// 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)
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 };
@ -383,7 +391,7 @@ const Context = struct {
}
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|
self.writeSpecial(wr.writer(), t) catch |e| writeErr(e);
@ -410,7 +418,7 @@ const Context = struct {
// Insert current path as a counted file/dir/hardlink, with information from self.stat
fn addStat(self: *Self, dir_dev: u64) void {
if (self.parents) |*p| {
var e = if (self.items_seen == 0) blk: {
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;
@ -420,12 +428,10 @@ const Context = struct {
model.root.dev = model.devices.getId(self.stat.dev);
break :blk e;
} 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 (self.items_seen != 0) p.push(d);
self.parent_entries.append(ScanDir.init(p)) catch unreachable;
}
if (e.dir()) |d| // Enter the directory
p.append(ScanDir.init(d)) catch unreachable;
} else if (self.wr) |wr|
self.writeStat(wr.writer(), dir_dev) catch |e| writeErr(e);
@ -435,11 +441,13 @@ const Context = struct {
fn deinit(self: *Self) void {
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);
self.path.deinit();
self.path_indices.deinit();
self.parent_entries.deinit();
main.allocator.destroy(self);
}
};
@ -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 {
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;
defer if (full_path) |p| main.allocator.free(p);
@ -545,16 +553,14 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void {
scan();
}
pub fn setupRefresh(parents: model.Parents) void {
active_context = Context.initMem(parents);
pub fn setupRefresh(parent: *model.Dir) void {
active_context = Context.initMem(parent);
var full_path = std.ArrayList(u8).init(main.allocator);
defer full_path.deinit();
parents.fmtPath(true, &full_path);
parent.fmtPath(true, &full_path);
active_context.pushPath(full_path.items);
active_context.parent_entries.append(ScanDir.init(&parents)) catch unreachable;
active_context.stat.dir = true;
active_context.stat.dev = model.devices.getDev(parents.top().dev);
active_context.items_seen = 1; // The "root" item has already been added.
active_context.stat.dev = model.devices.getDev(parent.dev);
}
// To be called after setupRefresh() (or from scanRoot())
@ -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)});
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 };
defer imp.ctx.deinit();
imp.root();