mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-15 18:18:41 -09:00
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.
This commit is contained in:
parent
1452b91032
commit
91281ef11f
4 changed files with 129 additions and 132 deletions
|
|
@ -83,22 +83,22 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool {
|
||||||
switch (main.config.sort_col) {
|
switch (main.config.sort_col) {
|
||||||
.name => {}, // name sorting is the fallback
|
.name => {}, // name sorting is the fallback
|
||||||
.blocks => {
|
.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;
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
||||||
},
|
},
|
||||||
.size => {
|
.size => {
|
||||||
if (sortIntLt(a.size, b.size)) |r| return r;
|
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 => {
|
.items => {
|
||||||
const ai = if (a.dir()) |d| d.items else 0;
|
const ai = if (a.dir()) |d| d.items else 0;
|
||||||
const bi = if (b.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(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;
|
if (sortIntLt(a.size, b.size)) |r| return r;
|
||||||
},
|
},
|
||||||
.mtime => {
|
.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;
|
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;
|
dir_items.append(null) catch unreachable;
|
||||||
var it = dir_parent.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.pack.blocks > dir_max_blocks) dir_max_blocks = e.pack.blocks;
|
||||||
if (e.size > dir_max_size) dir_max_size = e.size;
|
if (e.size > dir_max_size) dir_max_size = e.size;
|
||||||
const shown = main.config.show_hidden or blk: {
|
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();
|
const name = e.name();
|
||||||
break :blk !excl and name[0] != '.' and name[name.len-1] != '~';
|
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 item = self.item orelse return;
|
||||||
const ch: u7 = ch: {
|
const ch: u7 = ch: {
|
||||||
if (item.file()) |f| {
|
if (item.file()) |f| {
|
||||||
if (f.err) break :ch '!';
|
if (f.pack.err) break :ch '!';
|
||||||
if (f.excluded) break :ch '<';
|
if (f.pack.excluded) break :ch '<';
|
||||||
if (f.other_fs) break :ch '>';
|
if (f.pack.other_fs) break :ch '>';
|
||||||
if (f.kernfs) break :ch '^';
|
if (f.pack.kernfs) break :ch '^';
|
||||||
if (f.notreg) break :ch '@';
|
if (f.pack.notreg) break :ch '@';
|
||||||
} else if (item.dir()) |d| {
|
} else if (item.dir()) |d| {
|
||||||
if (d.err) break :ch '!';
|
if (d.pack.err) break :ch '!';
|
||||||
if (d.suberr) break :ch '.';
|
if (d.pack.suberr) break :ch '.';
|
||||||
if (d.sub == null) break :ch 'e';
|
if (d.sub == null) break :ch 'e';
|
||||||
} else if (item.link()) |_| break :ch 'H';
|
} else if (item.link()) |_| break :ch 'H';
|
||||||
return;
|
return;
|
||||||
|
|
@ -187,7 +187,7 @@ const Row = struct {
|
||||||
width += 2 + width;
|
width += 2 + width;
|
||||||
defer self.col += width;
|
defer self.col += width;
|
||||||
const item = self.item orelse return;
|
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;
|
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;
|
if (main.config.show_shared == .unique) shr = siz -| shr;
|
||||||
|
|
||||||
|
|
@ -216,8 +216,8 @@ const Row = struct {
|
||||||
if (main.config.show_percent) {
|
if (main.config.show_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_parent.entry.blocks))
|
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))
|
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('%');
|
||||||
|
|
@ -225,7 +225,7 @@ const Row = struct {
|
||||||
if (main.config.show_graph and main.config.show_percent) ui.addch(' ');
|
if (main.config.show_graph and main.config.show_percent) ui.addch(' ');
|
||||||
if (main.config.show_graph) {
|
if (main.config.show_graph) {
|
||||||
var max = if (main.config.show_blocks) dir_max_blocks else dir_max_size;
|
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) {
|
if (max < bar_size) {
|
||||||
max *= bar_size;
|
max *= bar_size;
|
||||||
num *= bar_size;
|
num *= bar_size;
|
||||||
|
|
@ -290,7 +290,7 @@ const Row = struct {
|
||||||
fn name(self: *Self) void {
|
fn name(self: *Self) void {
|
||||||
ui.move(self.row, self.col);
|
ui.move(self.row, self.col);
|
||||||
if (self.item) |i| {
|
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.addch(if (i.isDirectory()) '/' else ' ');
|
||||||
ui.addstr(ui.shorten(ui.toUtf8(i.name()), ui.cols -| self.col -| 1));
|
ui.addstr(ui.shorten(ui.toUtf8(i.name()), ui.cols -| self.col -| 1));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -460,7 +460,7 @@ const info = struct {
|
||||||
} else {
|
} else {
|
||||||
ui.addstr("Type: ");
|
ui.addstr("Type: ");
|
||||||
ui.style(.default);
|
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;
|
row.* += 1;
|
||||||
|
|
||||||
|
|
@ -474,7 +474,7 @@ const info = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disk usage & Apparent size
|
// 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);
|
drawSize(box, row, "Apparent size: ", e.size, if (e.dir()) |d| d.shared_size else 0);
|
||||||
|
|
||||||
// Number of items
|
// Number of items
|
||||||
|
|
@ -522,7 +522,7 @@ const info = struct {
|
||||||
var row: u32 = 2;
|
var row: u32 = 2;
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
if (e.etype == .link) {
|
if (e.pack.etype == .link) {
|
||||||
box.tab(cols-19, tab == .info, 1, "Info");
|
box.tab(cols-19, tab == .info, 1, "Info");
|
||||||
box.tab(cols-10, tab == .links, 2, "Links");
|
box.tab(cols-10, tab == .links, 2, "Links");
|
||||||
}
|
}
|
||||||
|
|
@ -543,7 +543,7 @@ const info = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyInput(ch: i32) bool {
|
fn keyInput(ch: i32) bool {
|
||||||
if (entry.?.etype == .link) {
|
if (entry.?.pack.etype == .link) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
'1', 'h', ui.c.KEY_LEFT => { set(entry, .info); return true; },
|
'1', 'h', ui.c.KEY_LEFT => { set(entry, .info); return true; },
|
||||||
'2', 'l', ui.c.KEY_RIGHT => { set(entry, .links); 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.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, 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.style(if (main.config.show_blocks) .hd else .bold_hd);
|
||||||
ui.addstr(" Apparent size: ");
|
ui.addstr(" Apparent size: ");
|
||||||
ui.addsize(.hd, dir_parent.entry.size);
|
ui.addsize(.hd, dir_parent.entry.size);
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ fn drawConfirm() void {
|
||||||
ui.addstr("Are you sure you want to delete \"");
|
ui.addstr("Are you sure you want to delete \"");
|
||||||
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
|
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
|
||||||
ui.addch('"');
|
ui.addch('"');
|
||||||
if (entry.etype != .dir)
|
if (entry.pack.etype != .dir)
|
||||||
ui.addch('?')
|
ui.addch('?')
|
||||||
else {
|
else {
|
||||||
box.move(2, 18);
|
box.move(2, 18);
|
||||||
|
|
|
||||||
179
src/model.zig
179
src/model.zig
|
|
@ -17,7 +17,7 @@ const allocator = allocator_state.allocator();
|
||||||
|
|
||||||
pub const EType = enum(u2) { dir, link, file };
|
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;
|
pub const Blocks = u60;
|
||||||
|
|
||||||
// Memory layout:
|
// Memory layout:
|
||||||
|
|
@ -31,38 +31,40 @@ pub const Blocks = u60;
|
||||||
// These are all packed structs and hence do not have any alignment, which is
|
// 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
|
// great for saving memory but perhaps not very great for code size or
|
||||||
// performance.
|
// performance.
|
||||||
// (TODO: What are the aliassing rules for Zig? There is a 'noalias' keyword,
|
pub const Entry = extern struct {
|
||||||
// but does that mean all unmarked pointers are allowed to alias?)
|
pack: Packed align(1),
|
||||||
pub const Entry = packed struct {
|
size: u64 align(1),
|
||||||
etype: EType,
|
next: ?*Entry align(1),
|
||||||
isext: bool,
|
|
||||||
// Whether or not this entry's size has been counted in its parents.
|
pub const Packed = packed struct(u64) {
|
||||||
// Counting of Link entries is deferred until the scan/delete operation has
|
etype: EType,
|
||||||
// completed, so for those entries this flag indicates an intention to be
|
isext: bool,
|
||||||
// counted.
|
// Whether or not this entry's size has been counted in its parents.
|
||||||
counted: bool,
|
// Counting of Link entries is deferred until the scan/delete operation has
|
||||||
blocks: Blocks, // 512-byte blocks
|
// completed, so for those entries this flag indicates an intention to be
|
||||||
size: u64,
|
// counted.
|
||||||
next: ?*Entry,
|
counted: bool,
|
||||||
|
blocks: Blocks, // 512-byte blocks
|
||||||
|
};
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn dir(self: *Self) ?*Dir {
|
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 {
|
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 {
|
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".
|
// Whether this entry should be displayed as a "directory".
|
||||||
// Some dirs are actually represented in this data model as a File for efficiency.
|
// Some dirs are actually represented in this data model as a File for efficiency.
|
||||||
pub fn isDirectory(self: *Self) bool {
|
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 {
|
fn nameOffset(etype: EType) usize {
|
||||||
|
|
@ -74,12 +76,12 @@ pub const Entry = packed struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(self: *const Self) [:0]const u8 {
|
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);
|
return std.mem.sliceTo(ptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ext(self: *Self) ?*Ext {
|
pub fn ext(self: *Self) ?*Ext {
|
||||||
if (!self.isext) return null;
|
if (!self.pack.isext) return null;
|
||||||
return @ptrCast(*Ext, @ptrCast([*]Ext, self) - 1);
|
return @ptrCast(*Ext, @ptrCast([*]Ext, self) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +90,7 @@ pub const Entry = packed struct {
|
||||||
const size = nameOffset(etype) + ename.len + 1 + extsize;
|
const size = nameOffset(etype) + ename.len + 1 + extsize;
|
||||||
var ptr = blk: {
|
var ptr = blk: {
|
||||||
while (true) {
|
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
|
break :blk p
|
||||||
else |_| {}
|
else |_| {}
|
||||||
ui.oom();
|
ui.oom();
|
||||||
|
|
@ -96,8 +98,8 @@ pub const Entry = packed struct {
|
||||||
};
|
};
|
||||||
std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
|
std.mem.set(u8, ptr, 0); // kind of ugly, but does the trick
|
||||||
var e = @ptrCast(*Entry, ptr.ptr + extsize);
|
var e = @ptrCast(*Entry, ptr.ptr + extsize);
|
||||||
e.etype = etype;
|
e.pack.etype = etype;
|
||||||
e.isext = isext;
|
e.pack.isext = isext;
|
||||||
var name_ptr = @ptrCast([*]u8, e) + nameOffset(etype);
|
var name_ptr = @ptrCast([*]u8, e) + nameOffset(etype);
|
||||||
std.mem.copy(u8, name_ptr[0..ename.len], ename);
|
std.mem.copy(u8, name_ptr[0..ename.len], ename);
|
||||||
return e;
|
return e;
|
||||||
|
|
@ -105,19 +107,19 @@ 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 setErr(self: *Self, parent: *Dir) void {
|
pub fn setErr(self: *Self, parent: *Dir) void {
|
||||||
if (self.dir()) |d| d.err = true
|
if (self.dir()) |d| d.pack.err = true
|
||||||
else if (self.file()) |f| f.err = true
|
else if (self.file()) |f| f.pack.err = true
|
||||||
else unreachable;
|
else unreachable;
|
||||||
var it: ?*Dir = if (&parent.entry == self) parent.parent else parent;
|
var it: ?*Dir = if (&parent.entry == self) parent.parent else parent;
|
||||||
while (it) |p| : (it = p.parent) {
|
while (it) |p| : (it = p.parent) {
|
||||||
if (p.suberr) break;
|
if (p.pack.suberr) break;
|
||||||
p.suberr = true;
|
p.pack.suberr = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addStats(self: *Entry, parent: *Dir, nlink: u31) void {
|
pub fn addStats(self: *Entry, parent: *Dir, nlink: u31) void {
|
||||||
if (self.counted) return;
|
if (self.pack.counted) return;
|
||||||
self.counted = true;
|
self.pack.counted = true;
|
||||||
|
|
||||||
// Add link to the inode map, but don't count its size (yet).
|
// Add link to the inode map, but don't count its size (yet).
|
||||||
if (self.link()) |l| {
|
if (self.link()) |l| {
|
||||||
|
|
@ -125,7 +127,7 @@ pub const Entry = packed struct {
|
||||||
var d = inodes.map.getOrPut(l) catch unreachable;
|
var d = inodes.map.getOrPut(l) catch unreachable;
|
||||||
if (!d.found_existing) {
|
if (!d.found_existing) {
|
||||||
d.value_ptr.* = .{ .counted = false, .nlink = nlink };
|
d.value_ptr.* = .{ .counted = false, .nlink = nlink };
|
||||||
inodes.total_blocks +|= self.blocks;
|
inodes.total_blocks +|= self.pack.blocks;
|
||||||
l.next = l;
|
l.next = l;
|
||||||
} else {
|
} else {
|
||||||
inodes.setStats(.{ .key_ptr = d.key_ptr, .value_ptr = d.value_ptr }, false);
|
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 (p.entry.ext()) |pe|
|
||||||
if (e.mtime > pe.mtime) { pe.mtime = e.mtime; };
|
if (e.mtime > pe.mtime) { pe.mtime = e.mtime; };
|
||||||
p.items +|= 1;
|
p.items +|= 1;
|
||||||
if (self.etype != .link) {
|
if (self.pack.etype != .link) {
|
||||||
p.entry.size +|= self.size;
|
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
|
// anymore, meaning that delStats() followed by addStats() with the same
|
||||||
// data may cause information to be lost.
|
// data may cause information to be lost.
|
||||||
pub fn delStats(self: *Entry, parent: *Dir) void {
|
pub fn delStats(self: *Entry, parent: *Dir) void {
|
||||||
if (!self.counted) return;
|
if (!self.pack.counted) return;
|
||||||
defer self.counted = false; // defer, to make sure inodes.setStats() still sees it as counted.
|
defer self.pack.counted = false; // defer, to make sure inodes.setStats() still sees it as counted.
|
||||||
|
|
||||||
if (self.link()) |l| {
|
if (self.link()) |l| {
|
||||||
var d = inodes.map.getEntry(l).?;
|
var d = inodes.map.getEntry(l).?;
|
||||||
|
|
@ -175,7 +177,7 @@ pub const Entry = packed struct {
|
||||||
if (l.next == l) {
|
if (l.next == l) {
|
||||||
_ = inodes.map.remove(l);
|
_ = inodes.map.remove(l);
|
||||||
_ = inodes.uncounted.remove(l);
|
_ = inodes.uncounted.remove(l);
|
||||||
inodes.total_blocks -|= self.blocks;
|
inodes.total_blocks -|= self.pack.blocks;
|
||||||
} else {
|
} else {
|
||||||
if (d.key_ptr.* == l)
|
if (d.key_ptr.* == l)
|
||||||
d.key_ptr.* = l.next;
|
d.key_ptr.* = l.next;
|
||||||
|
|
@ -195,9 +197,9 @@ pub const Entry = packed struct {
|
||||||
var it: ?*Dir = parent;
|
var it: ?*Dir = parent;
|
||||||
while(it) |p| : (it = p.parent) {
|
while(it) |p| : (it = p.parent) {
|
||||||
p.items -|= 1;
|
p.items -|= 1;
|
||||||
if (self.etype != .link) {
|
if (self.pack.etype != .link) {
|
||||||
p.entry.size -|= self.size;
|
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,
|
entry: Entry,
|
||||||
|
|
||||||
sub: ?*Entry,
|
sub: ?*Entry align(1),
|
||||||
parent: ?*Dir,
|
parent: ?*Dir align(1),
|
||||||
|
|
||||||
// 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)
|
||||||
// shared_*: Unique hardlinks that still have references outside of this directory.
|
// shared_*: Unique hardlinks that still have references outside of this directory.
|
||||||
// (i.e. the space you won't reclaim by deleting this dir)
|
// (i.e. the space you won't reclaim by deleting this dir)
|
||||||
// (space reclaimed by deleting a dir =~ entry. - shared_)
|
// (space reclaimed by deleting a dir =~ entry. - shared_)
|
||||||
shared_blocks: u64,
|
shared_blocks: u64 align(1),
|
||||||
shared_size: u64,
|
shared_size: u64 align(1),
|
||||||
items: u32,
|
items: u32 align(1),
|
||||||
|
|
||||||
// Indexes into the global 'devices.list' array
|
pack: Packed align(1),
|
||||||
dev: DevId,
|
|
||||||
|
|
||||||
err: bool,
|
|
||||||
suberr: bool,
|
|
||||||
|
|
||||||
// Only used to find the @offsetOff, the name is written at this point as a 0-terminated string.
|
// Only used to find the @offsetOff, 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: [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 {
|
pub fn fmtPath(self: *const @This(), withRoot: bool, out: *std.ArrayList(u8)) void {
|
||||||
if (!withRoot and self.parent == null) return;
|
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)
|
// File that's been hardlinked (i.e. nlink > 1)
|
||||||
pub const Link = packed struct {
|
pub const Link = extern struct {
|
||||||
entry: Entry,
|
entry: Entry,
|
||||||
parent: *Dir,
|
parent: *Dir align(1),
|
||||||
next: *Link, // Singly circular linked list of all *Link nodes with the same dev,ino.
|
next: *Link align(1), // Singly circular linked list of all *Link nodes with the same dev,ino.
|
||||||
// dev is inherited from the parent Dir
|
// dev is inherited from the parent Dir
|
||||||
ino: u64,
|
ino: u64 align(1),
|
||||||
name: u8,
|
name: [0]u8,
|
||||||
|
|
||||||
// Return value should be freed with main.allocator.
|
// Return value should be freed with main.allocator.
|
||||||
pub fn path(self: @This(), withRoot: bool) [:0]const u8 {
|
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".
|
// 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,
|
entry: Entry,
|
||||||
|
pack: Packed,
|
||||||
|
name: [0]u8,
|
||||||
|
|
||||||
err: bool,
|
pub const Packed = packed struct(u8) {
|
||||||
excluded: bool,
|
err: bool = false,
|
||||||
other_fs: bool,
|
excluded: bool = false,
|
||||||
kernfs: bool,
|
other_fs: bool = false,
|
||||||
notreg: bool,
|
kernfs: bool = false,
|
||||||
_pad: u3,
|
notreg: bool = false,
|
||||||
|
_pad: u3 = 0, // Make this struct "ABI sized" to allow inclusion in an extern struct
|
||||||
name: u8,
|
};
|
||||||
|
|
||||||
pub fn resetFlags(f: *@This()) void {
|
pub fn resetFlags(f: *@This()) void {
|
||||||
f.err = false;
|
f.pack = .{};
|
||||||
f.excluded = false;
|
|
||||||
f.other_fs = false;
|
|
||||||
f.kernfs = false;
|
|
||||||
f.notreg = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Ext = packed struct {
|
pub const Ext = extern struct {
|
||||||
mtime: u64 = 0,
|
mtime: u64 align(1) = 0,
|
||||||
uid: u32 = 0,
|
uid: u32 align(1) = 0,
|
||||||
gid: u32 = 0,
|
gid: u32 align(1) = 0,
|
||||||
mode: u16 = 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
|
// 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.
|
// of space when a typical scan won't cover many unique devices.
|
||||||
|
|
@ -367,13 +364,13 @@ pub const inodes = struct {
|
||||||
const HashContext = struct {
|
const HashContext = struct {
|
||||||
pub fn hash(_: @This(), l: *Link) u64 {
|
pub fn hash(_: @This(), l: *Link) u64 {
|
||||||
var h = std.hash.Wyhash.init(0);
|
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));
|
h.update(std.mem.asBytes(&l.ino));
|
||||||
return h.final();
|
return h.final();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eql(_: @This(), a: *Link, b: *Link) bool {
|
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();
|
defer dirs.deinit();
|
||||||
var it = entry.key_ptr.*;
|
var it = entry.key_ptr.*;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (it.entry.counted) {
|
if (it.entry.pack.counted) {
|
||||||
nlink += 1;
|
nlink += 1;
|
||||||
var parent: ?*Dir = it.parent;
|
var parent: ?*Dir = it.parent;
|
||||||
while (parent) |p| : (parent = p.parent) {
|
while (parent) |p| : (parent = p.parent) {
|
||||||
|
|
@ -419,19 +416,19 @@ pub const inodes = struct {
|
||||||
var dir_iter = dirs.iterator();
|
var dir_iter = dirs.iterator();
|
||||||
if (add) {
|
if (add) {
|
||||||
while (dir_iter.next()) |de| {
|
while (dir_iter.next()) |de| {
|
||||||
de.key_ptr.*.entry.blocks +|= entry.key_ptr.*.entry.blocks;
|
de.key_ptr.*.entry.pack.blocks +|= entry.key_ptr.*.entry.pack.blocks;
|
||||||
de.key_ptr.*.entry.size +|= entry.key_ptr.*.entry.size;
|
de.key_ptr.*.entry.size +|= entry.key_ptr.*.entry.size;
|
||||||
if (de.value_ptr.* < nlink) {
|
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;
|
de.key_ptr.*.shared_size +|= entry.key_ptr.*.entry.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (dir_iter.next()) |de| {
|
while (dir_iter.next()) |de| {
|
||||||
de.key_ptr.*.entry.blocks -|= entry.key_ptr.*.entry.blocks;
|
de.key_ptr.*.entry.pack.blocks -|= entry.key_ptr.*.entry.pack.blocks;
|
||||||
de.key_ptr.*.entry.size -|= entry.key_ptr.*.entry.size;
|
de.key_ptr.*.entry.size -|= entry.key_ptr.*.entry.size;
|
||||||
if (de.value_ptr.* < nlink) {
|
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;
|
de.key_ptr.*.shared_size -|= entry.key_ptr.*.entry.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -458,7 +455,7 @@ pub var root: *Dir = undefined;
|
||||||
|
|
||||||
test "entry" {
|
test "entry" {
|
||||||
var e = Entry.create(.file, false, "hello");
|
var e = Entry.create(.file, false, "hello");
|
||||||
try std.testing.expectEqual(e.etype, .file);
|
try std.testing.expectEqual(e.pack.etype, .file);
|
||||||
try std.testing.expect(!e.isext);
|
try std.testing.expect(!e.pack.isext);
|
||||||
try std.testing.expectEqualStrings(e.name(), "hello");
|
try std.testing.expectEqualStrings(e.name(), "hello");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
src/scan.zig
34
src/scan.zig
|
|
@ -156,11 +156,11 @@ const ScanDir = struct {
|
||||||
// in-place conversion to a File entry. That's more efficient,
|
// in-place conversion to a File entry. That's more efficient,
|
||||||
// but also more code. I don't expect this to happen often.
|
// but also more code. I don't expect this to happen often.
|
||||||
var e = entry.key_ptr.*.?;
|
var e = entry.key_ptr.*.?;
|
||||||
if (e.etype == .file) {
|
if (e.pack.etype == .file) {
|
||||||
if (e.size > 0 or e.blocks > 0) {
|
if (e.size > 0 or e.pack.blocks > 0) {
|
||||||
e.delStats(self.dir);
|
e.delStats(self.dir);
|
||||||
e.size = 0;
|
e.size = 0;
|
||||||
e.blocks = 0;
|
e.pack.blocks = 0;
|
||||||
e.addStats(self.dir, 0);
|
e.addStats(self.dir, 0);
|
||||||
}
|
}
|
||||||
e.file().?.resetFlags();
|
e.file().?.resetFlags();
|
||||||
|
|
@ -177,9 +177,9 @@ const ScanDir = struct {
|
||||||
var f = e.file().?;
|
var f = e.file().?;
|
||||||
switch (t) {
|
switch (t) {
|
||||||
.err => e.setErr(self.dir),
|
.err => e.setErr(self.dir),
|
||||||
.other_fs => f.other_fs = true,
|
.other_fs => f.pack.other_fs = true,
|
||||||
.kernfs => f.kernfs = true,
|
.kernfs => f.pack.kernfs = true,
|
||||||
.excluded => f.excluded = true,
|
.excluded => f.pack.excluded = true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,9 +192,9 @@ const ScanDir = struct {
|
||||||
// XXX: In-place conversion may also be possible here.
|
// XXX: In-place conversion may also be possible here.
|
||||||
var e = entry.key_ptr.*.?;
|
var e = entry.key_ptr.*.?;
|
||||||
// changes of dev/ino affect hard link counting in a way we can't simply merge.
|
// 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;
|
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 });
|
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
|
||||||
break :blk e;
|
break :blk e;
|
||||||
} else e.delStatsRec(self.dir);
|
} else e.delStatsRec(self.dir);
|
||||||
|
|
@ -209,18 +209,18 @@ const ScanDir = struct {
|
||||||
// entire subtree, which, in turn, would break all shared hardlink
|
// entire subtree, which, in turn, would break all shared hardlink
|
||||||
// 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.pack.etype == .dir and e.pack.counted) and (e.pack.blocks != stat.blocks or e.size != stat.size)) {
|
||||||
e.delStats(self.dir);
|
e.delStats(self.dir);
|
||||||
e.blocks = stat.blocks;
|
e.pack.blocks = stat.blocks;
|
||||||
e.size = stat.size;
|
e.size = stat.size;
|
||||||
}
|
}
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| {
|
||||||
d.parent = self.dir;
|
d.parent = self.dir;
|
||||||
d.dev = model.devices.getId(stat.dev);
|
d.pack.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.pack.notreg = !stat.dir and !stat.reg;
|
||||||
}
|
}
|
||||||
if (e.link()) |l| l.ino = stat.ino;
|
if (e.link()) |l| l.ino = stat.ino;
|
||||||
if (e.ext()) |ext| {
|
if (e.ext()) |ext| {
|
||||||
|
|
@ -415,11 +415,11 @@ const Context = struct {
|
||||||
var e = if (p.items.len == 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.pack.blocks = self.stat.blocks;
|
||||||
e.size = self.stat.size;
|
e.size = self.stat.size;
|
||||||
if (e.ext()) |ext| ext.* = self.stat.ext;
|
if (e.ext()) |ext| ext.* = self.stat.ext;
|
||||||
model.root = e.dir().?;
|
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;
|
break :blk e;
|
||||||
} else
|
} else
|
||||||
p.items[p.items.len-1].addStat(self.name, &self.stat);
|
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);
|
parent.fmtPath(true, &full_path);
|
||||||
active_context.pushPath(full_path.items);
|
active_context.pushPath(full_path.items);
|
||||||
active_context.stat.dir = true;
|
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())
|
// To be called after setupRefresh() (or from scanRoot())
|
||||||
|
|
@ -1021,7 +1021,7 @@ fn drawBox() void {
|
||||||
box.move(2, 30);
|
box.move(2, 30);
|
||||||
ui.addstr("size: ");
|
ui.addstr("size: ");
|
||||||
// TODO: Should display the size of the dir-to-be-refreshed on refreshing, not the root.
|
// 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);
|
box.move(3, 2);
|
||||||
|
|
@ -1088,7 +1088,7 @@ pub fn draw() void {
|
||||||
.{ ui.shorten(active_context.pathZ(), 63), active_context.items_seen }
|
.{ ui.shorten(active_context.pathZ(), 63), active_context.items_seen }
|
||||||
) catch return;
|
) catch return;
|
||||||
} else {
|
} 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",
|
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 }
|
.{ ui.shorten(active_context.pathZ(), 51), active_context.items_seen, r.num(), r.unit }
|
||||||
) catch return;
|
) catch return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue