summaryrefslogtreecommitdiff
path: root/src/fs.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/fs.zig')
-rw-r--r--src/fs.zig57
1 files changed, 50 insertions, 7 deletions
diff --git a/src/fs.zig b/src/fs.zig
index e5980fe9..0c04e873 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -1079,7 +1079,10 @@ const find_skip = [_][]const u8{
".venv", "__pycache__", ".zig-cache", "zig-out",
};
-pub fn find(arena: std.mem.Allocator, dir: []const u8, pat: []const u8, out: []u8) !usize {
+/// A directory the walk may not open or read is passed over and counted in
+/// `skipped`, its siblings' hits kept: one locked directory is not every
+/// search's failure.
+pub fn find(arena: std.mem.Allocator, dir: []const u8, pat: []const u8, out: []u8, skipped: *usize) !usize {
var hits: [find_max_hits][]const u8 = undefined;
var hits_len: usize = 0;
if (platform_has_fs) {
@@ -1093,14 +1096,19 @@ pub fn find(arena: std.mem.Allocator, dir: []const u8, pat: []const u8, out: []u
var steps: usize = 0;
walk: while (steps < find_max_steps and hits_len < hits.len) {
steps += 1; // an unreadable dir burns a step too, so it cannot spin
- const e = (try w.next(io)) orelse break;
+ const e = (w.next(io) catch {
+ skipped.* += 1;
+ continue;
+ }) orelse break;
if (std.ascii.indexOfIgnoreCase(e.basename, pat) != null) {
hits[hits_len] = try arena.dupe(u8, e.path);
hits_len += 1;
}
if (e.kind != .directory or e.depth() >= find_max_depth) continue;
for (find_skip) |s| if (std.mem.eql(u8, e.basename, s)) continue :walk;
- try w.enter(io, e);
+ w.enter(io, e) catch {
+ skipped.* += 1;
+ };
}
} else {
for (sources) |s| {
@@ -1152,7 +1160,7 @@ fn grepText(path: []const u8, text: []const u8, pat: []const u8, out: []u8, budg
return result;
}
-pub fn grep(arena: std.mem.Allocator, gpa: std.mem.Allocator, dir: []const u8, base: []const u8, pat: []const u8, out: []u8) !usize {
+pub fn grep(arena: std.mem.Allocator, gpa: std.mem.Allocator, dir: []const u8, base: []const u8, pat: []const u8, out: []u8, skipped: *usize) !usize {
var hits: usize = 0;
var written: usize = 0;
if (!platform_has_fs) {
@@ -1180,11 +1188,16 @@ pub fn grep(arena: std.mem.Allocator, gpa: std.mem.Allocator, dir: []const u8, b
var steps: usize = 0;
walk: while (steps < find_max_steps and files_len < files.len) {
steps += 1;
- const e = (try w.next(io)) orelse break;
+ const e = (w.next(io) catch {
+ skipped.* += 1;
+ continue;
+ }) orelse break;
if (e.kind == .directory) {
if (e.depth() >= find_max_depth) continue;
for (find_skip) |s| if (std.mem.eql(u8, e.basename, s)) continue :walk;
- try w.enter(io, e);
+ w.enter(io, e) catch {
+ skipped.* += 1;
+ };
continue;
}
if (e.kind != .file) continue;
@@ -1253,13 +1266,43 @@ test "grep skips a file it cannot read instead of abandoning the search" {
defer arena.deinit();
const out = try gpa.alloc(u8, 64 * 1024);
defer gpa.free(out);
- const n = try grep(arena.allocator(), gpa, dir, dir, "needle", out);
+ var skipped: usize = 0;
+ const n = try grep(arena.allocator(), gpa, dir, dir, "needle", out, &skipped);
_ = libc.chmod(locked, 0o644); // so `tmp.cleanup` can remove it
try std.testing.expect(std.mem.indexOf(u8, out[0..n], "b-open.txt") != null);
try std.testing.expect(std.mem.indexOf(u8, out[0..n], "a-locked.txt") == null);
}
+test "a directory the walk may not open is skipped and counted: Grep and Find keep every other hit" {
+ if (!platform_has_fs) return;
+ const gpa = std.testing.allocator;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ var base_buf: [std.fs.max_path_bytes]u8 = undefined;
+ const dir = base_buf[0..try tmp.dir.realPath(std.testing.io, &base_buf)];
+ try tmp.dir.createDirPath(std.testing.io, "locked");
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "locked/needle-in.txt", .data = "needle\n" });
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "needle-out.txt", .data = "needle\n" });
+ var locked_buf: [std.fs.max_path_bytes]u8 = undefined;
+ const locked = try std.fmt.bufPrintSentinel(&locked_buf, "{s}/locked", .{dir}, 0);
+ if (libc.chmod(locked, 0) != 0) return;
+ defer _ = libc.chmod(locked, 0o755); // so `tmp.cleanup` can remove it
+ if (isDir(locked)) return error.SkipZigTest; // root reads it anyway
+ var arena: std.heap.ArenaAllocator = .init(gpa);
+ defer arena.deinit();
+ const out = try gpa.alloc(u8, 64 * 1024);
+ defer gpa.free(out);
+ var skipped: usize = 0;
+ var n = try grep(arena.allocator(), gpa, dir, dir, "needle", out, &skipped);
+ try std.testing.expect(std.mem.indexOf(u8, out[0..n], "needle-out.txt:1:") != null);
+ try std.testing.expectEqual(@as(usize, 1), skipped);
+ skipped = 0;
+ n = try find(arena.allocator(), dir, "needle", out, &skipped);
+ try std.testing.expect(std.mem.indexOf(u8, out[0..n], "needle-out.txt") != null);
+ try std.testing.expectEqual(@as(usize, 1), skipped);
+}
+
fn isDir(path: [*:0]const u8) bool {
const fd = libc.open(path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true, .CLOEXEC = true });
if (fd < 0) return false;