mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
321 lines
12 KiB
Zig
321 lines
12 KiB
Zig
const std = @import("std.zig");
|
|
const builtin = @import("builtin");
|
|
const root = @import("root");
|
|
const c = std.c;
|
|
|
|
const math = std.math;
|
|
const assert = std.debug.assert;
|
|
const os = std.os;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const meta = std.meta;
|
|
const File = std.fs.File;
|
|
|
|
pub const Mode = enum {
|
|
/// I/O operates normally, waiting for the operating system syscalls to complete.
|
|
blocking,
|
|
|
|
/// I/O functions are generated async and rely on a global event loop. Event-based I/O.
|
|
evented,
|
|
};
|
|
|
|
const mode = std.options.io_mode;
|
|
pub const is_async = mode != .blocking;
|
|
|
|
/// This is an enum value to use for I/O mode at runtime, since it takes up zero bytes at runtime,
|
|
/// and makes expressions comptime-known when `is_async` is `false`.
|
|
pub const ModeOverride = if (is_async) Mode else enum { blocking };
|
|
pub const default_mode: ModeOverride = if (is_async) Mode.evented else .blocking;
|
|
|
|
fn getStdOutHandle() os.fd_t {
|
|
if (builtin.os.tag == .windows) {
|
|
if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_aarch64) {
|
|
// TODO: this is just a temporary workaround until we advance x86 backend further along.
|
|
return os.windows.GetStdHandle(os.windows.STD_OUTPUT_HANDLE) catch os.windows.INVALID_HANDLE_VALUE;
|
|
}
|
|
return os.windows.peb().ProcessParameters.hStdOutput;
|
|
}
|
|
|
|
if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdOutHandle")) {
|
|
return root.os.io.getStdOutHandle();
|
|
}
|
|
|
|
return os.STDOUT_FILENO;
|
|
}
|
|
|
|
/// TODO: async stdout on windows without a dedicated thread.
|
|
/// https://github.com/ziglang/zig/pull/4816#issuecomment-604521023
|
|
pub fn getStdOut() File {
|
|
return File{
|
|
.handle = getStdOutHandle(),
|
|
.capable_io_mode = .blocking,
|
|
.intended_io_mode = default_mode,
|
|
};
|
|
}
|
|
|
|
fn getStdErrHandle() os.fd_t {
|
|
if (builtin.os.tag == .windows) {
|
|
if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_aarch64) {
|
|
// TODO: this is just a temporary workaround until we advance x86 backend further along.
|
|
return os.windows.GetStdHandle(os.windows.STD_ERROR_HANDLE) catch os.windows.INVALID_HANDLE_VALUE;
|
|
}
|
|
return os.windows.peb().ProcessParameters.hStdError;
|
|
}
|
|
|
|
if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdErrHandle")) {
|
|
return root.os.io.getStdErrHandle();
|
|
}
|
|
|
|
return os.STDERR_FILENO;
|
|
}
|
|
|
|
/// This returns a `File` that is configured to block with every write, in order
|
|
/// to facilitate better debugging. This can be changed by modifying the `intended_io_mode` field.
|
|
pub fn getStdErr() File {
|
|
return File{
|
|
.handle = getStdErrHandle(),
|
|
.capable_io_mode = .blocking,
|
|
.intended_io_mode = .blocking,
|
|
};
|
|
}
|
|
|
|
fn getStdInHandle() os.fd_t {
|
|
if (builtin.os.tag == .windows) {
|
|
if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_aarch64) {
|
|
// TODO: this is just a temporary workaround until we advance x86 backend further along.
|
|
return os.windows.GetStdHandle(os.windows.STD_INPUT_HANDLE) catch os.windows.INVALID_HANDLE_VALUE;
|
|
}
|
|
return os.windows.peb().ProcessParameters.hStdInput;
|
|
}
|
|
|
|
if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdInHandle")) {
|
|
return root.os.io.getStdInHandle();
|
|
}
|
|
|
|
return os.STDIN_FILENO;
|
|
}
|
|
|
|
/// TODO: async stdin on windows without a dedicated thread.
|
|
/// https://github.com/ziglang/zig/pull/4816#issuecomment-604521023
|
|
pub fn getStdIn() File {
|
|
return File{
|
|
.handle = getStdInHandle(),
|
|
.capable_io_mode = .blocking,
|
|
.intended_io_mode = default_mode,
|
|
};
|
|
}
|
|
|
|
pub const Reader = @import("io/reader.zig").Reader;
|
|
pub const Writer = @import("io/writer.zig").Writer;
|
|
pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
|
|
|
|
pub const BufferedWriter = @import("io/buffered_writer.zig").BufferedWriter;
|
|
pub const bufferedWriter = @import("io/buffered_writer.zig").bufferedWriter;
|
|
|
|
pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader;
|
|
pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader;
|
|
pub const bufferedReaderSize = @import("io/buffered_reader.zig").bufferedReaderSize;
|
|
|
|
pub const PeekStream = @import("io/peek_stream.zig").PeekStream;
|
|
pub const peekStream = @import("io/peek_stream.zig").peekStream;
|
|
|
|
pub const FixedBufferStream = @import("io/fixed_buffer_stream.zig").FixedBufferStream;
|
|
pub const fixedBufferStream = @import("io/fixed_buffer_stream.zig").fixedBufferStream;
|
|
|
|
pub const CWriter = @import("io/c_writer.zig").CWriter;
|
|
pub const cWriter = @import("io/c_writer.zig").cWriter;
|
|
|
|
pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader;
|
|
pub const limitedReader = @import("io/limited_reader.zig").limitedReader;
|
|
|
|
pub const CountingWriter = @import("io/counting_writer.zig").CountingWriter;
|
|
pub const countingWriter = @import("io/counting_writer.zig").countingWriter;
|
|
pub const CountingReader = @import("io/counting_reader.zig").CountingReader;
|
|
pub const countingReader = @import("io/counting_reader.zig").countingReader;
|
|
|
|
pub const MultiWriter = @import("io/multi_writer.zig").MultiWriter;
|
|
pub const multiWriter = @import("io/multi_writer.zig").multiWriter;
|
|
|
|
pub const BitReader = @import("io/bit_reader.zig").BitReader;
|
|
pub const bitReader = @import("io/bit_reader.zig").bitReader;
|
|
|
|
pub const BitWriter = @import("io/bit_writer.zig").BitWriter;
|
|
pub const bitWriter = @import("io/bit_writer.zig").bitWriter;
|
|
|
|
pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream;
|
|
pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream;
|
|
|
|
pub const FindByteWriter = @import("io/find_byte_writer.zig").FindByteWriter;
|
|
pub const findByteWriter = @import("io/find_byte_writer.zig").findByteWriter;
|
|
|
|
pub const FindByteOutStream = @compileError("deprecated; use `FindByteWriter`");
|
|
pub const findByteOutStream = @compileError("deprecated; use `findByteWriter`");
|
|
|
|
pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile;
|
|
|
|
pub const StreamSource = @import("io/stream_source.zig").StreamSource;
|
|
|
|
/// A Writer that doesn't write to anything.
|
|
pub const null_writer = @as(NullWriter, .{ .context = {} });
|
|
|
|
const NullWriter = Writer(void, error{}, dummyWrite);
|
|
fn dummyWrite(context: void, data: []const u8) error{}!usize {
|
|
_ = context;
|
|
return data.len;
|
|
}
|
|
|
|
test "null_writer" {
|
|
null_writer.writeAll("yay" ** 10) catch |err| switch (err) {};
|
|
}
|
|
|
|
pub fn poll(
|
|
allocator: std.mem.Allocator,
|
|
comptime StreamEnum: type,
|
|
files: PollFiles(StreamEnum),
|
|
) Poller(StreamEnum) {
|
|
const enum_fields = @typeInfo(StreamEnum).Enum.fields;
|
|
var result: Poller(StreamEnum) = undefined;
|
|
inline for (0..enum_fields.len) |i| {
|
|
result.fifos[i] = .{
|
|
.allocator = allocator,
|
|
.buf = &.{},
|
|
.head = 0,
|
|
.count = 0,
|
|
};
|
|
result.poll_fds[i] = .{
|
|
.fd = @field(files, enum_fields[i].name).file.handle,
|
|
.events = switch (@field(files, enum_fields[i].name).direction) {
|
|
.in => os.POLL.IN,
|
|
.out => os.POLL.OUT,
|
|
},
|
|
.revents = undefined,
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
pub fn Poller(comptime StreamEnum: type) type {
|
|
return struct {
|
|
const enum_fields = @typeInfo(StreamEnum).Enum.fields;
|
|
const Fifo = std.fifo.LinearFifo(u8, .Dynamic);
|
|
|
|
fifos: [enum_fields.len]Fifo,
|
|
//directions: [enum_fields.len]PollFile.Direction,
|
|
//handles: [enum_fields.len]std.fs.File.Handle,
|
|
poll_fds: [enum_fields.len]std.os.pollfd,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn poll(self: *Self) !void {
|
|
if (builtin.os.tag == .windows) {
|
|
return pollWindows(self);
|
|
} else {
|
|
return pollPosix(self);
|
|
}
|
|
}
|
|
|
|
pub inline fn fifo(self: *Self, comptime which: StreamEnum) *Fifo {
|
|
return &self.fifos[@enumToInt(which)];
|
|
}
|
|
|
|
pub fn done(self: Self) bool {
|
|
for (self.poll_fds) |poll_fd| {
|
|
if (poll_fd.fd != -1) return false;
|
|
} else return true;
|
|
}
|
|
|
|
fn pollWindows(self: *Self) !void {
|
|
_ = self;
|
|
@compileError("TODO");
|
|
}
|
|
|
|
fn pollPosix(self: *Self) !void {
|
|
// We ask for ensureUnusedCapacity with this much extra space. This
|
|
// has more of an effect on small reads because once the reads
|
|
// start to get larger the amount of space an ArrayList will
|
|
// allocate grows exponentially.
|
|
const bump_amt = 512;
|
|
|
|
const err_mask = os.POLL.ERR | os.POLL.NVAL | os.POLL.HUP;
|
|
|
|
const events_len = try os.poll(&self.poll_fds, std.math.maxInt(i32));
|
|
if (events_len == 0) return;
|
|
|
|
inline for (0..enum_fields.len) |i| {
|
|
// Try reading whatever is available before checking the error
|
|
// conditions.
|
|
// It's still possible to read after a POLL.HUP is received,
|
|
// always check if there's some data waiting to be read first.
|
|
if (self.poll_fds[i].revents & os.POLL.IN != 0) {
|
|
const q = &self.fifos[i];
|
|
const buf = try q.writableWithSize(bump_amt);
|
|
const amt = try os.read(self.poll_fds[i].fd, buf);
|
|
q.update(amt);
|
|
std.debug.print("read {d} bytes\n", .{amt});
|
|
if (amt == 0) {
|
|
// Remove the fd when the EOF condition is met.
|
|
self.poll_fds[i].fd = -1;
|
|
}
|
|
} else if (self.poll_fds[i].revents & err_mask != 0) {
|
|
// Exclude the fds that signaled an error.
|
|
self.poll_fds[i].fd = -1;
|
|
} else if (self.poll_fds[i].revents & os.POLL.OUT != 0) {
|
|
const q = &self.fifos[i];
|
|
const amt = try os.write(self.poll_fds[i].fd, q.readableSlice(0));
|
|
q.discard(amt);
|
|
if (amt == 0) {
|
|
self.poll_fds[i].fd = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Given an enum, returns a struct with fields of that enum, each field
|
|
/// representing an I/O stream for polling.
|
|
pub fn PollFiles(comptime StreamEnum: type) type {
|
|
const enum_fields = @typeInfo(StreamEnum).Enum.fields;
|
|
var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined;
|
|
for (&struct_fields, enum_fields) |*struct_field, enum_field| {
|
|
struct_field.* = .{
|
|
.name = enum_field.name,
|
|
.type = PollFile,
|
|
.default_value = null,
|
|
.is_comptime = false,
|
|
.alignment = @alignOf(PollFile),
|
|
};
|
|
}
|
|
return @Type(.{ .Struct = .{
|
|
.layout = .Auto,
|
|
.fields = &struct_fields,
|
|
.decls = &.{},
|
|
.is_tuple = false,
|
|
} });
|
|
}
|
|
|
|
pub const PollFile = struct {
|
|
file: File,
|
|
direction: Direction,
|
|
|
|
pub const Direction = enum { in, out };
|
|
};
|
|
|
|
test {
|
|
_ = @import("io/bit_reader.zig");
|
|
_ = @import("io/bit_writer.zig");
|
|
_ = @import("io/buffered_atomic_file.zig");
|
|
_ = @import("io/buffered_reader.zig");
|
|
_ = @import("io/buffered_writer.zig");
|
|
_ = @import("io/c_writer.zig");
|
|
_ = @import("io/counting_writer.zig");
|
|
_ = @import("io/counting_reader.zig");
|
|
_ = @import("io/fixed_buffer_stream.zig");
|
|
_ = @import("io/reader.zig");
|
|
_ = @import("io/writer.zig");
|
|
_ = @import("io/peek_stream.zig");
|
|
_ = @import("io/seekable_stream.zig");
|
|
_ = @import("io/stream_source.zig");
|
|
_ = @import("io/test.zig");
|
|
}
|