handle: Handle,
pub const Handle = posix.fd_t;
pub const Mode = posix.mode_t;
pub const INode = posix.ino_t;
pub const Uid = posix.uid_t;
pub const Gid = posix.gid_t;
pub const Kind = enum {
block_device,
character_device,
directory,
named_pipe,
sym_link,
file,
unix_domain_socket,
whiteout,
door,
event_port,
unknown,
};
pub const default_mode = switch (builtin.os.tag) {
.windows => 0,
.wasi => 0,
else => 0o666,
};
pub const OpenError = error{
SharingViolation,
PathAlreadyExists,
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
InvalidUtf8,
InvalidWtf8,
BadPathName,
Unexpected,
NetworkNotFound,
AntivirusInterference,
} || posix.OpenError || posix.FlockError;
pub const OpenMode = enum {
read_only,
write_only,
read_write,
};
pub const Lock = enum {
none,
shared,
exclusive,
};
pub const OpenFlags = struct {
mode: OpenMode = .read_only,
lock: Lock = .none,
lock_nonblocking: bool = false,
allow_ctty: bool = false,
pub fn isRead(self: OpenFlags) bool {
return self.mode != .write_only;
}
pub fn isWrite(self: OpenFlags) bool {
return self.mode != .read_only;
}
};
pub const CreateFlags = struct {
read: bool = false,
truncate: bool = true,
exclusive: bool = false,
lock: Lock = .none,
lock_nonblocking: bool = false,
mode: Mode = default_mode,
};
pub fn close(self: File) void {
if (is_windows) {
windows.CloseHandle(self.handle);
} else {
posix.close(self.handle);
}
}
pub const SyncError = posix.SyncError;
pub fn sync(self: File) SyncError!void {
return posix.fsync(self.handle);
}
pub fn isTty(self: File) bool {
return posix.isatty(self.handle);
}
pub fn supportsAnsiEscapeCodes(self: File) bool {
if (builtin.os.tag == .windows) {
var console_mode: windows.DWORD = 0;
if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
}
return posix.isCygwinPty(self.handle);
}
if (builtin.os.tag == .wasi) {
return false;
}
if (self.isTty()) {
if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) {
if (posix.getenvZ("TERM")) |term| {
if (std.mem.eql(u8, term, "dumb"))
return false;
}
}
return true;
}
return false;
}
pub const SetEndPosError = posix.TruncateError;
pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
try posix.ftruncate(self.handle, length);
}
pub const SeekError = posix.SeekError;
pub fn seekBy(self: File, offset: i64) SeekError!void {
return posix.lseek_CUR(self.handle, offset);
}
pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
return posix.lseek_END(self.handle, offset);
}
pub fn seekTo(self: File, offset: u64) SeekError!void {
return posix.lseek_SET(self.handle, offset);
}
pub const GetSeekPosError = posix.SeekError || posix.FStatError;
pub fn getPos(self: File) GetSeekPosError!u64 {
return posix.lseek_CUR_get(self.handle);
}
pub fn getEndPos(self: File) GetSeekPosError!u64 {
if (builtin.os.tag == .windows) {
return windows.GetFileSizeEx(self.handle);
}
return (try self.stat()).size;
}
pub const ModeError = posix.FStatError;
pub fn mode(self: File) ModeError!Mode {
if (builtin.os.tag == .windows) {
return 0;
}
return (try self.stat()).mode;
}
pub const Stat = struct {
inode: INode,
size: u64,
mode: Mode,
kind: Kind,
atime: i128,
mtime: i128,
ctime: i128,
pub fn fromSystem(st: posix.Stat) Stat {
const atime = st.atime();
const mtime = st.mtime();
const ctime = st.ctime();
return .{
.inode = st.ino,
.size = @bitCast(st.size),
.mode = st.mode,
.kind = k: {
const m = st.mode & posix.S.IFMT;
switch (m) {
posix.S.IFBLK => break :k .block_device,
posix.S.IFCHR => break :k .character_device,
posix.S.IFDIR => break :k .directory,
posix.S.IFIFO => break :k .named_pipe,
posix.S.IFLNK => break :k .sym_link,
posix.S.IFREG => break :k .file,
posix.S.IFSOCK => break :k .unix_domain_socket,
else => {},
}
if (builtin.os.tag.isSolarish()) switch (m) {
posix.S.IFDOOR => break :k .door,
posix.S.IFPORT => break :k .event_port,
else => {},
};
break :k .unknown;
},
.atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
.mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
.ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
};
}
pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
return .{
.inode = st.ino,
.size = @bitCast(st.size),
.mode = 0,
.kind = switch (st.filetype) {
.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,
},
.atime = st.atim,
.mtime = st.mtim,
.ctime = st.ctim,
};
}
};
pub const StatError = posix.FStatError;
pub fn stat(self: File) StatError!Stat {
if (builtin.os.tag == .windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info: windows.FILE_ALL_INFORMATION = undefined;
const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
switch (rc) {
.SUCCESS => {},
.BUFFER_OVERFLOW => {},
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
return .{
.inode = info.InternalInformation.IndexNumber,
.size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
.mode = 0,
.kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: {
var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
switch (tag_rc) {
.SUCCESS => {},
.INFO_LENGTH_MISMATCH => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
if (tag_info.ReparseTag & windows.reparse_tag_name_surrogate_bit != 0) {
break :reparse_point .sym_link;
}
break :reparse_point .unknown;
} else if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0)
.directory
else
.file,
.atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
.mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
.ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
};
}
if (builtin.os.tag == .wasi and !builtin.link_libc) {
const st = try posix.fstat_wasi(self.handle);
return Stat.fromWasi(st);
}
const st = try posix.fstat(self.handle);
return Stat.fromSystem(st);
}
pub const ChmodError = posix.FChmodError;
pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
try posix.fchmod(self.handle, new_mode);
}
pub const ChownError = posix.FChownError;
pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
try posix.fchown(self.handle, owner, group);
}
pub const Permissions = struct {
inner: switch (builtin.os.tag) {
.windows => PermissionsWindows,
else => PermissionsUnix,
},
const Self = @This();
pub fn readOnly(self: Self) bool {
return self.inner.readOnly();
}
pub fn setReadOnly(self: *Self, read_only: bool) void {
self.inner.setReadOnly(read_only);
}
};
pub const PermissionsWindows = struct {
attributes: windows.DWORD,
const Self = @This();
pub fn readOnly(self: Self) bool {
return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
}
pub fn setReadOnly(self: *Self, read_only: bool) void {
if (read_only) {
self.attributes |= windows.FILE_ATTRIBUTE_READONLY;
} else {
self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY);
}
}
};
pub const PermissionsUnix = struct {
mode: Mode,
const Self = @This();
pub fn readOnly(self: Self) bool {
return self.mode & 0o222 == 0;
}
pub fn setReadOnly(self: *Self, read_only: bool) void {
if (read_only) {
self.mode &= ~@as(Mode, 0o222);
} else {
self.mode |= @as(Mode, 0o222);
}
}
pub const Class = enum(u2) {
user = 2,
group = 1,
other = 0,
};
pub const Permission = enum(u3) {
read = 0o4,
write = 0o2,
execute = 0o1,
};
pub fn unixHas(self: Self, class: Class, permission: Permission) bool {
const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3;
return self.mode & mask != 0;
}
pub fn unixSet(self: *Self, class: Class, permissions: struct {
read: ?bool = null,
write: ?bool = null,
execute: ?bool = null,
}) void {
const shift = @as(u3, @intFromEnum(class)) * 3;
if (permissions.read) |r| {
if (r) {
self.mode |= @as(Mode, 0o4) << shift;
} else {
self.mode &= ~(@as(Mode, 0o4) << shift);
}
}
if (permissions.write) |w| {
if (w) {
self.mode |= @as(Mode, 0o2) << shift;
} else {
self.mode &= ~(@as(Mode, 0o2) << shift);
}
}
if (permissions.execute) |x| {
if (x) {
self.mode |= @as(Mode, 0o1) << shift;
} else {
self.mode &= ~(@as(Mode, 0o1) << shift);
}
}
}
pub fn unixNew(new_mode: Mode) Self {
return Self{
.mode = new_mode,
};
}
};
pub const SetPermissionsError = ChmodError;
pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void {
switch (builtin.os.tag) {
.windows => {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info = windows.FILE_BASIC_INFORMATION{
.CreationTime = 0,
.LastAccessTime = 0,
.LastWriteTime = 0,
.ChangeTime = 0,
.FileAttributes = permissions.inner.attributes,
};
const rc = windows.ntdll.NtSetInformationFile(
self.handle,
&io_status_block,
&info,
@sizeOf(windows.FILE_BASIC_INFORMATION),
.FileBasicInformation,
);
switch (rc) {
.SUCCESS => return,
.INVALID_HANDLE => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
},
.wasi => @compileError("Unsupported OS"),
else => {
try self.chmod(permissions.inner.mode);
},
}
}
pub const Metadata = struct {
inner: switch (builtin.os.tag) {
.windows => MetadataWindows,
.linux => MetadataLinux,
.wasi => MetadataWasi,
else => MetadataUnix,
},
const Self = @This();
pub fn size(self: Self) u64 {
return self.inner.size();
}
pub fn permissions(self: Self) Permissions {
return self.inner.permissions();
}
pub fn kind(self: Self) Kind {
return self.inner.kind();
}
pub fn accessed(self: Self) i128 {
return self.inner.accessed();
}
pub fn modified(self: Self) i128 {
return self.inner.modified();
}
pub fn created(self: Self) ?i128 {
return self.inner.created();
}
};
pub const MetadataUnix = struct {
stat: posix.Stat,
const Self = @This();
pub fn size(self: Self) u64 {
return @intCast(self.stat.size);
}
pub fn permissions(self: Self) Permissions {
return .{ .inner = .{ .mode = self.stat.mode } };
}
pub fn kind(self: Self) Kind {
if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) {
.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,
};
const m = self.stat.mode & posix.S.IFMT;
switch (m) {
posix.S.IFBLK => return .block_device,
posix.S.IFCHR => return .character_device,
posix.S.IFDIR => return .directory,
posix.S.IFIFO => return .named_pipe,
posix.S.IFLNK => return .sym_link,
posix.S.IFREG => return .file,
posix.S.IFSOCK => return .unix_domain_socket,
else => {},
}
if (builtin.os.tag.isSolarish()) switch (m) {
posix.S.IFDOOR => return .door,
posix.S.IFPORT => return .event_port,
else => {},
};
return .unknown;
}
pub fn accessed(self: Self) i128 {
const atime = self.stat.atime();
return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec;
}
pub fn modified(self: Self) i128 {
const mtime = self.stat.mtime();
return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec;
}
pub fn created(self: Self) ?i128 {
if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null;
const birthtime = self.stat.birthtime();
switch (builtin.os.tag) {
.freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null,
.netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null,
.macos => {},
else => @compileError("Creation time detection not implemented for OS"),
}
return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec;
}
};
pub const MetadataLinux = struct {
statx: std.os.linux.Statx,
const Self = @This();
pub fn size(self: Self) u64 {
return self.statx.size;
}
pub fn permissions(self: Self) Permissions {
return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
}
pub fn kind(self: Self) Kind {
const m = self.statx.mode & posix.S.IFMT;
switch (m) {
posix.S.IFBLK => return .block_device,
posix.S.IFCHR => return .character_device,
posix.S.IFDIR => return .directory,
posix.S.IFIFO => return .named_pipe,
posix.S.IFLNK => return .sym_link,
posix.S.IFREG => return .file,
posix.S.IFSOCK => return .unix_domain_socket,
else => {},
}
return .unknown;
}
pub fn accessed(self: Self) i128 {
return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec;
}
pub fn modified(self: Self) i128 {
return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec;
}
pub fn created(self: Self) ?i128 {
if (self.statx.mask & std.os.linux.STATX_BTIME == 0) return null;
return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec;
}
};
pub const MetadataWasi = struct {
stat: std.os.wasi.filestat_t,
pub fn size(self: @This()) u64 {
return self.stat.size;
}
pub fn permissions(self: @This()) Permissions {
return .{ .inner = .{ .mode = self.stat.mode } };
}
pub fn kind(self: @This()) Kind {
return switch (self.stat.filetype) {
.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,
};
}
pub fn accessed(self: @This()) i128 {
return self.stat.atim;
}
pub fn modified(self: @This()) i128 {
return self.stat.mtim;
}
pub fn created(self: @This()) ?i128 {
return self.stat.ctim;
}
};
pub const MetadataWindows = struct {
attributes: windows.DWORD,
reparse_tag: windows.DWORD,
_size: u64,
access_time: i128,
modified_time: i128,
creation_time: i128,
const Self = @This();
pub fn size(self: Self) u64 {
return self._size;
}
pub fn permissions(self: Self) Permissions {
return .{ .inner = .{ .attributes = self.attributes } };
}
pub fn kind(self: Self) Kind {
if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
if (self.reparse_tag & windows.reparse_tag_name_surrogate_bit != 0) {
return .sym_link;
}
} else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
return .directory;
} else {
return .file;
}
return .unknown;
}
pub fn accessed(self: Self) i128 {
return self.access_time;
}
pub fn modified(self: Self) i128 {
return self.modified_time;
}
pub fn created(self: Self) ?i128 {
return self.creation_time;
}
};
pub const MetadataError = posix.FStatError;
pub fn metadata(self: File) MetadataError!Metadata {
return .{
.inner = switch (builtin.os.tag) {
.windows => blk: {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info: windows.FILE_ALL_INFORMATION = undefined;
const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
switch (rc) {
.SUCCESS => {},
.BUFFER_OVERFLOW => {},
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
const reparse_tag: windows.DWORD = reparse_blk: {
if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
switch (tag_rc) {
.SUCCESS => {},
.INFO_LENGTH_MISMATCH => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
break :reparse_blk tag_info.ReparseTag;
}
break :reparse_blk 0;
};
break :blk .{
.attributes = info.BasicInformation.FileAttributes,
.reparse_tag = reparse_tag,
._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
.access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
.modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
.creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
};
},
.linux => blk: {
const l = std.os.linux;
var stx = std.mem.zeroes(l.Statx);
const rcx = l.statx(self.handle, "\x00", l.AT.EMPTY_PATH, l.STATX_TYPE |
l.STATX_MODE | l.STATX_ATIME | l.STATX_MTIME | l.STATX_BTIME, &stx);
switch (posix.errno(rcx)) {
.SUCCESS => {},
.NOSYS => {
const st = try posix.fstat(self.handle);
stx.mode = @as(u16, @intCast(st.mode));
stx.atime = std.mem.zeroes(l.statx_timestamp);
stx.atime.tv_sec = st.atim.tv_sec;
stx.atime.tv_nsec = @as(u32, @intCast(st.atim.tv_nsec));
stx.mtime = std.mem.zeroes(l.statx_timestamp);
stx.mtime.tv_sec = st.mtim.tv_sec;
stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec));
stx.mask = l.STATX_BASIC_STATS | l.STATX_MTIME;
},
.BADF => unreachable,
.FAULT => unreachable,
.NOMEM => return error.SystemResources,
else => |err| return posix.unexpectedErrno(err),
}
break :blk .{
.statx = stx,
};
},
.wasi => .{ .stat = try posix.fstat_wasi(self.handle) },
else => .{ .stat = try posix.fstat(self.handle) },
},
};
}
pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
pub fn updateTimes(
self: File,
atime: i128,
mtime: i128,
) UpdateTimesError!void {
if (builtin.os.tag == .windows) {
const atime_ft = windows.nanoSecondsToFileTime(atime);
const mtime_ft = windows.nanoSecondsToFileTime(mtime);
return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
}
const times = [2]posix.timespec{
posix.timespec{
.tv_sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
.tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
},
posix.timespec{
.tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
.tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
},
};
try posix.futimens(self.handle, ×);
}
pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
}
pub fn readToEndAllocOptions(
self: File,
allocator: Allocator,
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) {
const size = size_hint orelse 0;
const initial_cap = (if (size > 0) size else 1024) + @intFromBool(optional_sentinel != null);
var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
defer array_list.deinit();
self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
error.StreamTooLong => return error.FileTooBig,
else => |e| return e,
};
if (optional_sentinel) |sentinel| {
return try array_list.toOwnedSliceSentinel(sentinel);
} else {
return try array_list.toOwnedSlice();
}
}
pub const ReadError = posix.ReadError;
pub const PReadError = posix.PReadError;
pub fn read(self: File, buffer: []u8) ReadError!usize {
if (is_windows) {
return windows.ReadFile(self.handle, buffer, null);
}
return posix.read(self.handle, buffer);
}
pub fn readAll(self: File, buffer: []u8) ReadError!usize {
var index: usize = 0;
while (index != buffer.len) {
const amt = try self.read(buffer[index..]);
if (amt == 0) break;
index += amt;
}
return index;
}
pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
if (is_windows) {
return windows.ReadFile(self.handle, buffer, offset);
}
return posix.pread(self.handle, buffer, offset);
}
pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
var index: usize = 0;
while (index != buffer.len) {
const amt = try self.pread(buffer[index..], offset + index);
if (amt == 0) break;
index += amt;
}
return index;
}
pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
if (is_windows) {
if (iovecs.len == 0) return @as(usize, 0);
const first = iovecs[0];
return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null);
}
return posix.readv(self.handle, iovecs);
}
pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
if (iovecs.len == 0) return 0;
var garbage: [1]u8 = undefined;
for (iovecs) |*v| {
if (v.iov_len == 0) v.iov_base = &garbage;
}
var i: usize = 0;
var off: usize = 0;
while (true) {
var amt = try self.readv(iovecs[i..]);
var eof = amt == 0;
off += amt;
while (amt >= iovecs[i].iov_len) {
amt -= iovecs[i].iov_len;
i += 1;
if (i >= iovecs.len) return off;
eof = false;
}
if (eof) return off;
iovecs[i].iov_base += amt;
iovecs[i].iov_len -= amt;
}
}
pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize {
if (is_windows) {
if (iovecs.len == 0) return @as(usize, 0);
const first = iovecs[0];
return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset);
}
return posix.preadv(self.handle, iovecs, offset);
}
pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
if (iovecs.len == 0) return 0;
var i: usize = 0;
var off: usize = 0;
while (true) {
var amt = try self.preadv(iovecs[i..], offset + off);
var eof = amt == 0;
off += amt;
while (amt >= iovecs[i].iov_len) {
amt -= iovecs[i].iov_len;
i += 1;
if (i >= iovecs.len) return off;
eof = false;
}
if (eof) return off;
iovecs[i].iov_base += amt;
iovecs[i].iov_len -= amt;
}
}
pub const WriteError = posix.WriteError;
pub const PWriteError = posix.PWriteError;
pub fn write(self: File, bytes: []const u8) WriteError!usize {
if (is_windows) {
return windows.WriteFile(self.handle, bytes, null);
}
return posix.write(self.handle, bytes);
}
pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
var index: usize = 0;
while (index < bytes.len) {
index += try self.write(bytes[index..]);
}
}
pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
if (is_windows) {
return windows.WriteFile(self.handle, bytes, offset);
}
return posix.pwrite(self.handle, bytes, offset);
}
pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
var index: usize = 0;
while (index < bytes.len) {
index += try self.pwrite(bytes[index..], offset + index);
}
}
pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
if (is_windows) {
if (iovecs.len == 0) return @as(usize, 0);
const first = iovecs[0];
return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null);
}
return posix.writev(self.handle, iovecs);
}
pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
if (iovecs.len == 0) return;
var garbage: [1]u8 = undefined;
for (iovecs) |*v| {
if (v.iov_len == 0) v.iov_base = &garbage;
}
var i: usize = 0;
while (true) {
var amt = try self.writev(iovecs[i..]);
while (amt >= iovecs[i].iov_len) {
amt -= iovecs[i].iov_len;
i += 1;
if (i >= iovecs.len) return;
}
iovecs[i].iov_base += amt;
iovecs[i].iov_len -= amt;
}
}
pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize {
if (is_windows) {
if (iovecs.len == 0) return @as(usize, 0);
const first = iovecs[0];
return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset);
}
return posix.pwritev(self.handle, iovecs, offset);
}
pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
if (iovecs.len == 0) return;
var i: usize = 0;
var off: u64 = 0;
while (true) {
var amt = try self.pwritev(iovecs[i..], offset + off);
off += amt;
while (amt >= iovecs[i].iov_len) {
amt -= iovecs[i].iov_len;
i += 1;
if (i >= iovecs.len) return;
}
iovecs[i].iov_base += amt;
iovecs[i].iov_len -= amt;
}
}
pub const CopyRangeError = posix.CopyFileRangeError;
pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
const adjusted_len = math.cast(usize, len) orelse maxInt(usize);
const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
return result;
}
pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
var total_bytes_copied: u64 = 0;
var in_off = in_offset;
var out_off = out_offset;
while (total_bytes_copied < len) {
const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
if (amt_copied == 0) return total_bytes_copied;
total_bytes_copied += amt_copied;
in_off += amt_copied;
out_off += amt_copied;
}
return total_bytes_copied;
}
pub const WriteFileOptions = struct {
in_offset: u64 = 0,
in_len: ?u64 = null,
headers_and_trailers: []posix.iovec_const = &[0]posix.iovec_const{},
header_count: usize = 0,
};
pub const WriteFileError = ReadError || error{EndOfStream} || WriteError;
pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
error.Unseekable,
error.FastOpenAlreadyInProgress,
error.MessageTooBig,
error.FileDescriptorNotASocket,
error.NetworkUnreachable,
error.NetworkSubsystemFailed,
=> return self.writeFileAllUnseekable(in_file, args),
else => |e| return e,
};
}
pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
const headers = args.headers_and_trailers[0..args.header_count];
const trailers = args.headers_and_trailers[args.header_count..];
try self.writevAll(headers);
try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });
var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
if (args.in_len) |len| {
var stream = std.io.limitedReader(in_file.reader(), len);
try fifo.pump(stream.reader(), self.writer());
} else {
try fifo.pump(in_file.reader(), self.writer());
}
try self.writevAll(trailers);
}
fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix.SendFileError!void {
const count = blk: {
if (args.in_len) |l| {
if (l == 0) {
return self.writevAll(args.headers_and_trailers);
} else {
break :blk l;
}
} else {
break :blk 0;
}
};
const headers = args.headers_and_trailers[0..args.header_count];
const trailers = args.headers_and_trailers[args.header_count..];
const zero_iovec = &[0]posix.iovec_const{};
const trls = if (count == 0) zero_iovec else trailers;
const offset = args.in_offset;
const out_fd = self.handle;
const in_fd = in_file.handle;
const flags = 0;
var amt: usize = 0;
hdrs: {
var i: usize = 0;
while (i < headers.len) {
amt = try posix.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
while (amt >= headers[i].iov_len) {
amt -= headers[i].iov_len;
i += 1;
if (i >= headers.len) break :hdrs;
}
headers[i].iov_base += amt;
headers[i].iov_len -= amt;
}
}
if (count == 0) {
var off: u64 = amt;
while (true) {
amt = try posix.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
if (amt == 0) break;
off += amt;
}
} else {
var off: u64 = amt;
while (off < count) {
amt = try posix.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
off += amt;
}
amt = @as(usize, @intCast(off - count));
}
var i: usize = 0;
while (i < trailers.len) {
while (amt >= trailers[i].iov_len) {
amt -= trailers[i].iov_len;
i += 1;
if (i >= trailers.len) return;
}
trailers[i].iov_base += amt;
trailers[i].iov_len -= amt;
amt = try posix.writev(self.handle, trailers[i..]);
}
}
pub const Reader = io.Reader(File, ReadError, read);
pub fn reader(file: File) Reader {
return .{ .context = file };
}
pub const Writer = io.Writer(File, WriteError, write);
pub fn writer(file: File) Writer {
return .{ .context = file };
}
pub const SeekableStream = io.SeekableStream(
File,
SeekError,
GetSeekPosError,
seekTo,
seekBy,
getPos,
getEndPos,
);
pub fn seekableStream(file: File) SeekableStream {
return .{ .context = file };
}
const range_off: windows.LARGE_INTEGER = 0;
const range_len: windows.LARGE_INTEGER = 1;
pub const LockError = error{
SystemResources,
FileLocksNotSupported,
} || posix.UnexpectedError;
pub fn lock(file: File, l: Lock) LockError!void {
if (is_windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
const exclusive = switch (l) {
.none => return,
.shared => false,
.exclusive => true,
};
return windows.LockFile(
file.handle,
null,
null,
null,
&io_status_block,
&range_off,
&range_len,
null,
windows.FALSE,
@intFromBool(exclusive),
) catch |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
};
} else {
return posix.flock(file.handle, switch (l) {
.none => posix.LOCK.UN,
.shared => posix.LOCK.SH,
.exclusive => posix.LOCK.EX,
}) catch |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
};
}
}
pub fn unlock(file: File) void {
if (is_windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
return windows.UnlockFile(
file.handle,
&io_status_block,
&range_off,
&range_len,
null,
) catch |err| switch (err) {
error.RangeNotLocked => unreachable,
error.Unexpected => unreachable,
};
} else {
return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) {
error.WouldBlock => unreachable,
error.SystemResources => unreachable,
error.FileLocksNotSupported => unreachable,
error.Unexpected => unreachable,
};
}
}
pub fn tryLock(file: File, l: Lock) LockError!bool {
if (is_windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
const exclusive = switch (l) {
.none => return,
.shared => false,
.exclusive => true,
};
windows.LockFile(
file.handle,
null,
null,
null,
&io_status_block,
&range_off,
&range_len,
null,
windows.TRUE,
@intFromBool(exclusive),
) catch |err| switch (err) {
error.WouldBlock => return false,
else => |e| return e,
};
} else {
posix.flock(file.handle, switch (l) {
.none => posix.LOCK.UN,
.shared => posix.LOCK.SH | posix.LOCK.NB,
.exclusive => posix.LOCK.EX | posix.LOCK.NB,
}) catch |err| switch (err) {
error.WouldBlock => return false,
else => |e| return e,
};
}
return true;
}
pub fn downgradeLock(file: File) LockError!void {
if (is_windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
windows.LockFile(
file.handle,
null,
null,
null,
&io_status_block,
&range_off,
&range_len,
null,
windows.TRUE,
windows.FALSE,
) catch |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
};
return windows.UnlockFile(
file.handle,
&io_status_block,
&range_off,
&range_len,
null,
) catch |err| switch (err) {
error.RangeNotLocked => unreachable,
error.Unexpected => unreachable,
};
} else {
return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
};
}
}
const File = @This();
const std = @import("../std.zig");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const posix = std.os;
const io = std.io;
const math = std.math;
const assert = std.debug.assert;
const windows = std.os.windows;
const Os = std.builtin.Os;
const maxInt = std.math.maxInt;
const is_windows = builtin.os.tag == .windows;