Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/std/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3245,7 +3245,7 @@ pub const LibExeObjStep = struct {
const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");

if (self.output_dir) |output_dir| {
var src_dir = try std.fs.cwd().openDir(build_output_dir, .{ .iterate = true });
var src_dir = try std.fs.cwd().openIterableDir(build_output_dir, .{});
defer src_dir.close();

// Create the output directory if it doesn't exist.
Expand All @@ -3265,7 +3265,7 @@ pub const LibExeObjStep = struct {
mem.eql(u8, entry.name, "zld.id") or
mem.eql(u8, entry.name, "lld.id")) continue;

_ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{});
_ = try src_dir.dir.updateFile(entry.name, dest_dir, entry.name, .{});
}
} else {
self.output_dir = build_output_dir;
Expand Down Expand Up @@ -3480,7 +3480,7 @@ pub const InstallDirStep = struct {
const self = @fieldParentPtr(InstallDirStep, "step", step);
const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
const full_src_dir = self.builder.pathFromRoot(self.options.source_dir);
var src_dir = try std.fs.cwd().openDir(full_src_dir, .{ .iterate = true });
var src_dir = try std.fs.cwd().openIterableDir(full_src_dir, .{});
defer src_dir.close();
var it = try src_dir.walk(self.builder.allocator);
next_entry: while (try it.next()) |entry| {
Expand Down
163 changes: 99 additions & 64 deletions lib/std/fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,10 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
}

pub const Dir = struct {
fd: os.fd_t,
/// A directory that can be iterated. It is *NOT* legal to initialize this with a regular `Dir`
/// that has been opened without iteration permission.
pub const IterableDir = struct {
dir: Dir,

pub const Entry = struct {
name: []const u8,
Expand Down Expand Up @@ -779,7 +781,7 @@ pub const Dir = struct {
else => @compileError("unimplemented"),
};

pub fn iterate(self: Dir) Iterator {
pub fn iterate(self: IterableDir) Iterator {
switch (builtin.os.tag) {
.macos,
.ios,
Expand All @@ -789,30 +791,30 @@ pub const Dir = struct {
.openbsd,
.solaris,
=> return Iterator{
.dir = self,
.dir = self.dir,
.seek = 0,
.index = 0,
.end_index = 0,
.buf = undefined,
.first_iter = true,
},
.linux, .haiku => return Iterator{
.dir = self,
.dir = self.dir,
.index = 0,
.end_index = 0,
.buf = undefined,
.first_iter = true,
},
.windows => return Iterator{
.dir = self,
.dir = self.dir,
.index = 0,
.end_index = 0,
.first_iter = true,
.buf = undefined,
.name_data = undefined,
},
.wasi => return Iterator{
.dir = self,
.dir = self.dir,
.cookie = os.wasi.DIRCOOKIE_START,
.index = 0,
.end_index = 0,
Expand All @@ -833,11 +835,11 @@ pub const Dir = struct {
dir: Dir,
basename: []const u8,
path: []const u8,
kind: Dir.Entry.Kind,
kind: IterableDir.Entry.Kind,
};

const StackItem = struct {
iter: Dir.Iterator,
iter: IterableDir.Iterator,
dirname_len: usize,
};

Expand All @@ -857,7 +859,7 @@ pub const Dir = struct {
}
try self.name_buffer.appendSlice(base.name);
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.openIterableDir(base.name, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable, // no path sep in base.name
else => |e| return e,
};
Expand Down Expand Up @@ -896,11 +898,10 @@ pub const Dir = struct {
};

/// Recursively iterates over a directory.
/// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
/// Must call `Walker.deinit` when done.
/// The order of returned file system entries is undefined.
/// `self` will not be closed after walking it.
pub fn walk(self: Dir, allocator: Allocator) !Walker {
pub fn walk(self: IterableDir, allocator: Allocator) !Walker {
var name_buffer = std.ArrayList(u8).init(allocator);
errdefer name_buffer.deinit();

Expand All @@ -918,6 +919,49 @@ pub const Dir = struct {
};
}

pub fn close(self: *IterableDir) void {
self.dir.close();
self.* = undefined;
}

pub const ChmodError = File.ChmodError;

/// Changes the mode of the directory.
/// The process must have the correct privileges in order to do this
/// successfully, or must have the effective user ID matching the owner
/// of the directory.
pub fn chmod(self: IterableDir, new_mode: File.Mode) ChmodError!void {
const file: File = .{
.handle = self.dir.fd,
.capable_io_mode = .blocking,
};
try file.chmod(new_mode);
}

/// Changes the owner and group of the directory.
/// The process must have the correct privileges in order to do this
/// successfully. The group may be changed by the owner of the directory to
/// any group of which the owner is a member. If the
/// owner or group is specified as `null`, the ID is not changed.
pub fn chown(self: IterableDir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
const file: File = .{
.handle = self.dir.fd,
.capable_io_mode = .blocking,
};
try file.chown(owner, group);
}

pub const ChownError = File.ChownError;
};

pub const Dir = struct {
fd: os.fd_t,

pub const iterate = @compileError("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir'");
pub const walk = @compileError("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir'");
pub const chmod = @compileError("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir'");
pub const chown = @compileError("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir'");

pub const OpenError = error{
FileNotFound,
NotDir,
Expand Down Expand Up @@ -1334,6 +1378,15 @@ pub const Dir = struct {
return self.openDir(sub_path, open_dir_options);
}

/// This function performs `makePath`, followed by `openIterableDir`.
/// If supported by the OS, this operation is atomic. It is not atomic on
/// all operating systems.
pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {
// TODO improve this implementation on Windows; we can avoid 1 call to NtClose
try self.makePath(sub_path);
return self.openIterableDir(sub_path, open_dir_options);
}

/// This function returns the canonicalized absolute pathname of
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
Expand Down Expand Up @@ -1483,10 +1536,6 @@ pub const Dir = struct {
/// such operations are Illegal Behavior.
access_sub_paths: bool = true,

/// `true` means the opened directory can be scanned for the files and sub-directories
/// of the result. It means the `iterate` function can be called.
iterate: bool = false,

/// `true` means it won't dereference the symlinks.
no_follow: bool = false,
};
Expand All @@ -1498,12 +1547,28 @@ pub const Dir = struct {
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.openDirW(sub_path_w.span().ptr, args);
return self.openDirW(sub_path_w.span().ptr, args, false);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
return self.openDirWasi(sub_path, args);
} else {
const sub_path_c = try os.toPosixPath(sub_path);
return self.openDirZ(&sub_path_c, args);
return self.openDirZ(&sub_path_c, args, false);
}
}

/// Opens an iterable directory at the given path. The directory is a system resource that remains
/// open until `close` is called on the result.
///
/// Asserts that the path parameter has no null bytes.
pub fn openIterableDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!IterableDir {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return IterableDir{ .dir = try self.openDirW(sub_path_w.span().ptr, args, true) };
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
return IterableDir{ .dir = try self.openDirWasi(sub_path, args) };
} else {
const sub_path_c = try os.toPosixPath(sub_path);
return IterableDir{ .dir = try self.openDirZ(&sub_path_c, args, true) };
}
}

Expand Down Expand Up @@ -1556,13 +1621,13 @@ pub const Dir = struct {
}

/// Same as `openDir` except the parameter is null-terminated.
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions, iterable: bool) OpenError!Dir {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
return self.openDirW(sub_path_w.span().ptr, args);
}
const symlink_flags: u32 = if (args.no_follow) os.O.NOFOLLOW else 0x0;
if (!args.iterate) {
if (!iterable) {
const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
} else {
Expand All @@ -1572,13 +1637,14 @@ pub const Dir = struct {

/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
/// This function asserts the target OS is Windows.
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions, iterable: bool) OpenError!Dir {
const w = os.windows;
// TODO remove some of these flags if args.access_sub_paths is false
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
w.SYNCHRONIZE | w.FILE_TRAVERSE;
const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
var dir = try self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
return dir;
}

/// `flags` must contain `os.O.DIRECTORY`.
Expand Down Expand Up @@ -1958,7 +2024,7 @@ pub const Dir = struct {
error.Unexpected,
=> |e| return e,
}
var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
var iterable_dir = self.openIterableDir(sub_path, .{ .no_follow = true }) catch |err| switch (err) {
error.NotDir => {
if (got_access_denied) {
return error.AccessDenied;
Expand All @@ -1984,11 +2050,11 @@ pub const Dir = struct {
error.DeviceBusy,
=> |e| return e,
};
var cleanup_dir_parent: ?Dir = null;
var cleanup_dir_parent: ?IterableDir = null;
defer if (cleanup_dir_parent) |*d| d.close();

var cleanup_dir = true;
defer if (cleanup_dir) dir.close();
defer if (cleanup_dir) iterable_dir.close();

// Valid use of MAX_PATH_BYTES because dir_name_buf will only
// ever store a single path component that was returned from the
Expand All @@ -2001,9 +2067,9 @@ pub const Dir = struct {
// open it, and close the original directory. Repeat. Then start the entire operation over.

scan_dir: while (true) {
var dir_it = dir.iterate();
var dir_it = iterable_dir.iterate();
while (try dir_it.next()) |entry| {
if (dir.deleteFile(entry.name)) {
if (iterable_dir.dir.deleteFile(entry.name)) {
continue;
} else |err| switch (err) {
error.FileNotFound => continue,
Expand All @@ -2026,7 +2092,7 @@ pub const Dir = struct {
=> |e| return e,
}

const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
error.NotDir => {
if (got_access_denied) {
return error.AccessDenied;
Expand All @@ -2053,19 +2119,19 @@ pub const Dir = struct {
=> |e| return e,
};
if (cleanup_dir_parent) |*d| d.close();
cleanup_dir_parent = dir;
dir = new_dir;
cleanup_dir_parent = iterable_dir;
iterable_dir = new_dir;
mem.copy(u8, &dir_name_buf, entry.name);
dir_name = dir_name_buf[0..entry.name.len];
continue :scan_dir;
}
// Reached the end of the directory entries, which means we successfully deleted all of them.
// Now to remove the directory itself.
dir.close();
iterable_dir.close();
cleanup_dir = false;

if (cleanup_dir_parent) |d| {
d.deleteDir(dir_name) catch |err| switch (err) {
d.dir.deleteDir(dir_name) catch |err| switch (err) {
// These two things can happen due to file system race conditions.
error.FileNotFound, error.DirNotEmpty => continue :start_over,
else => |e| return e,
Expand Down Expand Up @@ -2246,37 +2312,6 @@ pub const Dir = struct {
return file.stat();
}

pub const ChmodError = File.ChmodError;

/// Changes the mode of the directory.
/// The process must have the correct privileges in order to do this
/// successfully, or must have the effective user ID matching the owner
/// of the directory. Additionally, the directory must have been opened
/// with `OpenDirOptions{ .iterate = true }`.
pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
const file: File = .{
.handle = self.fd,
.capable_io_mode = .blocking,
};
try file.chmod(new_mode);
}

/// Changes the owner and group of the directory.
/// The process must have the correct privileges in order to do this
/// successfully. The group may be changed by the owner of the directory to
/// any group of which the owner is a member. Additionally, the directory
/// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
/// owner or group is specified as `null`, the ID is not changed.
pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
const file: File = .{
.handle = self.fd,
.capable_io_mode = .blocking,
};
try file.chown(owner, group);
}

pub const ChownError = File.ChownError;

const Permissions = File.Permissions;
pub const SetPermissionsError = File.SetPermissionsError;

Expand Down
Loading