mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 22:04:21 +00:00
251 lines
7.7 KiB
Zig
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,
|
|
=> {},
|
|
}
|
|
}
|