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;
|
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 {
|
fn zeroStatsRec(self: *Entry) void {
|
||||||
self.pack.blocks = 0;
|
self.pack.blocks = 0;
|
||||||
self.size = 0;
|
self.size = 0;
|
||||||
|
|
@ -111,7 +119,7 @@ pub const Entry = extern struct {
|
||||||
d.pack.err = false;
|
d.pack.err = false;
|
||||||
d.pack.suberr = false;
|
d.pack.suberr = false;
|
||||||
var it = d.sub;
|
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
|
// XXX: Does not update the 'suberr' flag of parent directories, make sure
|
||||||
// to call updateSubErr() afterwards.
|
// to call updateSubErr() afterwards.
|
||||||
pub fn zeroStats(self: *Entry, parent: ?*Dir) void {
|
pub fn zeroStats(self: *Entry, parent: ?*Dir) void {
|
||||||
// TODO: Uncount nested links.
|
self.removeLinks();
|
||||||
|
|
||||||
var it = parent;
|
var it = parent;
|
||||||
while (it) |p| : (it = p.parent) {
|
while (it) |p| : (it = p.parent) {
|
||||||
|
|
@ -198,7 +206,8 @@ pub const Dir = extern struct {
|
||||||
pub const Link = extern struct {
|
pub const Link = extern struct {
|
||||||
entry: Entry,
|
entry: Entry,
|
||||||
parent: *Dir align(1) = undefined,
|
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
|
// dev is inherited from the parent Dir
|
||||||
ino: u64 align(1) = undefined,
|
ino: u64 align(1) = undefined,
|
||||||
name: [0]u8 = undefined,
|
name: [0]u8 = undefined,
|
||||||
|
|
@ -211,6 +220,49 @@ pub const Link = extern struct {
|
||||||
out.appendSlice(self.entry.name()) catch unreachable;
|
out.appendSlice(self.entry.name()) catch unreachable;
|
||||||
return out.toOwnedSliceSentinel(0) 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".
|
// 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 = std.HashMap(*Link, void, HashContext, 80).init(main.allocator);
|
||||||
var uncounted_full = true; // start with true for the initial scan
|
var uncounted_full = true; // start with true for the initial scan
|
||||||
|
|
||||||
|
pub var lock = std.Thread.Mutex{};
|
||||||
|
|
||||||
const Inode = packed struct {
|
const Inode = packed struct {
|
||||||
// Whether this Inode is counted towards the parent directories.
|
// Whether this Inode is counted towards the parent directories.
|
||||||
counted: bool,
|
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 {
|
fn addSpecial(self: *MemDir, alloc: std.mem.Allocator, name: []const u8, t: Special) void {
|
||||||
self.dir.items += 1;
|
self.dir.items += 1;
|
||||||
|
if (t == .err) self.dir.pack.suberr = true;
|
||||||
|
|
||||||
const e = self.getEntry(alloc, .file, false, name);
|
const e = self.getEntry(alloc, .file, false, name);
|
||||||
e.file().?.pack = switch (t) {
|
e.file().?.pack = switch (t) {
|
||||||
|
|
@ -159,7 +160,13 @@ const MemDir = struct {
|
||||||
d.pack.dev = model.devices.getId(stat.dev);
|
d.pack.dev = model.devices.getId(stat.dev);
|
||||||
}
|
}
|
||||||
if (e.file()) |f| 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; // 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;
|
if (e.ext()) |ext| ext.* = stat.ext;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +180,7 @@ const MemDir = struct {
|
||||||
if (self.entries.count() > 0) {
|
if (self.entries.count() > 0) {
|
||||||
var it = &self.dir.sub;
|
var it = &self.dir.sub;
|
||||||
while (it.*) |e| {
|
while (it.*) |e| {
|
||||||
if (self.entries.contains(e)) it.* = e.next
|
if (self.entries.getKey(e) == e) it.* = e.next
|
||||||
else it = &e.next;
|
else it = &e.next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +204,7 @@ const MemDir = struct {
|
||||||
if (self.dir.entry.ext()) |e| {
|
if (self.dir.entry.ext()) |e| {
|
||||||
if (e.mtime > p.mtime) e.mtime = p.mtime;
|
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();
|
self.entries.deinit();
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +350,21 @@ pub fn createThreads(num: usize) []Thread {
|
||||||
|
|
||||||
// Must be the last thing to call from a source.
|
// Must be the last thing to call from a source.
|
||||||
pub fn done() void {
|
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;
|
state.status = .done;
|
||||||
main.allocator.free(state.threads);
|
main.allocator.free(state.threads);
|
||||||
// Clear the screen when done.
|
// Clear the screen when done.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue