std.fs.Dir.Walker: maintain a null byte in path names

This commit is contained in:
Andrew Kelley 2024-04-14 17:59:43 -07:00
parent e45bdc6bd6
commit 10106660e9

View file

@ -646,16 +646,17 @@ fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
} }
pub const Walker = struct { pub const Walker = struct {
stack: std.ArrayList(StackItem), stack: std.ArrayListUnmanaged(StackItem),
name_buffer: std.ArrayList(u8), name_buffer: std.ArrayListUnmanaged(u8),
allocator: Allocator,
pub const WalkerEntry = struct { pub const WalkerEntry = struct {
/// The containing directory. This can be used to operate directly on `basename` /// The containing directory. This can be used to operate directly on `basename`
/// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
/// The directory remains open until `next` or `deinit` is called. /// The directory remains open until `next` or `deinit` is called.
dir: Dir, dir: Dir,
basename: []const u8, basename: [:0]const u8,
path: []const u8, path: [:0]const u8,
kind: Dir.Entry.Kind, kind: Dir.Entry.Kind,
}; };
@ -668,6 +669,7 @@ pub const Walker = struct {
/// from this function becomes invalid. A copy must be made in order to keep /// from this function becomes invalid. A copy must be made in order to keep
/// a reference to the path. /// a reference to the path.
pub fn next(self: *Walker) !?WalkerEntry { pub fn next(self: *Walker) !?WalkerEntry {
const gpa = self.allocator;
while (self.stack.items.len != 0) { while (self.stack.items.len != 0) {
// `top` and `containing` become invalid after appending to `self.stack` // `top` and `containing` become invalid after appending to `self.stack`
var top = &self.stack.items[self.stack.items.len - 1]; var top = &self.stack.items[self.stack.items.len - 1];
@ -686,10 +688,12 @@ pub const Walker = struct {
}) |base| { }) |base| {
self.name_buffer.shrinkRetainingCapacity(dirname_len); self.name_buffer.shrinkRetainingCapacity(dirname_len);
if (self.name_buffer.items.len != 0) { if (self.name_buffer.items.len != 0) {
try self.name_buffer.append(fs.path.sep); try self.name_buffer.append(gpa, fs.path.sep);
dirname_len += 1; dirname_len += 1;
} }
try self.name_buffer.appendSlice(base.name); try self.name_buffer.ensureUnusedCapacity(gpa, base.name.len + 1);
self.name_buffer.appendSliceAssumeCapacity(base.name);
self.name_buffer.appendAssumeCapacity(0);
if (base.kind == .directory) { if (base.kind == .directory) {
var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
error.NameTooLong => unreachable, // no path sep in base.name error.NameTooLong => unreachable, // no path sep in base.name
@ -697,18 +701,18 @@ pub const Walker = struct {
}; };
{ {
errdefer new_dir.close(); errdefer new_dir.close();
try self.stack.append(StackItem{ try self.stack.append(gpa, .{
.iter = new_dir.iterateAssumeFirstIteration(), .iter = new_dir.iterateAssumeFirstIteration(),
.dirname_len = self.name_buffer.items.len, .dirname_len = self.name_buffer.items.len - 1,
}); });
top = &self.stack.items[self.stack.items.len - 1]; top = &self.stack.items[self.stack.items.len - 1];
containing = &self.stack.items[self.stack.items.len - 2]; containing = &self.stack.items[self.stack.items.len - 2];
} }
} }
return WalkerEntry{ return .{
.dir = containing.iter.dir, .dir = containing.iter.dir,
.basename = self.name_buffer.items[dirname_len..], .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
.path = self.name_buffer.items, .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
.kind = base.kind, .kind = base.kind,
}; };
} else { } else {
@ -722,37 +726,39 @@ pub const Walker = struct {
} }
pub fn deinit(self: *Walker) void { pub fn deinit(self: *Walker) void {
const gpa = self.allocator;
// Close any remaining directories except the initial one (which is always at index 0) // Close any remaining directories except the initial one (which is always at index 0)
if (self.stack.items.len > 1) { if (self.stack.items.len > 1) {
for (self.stack.items[1..]) |*item| { for (self.stack.items[1..]) |*item| {
item.iter.dir.close(); item.iter.dir.close();
} }
} }
self.stack.deinit(); self.stack.deinit(gpa);
self.name_buffer.deinit(); self.name_buffer.deinit(gpa);
} }
}; };
/// Recursively iterates over a directory. /// Recursively iterates over a directory.
///
/// `self` must have been opened with `OpenDirOptions{.iterate = true}`. /// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
/// Must call `Walker.deinit` when done. ///
/// `Walker.deinit` releases allocated memory and directory handles.
///
/// The order of returned file system entries is undefined. /// The order of returned file system entries is undefined.
///
/// `self` will not be closed after walking it. /// `self` will not be closed after walking it.
pub fn walk(self: Dir, allocator: Allocator) !Walker { pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
var name_buffer = std.ArrayList(u8).init(allocator); var stack: std.ArrayListUnmanaged(Walker.StackItem) = .{};
errdefer name_buffer.deinit();
var stack = std.ArrayList(Walker.StackItem).init(allocator); try stack.append(allocator, .{
errdefer stack.deinit();
try stack.append(Walker.StackItem{
.iter = self.iterate(), .iter = self.iterate(),
.dirname_len = 0, .dirname_len = 0,
}); });
return Walker{ return .{
.stack = stack, .stack = stack,
.name_buffer = name_buffer, .name_buffer = .{},
.allocator = allocator,
}; };
} }