2024-04-20 05:49:42 -08:00
|
|
|
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
2021-07-18 01:36:05 -08:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
2021-04-29 02:48:45 -08:00
|
|
|
const std = @import("std");
|
|
|
|
|
const main = @import("main.zig");
|
2024-07-12 02:31:57 -08:00
|
|
|
const util = @import("util.zig");
|
2021-04-29 02:48:45 -08:00
|
|
|
const model = @import("model.zig");
|
2024-07-12 02:31:57 -08:00
|
|
|
const sink = @import("sink.zig");
|
2021-05-09 10:58:17 -08:00
|
|
|
const ui = @import("ui.zig");
|
Improve exclude pattern matching performance (and behavior, a bit)
Behavioral changes:
- A single wildcard ('*') does not cross directory boundary anymore.
Previously 'a*b' would also match 'a/b', but no other tool that I am
aware of matches paths that way. This change breaks compatibility with
old exclude patterns but improves consistency with other tools.
- Patterns with a trailing '/' now prevent recursing into the directory.
Previously any directory excluded with such a pattern would show up as
a regular directory with all its contents excluded, but now the
directory entry itself shows up as excluded.
- If the path given to ncdu matches one of the exclude patterns, the old
implementation would exclude every file/dir being read, this new
implementation would instead ignore the rule. Not quite sure how to
best handle this case, perhaps just exit with an error message?
Performance wise, I haven't yet found a scenario where this
implementation is slower than the old one and it's *significantly*
faster in some cases - in particular when using a large amount of
patterns, especially with literal paths and file names.
That's not to say this implementation is anywhere near optimal:
- A list of relevant patterns is constructed for each directory being
scanned. It may be possible to merge pattern lists that share
the same prefix, which could both reduce memory use and the number of
patterns that need to be matched upon entering a directory.
- A hash table with dynamic arrays as values is just garbage from a
memory allocation point of view.
- This still uses libc fnmatch(), but there's an opportunity to
precompile patterns for faster matching.
2022-08-09 23:46:36 -08:00
|
|
|
const exclude = @import("exclude.zig");
|
2024-10-26 04:33:40 -08:00
|
|
|
const c = @import("c.zig").c;
|
2021-04-29 02:48:45 -08:00
|
|
|
|
|
|
|
|
|
2021-05-03 04:41:48 -08:00
|
|
|
// This function only works on Linux
|
2024-08-24 23:29:39 -08:00
|
|
|
fn isKernfs(dir: std.fs.Dir) bool {
|
2024-10-26 04:33:40 -08:00
|
|
|
var buf: c.struct_statfs = undefined;
|
|
|
|
|
if (c.fstatfs(dir.fd, &buf) != 0) return false; // silently ignoring errors isn't too nice.
|
2023-04-02 01:57:34 -08:00
|
|
|
const iskern = switch (util.castTruncate(u32, buf.f_type)) {
|
2021-05-03 04:41:48 -08:00
|
|
|
// These numbers are documented in the Linux 'statfs(2)' man page, so I assume they're stable.
|
|
|
|
|
0x42494e4d, // BINFMTFS_MAGIC
|
|
|
|
|
0xcafe4a11, // BPF_FS_MAGIC
|
|
|
|
|
0x27e0eb, // CGROUP_SUPER_MAGIC
|
|
|
|
|
0x63677270, // CGROUP2_SUPER_MAGIC
|
|
|
|
|
0x64626720, // DEBUGFS_MAGIC
|
|
|
|
|
0x1cd1, // DEVPTS_SUPER_MAGIC
|
|
|
|
|
0x9fa0, // PROC_SUPER_MAGIC
|
|
|
|
|
0x6165676c, // PSTOREFS_MAGIC
|
|
|
|
|
0x73636673, // SECURITYFS_MAGIC
|
|
|
|
|
0xf97cff8c, // SELINUX_MAGIC
|
|
|
|
|
0x62656572, // SYSFS_MAGIC
|
|
|
|
|
0x74726163 // TRACEFS_MAGIC
|
|
|
|
|
=> true,
|
|
|
|
|
else => false,
|
2021-04-29 02:48:45 -08:00
|
|
|
};
|
2021-05-03 04:41:48 -08:00
|
|
|
return iskern;
|
2021-04-29 02:48:45 -08:00
|
|
|
}
|
|
|
|
|
|
2021-05-12 01:04:06 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
fn clamp(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).type {
|
|
|
|
|
return util.castClamp(std.meta.fieldInfo(T, field).type, x);
|
|
|
|
|
}
|
2021-07-26 04:03:08 -08:00
|
|
|
|
2021-07-13 03:33:38 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
fn truncate(comptime T: type, comptime field: anytype, x: anytype) std.meta.fieldInfo(T, field).type {
|
|
|
|
|
return util.castTruncate(std.meta.fieldInfo(T, field).type, x);
|
|
|
|
|
}
|
2021-07-13 03:33:38 -08:00
|
|
|
|
|
|
|
|
|
2025-07-15 05:58:00 -08:00
|
|
|
pub fn statAt(parent: std.fs.Dir, name: [:0]const u8, follow: bool, symlink: ?*bool) !sink.Stat {
|
2025-04-28 03:11:54 -08:00
|
|
|
// std.posix.fstatatZ() in Zig 0.14 is not suitable due to https://github.com/ziglang/zig/issues/23463
|
|
|
|
|
var stat: std.c.Stat = undefined;
|
|
|
|
|
if (std.c.fstatat(parent.fd, name, &stat, if (follow) 0 else std.c.AT.SYMLINK_NOFOLLOW) != 0) {
|
2025-04-06 00:36:33 -08:00
|
|
|
return switch (std.c._errno().*) {
|
2025-04-28 03:11:54 -08:00
|
|
|
@intFromEnum(std.c.E.NOENT) => error.FileNotFound,
|
|
|
|
|
@intFromEnum(std.c.E.NAMETOOLONG) => error.NameTooLong,
|
|
|
|
|
@intFromEnum(std.c.E.NOMEM) => error.OutOfMemory,
|
|
|
|
|
@intFromEnum(std.c.E.ACCES) => error.AccessDenied,
|
2025-04-06 00:36:33 -08:00
|
|
|
else => error.Unexpected,
|
|
|
|
|
};
|
2025-04-28 03:11:54 -08:00
|
|
|
}
|
2025-07-15 05:58:00 -08:00
|
|
|
if (symlink) |s| s.* = std.c.S.ISLNK(stat.mode);
|
2024-07-12 02:31:57 -08:00
|
|
|
return sink.Stat{
|
2024-08-01 04:20:34 -08:00
|
|
|
.etype =
|
2025-04-28 03:11:54 -08:00
|
|
|
if (std.c.S.ISDIR(stat.mode)) .dir
|
|
|
|
|
else if (stat.nlink > 1) .link
|
|
|
|
|
else if (!std.c.S.ISREG(stat.mode)) .nonreg
|
2024-08-01 04:20:34 -08:00
|
|
|
else .reg,
|
2025-04-28 03:11:54 -08:00
|
|
|
.blocks = clamp(sink.Stat, .blocks, stat.blocks),
|
|
|
|
|
.size = clamp(sink.Stat, .size, stat.size),
|
|
|
|
|
.dev = truncate(sink.Stat, .dev, stat.dev),
|
|
|
|
|
.ino = truncate(sink.Stat, .ino, stat.ino),
|
|
|
|
|
.nlink = clamp(sink.Stat, .nlink, stat.nlink),
|
2024-07-12 02:31:57 -08:00
|
|
|
.ext = .{
|
2024-08-09 08:24:59 -08:00
|
|
|
.pack = .{
|
|
|
|
|
.hasmtime = true,
|
|
|
|
|
.hasuid = true,
|
|
|
|
|
.hasgid = true,
|
|
|
|
|
.hasmode = true,
|
|
|
|
|
},
|
2025-05-01 04:51:09 -08:00
|
|
|
.mtime = clamp(model.Ext, .mtime, stat.mtime().sec),
|
2025-04-28 03:11:54 -08:00
|
|
|
.uid = truncate(model.Ext, .uid, stat.uid),
|
|
|
|
|
.gid = truncate(model.Ext, .gid, stat.gid),
|
|
|
|
|
.mode = truncate(model.Ext, .mode, stat.mode),
|
2024-07-12 02:31:57 -08:00
|
|
|
},
|
2021-07-13 03:33:38 -08:00
|
|
|
};
|
2024-07-12 02:31:57 -08:00
|
|
|
}
|
2021-07-13 03:33:38 -08:00
|
|
|
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
fn isCacheDir(dir: std.fs.Dir) bool {
|
|
|
|
|
const sig = "Signature: 8a477f597d28d172789f06886806bc55";
|
|
|
|
|
const f = dir.openFileZ("CACHEDIR.TAG", .{}) catch return false;
|
|
|
|
|
defer f.close();
|
|
|
|
|
var buf: [sig.len]u8 = undefined;
|
|
|
|
|
const len = f.reader().readAll(&buf) catch return false;
|
|
|
|
|
return len == sig.len and std.mem.eql(u8, &buf, sig);
|
|
|
|
|
}
|
2021-07-13 03:33:38 -08:00
|
|
|
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const State = struct {
|
|
|
|
|
// Simple LIFO queue. Threads attempt to fully scan their assigned
|
|
|
|
|
// directory before consulting this queue for their next task, so there
|
|
|
|
|
// shouldn't be too much contention here.
|
|
|
|
|
// TODO: unless threads keep juggling around leaf nodes, need to measure
|
|
|
|
|
// actual use.
|
|
|
|
|
// There's no real reason for this to be LIFO other than that that was the
|
|
|
|
|
// easiest to implement. Queue order has an effect on scheduling, but it's
|
|
|
|
|
// impossible for me to predict how that ends up affecting performance.
|
|
|
|
|
queue: [QUEUE_SIZE]*Dir = undefined,
|
|
|
|
|
queue_len: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
|
|
|
|
|
queue_lock: std.Thread.Mutex = .{},
|
|
|
|
|
queue_cond: std.Thread.Condition = .{},
|
|
|
|
|
|
|
|
|
|
threads: []Thread,
|
|
|
|
|
waiting: usize = 0,
|
|
|
|
|
|
|
|
|
|
// No clue what this should be set to. Dir structs aren't small so we don't
|
|
|
|
|
// want too have too many of them.
|
|
|
|
|
const QUEUE_SIZE = 16;
|
|
|
|
|
|
|
|
|
|
// Returns true if the given Dir has been queued, false if the queue is full.
|
|
|
|
|
fn tryPush(self: *State, d: *Dir) bool {
|
|
|
|
|
if (self.queue_len.load(.acquire) == QUEUE_SIZE) return false;
|
|
|
|
|
{
|
|
|
|
|
self.queue_lock.lock();
|
|
|
|
|
defer self.queue_lock.unlock();
|
|
|
|
|
if (self.queue_len.load(.monotonic) == QUEUE_SIZE) return false;
|
|
|
|
|
const slot = self.queue_len.fetchAdd(1, .monotonic);
|
|
|
|
|
self.queue[slot] = d;
|
|
|
|
|
}
|
|
|
|
|
self.queue_cond.signal();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Blocks while the queue is empty, returns null when all threads are blocking.
|
|
|
|
|
fn waitPop(self: *State) ?*Dir {
|
|
|
|
|
self.queue_lock.lock();
|
|
|
|
|
defer self.queue_lock.unlock();
|
|
|
|
|
|
|
|
|
|
self.waiting += 1;
|
|
|
|
|
while (self.queue_len.load(.monotonic) == 0) {
|
|
|
|
|
if (self.waiting == self.threads.len) {
|
|
|
|
|
self.queue_cond.broadcast();
|
|
|
|
|
return null;
|
2023-12-05 02:03:39 -09:00
|
|
|
}
|
2024-07-12 02:31:57 -08:00
|
|
|
self.queue_cond.wait(&self.queue_lock);
|
2021-07-13 03:33:38 -08:00
|
|
|
}
|
2024-07-12 02:31:57 -08:00
|
|
|
self.waiting -= 1;
|
2021-07-13 03:33:38 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const slot = self.queue_len.fetchSub(1, .monotonic) - 1;
|
|
|
|
|
defer self.queue[slot] = undefined;
|
|
|
|
|
return self.queue[slot];
|
2021-07-13 03:33:38 -08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const Dir = struct {
|
|
|
|
|
fd: std.fs.Dir,
|
|
|
|
|
dev: u64,
|
|
|
|
|
pat: exclude.Patterns,
|
|
|
|
|
it: std.fs.Dir.Iterator,
|
|
|
|
|
sink: *sink.Dir,
|
|
|
|
|
|
|
|
|
|
fn create(fd: std.fs.Dir, dev: u64, pat: exclude.Patterns, s: *sink.Dir) *Dir {
|
|
|
|
|
const d = main.allocator.create(Dir) catch unreachable;
|
|
|
|
|
d.* = .{
|
|
|
|
|
.fd = fd,
|
|
|
|
|
.dev = dev,
|
|
|
|
|
.pat = pat,
|
|
|
|
|
.sink = s,
|
|
|
|
|
.it = fd.iterate(),
|
2023-12-05 02:03:39 -09:00
|
|
|
};
|
2024-07-12 02:31:57 -08:00
|
|
|
return d;
|
2021-05-29 00:51:17 -08:00
|
|
|
}
|
|
|
|
|
|
2024-07-27 08:40:48 -08:00
|
|
|
fn destroy(d: *Dir, t: *Thread) void {
|
2024-07-12 02:31:57 -08:00
|
|
|
d.pat.deinit();
|
|
|
|
|
d.fd.close();
|
2024-07-27 08:40:48 -08:00
|
|
|
d.sink.unref(t.sink);
|
2024-07-12 02:31:57 -08:00
|
|
|
main.allocator.destroy(d);
|
2021-05-23 07:18:49 -08:00
|
|
|
}
|
2021-05-03 04:41:48 -08:00
|
|
|
};
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const Thread = struct {
|
|
|
|
|
thread_num: usize,
|
|
|
|
|
sink: *sink.Thread,
|
|
|
|
|
state: *State,
|
|
|
|
|
stack: std.ArrayList(*Dir) = std.ArrayList(*Dir).init(main.allocator),
|
|
|
|
|
thread: std.Thread = undefined,
|
|
|
|
|
namebuf: [4096]u8 = undefined,
|
|
|
|
|
|
|
|
|
|
fn scanOne(t: *Thread, dir: *Dir, name_: []const u8) void {
|
|
|
|
|
if (name_.len > t.namebuf.len - 1) {
|
|
|
|
|
dir.sink.addSpecial(t.sink, name_, .err);
|
2021-04-29 02:48:45 -08:00
|
|
|
return;
|
2024-07-12 02:31:57 -08:00
|
|
|
}
|
2021-04-29 02:48:45 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
@memcpy(t.namebuf[0..name_.len], name_);
|
|
|
|
|
t.namebuf[name_.len] = 0;
|
|
|
|
|
const name = t.namebuf[0..name_.len:0];
|
2021-04-29 02:48:45 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const excluded = dir.pat.match(name);
|
Improve exclude pattern matching performance (and behavior, a bit)
Behavioral changes:
- A single wildcard ('*') does not cross directory boundary anymore.
Previously 'a*b' would also match 'a/b', but no other tool that I am
aware of matches paths that way. This change breaks compatibility with
old exclude patterns but improves consistency with other tools.
- Patterns with a trailing '/' now prevent recursing into the directory.
Previously any directory excluded with such a pattern would show up as
a regular directory with all its contents excluded, but now the
directory entry itself shows up as excluded.
- If the path given to ncdu matches one of the exclude patterns, the old
implementation would exclude every file/dir being read, this new
implementation would instead ignore the rule. Not quite sure how to
best handle this case, perhaps just exit with an error message?
Performance wise, I haven't yet found a scenario where this
implementation is slower than the old one and it's *significantly*
faster in some cases - in particular when using a large amount of
patterns, especially with literal paths and file names.
That's not to say this implementation is anywhere near optimal:
- A list of relevant patterns is constructed for each directory being
scanned. It may be possible to merge pattern lists that share
the same prefix, which could both reduce memory use and the number of
patterns that need to be matched upon entering a directory.
- A hash table with dynamic arrays as values is just garbage from a
memory allocation point of view.
- This still uses libc fnmatch(), but there's an opportunity to
precompile patterns for faster matching.
2022-08-09 23:46:36 -08:00
|
|
|
if (excluded == false) { // matched either a file or directory, so we can exclude this before stat()ing.
|
2024-08-01 04:20:34 -08:00
|
|
|
dir.sink.addSpecial(t.sink, name, .pattern);
|
2024-07-12 02:31:57 -08:00
|
|
|
return;
|
2021-05-03 04:41:48 -08:00
|
|
|
}
|
|
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
var symlink: bool = undefined;
|
|
|
|
|
var stat = statAt(dir.fd, name, false, &symlink) catch {
|
2024-07-12 02:31:57 -08:00
|
|
|
dir.sink.addSpecial(t.sink, name, .err);
|
|
|
|
|
return;
|
2021-04-29 02:48:45 -08:00
|
|
|
};
|
|
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
if (main.config.follow_symlinks and symlink) {
|
|
|
|
|
if (statAt(dir.fd, name, true, &symlink)) |nstat| {
|
|
|
|
|
if (nstat.etype != .dir) {
|
2024-07-12 02:31:57 -08:00
|
|
|
stat = nstat;
|
2021-04-30 09:15:29 -08:00
|
|
|
// Symlink targets may reside on different filesystems,
|
|
|
|
|
// this will break hardlink detection and counting so let's disable it.
|
2024-08-01 04:20:34 -08:00
|
|
|
if (stat.etype == .link and stat.dev != dir.dev) {
|
|
|
|
|
stat.etype = .reg;
|
2024-07-12 02:31:57 -08:00
|
|
|
stat.nlink = 1;
|
2021-04-30 09:15:29 -08:00
|
|
|
}
|
2024-07-12 02:31:57 -08:00
|
|
|
}
|
2021-04-30 09:15:29 -08:00
|
|
|
} else |_| {}
|
|
|
|
|
}
|
2021-04-29 02:48:45 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
if (main.config.same_fs and stat.dev != dir.dev) {
|
2024-08-01 04:20:34 -08:00
|
|
|
dir.sink.addSpecial(t.sink, name, .otherfs);
|
2024-07-12 02:31:57 -08:00
|
|
|
return;
|
Improve exclude pattern matching performance (and behavior, a bit)
Behavioral changes:
- A single wildcard ('*') does not cross directory boundary anymore.
Previously 'a*b' would also match 'a/b', but no other tool that I am
aware of matches paths that way. This change breaks compatibility with
old exclude patterns but improves consistency with other tools.
- Patterns with a trailing '/' now prevent recursing into the directory.
Previously any directory excluded with such a pattern would show up as
a regular directory with all its contents excluded, but now the
directory entry itself shows up as excluded.
- If the path given to ncdu matches one of the exclude patterns, the old
implementation would exclude every file/dir being read, this new
implementation would instead ignore the rule. Not quite sure how to
best handle this case, perhaps just exit with an error message?
Performance wise, I haven't yet found a scenario where this
implementation is slower than the old one and it's *significantly*
faster in some cases - in particular when using a large amount of
patterns, especially with literal paths and file names.
That's not to say this implementation is anywhere near optimal:
- A list of relevant patterns is constructed for each directory being
scanned. It may be possible to merge pattern lists that share
the same prefix, which could both reduce memory use and the number of
patterns that need to be matched upon entering a directory.
- A hash table with dynamic arrays as values is just garbage from a
memory allocation point of view.
- This still uses libc fnmatch(), but there's an opportunity to
precompile patterns for faster matching.
2022-08-09 23:46:36 -08:00
|
|
|
}
|
2021-06-01 06:13:59 -08:00
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
if (stat.etype != .dir) {
|
2024-07-12 02:31:57 -08:00
|
|
|
dir.sink.addStat(t.sink, name, &stat);
|
|
|
|
|
return;
|
2021-06-01 06:13:59 -08:00
|
|
|
}
|
2021-05-29 00:51:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
if (excluded == true) {
|
2024-08-01 04:20:34 -08:00
|
|
|
dir.sink.addSpecial(t.sink, name, .pattern);
|
2024-07-12 02:31:57 -08:00
|
|
|
return;
|
2021-05-29 00:51:17 -08:00
|
|
|
}
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
var edir = dir.fd.openDirZ(name, .{ .no_follow = true, .iterate = true }) catch {
|
2024-07-16 23:09:02 -08:00
|
|
|
const s = dir.sink.addDir(t.sink, name, &stat);
|
|
|
|
|
s.setReadError(t.sink);
|
2024-07-27 08:40:48 -08:00
|
|
|
s.unref(t.sink);
|
2024-07-12 02:31:57 -08:00
|
|
|
return;
|
2021-05-29 00:51:17 -08:00
|
|
|
};
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
if (@import("builtin").os.tag == .linux
|
|
|
|
|
and main.config.exclude_kernfs
|
|
|
|
|
and stat.dev != dir.dev
|
2024-08-24 23:29:39 -08:00
|
|
|
and isKernfs(edir)
|
2024-07-12 02:31:57 -08:00
|
|
|
) {
|
|
|
|
|
edir.close();
|
|
|
|
|
dir.sink.addSpecial(t.sink, name, .kernfs);
|
|
|
|
|
return;
|
2021-05-29 00:51:17 -08:00
|
|
|
}
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
if (main.config.exclude_caches and isCacheDir(edir)) {
|
2024-08-01 04:20:34 -08:00
|
|
|
dir.sink.addSpecial(t.sink, name, .pattern);
|
2024-07-12 02:31:57 -08:00
|
|
|
edir.close();
|
|
|
|
|
return;
|
2021-05-29 00:51:17 -08:00
|
|
|
}
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const s = dir.sink.addDir(t.sink, name, &stat);
|
|
|
|
|
const ndir = Dir.create(edir, stat.dev, dir.pat.enter(name), s);
|
|
|
|
|
if (main.config.threads == 1 or !t.state.tryPush(ndir))
|
|
|
|
|
t.stack.append(ndir) catch unreachable;
|
2021-05-29 00:51:17 -08:00
|
|
|
}
|
|
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
fn run(t: *Thread) void {
|
|
|
|
|
defer t.stack.deinit();
|
|
|
|
|
while (t.state.waitPop()) |dir| {
|
|
|
|
|
t.stack.append(dir) catch unreachable;
|
2021-05-29 00:51:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
while (t.stack.items.len > 0) {
|
|
|
|
|
const d = t.stack.items[t.stack.items.len - 1];
|
2021-05-29 00:51:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
t.sink.setDir(d.sink);
|
|
|
|
|
if (t.thread_num == 0) main.handleEvent(false, false);
|
2021-05-29 00:51:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const entry = d.it.next() catch blk: {
|
|
|
|
|
dir.sink.setReadError(t.sink);
|
|
|
|
|
break :blk null;
|
|
|
|
|
};
|
|
|
|
|
if (entry) |e| t.scanOne(d, e.name)
|
|
|
|
|
else {
|
|
|
|
|
t.sink.setDir(null);
|
2025-02-13 08:49:32 -09:00
|
|
|
t.stack.pop().?.destroy(t);
|
2024-07-12 02:31:57 -08:00
|
|
|
}
|
2021-05-29 00:51:17 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-09 10:58:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
pub fn scan(path: [:0]const u8) !void {
|
2024-07-14 06:21:59 -08:00
|
|
|
const sink_threads = sink.createThreads(main.config.threads);
|
|
|
|
|
defer sink.done();
|
|
|
|
|
|
2024-08-01 04:20:34 -08:00
|
|
|
var symlink: bool = undefined;
|
|
|
|
|
const stat = try statAt(std.fs.cwd(), path, true, &symlink);
|
2024-07-12 02:31:57 -08:00
|
|
|
const fd = try std.fs.cwd().openDirZ(path, .{ .iterate = true });
|
2021-07-13 03:33:38 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
var state = State{
|
|
|
|
|
.threads = main.allocator.alloc(Thread, main.config.threads) catch unreachable,
|
|
|
|
|
};
|
|
|
|
|
defer main.allocator.free(state.threads);
|
2021-05-09 10:58:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
const root = sink.createRoot(path, &stat);
|
|
|
|
|
const dir = Dir.create(fd, stat.dev, exclude.getPatterns(path), root);
|
|
|
|
|
_ = state.tryPush(dir);
|
2021-05-09 10:58:17 -08:00
|
|
|
|
2024-07-14 06:21:59 -08:00
|
|
|
for (sink_threads, state.threads, 0..) |*s, *t, n|
|
2024-07-12 02:31:57 -08:00
|
|
|
t.* = .{ .sink = s, .state = &state, .thread_num = n };
|
2021-05-09 10:58:17 -08:00
|
|
|
|
2024-07-12 02:31:57 -08:00
|
|
|
// XXX: Continue with fewer threads on error?
|
|
|
|
|
for (state.threads[1..]) |*t| {
|
|
|
|
|
t.thread = std.Thread.spawn(
|
|
|
|
|
.{ .stack_size = 128 * 1024, .allocator = main.allocator }, Thread.run, .{t}
|
|
|
|
|
) catch |e| ui.die("Error spawning thread: {}\n", .{e});
|
2021-05-09 10:58:17 -08:00
|
|
|
}
|
2024-07-12 02:31:57 -08:00
|
|
|
state.threads[0].run();
|
|
|
|
|
for (state.threads[1..]) |*t| t.thread.join();
|
2021-05-09 10:58:17 -08:00
|
|
|
}
|