mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
175 lines
4.9 KiB
Zig
175 lines
4.9 KiB
Zig
//! Pipe handles provide an abstraction over streaming files on Unix
|
|
//! (including local domain sockets, pipes, and FIFOs) and named pipes on
|
|
//! Windows.
|
|
const Pipe = @This();
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const testing = std.testing;
|
|
const c = @import("c.zig");
|
|
const errors = @import("error.zig");
|
|
const Loop = @import("Loop.zig");
|
|
const Handle = @import("handle.zig").Handle;
|
|
const stream = @import("stream.zig");
|
|
const Stream = stream.Stream;
|
|
const WriteReq = stream.WriteReq;
|
|
|
|
handle: *c.uv_pipe_t,
|
|
|
|
pub usingnamespace Handle(Pipe);
|
|
pub usingnamespace Stream(Pipe);
|
|
|
|
/// Valid flags for pipe.
|
|
pub const Flags = packed struct {
|
|
_ignore: u6 = 0,
|
|
nonblock: bool = false, // UV_NONBLOCK_PIPE = 0x40
|
|
_ignore_high: u1 = 0,
|
|
|
|
pub inline fn toInt(self: Flags, comptime IntType: type) IntType {
|
|
return @intCast(IntType, @bitCast(u8, self));
|
|
}
|
|
|
|
test "Flags: expected value" {
|
|
const f: Flags = .{ .nonblock = true };
|
|
try testing.expectEqual(c.UV_NONBLOCK_PIPE, f.toInt(c_int));
|
|
}
|
|
};
|
|
|
|
/// Pair is a pair of ends to a single pipe.
|
|
pub const Pair = struct {
|
|
read: c.uv_file,
|
|
write: c.uv_file,
|
|
};
|
|
|
|
/// Create a pair of connected pipe handles. Data may be written to fds[1] and
|
|
/// read from fds[0]. The resulting handles can be passed to uv_pipe_open,
|
|
/// used with uv_spawn, or for any other purpose.
|
|
pub fn pipe(read_flags: Flags, write_flags: Flags) !Pair {
|
|
var res: [2]c.uv_file = undefined;
|
|
try errors.convertError(c.uv_pipe(
|
|
&res,
|
|
read_flags.toInt(c_int),
|
|
write_flags.toInt(c_int),
|
|
));
|
|
return Pair{ .read = res[0], .write = res[1] };
|
|
}
|
|
|
|
pub fn init(alloc: Allocator, loop: Loop, ipc: bool) !Pipe {
|
|
var handle = try alloc.create(c.uv_pipe_t);
|
|
errdefer alloc.destroy(handle);
|
|
try errors.convertError(c.uv_pipe_init(loop.loop, handle, @boolToInt(ipc)));
|
|
return Pipe{ .handle = handle };
|
|
}
|
|
|
|
pub fn deinit(self: *Pipe, alloc: Allocator) void {
|
|
alloc.destroy(self.handle);
|
|
self.* = undefined;
|
|
}
|
|
|
|
/// Open an existing file descriptor or HANDLE as a pipe.
|
|
pub fn open(self: Pipe, file: c.uv_file) !void {
|
|
try errors.convertError(c.uv_pipe_open(self.handle, file));
|
|
}
|
|
|
|
test {
|
|
_ = Flags;
|
|
}
|
|
|
|
test "Pipe" {
|
|
const pair = try pipe(.{ .nonblock = true }, .{ .nonblock = true });
|
|
|
|
var loop = try Loop.init(testing.allocator);
|
|
defer loop.deinit(testing.allocator);
|
|
|
|
// Read side
|
|
var reader = try init(testing.allocator, loop, false);
|
|
defer reader.deinit(testing.allocator);
|
|
|
|
try reader.open(pair.read);
|
|
try testing.expect(try reader.isReadable());
|
|
try testing.expect(!try reader.isWritable());
|
|
|
|
// Write side
|
|
var writer = try init(testing.allocator, loop, false);
|
|
defer writer.deinit(testing.allocator);
|
|
|
|
try writer.open(pair.write);
|
|
try testing.expect(!try writer.isReadable());
|
|
try testing.expect(try writer.isWritable());
|
|
|
|
// Set our data that we'll use to assert
|
|
var data: TestData = .{};
|
|
defer data.deinit();
|
|
writer.setData(&data);
|
|
|
|
// Write
|
|
var writeReq = try WriteReq.init(testing.allocator);
|
|
defer writeReq.deinit(testing.allocator);
|
|
|
|
try writer.write(
|
|
writeReq,
|
|
&[_][]const u8{
|
|
"hello",
|
|
},
|
|
TestData.write,
|
|
);
|
|
|
|
// Run write and verify success
|
|
_ = try loop.run(.once);
|
|
try testing.expectEqual(@as(u8, 1), data.count);
|
|
try testing.expectEqual(@as(i32, 0), data.status);
|
|
|
|
// Read
|
|
try reader.readStart(TestData.alloc, TestData.read);
|
|
reader.setData(&data);
|
|
_ = try loop.run(.once);
|
|
|
|
// Check our data
|
|
try testing.expectEqual(@as(usize, 5), data.data.items.len);
|
|
try testing.expectEqualStrings("hello", data.data.items);
|
|
data.data.clearRetainingCapacity();
|
|
|
|
// Try writing directly
|
|
_ = try writer.tryWrite(&[_][]const u8{"world"});
|
|
_ = try loop.run(.once);
|
|
try testing.expectEqual(@as(usize, 5), data.data.items.len);
|
|
try testing.expectEqualStrings("world", data.data.items);
|
|
|
|
// End
|
|
reader.readStop();
|
|
reader.close(null);
|
|
writer.close(null);
|
|
_ = try loop.run(.default);
|
|
}
|
|
|
|
/// Logic for testing read/write of pipes.
|
|
const TestData = struct {
|
|
count: u8 = 0,
|
|
status: i32 = 0,
|
|
data: std.ArrayListUnmanaged(u8) = .{},
|
|
|
|
fn deinit(self: *TestData) void {
|
|
self.data.deinit(testing.allocator);
|
|
self.* = undefined;
|
|
}
|
|
|
|
fn write(req: *WriteReq, status: i32) void {
|
|
var data = req.handle(Pipe).?.getData(TestData).?;
|
|
data.count += 1;
|
|
data.status = status;
|
|
}
|
|
|
|
fn alloc(_: *Pipe, size: usize) ?[]u8 {
|
|
return testing.allocator.alloc(u8, size) catch null;
|
|
}
|
|
|
|
fn read(h: *Pipe, n: isize, buf: []const u8) void {
|
|
var data = h.getData(TestData).?;
|
|
data.data.appendSlice(
|
|
testing.allocator,
|
|
buf[0..@intCast(usize, n)],
|
|
) catch unreachable;
|
|
testing.allocator.free(buf);
|
|
}
|
|
};
|