mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-12 17:08:39 -09:00
Re-add hard link counting + parent suberror & stats propagation
Ended up turning the Links into a doubly-linked list, because the
current approach of refreshing a subdirectory makes it more likely to
run into problems with the O(n) removal behavior of singly-linked lists.
Also found a bug that was present in the old scanning code as well;
fixed here and in c41467f240.
This commit is contained in:
parent
cc12c90dbc
commit
db51987446
2 changed files with 82 additions and 7 deletions
|
|
@ -102,6 +102,14 @@ pub const Entry = extern struct {
|
|||
else false;
|
||||
}
|
||||
|
||||
fn removeLinks(self: *Entry) void {
|
||||
if (self.dir()) |d| {
|
||||
var it = d.sub;
|
||||
while (it) |e| : (it = e.next) e.removeLinks();
|
||||
}
|
||||
if (self.link()) |l| l.removeLink();
|
||||
}
|
||||
|
||||
fn zeroStatsRec(self: *Entry) void {
|
||||
self.pack.blocks = 0;
|
||||
self.size = 0;
|
||||
|
|
@ -111,7 +119,7 @@ pub const Entry = extern struct {
|
|||
d.pack.err = false;
|
||||
d.pack.suberr = false;
|
||||
var it = d.sub;
|
||||
while (it) |e| : (it = e.next) zeroStatsRec(e);
|
||||
while (it) |e| : (it = e.next) e.zeroStatsRec();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +128,7 @@ pub const Entry = extern struct {
|
|||
// XXX: Does not update the 'suberr' flag of parent directories, make sure
|
||||
// to call updateSubErr() afterwards.
|
||||
pub fn zeroStats(self: *Entry, parent: ?*Dir) void {
|
||||
// TODO: Uncount nested links.
|
||||
self.removeLinks();
|
||||
|
||||
var it = parent;
|
||||
while (it) |p| : (it = p.parent) {
|
||||
|
|
@ -198,7 +206,8 @@ pub const Dir = extern struct {
|
|||
pub const Link = extern struct {
|
||||
entry: Entry,
|
||||
parent: *Dir align(1) = undefined,
|
||||
next: *Link align(1) = undefined, // Singly circular linked list of all *Link nodes with the same dev,ino.
|
||||
next: *Link align(1) = undefined, // circular linked list of all *Link nodes with the same dev,ino.
|
||||
prev: *Link align(1) = undefined,
|
||||
// dev is inherited from the parent Dir
|
||||
ino: u64 align(1) = undefined,
|
||||
name: [0]u8 = undefined,
|
||||
|
|
@ -211,6 +220,49 @@ pub const Link = extern struct {
|
|||
out.appendSlice(self.entry.name()) catch unreachable;
|
||||
return out.toOwnedSliceSentinel(0) catch unreachable;
|
||||
}
|
||||
|
||||
// Add this link to the inodes map and mark it as 'uncounted'.
|
||||
pub fn addLink(l: *@This(), nlink: u31) void {
|
||||
var d = inodes.map.getOrPut(l) catch unreachable;
|
||||
if (!d.found_existing) {
|
||||
d.value_ptr.* = .{ .counted = false, .nlink = nlink };
|
||||
l.next = l;
|
||||
l.prev = l;
|
||||
} else {
|
||||
inodes.setStats(.{ .key_ptr = d.key_ptr, .value_ptr = d.value_ptr }, false);
|
||||
// If the nlink counts are not consistent, reset to 0 so we calculate with what we have instead.
|
||||
if (d.value_ptr.nlink != nlink)
|
||||
d.value_ptr.nlink = 0;
|
||||
l.next = d.key_ptr.*;
|
||||
l.prev = d.key_ptr.*.prev;
|
||||
l.next.prev = l;
|
||||
l.prev.next = l;
|
||||
}
|
||||
inodes.addUncounted(l);
|
||||
}
|
||||
|
||||
// Remove this link from the inodes map and remove its stats from parent directories.
|
||||
fn removeLink(l: *@This()) void {
|
||||
const entry = inodes.map.getEntry(l) orelse return;
|
||||
inodes.setStats(entry, false);
|
||||
if (l.next == l) {
|
||||
_ = inodes.map.remove(l);
|
||||
_ = inodes.uncounted.remove(l);
|
||||
} else {
|
||||
// XXX: If this link is actually removed from the filesystem, then
|
||||
// the nlink count of the existing links should be updated to
|
||||
// reflect that. But we can't do that here, because this function
|
||||
// is also called before doing a filesystem refresh - in which case
|
||||
// the nlink count likely won't change. Best we can hope for is
|
||||
// that a refresh will encounter another link to the same inode and
|
||||
// trigger an nlink change.
|
||||
if (entry.key_ptr.* == l)
|
||||
entry.key_ptr.* = l.next;
|
||||
inodes.addUncounted(l.next);
|
||||
l.next.prev = l.prev;
|
||||
l.prev.next = l.next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Anything that's not an (indexed) directory or hardlink. Excluded directories are also "Files".
|
||||
|
|
@ -275,6 +327,8 @@ pub const inodes = struct {
|
|||
var uncounted = std.HashMap(*Link, void, HashContext, 80).init(main.allocator);
|
||||
var uncounted_full = true; // start with true for the initial scan
|
||||
|
||||
pub var lock = std.Thread.Mutex{};
|
||||
|
||||
const Inode = packed struct {
|
||||
// Whether this Inode is counted towards the parent directories.
|
||||
counted: bool,
|
||||
|
|
|
|||
29
src/sink.zig
29
src/sink.zig
|
|
@ -131,6 +131,7 @@ const MemDir = struct {
|
|||
|
||||
fn addSpecial(self: *MemDir, alloc: std.mem.Allocator, name: []const u8, t: Special) void {
|
||||
self.dir.items += 1;
|
||||
if (t == .err) self.dir.pack.suberr = true;
|
||||
|
||||
const e = self.getEntry(alloc, .file, false, name);
|
||||
e.file().?.pack = switch (t) {
|
||||
|
|
@ -159,7 +160,13 @@ const MemDir = struct {
|
|||
d.pack.dev = model.devices.getId(stat.dev);
|
||||
}
|
||||
if (e.file()) |f| f.pack = .{ .notreg = !stat.dir and !stat.reg };
|
||||
if (e.link()) |l| l.ino = stat.ino; // TODO: Add to inodes table
|
||||
if (e.link()) |l| {
|
||||
l.parent = self.dir;
|
||||
l.ino = stat.ino;
|
||||
model.inodes.lock.lock();
|
||||
defer model.inodes.lock.unlock();
|
||||
l.addLink(stat.nlink);
|
||||
}
|
||||
if (e.ext()) |ext| ext.* = stat.ext;
|
||||
return e;
|
||||
}
|
||||
|
|
@ -173,7 +180,7 @@ const MemDir = struct {
|
|||
if (self.entries.count() > 0) {
|
||||
var it = &self.dir.sub;
|
||||
while (it.*) |e| {
|
||||
if (self.entries.contains(e)) it.* = e.next
|
||||
if (self.entries.getKey(e) == e) it.* = e.next
|
||||
else it = &e.next;
|
||||
}
|
||||
}
|
||||
|
|
@ -197,7 +204,7 @@ const MemDir = struct {
|
|||
if (self.dir.entry.ext()) |e| {
|
||||
if (e.mtime > p.mtime) e.mtime = p.mtime;
|
||||
}
|
||||
if (self.suberr or self.dir.pack.err) p.suberr = true;
|
||||
if (self.suberr or self.dir.pack.suberr or self.dir.pack.err) p.suberr = true;
|
||||
}
|
||||
self.entries.deinit();
|
||||
}
|
||||
|
|
@ -343,7 +350,21 @@ pub fn createThreads(num: usize) []Thread {
|
|||
|
||||
// Must be the last thing to call from a source.
|
||||
pub fn done() void {
|
||||
// TODO: Do hardlink stuff.
|
||||
if (state.out == .mem) {
|
||||
state.status = .hlcnt;
|
||||
main.handleEvent(false, true);
|
||||
model.inodes.addAllStats();
|
||||
const dir = state.out.mem orelse model.root;
|
||||
var it: ?*model.Dir = dir;
|
||||
while (it) |p| : (it = p.parent) {
|
||||
p.updateSubErr();
|
||||
if (p != dir) {
|
||||
p.entry.pack.blocks +|= dir.entry.pack.blocks;
|
||||
p.entry.size +|= dir.entry.size;
|
||||
p.items +|= dir.items + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.status = .done;
|
||||
main.allocator.free(state.threads);
|
||||
// Clear the screen when done.
|
||||
|
|
|
|||
Loading…
Reference in a new issue