zig/lib/std/Io/File/Writer.zig
Andrew Kelley 470e2d6796 std: fix some surface level compilation errors
And boldly remove preadv, pwritev, readv, writev, pread, pwrite from
std.posix.
2025-12-05 17:41:27 -08:00

251 lines
7.7 KiB
Zig

const Writer = @This();
const std = @import("../../std.zig");
const Io = std.Io;
const File = std.Io.File;
const assert = std.debug.assert;
io: Io,
file: File,
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.
pos: u64 = 0,
write_file_err: ?WriteFileError = null,
seek_err: ?SeekError = null,
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,
/// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
UnsupportedOperation,
/// Nonblocking I/O has been selected but the write would block.
WouldBlock,
/// Unspecified error while reading from in_fd.
InputOutput,
/// Insufficient kernel memory to read from in_fd.
SystemResources,
} || Io.Cancelable || Io.UnexpectedError;
pub const SeekError = Io.File.SeekError;
pub fn init(file: File, buffer: []u8) Writer {
return .{
.file = file,
.interface = initInterface(buffer),
.mode = .positional,
};
}
/// Positional is more threadsafe, since the global seek position is not
/// affected, but when such syscalls are not available, preemptively
/// initializing in streaming mode will skip a failed syscall.
pub fn initStreaming(file: File, buffer: []u8) Writer {
return .{
.file = file,
.interface = initInterface(buffer),
.mode = .streaming,
};
}
pub fn initInterface(buffer: []u8) Io.Writer {
return .{
.vtable = &.{
.drain = drain,
.sendFile = sendFile,
},
.buffer = buffer,
};
}
pub fn moveToReader(w: *Writer) File.Reader {
defer w.* = undefined;
return .{
.io = w.io,
.file = .{ .handle = w.file.handle },
.mode = w.mode,
.pos = w.pos,
.interface = File.Reader.initInterface(w.interface.buffer),
.seek_err = w.seek_err,
};
}
pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
switch (w.mode) {
.positional, .positional_reading => return drainPositional(w, data, splat),
.streaming, .streaming_reading => return drainStreaming(w, data, splat),
.failure => return error.WriteFailed,
}
}
fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
const io = w.io;
const header = w.interface.buffered();
const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) {
error.Unseekable => {
w.mode = w.mode.toStreaming();
const pos = w.pos;
if (pos != 0) {
w.pos = 0;
w.seekTo(@intCast(pos)) catch {
w.mode = .failure;
return error.WriteFailed;
};
}
return 0;
},
else => |e| {
w.err = e;
return error.WriteFailed;
},
};
w.pos += n;
return w.interface.consume(n);
}
fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
const io = w.io;
const header = w.interface.buffered();
const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
w.err = err;
return error.WriteFailed;
};
w.pos += n;
return w.interface.consume(n);
}
pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
switch (w.mode) {
.positional => return sendFilePositional(w, file_reader, limit),
.positional_reading => return error.Unimplemented,
.streaming => return sendFileStreaming(w, file_reader, limit),
.streaming_reading => return error.Unimplemented,
.failure => return error.WriteFailed,
}
}
fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
const io = w.io;
const header = w.interface.buffered();
const n = io.vtable.fileSendFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) {
error.Unseekable => {
w.mode = w.mode.toStreaming();
const pos = w.pos;
if (pos != 0) {
w.pos = 0;
w.seekTo(@intCast(pos)) catch {
w.mode = .failure;
return error.WriteFailed;
};
}
return 0;
},
error.Canceled => {
w.err = error.Canceled;
return error.WriteFailed;
},
else => |e| {
w.write_file_err = e;
return error.WriteFailed;
},
};
w.pos += n;
return w.interface.consume(n);
}
fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
const io = w.io;
const header = w.interface.buffered();
const n = io.vtable.fileSendFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) {
error.Canceled => {
w.err = error.Canceled;
return error.WriteFailed;
},
else => |e| {
w.write_file_err = e;
return error.WriteFailed;
},
};
w.pos += n;
return w.interface.consume(n);
}
pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void {
try w.interface.flush();
try seekToUnbuffered(w, offset);
}
/// Asserts that no data is currently buffered.
pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void {
assert(w.interface.buffered().len == 0);
const io = w.io;
switch (w.mode) {
.positional, .positional_reading => {
w.pos = offset;
},
.streaming, .streaming_reading => {
if (w.seek_err) |err| return err;
io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| {
w.seek_err = err;
return err;
};
w.pos = offset;
},
.failure => return w.seek_err.?,
}
}
pub const EndError = File.SetEndPosError || Io.Writer.Error;
/// Flushes any buffered data and sets the end position of the file.
///
/// If not overwriting existing contents, then calling `interface.flush`
/// directly is sufficient.
///
/// Flush failure is handled by setting `err` so that it can be handled
/// along with other write failures.
pub fn end(w: *Writer) EndError!void {
try w.interface.flush();
switch (w.mode) {
.positional,
.positional_reading,
=> w.file.setLength(w.pos) catch |err| switch (err) {
error.NonResizable => return,
else => |e| return e,
},
.streaming,
.streaming_reading,
.failure,
=> {},
}
}