diff options
Diffstat (limited to 'lib/std/Io/File/Writer.zig')
| -rw-r--r-- | lib/std/Io/File/Writer.zig | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig new file mode 100644 index 0000000000..bf8c0bf289 --- /dev/null +++ b/lib/std/Io/File/Writer.zig @@ -0,0 +1,274 @@ +const Writer = @This(); +const builtin = @import("builtin"); +const is_windows = builtin.os.tag == .windows; + +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, +/// use `logicalPos`. +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, + /// File descriptor does not hold the required rights to write to it. + AccessDenied, + PermissionDenied, + /// File is an unconnected socket, or closed its read end. + BrokenPipe, + /// Insufficient kernel memory to read from in_fd. + 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, + FileBusy, +} || Io.Cancelable || Io.UnexpectedError; + +pub const WriteFileError = Error || error{ + /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. + Unimplemented, + /// Can happen on FreeBSD when using copy_file_range. + CorruptedData, + EndOfStream, + ReadFailed, +}; + +pub const SeekError = Io.File.SeekError; + +pub fn init(file: File, io: Io, buffer: []u8) Writer { + return .{ + .io = io, + .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, io: Io, buffer: []u8) Writer { + return .{ + .io = io, + .file = file, + .interface = initInterface(buffer), + .mode = .streaming, + }; +} + +/// Detects if `file` is terminal and sets the mode accordingly. +pub fn initDetect(file: File, io: Io, buffer: []u8) Io.Cancelable!Writer { + return .{ + .io = io, + .file = file, + .interface = initInterface(buffer), + .mode = try .detect(io, file, true, .positional), + }; +} + +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_simple => return drainPositional(w, data, splat), + .streaming, .streaming_simple => 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_simple => return error.Unimplemented, + .streaming => return sendFileStreaming(w, file_reader, limit), + .streaming_simple => 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.fileWriteFilePositional(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; + }, + error.EndOfStream => return error.EndOfStream, + error.Unimplemented => return error.Unimplemented, + error.ReadFailed => return error.ReadFailed, + 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.fileWriteFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) { + error.Canceled => { + w.err = error.Canceled; + return error.WriteFailed; + }, + error.EndOfStream => return error.EndOfStream, + error.Unimplemented => return error.Unimplemented, + error.ReadFailed => return error.ReadFailed, + 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); +} + +pub fn logicalPos(w: *const Writer) u64 { + return w.pos + w.interface.end; +} + +/// 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_simple => { + w.pos = offset; + }, + .streaming, .streaming_simple => { + 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.SetLengthError || 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 { + const io = w.io; + try w.interface.flush(); + switch (w.mode) { + .positional, + .positional_simple, + => w.file.setLength(io, w.pos) catch |err| switch (err) { + error.NonResizable => return, + else => |e| return e, + }, + + .streaming, + .streaming_simple, + .failure, + => {}, + } +} |
