From 2f188290e203780752597b0263581590af3a69b2 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 7 Jun 2023 22:37:53 -0700 Subject: [PATCH 1/3] Use `iterateAssumeFirstIteration` in `Walker.next` to avoid unnecessary lseek calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we are opening each directory for iteration, we know that we don't need to reset the cursor's directory before iterating. Using `iterateAssumeFirstIteration` skips the cursor resetting which eliminates an `lseek` syscall for every directory opened on non-Windows platforms. This doesn't seem to actually matter much for performance (1.01 ± 0.02 times faster when walking /home/ on my system) but avoiding unnecessary syscalls is always nice anyway. --- lib/std/fs.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 037f0c81f6..d3c8fb322e 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -972,7 +972,7 @@ pub const IterableDir = struct { { errdefer new_dir.close(); try self.stack.append(StackItem{ - .iter = new_dir.iterate(), + .iter = new_dir.iterateAssumeFirstIteration(), .dirname_len = self.name_buffer.items.len, }); top = &self.stack.items[self.stack.items.len - 1]; From 7555085e6304419c25bb870567eb265bb22d0337 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 7 Jun 2023 22:44:21 -0700 Subject: [PATCH 2/3] Directory iteration: handle `EACCES` returned from `getdents64` This can occur for directories that the user does not have the necessary permissions to be able to iterate. --- lib/std/fs.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index d3c8fb322e..8ce8b74650 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -662,6 +662,7 @@ pub const IterableDir = struct { .NOTDIR => unreachable, .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. + .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory. else => |err| return os.unexpectedErrno(err), } if (rc == 0) return null; From af835111fa1397578392a4774454ac0d71eca77d Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 7 Jun 2023 22:50:45 -0700 Subject: [PATCH 3/3] Allow recovering from Walker.next errors without continually hitting the same error Before this commit, if Walker.next errored with e.g. `error.AccessDenied` and the caller did something like `while (true) { walker.next() catch continue; }`, then the directory that errored with AccessDenied would be continually iterated in each `next` call and error every time with AccessDenied. After this commit, the directory that errored will be popped off the stack before the error is returned, meaning that in the subsequent `next` call, it won't be retried and the Walker will continue with whatever directories remain on its stack. For a real example, before this commit, walking `/proc/` on my system would infinitely loop due to repeated AccessDenied errors on the same directory. After this commit, I am able to walk `/proc/` on my system fully (skipping over any directories that are unable to be iterated). --- lib/std/fs.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 8ce8b74650..38b94873e6 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -958,7 +958,17 @@ pub const IterableDir = struct { var top = &self.stack.items[self.stack.items.len - 1]; var containing = top; var dirname_len = top.dirname_len; - if (try top.iter.next()) |base| { + if (top.iter.next() catch |err| { + // If we get an error, then we want the user to be able to continue + // walking if they want, which means that we need to pop the directory + // that errored from the stack. Otherwise, all future `next` calls would + // likely just fail with the same error. + var item = self.stack.pop(); + if (self.stack.items.len != 0) { + item.iter.dir.close(); + } + return err; + }) |base| { self.name_buffer.shrinkRetainingCapacity(dirname_len); if (self.name_buffer.items.len != 0) { try self.name_buffer.append(path.sep);