std: fix some surface level compilation errors

And boldly remove preadv, pwritev, readv, writev, pread, pwrite from
std.posix.
This commit is contained in:
Andrew Kelley 2025-12-05 17:41:27 -08:00
parent fc8b792251
commit 470e2d6796
8 changed files with 64 additions and 407 deletions

View file

@ -705,8 +705,8 @@ pub const VTable = struct {
fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void,
fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void,
fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void,
fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp, File.SetTimestampsOptions) File.SetTimestampsError!void,
fileSetTimestampsNow: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void,
fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp) File.SetTimestampsError!void,
fileSetTimestampsNow: *const fn (?*anyopaque, File) File.SetTimestampsError!void,
fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void,
fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,
fileUnlock: *const fn (?*anyopaque, File) void,
@ -726,7 +726,7 @@ pub const VTable = struct {
/// Returns 0 on end of stream.
netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.WriteFileError!usize,
netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.Writer.WriteFileError!usize,
netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,

View file

@ -274,7 +274,7 @@ pub fn sync(file: File, io: Io) SyncError!void {
/// Test whether the file refers to a terminal.
///
/// See also:
/// * `getOrEnableAnsiEscapeSupport`
/// * `enableAnsiEscapeCodes`
/// * `supportsAnsiEscapeCodes`.
pub fn isTty(file: File, io: Io) bool {
return io.vtable.fileIsTty(io.userdata, file);
@ -282,7 +282,7 @@ pub fn isTty(file: File, io: Io) bool {
pub const EnableAnsiEscapeCodesError = error{} || Io.Cancelable || Io.UnexpectedError;
pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError {
pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void {
return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file);
}

View file

@ -7,7 +7,7 @@ const assert = std.debug.assert;
io: Io,
file: File,
err: ?File.WriteError = null,
err: ?Error = null,
mode: Mode = .positional,
/// Tracks the true seek position in the file. To obtain the logical
/// position, add the buffer size to this value.
@ -18,6 +18,29 @@ interface: Io.Writer,
pub const Mode = File.Reader.Mode;
pub const Error = error{
DiskQuota,
FileTooBig,
InputOutput,
NoSpaceLeft,
DeviceBusy,
InvalidArgument,
/// File descriptor does not hold the required rights to write to it.
AccessDenied,
PermissionDenied,
BrokenPipe,
SystemResources,
NotOpenForWriting,
/// The process cannot access the file because another process has locked
/// a portion of the file. Windows-only.
LockViolation,
/// Non-blocking has been enabled and this operation would block.
WouldBlock,
/// This error occurs when a device gets disconnected before or mid-flush
/// while it's being written to - errno(6): No such device or address.
NoDevice,
} || Io.Cancelable || Io.UnexpectedError;
pub const WriteFileError = error{
/// `out_fd` is an unconnected socket, or out_fd closed its read end.
BrokenPipe,

View file

@ -6400,6 +6400,22 @@ fn fileWriteFileStreaming(
return error.Unimplemented;
}
fn netWriteFile(
userdata: ?*anyopaque,
socket_handle: net.Socket.Handle,
header: []const u8,
file_reader: *Io.File.Reader,
limit: Io.Limit,
) net.Stream.WriteFileError!usize {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
_ = socket_handle;
_ = header;
_ = file_reader;
_ = limit;
return error.Unimplemented; // TODO
}
fn fileWriteFilePositional(
userdata: ?*anyopaque,
file: Io.File,

View file

@ -1256,6 +1256,7 @@ pub const Stream = struct {
interface: Io.Writer,
stream: Stream,
err: ?Error = null,
write_file_err: ?WriteFileError = null,
pub const Error = error{
/// Another TCP Fast Open is already in progress.
@ -1285,12 +1286,17 @@ pub const Stream = struct {
SocketNotBound,
} || Io.UnexpectedError || Io.Cancelable;
pub const WriteFileError = error{} || Io.Cancelable || Io.UnexpectedError;
pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
return .{
.io = io,
.stream = stream,
.interface = .{
.vtable = &.{ .drain = drain },
.vtable = &.{
.drain = drain,
.sendFile = sendFile,
},
.buffer = buffer,
},
};
@ -1307,6 +1313,13 @@ pub const Stream = struct {
};
return io_w.consume(n);
}
fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
_ = io_w;
_ = file_reader;
_ = limit;
return error.Unimplemented; // TODO
}
};
pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader {

View file

@ -55,7 +55,9 @@ pub const Config = union(enum) {
if (force_color == false) return .no_color;
if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes;
if (file.enableAnsiEscapeCodes()) |_| {
return .escape_codes;
} else |_| {}
if (native_os == .windows and file.isTty()) {
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;

View file

@ -474,9 +474,9 @@ pub fn start(options: Options) Node {
}
const stderr: std.fs.File = .stderr();
global_progress.terminal = stderr;
if (stderr.getOrEnableAnsiEscapeSupport()) {
if (stderr.enableAnsiEscapeCodes()) |_| {
global_progress.terminal_mode = .ansi_escape_codes;
} else if (is_windows and stderr.isTty()) {
} else |_| if (is_windows and stderr.isTty()) {
global_progress.terminal_mode = TerminalMode{ .windows_api = .{
.code_page = windows.kernel32.GetConsoleOutputCP(),
} };

View file

@ -796,226 +796,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
}
}
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
///
/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
/// return error.WouldBlock when EAGAIN is received.
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
///
/// This operation is non-atomic on the following systems:
/// * Windows
/// On these systems, the read races with concurrent writes to the same file descriptor.
///
/// This function assumes that all vectors, including zero-length vectors, have
/// a pointer within the address space of the application.
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
if (native_os == .windows) {
if (iov.len == 0) return 0;
const first = iov[0];
return read(fd, first.base[0..first.len]);
}
if (native_os == .wasi and !builtin.link_libc) {
var nread: usize = undefined;
switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) {
.SUCCESS => return nread,
.INTR => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable, // currently not support in WASI
.BADF => return error.NotOpenForReading, // can be a race condition
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketUnconnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.Timeout,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
while (true) {
const rc = system.readv(fd, iov.ptr, @min(iov.len, IOV_MAX));
switch (errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
.INVAL => unreachable,
.FAULT => unreachable,
.SRCH => return error.ProcessNotFound,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForReading, // can be a race condition
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketUnconnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.Timeout,
else => |err| return unexpectedErrno(err),
}
}
}
pub const PReadError = std.Io.File.ReadPositionalError;
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
///
/// Retries when interrupted by a signal.
///
/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
/// return error.WouldBlock when EAGAIN is received.
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
///
/// Linux has a limit on how many bytes may be transferred in one `pread` call, which is `0x7ffff000`
/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
/// The corresponding POSIX limit is `maxInt(isize)`.
pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
if (buf.len == 0) return 0;
if (native_os == .windows) {
return windows.ReadFile(fd, buf, offset);
}
if (native_os == .wasi and !builtin.link_libc) {
const iovs = [1]iovec{iovec{
.base = buf.ptr,
.len = buf.len,
}};
var nread: usize = undefined;
switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) {
.SUCCESS => return nread,
.INTR => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable,
.BADF => return error.NotOpenForReading, // Can be a race condition.
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketUnconnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.Timeout,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
// Prevent EINVAL.
const max_count = switch (native_os) {
.linux => 0x7ffff000,
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),
else => maxInt(isize),
};
const pread_sym = if (lfs64_abi) system.pread64 else system.pread;
while (true) {
const rc = pread_sym(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset));
switch (errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
.INVAL => unreachable,
.FAULT => unreachable,
.SRCH => return error.ProcessNotFound,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForReading, // Can be a race condition.
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketUnconnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.Timeout,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
else => |err| return unexpectedErrno(err),
}
}
}
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
///
/// Retries when interrupted by a signal.
///
/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
/// return error.WouldBlock when EAGAIN is received.
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
///
/// This operation is non-atomic on the following systems:
/// * Darwin
/// * Windows
/// On these systems, the read races with concurrent writes to the same file descriptor.
pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
const have_pread_but_not_preadv = switch (native_os) {
.windows, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true,
else => false,
};
if (have_pread_but_not_preadv) {
// We could loop here; but proper usage of `preadv` must handle partial reads anyway.
// So we simply read into the first vector only.
if (iov.len == 0) return 0;
const first = iov[0];
return pread(fd, first.base[0..first.len], offset);
}
if (native_os == .wasi and !builtin.link_libc) {
var nread: usize = undefined;
switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) {
.SUCCESS => return nread,
.INTR => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable,
.BADF => return error.NotOpenForReading, // can be a race condition
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketUnconnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.Timeout,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv;
while (true) {
const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));
switch (errno(rc)) {
.SUCCESS => return @bitCast(rc),
.INTR => continue,
.INVAL => unreachable,
.FAULT => unreachable,
.SRCH => return error.ProcessNotFound,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForReading, // can be a race condition
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTCONN => return error.SocketUnconnected,
.CONNRESET => return error.ConnectionResetByPeer,
.TIMEDOUT => return error.Timeout,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
else => |err| return unexpectedErrno(err),
}
}
}
pub const WriteError = error{
DiskQuota,
FileTooBig,
@ -1140,183 +920,6 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
}
}
pub const PWriteError = WriteError || error{Unseekable};
/// Write to a file descriptor, with a position offset.
/// Retries when interrupted by a signal.
/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
///
/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
/// occur for various reasons; for example, because there was insufficient space on the disk
/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
/// similar was interrupted by a signal handler after it had transferred some, but before it had
/// transferred all of the requested bytes. In the event of a partial write, the caller can make
/// another write() call to transfer the remaining bytes. The subsequent call will either
/// transfer further bytes or may result in an error (e.g., if the disk is now full).
///
/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
/// return error.WouldBlock when EAGAIN is received.
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
///
/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000`
/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.
/// The corresponding POSIX limit is `maxInt(isize)`.
pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
if (bytes.len == 0) return 0;
if (native_os == .windows) {
return windows.WriteFile(fd, bytes, offset);
}
if (native_os == .wasi and !builtin.link_libc) {
const ciovs = [1]iovec_const{iovec_const{
.base = bytes.ptr,
.len = bytes.len,
}};
var nwritten: usize = undefined;
switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) {
.SUCCESS => return nwritten,
.INTR => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable,
.BADF => return error.NotOpenForWriting, // can be a race condition.
.DESTADDRREQ => unreachable, // `connect` was never called.
.DQUOT => return error.DiskQuota,
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
.NOSPC => return error.NoSpaceLeft,
.PERM => return error.PermissionDenied,
.PIPE => return error.BrokenPipe,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
// Prevent EINVAL.
const max_count = switch (native_os) {
.linux => 0x7ffff000,
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),
else => maxInt(isize),
};
const pwrite_sym = if (lfs64_abi) system.pwrite64 else system.pwrite;
while (true) {
const rc = pwrite_sym(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset));
switch (errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
.INVAL => return error.InvalidArgument,
.FAULT => unreachable,
.SRCH => return error.ProcessNotFound,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForWriting, // Can be a race condition.
.DESTADDRREQ => unreachable, // `connect` was never called.
.DQUOT => return error.DiskQuota,
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
.NOSPC => return error.NoSpaceLeft,
.PERM => return error.PermissionDenied,
.PIPE => return error.BrokenPipe,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.BUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
}
/// Write multiple buffers to a file descriptor, with a position offset.
/// Retries when interrupted by a signal.
/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
///
/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
/// occur for various reasons; for example, because there was insufficient space on the disk
/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
/// similar was interrupted by a signal handler after it had transferred some, but before it had
/// transferred all of the requested bytes. In the event of a partial write, the caller can make
/// another write() call to transfer the remaining bytes. The subsequent call will either
/// transfer further bytes or may result in an error (e.g., if the disk is now full).
///
/// If `fd` is opened in non blocking mode, the function will
/// return error.WouldBlock when EAGAIN is received.
///
/// The following systems do not have this syscall, and will return partial writes if more than one
/// vector is provided:
/// * Darwin
/// * Windows
///
/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize {
const have_pwrite_but_not_pwritev = switch (native_os) {
.windows, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true,
else => false,
};
if (have_pwrite_but_not_pwritev) {
// We could loop here; but proper usage of `pwritev` must handle partial writes anyway.
// So we simply write the first vector only.
if (iov.len == 0) return 0;
const first = iov[0];
return pwrite(fd, first.base[0..first.len], offset);
}
if (native_os == .wasi and !builtin.link_libc) {
var nwritten: usize = undefined;
switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) {
.SUCCESS => return nwritten,
.INTR => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable,
.BADF => return error.NotOpenForWriting, // Can be a race condition.
.DESTADDRREQ => unreachable, // `connect` was never called.
.DQUOT => return error.DiskQuota,
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
.NOSPC => return error.NoSpaceLeft,
.PERM => return error.PermissionDenied,
.PIPE => return error.BrokenPipe,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.NOTCAPABLE => return error.AccessDenied,
else => |err| return unexpectedErrno(err),
}
}
const pwritev_sym = if (lfs64_abi) system.pwritev64 else system.pwritev;
while (true) {
const rc = pwritev_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));
switch (errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
.INVAL => return error.InvalidArgument,
.FAULT => unreachable,
.SRCH => return error.ProcessNotFound,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForWriting, // Can be a race condition.
.DESTADDRREQ => unreachable, // `connect` was never called.
.DQUOT => return error.DiskQuota,
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
.NOSPC => return error.NoSpaceLeft,
.PERM => return error.PermissionDenied,
.PIPE => return error.BrokenPipe,
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.BUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
}
pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
/// Open and possibly create a file. Keeps trying if it gets interrupted.