fd: posix.fd_t,
pub const default_mode = 0o755;
pub const Entry = struct {
name: []const u8,
kind: Kind,
pub const Kind = File.Kind;
};
const IteratorError = error{
AccessDenied,
SystemResources,
InvalidUtf8,
} || posix.UnexpectedError;
pub const Iterator = switch (builtin.os.tag) {
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
dir: Dir,
seek: i64,
buf: [1024]u8,
index: usize,
end_index: usize,
first_iter: bool,
const Self = @This();
pub const Error = IteratorError;
pub fn next(self: *Self) Error!?Entry {
switch (builtin.os.tag) {
.macos, .ios => return self.nextDarwin(),
.freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
.solaris, .illumos => return self.nextSolaris(),
else => @compileError("unimplemented"),
}
}
fn nextDarwin(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable;
self.first_iter = false;
}
const rc = posix.system.__getdirentries64(
self.dir.fd,
&self.buf,
self.buf.len,
&self.seek,
);
if (rc == 0) return null;
if (rc < 0) {
switch (posix.errno(rc)) {
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
else => |err| return posix.unexpectedErrno(err),
}
}
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index + darwin_entry.reclen;
self.index = next_index;
const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) {
continue :start_over;
}
const entry_kind: Entry.Kind = switch (darwin_entry.type) {
posix.DT.BLK => .block_device,
posix.DT.CHR => .character_device,
posix.DT.DIR => .directory,
posix.DT.FIFO => .named_pipe,
posix.DT.LNK => .sym_link,
posix.DT.REG => .file,
posix.DT.SOCK => .unix_domain_socket,
posix.DT.WHT => .whiteout,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
fn nextSolaris(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable;
self.first_iter = false;
}
const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
switch (posix.errno(rc)) {
.SUCCESS => {},
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
else => |err| return posix.unexpectedErrno(err),
}
if (rc == 0) return null;
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index + entry.reclen;
self.index = next_index;
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
continue :start_over;
const stat_info = posix.fstatat(
self.dir.fd,
name,
posix.AT.SYMLINK_NOFOLLOW,
) catch |err| switch (err) {
error.NameTooLong => unreachable,
error.SymLinkLoop => unreachable,
error.FileNotFound => unreachable,
else => |e| return e,
};
const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
posix.S.IFIFO => .named_pipe,
posix.S.IFCHR => .character_device,
posix.S.IFDIR => .directory,
posix.S.IFBLK => .block_device,
posix.S.IFREG => .file,
posix.S.IFLNK => .sym_link,
posix.S.IFSOCK => .unix_domain_socket,
posix.S.IFDOOR => .door,
posix.S.IFPORT => .event_port,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
fn nextBsd(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable;
self.first_iter = false;
}
const rc = if (builtin.os.tag == .netbsd)
posix.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
else
posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
switch (posix.errno(rc)) {
.SUCCESS => {},
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
.NOENT => return null,
else => |err| return posix.unexpectedErrno(err),
}
if (rc == 0) return null;
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index + if (@hasDecl(posix.system.dirent, "reclen")) bsd_entry.reclen() else bsd_entry.reclen;
self.index = next_index;
const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
const skip_zero_fileno = switch (builtin.os.tag) {
.openbsd, .netbsd => true,
else => false,
};
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
(skip_zero_fileno and bsd_entry.fileno == 0))
{
continue :start_over;
}
const entry_kind: Entry.Kind = switch (bsd_entry.type) {
posix.DT.BLK => .block_device,
posix.DT.CHR => .character_device,
posix.DT.DIR => .directory,
posix.DT.FIFO => .named_pipe,
posix.DT.LNK => .sym_link,
posix.DT.REG => .file,
posix.DT.SOCK => .unix_domain_socket,
posix.DT.WHT => .whiteout,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.haiku => struct {
dir: Dir,
buf: [1024]u8,
index: usize,
end_index: usize,
first_iter: bool,
const Self = @This();
pub const Error = IteratorError;
pub fn next(self: *Self) Error!?Entry {
start_over: while (true) {
const HAIKU_MAX_COUNT = 10000;
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable;
self.first_iter = false;
}
const rc = posix.system._kern_read_dir(
self.dir.fd,
&self.buf,
self.buf.len,
HAIKU_MAX_COUNT,
);
if (rc == 0) return null;
if (rc < 0) {
switch (posix.errno(rc)) {
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
else => |err| return posix.unexpectedErrno(err),
}
}
self.index = 0;
self.end_index = @as(usize, @intCast(rc));
}
const haiku_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
const next_index = self.index + haiku_entry.reclen;
self.index = next_index;
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.name)), 0);
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.ino == 0)) {
continue :start_over;
}
var stat_info: posix.Stat = undefined;
const rc = posix.system._kern_read_stat(
self.dir.fd,
&haiku_entry.name,
false,
&stat_info,
0,
);
if (rc != 0) {
switch (posix.errno(rc)) {
.SUCCESS => {},
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
else => |err| return posix.unexpectedErrno(err),
}
}
const statmode = stat_info.mode & posix.S.IFMT;
const entry_kind: Entry.Kind = switch (statmode) {
posix.S.IFDIR => .directory,
posix.S.IFBLK => .block_device,
posix.S.IFCHR => .character_device,
posix.S.IFLNK => .sym_link,
posix.S.IFREG => .file,
posix.S.IFIFO => .named_pipe,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.linux => struct {
dir: Dir,
buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
index: usize,
end_index: usize,
first_iter: bool,
const Self = @This();
const linux = std.os.linux;
pub const Error = IteratorError;
pub fn next(self: *Self) Error!?Entry {
return self.nextLinux() catch |err| switch (err) {
error.DirNotFound => null,
else => |e| return e,
};
}
pub const ErrorLinux = error{DirNotFound} || IteratorError;
pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.first_iter) {
posix.lseek_SET(self.dir.fd, 0) catch unreachable;
self.first_iter = false;
}
const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
switch (linux.getErrno(rc)) {
.SUCCESS => {},
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.NOENT => return error.DirNotFound,
.INVAL => return error.Unexpected,
.ACCES => return error.AccessDenied,
else => |err| return posix.unexpectedErrno(err),
}
if (rc == 0) return null;
self.index = 0;
self.end_index = rc;
}
const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
const next_index = self.index + linux_entry.reclen;
self.index = next_index;
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
continue :start_over;
}
const entry_kind: Entry.Kind = switch (linux_entry.type) {
linux.DT.BLK => .block_device,
linux.DT.CHR => .character_device,
linux.DT.DIR => .directory,
linux.DT.FIFO => .named_pipe,
linux.DT.LNK => .sym_link,
linux.DT.REG => .file,
linux.DT.SOCK => .unix_domain_socket,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.windows => struct {
dir: Dir,
buf: [1024]u8 align(@alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION)),
index: usize,
end_index: usize,
first_iter: bool,
name_data: [fs.MAX_NAME_BYTES]u8,
const Self = @This();
pub const Error = IteratorError;
pub fn next(self: *Self) Error!?Entry {
while (true) {
const w = std.os.windows;
if (self.index >= self.end_index) {
var io: w.IO_STATUS_BLOCK = undefined;
const rc = w.ntdll.NtQueryDirectoryFile(
self.dir.fd,
null,
null,
null,
&io,
&self.buf,
self.buf.len,
.FileBothDirectoryInformation,
w.FALSE,
null,
if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
);
self.first_iter = false;
if (io.Information == 0) return null;
self.index = 0;
self.end_index = io.Information;
switch (rc) {
.SUCCESS => {},
.ACCESS_DENIED => return error.AccessDenied,
else => return w.unexpectedStatus(rc),
}
}
const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
if (dir_info.NextEntryOffset != 0) {
self.index += dir_info.NextEntryOffset;
} else {
self.index = self.buf.len;
}
const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
continue;
const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
const name_wtf8 = self.name_data[0..name_wtf8_len];
const kind: Entry.Kind = blk: {
const attrs = dir_info.FileAttributes;
if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
break :blk .file;
};
return Entry{
.name = name_wtf8,
.kind = kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.first_iter = true;
}
},
.wasi => struct {
dir: Dir,
buf: [1024]u8,
cookie: u64,
index: usize,
end_index: usize,
const Self = @This();
pub const Error = IteratorError;
pub fn next(self: *Self) Error!?Entry {
return self.nextWasi() catch |err| switch (err) {
error.DirNotFound => null,
else => |e| return e,
};
}
pub const ErrorWasi = error{DirNotFound} || IteratorError;
pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
const w = std.os.wasi;
start_over: while (true) {
if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
var bufused: usize = undefined;
switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
.SUCCESS => {},
.BADF => unreachable,
.FAULT => unreachable,
.NOTDIR => unreachable,
.INVAL => unreachable,
.NOENT => return error.DirNotFound,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8,
else => |err| return posix.unexpectedErrno(err),
}
if (bufused == 0) return null;
self.index = 0;
self.end_index = bufused;
}
const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
const entry_size = @sizeOf(w.dirent_t);
const name_index = self.index + entry_size;
if (name_index + entry.namlen > self.end_index) {
self.end_index = self.index;
continue :start_over;
}
const name = self.buf[name_index .. name_index + entry.namlen];
const next_index = name_index + entry.namlen;
self.index = next_index;
self.cookie = entry.next;
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
continue :start_over;
}
const entry_kind: Entry.Kind = switch (entry.type) {
.BLOCK_DEVICE => .block_device,
.CHARACTER_DEVICE => .character_device,
.DIRECTORY => .directory,
.SYMBOLIC_LINK => .sym_link,
.REGULAR_FILE => .file,
.SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
else => .unknown,
};
return Entry{
.name = name,
.kind = entry_kind,
};
}
}
pub fn reset(self: *Self) void {
self.index = 0;
self.end_index = 0;
self.cookie = std.os.wasi.DIRCOOKIE_START;
}
},
else => @compileError("unimplemented"),
};
pub fn iterate(self: Dir) Iterator {
return self.iterateImpl(true);
}
pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
return self.iterateImpl(false);
}
fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
switch (builtin.os.tag) {
.macos,
.ios,
.freebsd,
.netbsd,
.dragonfly,
.openbsd,
.solaris,
.illumos,
=> return Iterator{
.dir = self,
.seek = 0,
.index = 0,
.end_index = 0,
.buf = undefined,
.first_iter = first_iter_start_value,
},
.linux, .haiku => return Iterator{
.dir = self,
.index = 0,
.end_index = 0,
.buf = undefined,
.first_iter = first_iter_start_value,
},
.windows => return Iterator{
.dir = self,
.index = 0,
.end_index = 0,
.first_iter = first_iter_start_value,
.buf = undefined,
.name_data = undefined,
},
.wasi => return Iterator{
.dir = self,
.cookie = std.os.wasi.DIRCOOKIE_START,
.index = 0,
.end_index = 0,
.buf = undefined,
},
else => @compileError("unimplemented"),
}
}
pub const Walker = struct {
stack: std.ArrayList(StackItem),
name_buffer: std.ArrayList(u8),
pub const WalkerEntry = struct {
dir: Dir,
basename: []const u8,
path: []const u8,
kind: Dir.Entry.Kind,
};
const StackItem = struct {
iter: Dir.Iterator,
dirname_len: usize,
};
pub fn next(self: *Walker) !?WalkerEntry {
while (self.stack.items.len != 0) {
var top = &self.stack.items[self.stack.items.len - 1];
var containing = top;
var dirname_len = top.dirname_len;
if (top.iter.next() catch |err| {
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(fs.path.sep);
dirname_len += 1;
}
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) {
error.NameTooLong => unreachable,
else => |e| return e,
};
{
errdefer new_dir.close();
try self.stack.append(StackItem{
.iter = new_dir.iterateAssumeFirstIteration(),
.dirname_len = self.name_buffer.items.len,
});
top = &self.stack.items[self.stack.items.len - 1];
containing = &self.stack.items[self.stack.items.len - 2];
}
}
return WalkerEntry{
.dir = containing.iter.dir,
.basename = self.name_buffer.items[dirname_len..],
.path = self.name_buffer.items,
.kind = base.kind,
};
} else {
var item = self.stack.pop();
if (self.stack.items.len != 0) {
item.iter.dir.close();
}
}
}
return null;
}
pub fn deinit(self: *Walker) void {
if (self.stack.items.len > 1) {
for (self.stack.items[1..]) |*item| {
item.iter.dir.close();
}
}
self.stack.deinit();
self.name_buffer.deinit();
}
};
pub fn walk(self: Dir, allocator: Allocator) !Walker {
var name_buffer = std.ArrayList(u8).init(allocator);
errdefer name_buffer.deinit();
var stack = std.ArrayList(Walker.StackItem).init(allocator);
errdefer stack.deinit();
try stack.append(Walker.StackItem{
.iter = self.iterate(),
.dirname_len = 0,
});
return Walker{
.stack = stack,
.name_buffer = name_buffer,
};
}
pub const OpenError = error{
FileNotFound,
NotDir,
AccessDenied,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
InvalidUtf8,
InvalidWtf8,
BadPathName,
DeviceBusy,
NetworkNotFound,
} || posix.UnexpectedError;
pub fn close(self: *Dir) void {
posix.close(self.fd);
self.* = undefined;
}
pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
if (builtin.os.tag == .windows) {
const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.openFileW(path_w.span(), flags);
}
if (builtin.os.tag == .wasi) {
var base: std.os.wasi.rights_t = .{};
if (flags.isRead()) {
base.FD_READ = true;
base.FD_TELL = true;
base.FD_SEEK = true;
base.FD_FILESTAT_GET = true;
}
if (flags.isWrite()) {
base.FD_WRITE = true;
base.FD_TELL = true;
base.FD_SEEK = true;
base.FD_DATASYNC = true;
base.FD_FDSTAT_SET_FLAGS = true;
base.FD_SYNC = true;
base.FD_ALLOCATE = true;
base.FD_ADVISE = true;
base.FD_FILESTAT_SET_TIMES = true;
base.FD_FILESTAT_SET_SIZE = true;
}
const fd = try posix.openatWasi(self.fd, sub_path, .{}, .{}, .{}, base, .{});
return .{ .handle = fd };
}
const path_c = try posix.toPosixPath(sub_path);
return self.openFileZ(&path_c, flags);
}
pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
switch (builtin.os.tag) {
.windows => {
const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path);
return self.openFileW(path_w.span(), flags);
},
.wasi => {
return openFile(self, mem.sliceTo(sub_path, 0), flags);
},
else => {},
}
var os_flags: posix.O = .{
.ACCMODE = switch (flags.mode) {
.read_only => .RDONLY,
.write_only => .WRONLY,
.read_write => .RDWR,
},
};
if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
if (has_flock_open_flags) {
switch (flags.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
}
}
const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
errdefer posix.close(fd);
if (@hasDecl(posix.system, "LOCK")) {
if (!has_flock_open_flags and flags.lock != .none) {
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
try posix.flock(fd, switch (flags.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
});
}
}
if (has_flock_open_flags and flags.lock_nonblocking) {
var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
_ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
}
return .{ .handle = fd };
}
pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
const w = std.os.windows;
const file: File = .{
.handle = try w.OpenFile(sub_path_w, .{
.dir = self.fd,
.access_mask = w.SYNCHRONIZE |
(if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
(if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
.creation = w.FILE_OPEN,
}),
};
errdefer file.close();
var io: w.IO_STATUS_BLOCK = undefined;
const range_off: w.LARGE_INTEGER = 0;
const range_len: w.LARGE_INTEGER = 1;
const exclusive = switch (flags.lock) {
.none => return file,
.shared => false,
.exclusive => true,
};
try w.LockFile(
file.handle,
null,
null,
null,
&io,
&range_off,
&range_len,
null,
@intFromBool(flags.lock_nonblocking),
@intFromBool(exclusive),
);
return file;
}
pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
if (builtin.os.tag == .windows) {
const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.createFileW(path_w.span(), flags);
}
if (builtin.os.tag == .wasi) {
return .{
.handle = try posix.openatWasi(self.fd, sub_path, .{}, .{
.CREAT = true,
.TRUNC = flags.truncate,
.EXCL = flags.exclusive,
}, .{}, .{
.FD_READ = flags.read,
.FD_WRITE = true,
.FD_DATASYNC = true,
.FD_SEEK = true,
.FD_TELL = true,
.FD_FDSTAT_SET_FLAGS = true,
.FD_SYNC = true,
.FD_ALLOCATE = true,
.FD_ADVISE = true,
.FD_FILESTAT_SET_TIMES = true,
.FD_FILESTAT_SET_SIZE = true,
.FD_FILESTAT_GET = true,
}, .{}),
};
}
const path_c = try posix.toPosixPath(sub_path);
return self.createFileZ(&path_c, flags);
}
pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
switch (builtin.os.tag) {
.windows => {
const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
return self.createFileW(path_w.span(), flags);
},
.wasi => {
return createFile(self, mem.sliceTo(sub_path_c, 0), flags);
},
else => {},
}
var os_flags: std.os.O = .{
.ACCMODE = if (flags.read) .RDWR else .WRONLY,
.CREAT = true,
.TRUNC = flags.truncate,
.EXCL = flags.exclusive,
};
if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
if (has_flock_open_flags) switch (flags.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
};
const fd = try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
errdefer posix.close(fd);
if (!has_flock_open_flags and flags.lock != .none) {
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
try posix.flock(fd, switch (flags.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
});
}
if (has_flock_open_flags and flags.lock_nonblocking) {
var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
_ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
}
return .{ .handle = fd };
}
pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
const w = std.os.windows;
const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
const file: File = .{
.handle = try w.OpenFile(sub_path_w, .{
.dir = self.fd,
.access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
.creation = if (flags.exclusive)
@as(u32, w.FILE_CREATE)
else if (flags.truncate)
@as(u32, w.FILE_OVERWRITE_IF)
else
@as(u32, w.FILE_OPEN_IF),
}),
};
errdefer file.close();
var io: w.IO_STATUS_BLOCK = undefined;
const range_off: w.LARGE_INTEGER = 0;
const range_len: w.LARGE_INTEGER = 1;
const exclusive = switch (flags.lock) {
.none => return file,
.shared => false,
.exclusive => true,
};
try w.LockFile(
file.handle,
null,
null,
null,
&io,
&range_off,
&range_len,
null,
@intFromBool(flags.lock_nonblocking),
@intFromBool(exclusive),
);
return file;
}
pub fn makeDir(self: Dir, sub_path: []const u8) !void {
try posix.mkdirat(self.fd, sub_path, default_mode);
}
pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
try posix.mkdiratZ(self.fd, sub_path, default_mode);
}
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
try posix.mkdiratW(self.fd, sub_path, default_mode);
}
pub fn makePath(self: Dir, sub_path: []const u8) !void {
var it = try fs.path.componentIterator(sub_path);
var component = it.last() orelse return;
while (true) {
self.makeDir(component.path) catch |err| switch (err) {
error.PathAlreadyExists => {
check_dir: {
const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
error.IsDir => break :check_dir,
else => |e| return e,
};
if (fstat.kind != .directory) return error.NotDir;
}
},
error.FileNotFound => |e| {
component = it.previous() orelse return e;
continue;
},
else => |e| return e,
};
component = it.next() orelse return;
}
}
fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
const w = std.os.windows;
var it = try fs.path.componentIterator(sub_path);
var component = it.last() orelse fs.path.NativeComponentIterator.Component{
.name = "",
.path = sub_path,
};
while (true) {
const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
const is_last = it.peekNext() == null;
var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
.no_follow = no_follow,
.create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
}) catch |err| switch (err) {
error.FileNotFound => |e| {
component = it.previous() orelse return e;
continue;
},
else => |e| return e,
};
component = it.next() orelse return result;
result.close();
}
}
pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
return switch (builtin.os.tag) {
.windows => {
const w = std.os.windows;
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
w.SYNCHRONIZE | w.FILE_TRAVERSE |
(if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
},
else => {
return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
error.FileNotFound => {
try self.makePath(sub_path);
return self.openDir(sub_path, open_dir_options);
},
else => |e| return e,
};
},
};
}
pub const RealPathError = posix.RealPathError;
pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 {
if (builtin.os.tag == .wasi) {
@compileError("realpath is not available on WASI");
}
if (builtin.os.tag == .windows) {
const pathname_w = try std.os.windows.sliceToPrefixedFileW(self.fd, pathname);
return self.realpathW(pathname_w.span(), out_buffer);
}
const pathname_c = try posix.toPosixPath(pathname);
return self.realpathZ(&pathname_c, out_buffer);
}
pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try posix.windows.cStrToPrefixedFileW(self.fd, pathname);
return self.realpathW(pathname_w.span(), out_buffer);
}
const flags: posix.O = switch (builtin.os.tag) {
.linux => .{
.NONBLOCK = true,
.CLOEXEC = true,
.PATH = true,
},
else => .{
.NONBLOCK = true,
.CLOEXEC = true,
},
};
const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
error.FileLocksNotSupported => return error.Unexpected,
error.FileBusy => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.InvalidUtf8 => unreachable,
else => |e| return e,
};
defer posix.close(fd);
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
const out_path = try posix.getFdPath(fd, &buffer);
if (out_path.len > out_buffer.len) {
return error.NameTooLong;
}
const result = out_buffer[0..out_path.len];
@memcpy(result, out_path);
return result;
}
pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
const w = std.os.windows;
const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
const share_access = w.FILE_SHARE_READ;
const creation = w.FILE_OPEN;
const h_file = blk: {
const res = w.OpenFile(pathname, .{
.dir = self.fd,
.access_mask = access_mask,
.share_access = share_access,
.creation = creation,
.filter = .any,
}) catch |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
};
break :blk res;
};
defer w.CloseHandle(h_file);
var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
var big_out_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
const end_index = std.unicode.wtf16LeToWtf8(&big_out_buf, wide_slice);
if (end_index > out_buffer.len)
return error.NameTooLong;
const result = out_buffer[0..end_index];
@memcpy(result, big_out_buf[0..end_index]);
return result;
}
pub const RealPathAllocError = RealPathError || Allocator.Error;
pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 {
var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
}
pub fn setAsCwd(self: Dir) !void {
if (builtin.os.tag == .wasi) {
@compileError("changing cwd is not currently possible in WASI");
}
if (builtin.os.tag == .windows) {
var dir_path_buffer: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
const dir_path = try std.os.windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
if (builtin.link_libc) {
return posix.chdirW(dir_path);
}
return std.os.windows.SetCurrentDirectory(dir_path);
}
try posix.fchdir(self.fd);
}
pub const OpenDirOptions = struct {
access_sub_paths: bool = true,
iterate: bool = false,
no_follow: bool = false,
};
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
switch (builtin.os.tag) {
.windows => {
const sub_path_w = try posix.windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.openDirW(sub_path_w.span().ptr, args);
},
.wasi => {
var base: std.os.wasi.rights_t = .{
.FD_FILESTAT_GET = true,
.FD_FDSTAT_SET_FLAGS = true,
.FD_FILESTAT_SET_TIMES = true,
};
if (args.access_sub_paths) {
base.FD_READDIR = true;
base.PATH_CREATE_DIRECTORY = true;
base.PATH_CREATE_FILE = true;
base.PATH_LINK_SOURCE = true;
base.PATH_LINK_TARGET = true;
base.PATH_OPEN = true;
base.PATH_READLINK = true;
base.PATH_RENAME_SOURCE = true;
base.PATH_RENAME_TARGET = true;
base.PATH_FILESTAT_GET = true;
base.PATH_FILESTAT_SET_SIZE = true;
base.PATH_FILESTAT_SET_TIMES = true;
base.PATH_SYMLINK = true;
base.PATH_REMOVE_DIRECTORY = true;
base.PATH_UNLINK_FILE = true;
}
const result = posix.openatWasi(
self.fd,
sub_path,
.{ .SYMLINK_FOLLOW = !args.no_follow },
.{ .DIRECTORY = true },
.{},
base,
base,
);
const fd = result catch |err| switch (err) {
error.FileTooBig => unreachable,
error.IsDir => unreachable,
error.NoSpaceLeft => unreachable,
error.PathAlreadyExists => unreachable,
error.FileLocksNotSupported => unreachable,
error.WouldBlock => unreachable,
error.FileBusy => unreachable,
else => |e| return e,
};
return .{ .fd = fd };
},
else => {
const sub_path_c = try posix.toPosixPath(sub_path);
return self.openDirZ(&sub_path_c, args);
},
}
}
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
switch (builtin.os.tag) {
.windows => {
const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
return self.openDirW(sub_path_w.span().ptr, args);
},
.wasi => {
return openDir(self, mem.sliceTo(sub_path_c, 0), args);
},
else => {
var symlink_flags: posix.O = .{
.ACCMODE = .RDONLY,
.NOFOLLOW = args.no_follow,
.DIRECTORY = true,
.CLOEXEC = true,
};
if (@hasField(posix.O, "PATH") and !args.iterate)
symlink_flags.PATH = true;
return self.openDirFlagsZ(sub_path_c, symlink_flags);
},
}
}
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
const w = std.os.windows;
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;
const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
.no_follow = args.no_follow,
.create_disposition = w.FILE_OPEN,
});
return dir;
}
fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError!Dir {
assert(flags.DIRECTORY);
const fd = posix.openatZ(self.fd, sub_path_c, flags, 0) catch |err| switch (err) {
error.FileTooBig => unreachable,
error.IsDir => unreachable,
error.NoSpaceLeft => unreachable,
error.PathAlreadyExists => unreachable,
error.FileLocksNotSupported => unreachable,
error.WouldBlock => unreachable,
error.FileBusy => unreachable,
else => |e| return e,
};
return Dir{ .fd = fd };
}
const MakeOpenDirAccessMaskWOptions = struct {
no_follow: bool,
create_disposition: u32,
};
fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
const w = std.os.windows;
var result = Dir{
.fd = undefined,
};
const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
var nt_name = w.UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(sub_path_w),
};
var attr = w.OBJECT_ATTRIBUTES{
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
.Attributes = 0,
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
var io: w.IO_STATUS_BLOCK = undefined;
const rc = w.ntdll.NtCreateFile(
&result.fd,
access_mask,
&attr,
&io,
null,
w.FILE_ATTRIBUTE_NORMAL,
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
flags.create_disposition,
w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
null,
0,
);
switch (rc) {
.SUCCESS => return result,
.OBJECT_NAME_INVALID => return error.BadPathName,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NOT_A_DIRECTORY => return error.NotDir,
.ACCESS_DENIED => return error.AccessDenied,
.INVALID_PARAMETER => unreachable,
else => return w.unexpectedStatus(rc),
}
}
pub const DeleteFileError = posix.UnlinkError;
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.deleteFileW(sub_path_w.span());
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable,
else => |e| return e,
};
} else {
const sub_path_c = try posix.toPosixPath(sub_path);
return self.deleteFileZ(&sub_path_c);
}
}
pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable,
error.AccessDenied => |e| switch (builtin.os.tag) {
.macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => {
const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
return if (is_dir) error.IsDir else e;
},
else => return e,
},
else => |e| return e,
};
}
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable,
else => |e| return e,
};
}
pub const DeleteDirError = error{
DirNotEmpty,
FileNotFound,
AccessDenied,
FileBusy,
FileSystem,
SymLinkLoop,
NameTooLong,
NotDir,
SystemResources,
ReadOnlyFileSystem,
InvalidUtf8,
InvalidWtf8,
BadPathName,
NetworkNotFound,
Unexpected,
};
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.deleteDirW(sub_path_w.span());
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable,
else => |e| return e,
};
} else {
const sub_path_c = try posix.toPosixPath(sub_path);
return self.deleteDirZ(&sub_path_c);
}
}
pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable,
else => |e| return e,
};
}
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable,
else => |e| return e,
};
}
pub const RenameError = posix.RenameError;
pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
}
pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
}
pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
}
pub const SymLinkFlags = struct {
is_directory: bool = false,
};
pub fn symLink(
self: Dir,
target_path: []const u8,
sym_link_path: []const u8,
flags: SymLinkFlags,
) !void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return self.symLinkWasi(target_path, sym_link_path, flags);
}
if (builtin.os.tag == .windows) {
var target_path_w: std.os.windows.PathSpace = undefined;
target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
target_path_w.data[target_path_w.len] = 0;
mem.replaceScalar(
u16,
target_path_w.data[0..target_path_w.len],
mem.nativeToLittle(u16, '/'),
mem.nativeToLittle(u16, '\\'),
);
const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
}
const target_path_c = try posix.toPosixPath(target_path);
const sym_link_path_c = try posix.toPosixPath(sym_link_path);
return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
}
pub fn symLinkWasi(
self: Dir,
target_path: []const u8,
sym_link_path: []const u8,
_: SymLinkFlags,
) !void {
return posix.symlinkat(target_path, self.fd, sym_link_path);
}
pub fn symLinkZ(
self: Dir,
target_path_c: [*:0]const u8,
sym_link_path_c: [*:0]const u8,
flags: SymLinkFlags,
) !void {
if (builtin.os.tag == .windows) {
const target_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, target_path_c);
const sym_link_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
}
return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
}
pub fn symLinkW(
self: Dir,
target_path_w: [:0]const u16,
sym_link_path_w: []const u16,
flags: SymLinkFlags,
) !void {
return std.os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
}
pub const ReadLinkError = posix.ReadLinkError;
pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
return self.readLinkWasi(sub_path, buffer);
}
if (builtin.os.tag == .windows) {
const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
return self.readLinkW(sub_path_w.span(), buffer);
}
const sub_path_c = try posix.toPosixPath(sub_path);
return self.readLinkZ(&sub_path_c, buffer);
}
pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
return posix.readlinkat(self.fd, sub_path, buffer);
}
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
if (builtin.os.tag == .windows) {
const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
return self.readLinkW(sub_path_w.span(), buffer);
}
return posix.readlinkatZ(self.fd, sub_path_c, buffer);
}
pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
}
pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
var file = try self.openFile(file_path, .{});
defer file.close();
const end_index = try file.readAll(buffer);
return buffer[0..end_index];
}
pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
}
pub fn readFileAllocOptions(
self: Dir,
allocator: mem.Allocator,
file_path: []const u8,
max_bytes: usize,
size_hint: ?usize,
comptime alignment: u29,
comptime optional_sentinel: ?u8,
) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
var file = try self.openFile(file_path, .{});
defer file.close();
const stat_size = size_hint orelse std.math.cast(usize, try file.getEndPos()) orelse
return error.FileTooBig;
return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
}
pub const DeleteTreeError = error{
AccessDenied,
FileTooBig,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
ReadOnlyFileSystem,
FileSystem,
FileBusy,
DeviceBusy,
NotDir,
InvalidUtf8,
InvalidWtf8,
BadPathName,
NetworkNotFound,
} || posix.UnexpectedError;
pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
const StackItem = struct {
name: []const u8,
parent_dir: Dir,
iter: Dir.Iterator,
fn closeAll(items: []@This()) void {
for (items) |*item| item.iter.dir.close();
}
};
var stack_buffer: [16]StackItem = undefined;
var stack = std.ArrayListUnmanaged(StackItem).initBuffer(&stack_buffer);
defer StackItem.closeAll(stack.items);
stack.appendAssumeCapacity(.{
.name = sub_path,
.parent_dir = self,
.iter = initial_iterable_dir.iterateAssumeFirstIteration(),
});
process_stack: while (stack.items.len != 0) {
var top = &stack.items[stack.items.len - 1];
while (try top.iter.next()) |entry| {
var treat_as_dir = entry.kind == .directory;
handle_entry: while (true) {
if (treat_as_dir) {
if (stack.unusedCapacitySlice().len >= 1) {
var iterable_dir = top.iter.dir.openDir(entry.name, .{
.no_follow = true,
.iterate = true,
}) catch |err| switch (err) {
error.NotDir => {
treat_as_dir = false;
continue :handle_entry;
},
error.FileNotFound => {
break :handle_entry;
},
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
=> |e| return e,
};
stack.appendAssumeCapacity(.{
.name = entry.name,
.parent_dir = top.iter.dir,
.iter = iterable_dir.iterateAssumeFirstIteration(),
});
continue :process_stack;
} else {
try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
break :handle_entry;
}
} else {
if (top.iter.dir.deleteFile(entry.name)) {
break :handle_entry;
} else |err| switch (err) {
error.FileNotFound => break :handle_entry,
error.NotDir => unreachable,
error.IsDir => {
treat_as_dir = true;
continue :handle_entry;
},
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
error.ReadOnlyFileSystem,
error.FileSystem,
error.FileBusy,
error.BadPathName,
error.NetworkNotFound,
error.Unexpected,
=> |e| return e,
}
}
}
}
top.iter.dir.close();
const parent_dir = top.parent_dir;
const name = top.name;
stack.items.len -= 1;
var need_to_retry: bool = false;
parent_dir.deleteDir(name) catch |err| switch (err) {
error.FileNotFound => {},
error.DirNotEmpty => need_to_retry = true,
else => |e| return e,
};
if (need_to_retry) {
var iterable_dir = iterable_dir: {
var treat_as_dir = true;
handle_entry: while (true) {
if (treat_as_dir) {
break :iterable_dir parent_dir.openDir(name, .{
.no_follow = true,
.iterate = true,
}) catch |err| switch (err) {
error.NotDir => {
treat_as_dir = false;
continue :handle_entry;
},
error.FileNotFound => {
continue :process_stack;
},
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
=> |e| return e,
};
} else {
if (parent_dir.deleteFile(name)) {
continue :process_stack;
} else |err| switch (err) {
error.FileNotFound => continue :process_stack,
error.NotDir => unreachable,
error.IsDir => {
treat_as_dir = true;
continue :handle_entry;
},
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
error.ReadOnlyFileSystem,
error.FileSystem,
error.FileBusy,
error.BadPathName,
error.NetworkNotFound,
error.Unexpected,
=> |e| return e,
}
}
}
};
stack.appendAssumeCapacity(.{
.name = name,
.parent_dir = parent_dir,
.iter = iterable_dir.iterateAssumeFirstIteration(),
});
continue :process_stack;
}
}
}
pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
}
fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
start_over: while (true) {
var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
var cleanup_dir_parent: ?Dir = null;
defer if (cleanup_dir_parent) |*d| d.close();
var cleanup_dir = true;
defer if (cleanup_dir) dir.close();
var dir_name_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
var dir_name: []const u8 = sub_path;
scan_dir: while (true) {
var dir_it = dir.iterateAssumeFirstIteration();
dir_it: while (try dir_it.next()) |entry| {
var treat_as_dir = entry.kind == .directory;
handle_entry: while (true) {
if (treat_as_dir) {
const new_dir = dir.openDir(entry.name, .{
.no_follow = true,
.iterate = true,
}) catch |err| switch (err) {
error.NotDir => {
treat_as_dir = false;
continue :handle_entry;
},
error.FileNotFound => {
continue :dir_it;
},
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
=> |e| return e,
};
if (cleanup_dir_parent) |*d| d.close();
cleanup_dir_parent = dir;
dir = new_dir;
const result = dir_name_buf[0..entry.name.len];
@memcpy(result, entry.name);
dir_name = result;
continue :scan_dir;
} else {
if (dir.deleteFile(entry.name)) {
continue :dir_it;
} else |err| switch (err) {
error.FileNotFound => continue :dir_it,
error.NotDir => unreachable,
error.IsDir => {
treat_as_dir = true;
continue :handle_entry;
},
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
error.ReadOnlyFileSystem,
error.FileSystem,
error.FileBusy,
error.BadPathName,
error.NetworkNotFound,
error.Unexpected,
=> |e| return e,
}
}
}
}
dir.close();
cleanup_dir = false;
if (cleanup_dir_parent) |d| {
d.deleteDir(dir_name) catch |err| switch (err) {
error.FileNotFound, error.DirNotEmpty => continue :start_over,
else => |e| return e,
};
continue :start_over;
} else {
self.deleteDir(sub_path) catch |err| switch (err) {
error.FileNotFound => return,
error.DirNotEmpty => continue :start_over,
else => |e| return e,
};
return;
}
}
}
}
fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
return iterable_dir: {
var treat_as_dir = kind_hint == .directory;
handle_entry: while (true) {
if (treat_as_dir) {
break :iterable_dir self.openDir(sub_path, .{
.no_follow = true,
.iterate = true,
}) catch |err| switch (err) {
error.NotDir => {
treat_as_dir = false;
continue :handle_entry;
},
error.FileNotFound => {
return null;
},
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName,
error.DeviceBusy,
error.NetworkNotFound,
=> |e| return e,
};
} else {
if (self.deleteFile(sub_path)) {
return null;
} else |err| switch (err) {
error.FileNotFound => return null,
error.IsDir => {
treat_as_dir = true;
continue :handle_entry;
},
error.AccessDenied,
error.InvalidUtf8,
error.InvalidWtf8,
error.SymLinkLoop,
error.NameTooLong,
error.SystemResources,
error.ReadOnlyFileSystem,
error.NotDir,
error.FileSystem,
error.FileBusy,
error.BadPathName,
error.NetworkNotFound,
error.Unexpected,
=> |e| return e,
}
}
}
};
}
pub const WriteFileError = File.WriteError || File.OpenError;
pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
return writeFile2(self, .{
.sub_path = sub_path,
.data = data,
.flags = .{},
});
}
pub const WriteFileOptions = struct {
sub_path: []const u8,
data: []const u8,
flags: File.CreateFlags = .{},
};
pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
var file = try self.createFile(options.sub_path, options.flags);
defer file.close();
try file.writeAll(options.data);
}
pub const AccessError = posix.AccessError;
pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = std.os.windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
error.AccessDenied => return error.PermissionDenied,
else => |e| return e,
};
return self.accessW(sub_path_w.span().ptr, flags);
}
const path_c = try posix.toPosixPath(sub_path);
return self.accessZ(&path_c, flags);
}
pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = std.os.windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
error.AccessDenied => return error.PermissionDenied,
else => |e| return e,
};
return self.accessW(sub_path_w.span().ptr, flags);
}
const os_mode = switch (flags.mode) {
.read_only => @as(u32, posix.F_OK),
.write_only => @as(u32, posix.W_OK),
.read_write => @as(u32, posix.R_OK | posix.W_OK),
};
const result = posix.faccessatZ(self.fd, sub_path, os_mode, 0);
return result;
}
pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
_ = flags;
return posix.faccessatW(self.fd, sub_path_w, 0, 0);
}
pub const CopyFileOptions = struct {
override_mode: ?File.Mode = null,
};
pub const PrevStatus = enum {
stale,
fresh,
};
pub fn updateFile(
source_dir: Dir,
source_path: []const u8,
dest_dir: Dir,
dest_path: []const u8,
options: CopyFileOptions,
) !PrevStatus {
var src_file = try source_dir.openFile(source_path, .{});
defer src_file.close();
const src_stat = try src_file.stat();
const actual_mode = options.override_mode orelse src_stat.mode;
check_dest_stat: {
const dest_stat = blk: {
var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
error.FileNotFound => break :check_dest_stat,
else => |e| return e,
};
defer dest_file.close();
break :blk try dest_file.stat();
};
if (src_stat.size == dest_stat.size and
src_stat.mtime == dest_stat.mtime and
actual_mode == dest_stat.mode)
{
return PrevStatus.fresh;
}
}
if (fs.path.dirname(dest_path)) |dirname| {
try dest_dir.makePath(dirname);
}
var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
defer atomic_file.deinit();
try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
try atomic_file.finish();
return PrevStatus.stale;
}
pub const CopyFileError = File.OpenError || File.StatError ||
AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError;
pub fn copyFile(
source_dir: Dir,
source_path: []const u8,
dest_dir: Dir,
dest_path: []const u8,
options: CopyFileOptions,
) CopyFileError!void {
var in_file = try source_dir.openFile(source_path, .{});
defer in_file.close();
var size: ?u64 = null;
const mode = options.override_mode orelse blk: {
const st = try in_file.stat();
size = st.size;
break :blk st.mode;
};
var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
defer atomic_file.deinit();
try copy_file(in_file.handle, atomic_file.file.handle, size);
try atomic_file.finish();
}
const CopyFileRawError = error{SystemResources} || posix.CopyFileRangeError || posix.SendFileError;
fn copy_file(fd_in: posix.fd_t, fd_out: posix.fd_t, maybe_size: ?u64) CopyFileRawError!void {
if (comptime builtin.target.isDarwin()) {
const rc = posix.system.fcopyfile(fd_in, fd_out, null, posix.system.COPYFILE_DATA);
switch (posix.errno(rc)) {
.SUCCESS => return,
.INVAL => unreachable,
.NOMEM => return error.SystemResources,
.OPNOTSUPP => {},
else => |err| return posix.unexpectedErrno(err),
}
}
if (builtin.os.tag == .linux) {
var offset: u64 = 0;
cfr_loop: while (true) {
const amt = try posix.copy_file_range(fd_in, offset, fd_out, offset, std.math.maxInt(u32), 0);
if (maybe_size) |s| {
if (s == amt) break :cfr_loop;
}
if (amt == 0) break :cfr_loop;
offset += amt;
}
return;
}
const empty_iovec = [0]posix.iovec_const{};
var offset: u64 = 0;
sendfile_loop: while (true) {
const amt = try posix.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
if (maybe_size) |s| {
if (s == amt) break :sendfile_loop;
}
if (amt == 0) break :sendfile_loop;
offset += amt;
}
}
pub const AtomicFileOptions = struct {
mode: File.Mode = File.default_mode,
make_path: bool = false,
};
pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
if (fs.path.dirname(dest_path)) |dirname| {
const dir = if (options.make_path)
try self.makeOpenPath(dirname, .{})
else
try self.openDir(dirname, .{});
return AtomicFile.init(fs.path.basename(dest_path), options.mode, dir, true);
} else {
return AtomicFile.init(dest_path, options.mode, self, false);
}
}
pub const Stat = File.Stat;
pub const StatError = File.StatError;
pub fn stat(self: Dir) StatError!Stat {
const file: File = .{ .handle = self.fd };
return file.stat();
}
pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
if (builtin.os.tag == .windows) {
var file = try self.openFile(sub_path, .{});
defer file.close();
return file.stat();
}
if (builtin.os.tag == .wasi and !builtin.link_libc) {
const st = try posix.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
return Stat.fromWasi(st);
}
const st = try posix.fstatat(self.fd, sub_path, 0);
return Stat.fromSystem(st);
}
pub const ChmodError = File.ChmodError;
pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
const file: File = .{ .handle = self.fd };
try file.chmod(new_mode);
}
pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
const file: File = .{ .handle = self.fd };
try file.chown(owner, group);
}
pub const ChownError = File.ChownError;
const Permissions = File.Permissions;
pub const SetPermissionsError = File.SetPermissionsError;
pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
const file: File = .{ .handle = self.fd };
try file.setPermissions(permissions);
}
const Metadata = File.Metadata;
pub const MetadataError = File.MetadataError;
pub fn metadata(self: Dir) MetadataError!Metadata {
const file: File = .{ .handle = self.fd };
return try file.metadata();
}
const Dir = @This();
const builtin = @import("builtin");
const std = @import("../std.zig");
const File = std.fs.File;
const AtomicFile = std.fs.AtomicFile;
const posix = std.os;
const mem = std.mem;
const fs = std.fs;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;