aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/termio/Exec.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/termio/Exec.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/termio/Exec.zig')
-rw-r--r--vendor/libghostty-vt/src/termio/Exec.zig320
1 files changed, 187 insertions, 133 deletions
diff --git a/vendor/libghostty-vt/src/termio/Exec.zig b/vendor/libghostty-vt/src/termio/Exec.zig
index 0894f87b..01629171 100644
--- a/vendor/libghostty-vt/src/termio/Exec.zig
+++ b/vendor/libghostty-vt/src/termio/Exec.zig
@@ -9,7 +9,8 @@ const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const posix = std.posix;
-const xev = @import("../global.zig").xev;
+const global = @import("../global.zig");
+const xev = global.xev;
const apprt = @import("../apprt.zig");
const build_config = @import("../build_config.zig");
const configpkg = @import("../config.zig");
@@ -21,13 +22,13 @@ const shell_integration = @import("shell_integration.zig");
const terminal = @import("../terminal/main.zig");
const termio = @import("../termio.zig");
const Command = @import("../Command.zig");
-const SegmentedPool = @import("../datastruct/main.zig").SegmentedPool;
const ptypkg = @import("../pty.zig");
const Pty = ptypkg.Pty;
-const EnvMap = std.process.EnvMap;
+const EnvMap = std.process.Environ.Map;
const PasswdEntry = internal_os.passwd.Entry;
const windows = internal_os.windows;
const ProcessInfo = @import("../pty.zig").ProcessInfo;
+const compat_fd = @import("../lib/compat/fd.zig");
const log = std.log.scoped(.io_exec);
@@ -98,7 +99,7 @@ pub fn threadEnter(
// We're in the child. Nothing more we can do but abnormal exit.
// The Command will output some additional information.
- posix.exit(1);
+ std.process.exit(1);
};
errdefer self.subprocess.stop();
@@ -117,13 +118,13 @@ pub fn threadEnter(
errdefer if (process) |*p| p.deinit();
// Track our process start time for abnormal exits
- const process_start = try std.time.Instant.now();
+ const process_start: std.Io.Timestamp = .now(global.io(), .awake);
// Create our pipe that we'll use to kill our read thread.
// pipe[0] is the read end, pipe[1] is the write end.
const pipe = try internal_os.pipe();
- errdefer posix.close(pipe[0]);
- errdefer posix.close(pipe[1]);
+ errdefer _ = posix.system.close(pipe[0]);
+ errdefer _ = posix.system.close(pipe[1]);
// Setup our stream so that we can write.
var stream = xev.Stream.initFd(pty_fds.write);
@@ -141,7 +142,7 @@ pub fn threadEnter(
if (builtin.os.tag == .windows) ReadThread.threadMainWindows else ReadThread.threadMainPosix,
.{ pty_fds.read, io, pipe[0] },
);
- read_thread.setName("io-reader") catch {};
+ read_thread.setName(global.io(), "io-reader") catch {};
// Setup our threadata backend state to be our own
td.backend = .{ .exec = .{
@@ -202,22 +203,23 @@ pub fn threadExit(self: *Exec, td: *termio.Termio.ThreadData) void {
// Quit our read thread after exiting the subprocess so that
// we don't get stuck waiting for data to stop flowing if it is
// a particularly noisy process.
- _ = posix.write(exec.read_thread_pipe, "x") catch |err| switch (err) {
- // BrokenPipe means that our read thread is closed already,
- // which is completely fine since that is what we were trying
- // to achieve.
- error.BrokenPipe => {},
-
- else => log.warn(
- "error writing to read thread quit pipe err={}",
- .{err},
+ switch (posix.errno(posix.system.write(exec.read_thread_pipe, "x", 1))) {
+ .SUCCESS => {},
+
+ // EPIPE means that our read thread is closed already, which is
+ // completely fine since that is what we were trying to achieve.
+ .PIPE => {},
+
+ else => |e| log.warn(
+ "error writing to read thread quit pipe err=E{s}",
+ .{@tagName(e)},
),
- };
+ }
if (comptime builtin.os.tag == .windows) {
// Interrupt the blocking read so the thread can see the quit message
- if (windows.kernel32.CancelIoEx(exec.read_thread_fd, null) == 0) {
- switch (windows.kernel32.GetLastError()) {
+ if (windows.exp.kernel32.CancelIoEx(exec.read_thread_fd, null) == windows.FALSE) {
+ switch (windows.GetLastError()) {
.NOT_FOUND => {},
else => |err| log.warn("error interrupting read thread err={}", .{err}),
}
@@ -275,23 +277,18 @@ fn processExitCommon(td: *termio.Termio.ThreadData, exit_code: u32) void {
execdata.exited = true;
// Determine how long the process was running for.
- const runtime_ms: ?u64 = runtime: {
- const process_end = std.time.Instant.now() catch break :runtime null;
- const runtime_ns = process_end.since(execdata.start);
- const runtime_ms = runtime_ns / std.time.ns_per_ms;
- break :runtime runtime_ms;
- };
- log.debug(
- "child process exited status={} runtime={}ms",
- .{ exit_code, runtime_ms orelse 0 },
+ const runtime_ms: u64 = @max(
+ 0,
+ execdata.start.untilNow(global.io(), .awake).toMilliseconds(),
);
+ log.debug("child process exited status={} runtime={}ms", .{ exit_code, runtime_ms });
// We always notify the surface immediately that the child has
// exited and some metadata about the exit.
_ = td.surface_mailbox.push(.{
.child_exited = .{
.exit_code = exit_code,
- .runtime_ms = runtime_ms orelse 0,
+ .runtime_ms = runtime_ms,
},
}, .{ .forever = {} });
}
@@ -372,8 +369,8 @@ fn termiosTimer(
// If our password input state changed on the terminal then
// we notify the surface.
{
- td.renderer_state.mutex.lock();
- defer td.renderer_state.mutex.unlock();
+ td.renderer_state.mutex.lockUncancelable(global.io());
+ defer td.renderer_state.mutex.unlock(global.io());
const t = td.renderer_state.terminal;
if (t.flags.password_input == password_input) {
break :mode_change;
@@ -420,8 +417,9 @@ pub fn queueWrite(
// our cached buffers that we can queue to the stream.
var i: usize = 0;
while (i < data.len) {
- const req = try exec.write_req_pool.getGrow(alloc);
- const buf = try exec.write_buf_pool.getGrow(alloc);
+ const w = try exec.write_pool.create(alloc);
+ w.td = exec;
+ const buf = &w.buf;
const slice = slice: {
// The maximum end index is either the end of our data or
// the end of our buffer, whichever is smaller.
@@ -461,26 +459,25 @@ pub fn queueWrite(
exec.write_stream.queueWrite(
td.loop,
&exec.write_queue,
- req,
+ &w.req,
.{ .slice = slice },
- termio.Exec.ThreadData,
- exec,
+ ThreadData.Write,
+ w,
ttyWrite,
);
}
}
fn ttyWrite(
- td_: ?*ThreadData,
+ w_: ?*ThreadData.Write,
_: *xev.Loop,
_: *xev.Completion,
_: xev.Stream,
_: xev.WriteBuffer,
r: xev.WriteError!usize,
) xev.CallbackAction {
- const td = td_.?;
- td.write_req_pool.put();
- td.write_buf_pool.put();
+ const w = w_.?;
+ w.td.write_pool.destroy(w);
const d = r catch |err| {
log.err("write error: {}", .{err});
@@ -494,12 +491,24 @@ fn ttyWrite(
/// The thread local data for the exec implementation.
pub const ThreadData = struct {
- // The preallocation size for the write request pool. This should be big
- // enough to satisfy most write requests. It must be a power of 2.
- const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5);
+ /// The state for a single queued pty write. The write request and
+ /// the buffer it writes from must both remain pointer-stable until
+ /// the write completes, so they're pooled together and checked out
+ /// per write.
+ pub const Write = struct {
+ /// Backpointer to the thread data so the write completion
+ /// callback can put this back into the pool.
+ td: *ThreadData,
+
+ /// The libxev write request.
+ req: xev.WriteRequest,
+
+ /// The buffer for the data being written.
+ buf: [64]u8,
+ };
/// Process start time and boolean of whether its already exited.
- start: std.time.Instant,
+ start: std.Io.Timestamp,
exited: bool = false,
/// The data stream is the main IO for the pty.
@@ -508,12 +517,9 @@ pub const ThreadData = struct {
/// The process watcher
process: ?xev.Process,
- /// This is the pool of available (unused) write requests. If you grab
+ /// This is the pool of available (unused) write states. If you grab
/// one from the pool, you must put it back when you're done!
- write_req_pool: SegmentedPool(xev.WriteRequest, WRITE_REQ_PREALLOC) = .{},
-
- /// The pool of available buffers for writing to the pty.
- write_buf_pool: SegmentedPool([64]u8, WRITE_REQ_PREALLOC) = .{},
+ write_pool: std.heap.MemoryPool(Write) = .empty,
/// The write queue for the data stream.
write_queue: xev.WriteQueue = .{},
@@ -541,13 +547,12 @@ pub const ThreadData = struct {
termios_mode: ptypkg.Mode = .{},
pub fn deinit(self: *ThreadData, alloc: Allocator) void {
- posix.close(self.read_thread_pipe);
+ _ = posix.system.close(self.read_thread_pipe);
- // Clear our write pools. We know we aren't ever going to do
+ // Clear our write pool. We know we aren't ever going to do
// any more IO since we stop our data stream below so we can just
// drop this.
- self.write_req_pool.deinit(alloc);
- self.write_buf_pool.deinit(alloc);
+ self.write_pool.deinit(alloc);
// Stop our process watcher
if (self.process) |*p| p.deinit();
@@ -671,10 +676,13 @@ const Subprocess = struct {
}
var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
- const exe_bin_path = std.fs.selfExePath(&exe_buf) catch |err| {
+ const exe_bin_path = exe_buf[0 .. std.process.executablePath(
+ global.io(),
+ &exe_buf,
+ ) catch |err| {
log.warn("failed to get ghostty exe path err={}", .{err});
break :ghostty_path;
- };
+ }];
const exe_dir = std.fs.path.dirname(exe_bin_path) orelse break :ghostty_path;
log.debug("appending ghostty bin to path dir={s}", .{exe_dir});
@@ -695,7 +703,7 @@ const Subprocess = struct {
try env.put(
"PATH",
- try internal_os.appendEnv(alloc, path, exe_dir),
+ try appendEnv(alloc, path, exe_dir),
);
} else {
try env.put("PATH", exe_dir);
@@ -713,7 +721,7 @@ const Subprocess = struct {
if (std.fmt.bufPrint(&buf, "{s}/..", .{resources_dir})) |data_dir| {
try env.put(
xdg_data_dir_key,
- try internal_os.appendEnv(
+ try appendEnv(
alloc,
env.get(xdg_data_dir_key) orelse "/usr/local/share:/usr/share",
data_dir,
@@ -730,7 +738,7 @@ const Subprocess = struct {
// path instead of overriding all paths set by OS.
try env.put(
manpath_key,
- try internal_os.appendEnvAlways(
+ try appendEnvAlways(
alloc,
env.get(manpath_key) orelse "",
man_dir,
@@ -749,7 +757,7 @@ const Subprocess = struct {
// VTE_VERSION is set by gnome-terminal and other VTE-based terminals.
// We don't want our child processes to think we're running under VTE.
// This is not apprt-specific, so we do it here.
- env.remove("VTE_VERSION");
+ _ = env.orderedRemove("VTE_VERSION");
// Setup our shell integration, if we can.
const shell_command: configpkg.Command = shell: {
@@ -907,7 +915,7 @@ const Subprocess = struct {
self.pty = pty;
errdefer if (!in_child) {
if (comptime builtin.os.tag != .windows) {
- _ = posix.close(pty.slave);
+ _ = posix.system.close(pty.slave);
}
pty.deinit();
@@ -921,7 +929,7 @@ const Subprocess = struct {
// Once our subcommand is started we can close the slave
// side. This prevents the slave fd from being leaked to
// future children.
- _ = posix.close(pty.slave);
+ _ = posix.system.close(pty.slave);
}
// Successful start we can clear out some memory.
@@ -944,8 +952,12 @@ const Subprocess = struct {
//
// https://docs.flatpak.org/en/latest/sandbox-permissions.html#reserved-paths
log.info("flatpak detected, will use host command to verify cwd access", .{});
- const dev_null = try std.fs.cwd().openFile("/dev/null", .{ .mode = .read_write });
- defer dev_null.close();
+ const dev_null = try std.Io.Dir.cwd().openFile(
+ global.io(),
+ "/dev/null",
+ .{ .mode = .read_write },
+ );
+ defer dev_null.close(global.io());
var cmd: internal_os.FlatpakHostCommand = .{
.argv = &[_][]const u8{
"/bin/sh",
@@ -966,7 +978,7 @@ const Subprocess = struct {
break :cwd proposed;
}
- if (std.fs.cwd().access(proposed, .{})) {
+ if (std.Io.Dir.cwd().access(global.io(), proposed, .{})) {
break :cwd proposed;
} else |err| {
log.warn("cannot access cwd, ignoring: {}", .{err});
@@ -1011,9 +1023,18 @@ const Subprocess = struct {
.args = self.args,
.env = if (self.env) |*env| env else null,
.cwd = cwd,
- .stdin = if (builtin.os.tag == .windows) null else .{ .handle = pty.slave },
- .stdout = if (builtin.os.tag == .windows) null else .{ .handle = pty.slave },
- .stderr = if (builtin.os.tag == .windows) null else .{ .handle = pty.slave },
+ .stdin = if (builtin.os.tag == .windows) null else .{
+ .handle = pty.slave,
+ .flags = .{ .nonblocking = false },
+ },
+ .stdout = if (builtin.os.tag == .windows) null else .{
+ .handle = pty.slave,
+ .flags = .{ .nonblocking = false },
+ },
+ .stderr = if (builtin.os.tag == .windows) null else .{
+ .handle = pty.slave,
+ .flags = .{ .nonblocking = false },
+ },
.pseudo_console = if (builtin.os.tag == .windows) pty.pseudo_console else {},
.os_pre_exec = switch (comptime builtin.os.tag) {
.windows => null,
@@ -1140,8 +1161,8 @@ const Subprocess = struct {
if (command.pid) |pid| {
switch (builtin.os.tag) {
.windows => {
- if (windows.kernel32.TerminateProcess(pid, 0) == 0) {
- return windows.unexpectedError(windows.kernel32.GetLastError());
+ if (windows.exp.kernel32.TerminateProcess(pid, 0) == windows.FALSE) {
+ return windows.unexpectedError(windows.GetLastError());
}
_ = try command.wait(false);
@@ -1182,10 +1203,14 @@ const Subprocess = struct {
// The gist is that it lets us detect when children
// are still alive without blocking so that we can
// kill them again.
- const res = posix.waitpid(pid, std.c.W.NOHANG);
- log.debug("waitpid result={}", .{res.pid});
- if (res.pid != 0) break;
- std.Thread.sleep(10 * std.time.ns_per_ms);
+ const res_pid = while (true) {
+ const rc = posix.system.waitpid(pid, null, std.c.W.NOHANG);
+ if (posix.errno(rc) == .INTR) continue;
+ break rc;
+ };
+ log.debug("waitpid result={}", .{res_pid});
+ if (res_pid != 0) break;
+ try std.Io.sleep(global.io(), .fromMilliseconds(10), .awake);
}
}
@@ -1205,7 +1230,7 @@ const Subprocess = struct {
const pgid = c.getpgid(pid);
if (pgid == my_pgid) {
log.warn("pgid is our own, retrying", .{});
- std.Thread.sleep(10 * std.time.ns_per_ms);
+ std.Io.sleep(global.io(), .fromMilliseconds(10), .awake) catch {};
continue;
}
@@ -1336,15 +1361,15 @@ pub const ReadThread = struct {
/// stage at a time, so buffer contents need no locking. Only the
/// ring metadata is guarded by the mutex.
const Pipeline = struct {
- mutex: std.Thread.Mutex = .{},
+ mutex: std.Io.Mutex = .init,
/// Signaled when a batch is published or the gather stage is
/// done. Waited on by the parse stage.
- batch_ready: std.Thread.Condition = .{},
+ batch_ready: std.Io.Condition = .init,
/// Signaled when a batch has been consumed. Waited on by the
/// gather stage when all buffers are in flight (backpressure).
- slot_free: std.Thread.Condition = .{},
+ slot_free: std.Io.Condition = .init,
/// The number of valid bytes in each buffer. Set at publish
/// time by the gather stage, read by the parse stage.
@@ -1385,7 +1410,7 @@ pub const ReadThread = struct {
fn threadMainPosix(fd: posix.fd_t, io: *termio.Termio, quit: posix.fd_t) void {
// Always close our end of the pipe when we exit.
- defer posix.close(quit);
+ defer _ = posix.system.close(quit);
// Right now, on Darwin, `std.Thread.setName` can only name the current
// thread, and we have no way to get the current thread from within it,
@@ -1419,7 +1444,7 @@ pub const ReadThread = struct {
// can't create it we still run correctly, bridge polls are
// just bounded by their timeout instead of being interrupted
// when the parse stage goes idle.
- if (posix.pipe2(.{
+ if (compat_fd.pipe2(.{
.CLOEXEC = true,
.NONBLOCK = true,
})) |fds| {
@@ -1429,8 +1454,8 @@ pub const ReadThread = struct {
log.warn("read thread failed to create idle pipe err={}", .{err});
}
defer if (pipeline.idle_read_fd >= 0) {
- posix.close(pipeline.idle_read_fd);
- posix.close(pipeline.idle_write_fd);
+ compat_fd.close(pipeline.idle_read_fd);
+ compat_fd.close(pipeline.idle_write_fd);
};
const gather_thread = std.Thread.spawn(
@@ -1446,7 +1471,7 @@ pub const ReadThread = struct {
};
defer gather_thread.join();
if (comptime !builtin.os.tag.isDarwin()) {
- gather_thread.setName("io-gather") catch {};
+ gather_thread.setName(global.io(), "io-gather") catch {};
}
// This thread is the parse stage. We consume batches in ring
@@ -1454,11 +1479,11 @@ pub const ReadThread = struct {
// the ring is drained.
while (true) {
const batch: []const u8 = batch: {
- pipeline.mutex.lock();
- defer pipeline.mutex.unlock();
+ pipeline.mutex.lockUncancelable(global.io());
+ defer pipeline.mutex.unlock(global.io());
while (pipeline.count == 0) {
if (pipeline.done) return;
- pipeline.batch_ready.wait(&pipeline.mutex);
+ pipeline.batch_ready.waitUncancelable(global.io(), &pipeline.mutex);
}
const slot = pipeline.tail;
break :batch pipeline.bufs[slot][0..pipeline.lens[slot]];
@@ -1469,27 +1494,27 @@ pub const ReadThread = struct {
io.processOutput(batch);
{
- pipeline.mutex.lock();
+ pipeline.mutex.lockUncancelable(global.io());
pipeline.tail = (pipeline.tail + 1) % buffer_count;
pipeline.count -= 1;
const wake = pipeline.count == 0 and
pipeline.bridging and
pipeline.idle_write_fd >= 0;
- pipeline.mutex.unlock();
- pipeline.slot_free.signal();
+ pipeline.mutex.unlock(global.io());
+ pipeline.slot_free.signal(global.io());
// We ran out of batches while the gather stage is
// bridging a refill gap: interrupt its poll so it
// delivers what it has instead of sleeping out the
// timeout while we sit idle.
if (wake) {
- _ = posix.write(pipeline.idle_write_fd, "i") catch {};
+ _ = posix.system.write(pipeline.idle_write_fd, "i", 1);
}
}
// Batch boundary: hand the renderer state mutex off if
// the renderer is waiting. See renderer.State.lockDemand.
- io.renderer_state.yieldToDemand();
+ io.renderer_state.yieldToDemand(global.io());
}
}
@@ -1506,10 +1531,10 @@ pub const ReadThread = struct {
// However we exit, tell the parse stage the stream is over so
// it drains the ring and joins us.
defer {
- pipeline.mutex.lock();
+ pipeline.mutex.lockUncancelable(global.io());
pipeline.done = true;
- pipeline.mutex.unlock();
- pipeline.batch_ready.signal();
+ pipeline.mutex.unlock(global.io());
+ pipeline.batch_ready.signal(global.io());
}
// The fds we poll: data on the pty, our quit notification,
@@ -1528,16 +1553,16 @@ pub const ReadThread = struct {
// we should stop reading and let the kernel queue exert
// backpressure on the child.
const buf: *[buffer_capacity]u8 = buf: {
- pipeline.mutex.lock();
- defer pipeline.mutex.unlock();
+ pipeline.mutex.lockUncancelable(global.io());
+ defer pipeline.mutex.unlock(global.io());
while (pipeline.count == buffer_count) {
- pipeline.slot_free.wait(&pipeline.mutex);
+ pipeline.slot_free.waitUncancelable(global.io(), &pipeline.mutex);
}
break :buf &pipeline.bufs[pipeline.head];
};
var total: usize = 0;
- var bridge_start: ?std.time.Instant = null;
+ var bridge_start: ?std.Io.Timestamp = null;
var spins: usize = 0;
var fatal = false;
@@ -1566,10 +1591,9 @@ pub const ReadThread = struct {
// Still dry, so we want to sleep in poll for
// the next refill, within our latency budget.
- const now = std.time.Instant.now() catch
- break :gather;
+ const now = std.Io.Timestamp.now(global.io(), .awake);
if (bridge_start) |start| {
- if (now.since(start) >= gather_budget_ns)
+ if (start.durationTo(now).toNanoseconds() >= gather_budget_ns)
break :gather;
} else bridge_start = now;
@@ -1587,8 +1611,8 @@ pub const ReadThread = struct {
// the idle wake so it can interrupt our poll
// the moment that changes.
{
- pipeline.mutex.lock();
- defer pipeline.mutex.unlock();
+ pipeline.mutex.lockUncancelable(global.io());
+ defer pipeline.mutex.unlock(global.io());
if (pipeline.count == 0) break :gather;
pipeline.bridging = true;
}
@@ -1671,12 +1695,12 @@ pub const ReadThread = struct {
// Publish the batch (if any) to the parse stage and rotate
// to the next buffer.
if (total > 0) {
- pipeline.mutex.lock();
+ pipeline.mutex.lockUncancelable(global.io());
pipeline.lens[pipeline.head] = total;
pipeline.head = (pipeline.head + 1) % buffer_count;
pipeline.count += 1;
- pipeline.mutex.unlock();
- pipeline.batch_ready.signal();
+ pipeline.mutex.unlock(global.io());
+ pipeline.batch_ready.signal(global.io());
}
if (fatal) return;
@@ -1710,8 +1734,8 @@ pub const ReadThread = struct {
/// Clears the bridging flag armed before a bridge poll, closing
/// the window in which the parse stage writes idle wakes.
fn clearBridging(pipeline: *Pipeline) void {
- pipeline.mutex.lock();
- defer pipeline.mutex.unlock();
+ pipeline.mutex.lockUncancelable(global.io());
+ defer pipeline.mutex.unlock(global.io());
pipeline.bridging = false;
}
@@ -1730,30 +1754,29 @@ pub const ReadThread = struct {
/// Sets the fd to non-blocking mode. Returns false on failure.
fn setNonblock(fd: posix.fd_t) bool {
- const flags = posix.fcntl(
- fd,
- posix.F.GETFL,
- 0,
- ) catch |err| {
- log.warn("read thread failed to get flags err={}", .{err});
+ const flags = posix.system.fcntl(fd, posix.F.GETFL);
+ if (flags == -1) {
+ log.warn("read thread failed to get flags err=E{s}", .{@tagName(posix.errno(-1))});
return false;
- };
+ }
- _ = posix.fcntl(
+ switch (posix.errno(posix.system.fcntl(
fd,
posix.F.SETFL,
flags | @as(u32, @bitCast(posix.O{ .NONBLOCK = true })),
- ) catch |err| {
- log.warn("read thread failed to set flags err={}", .{err});
- return false;
- };
-
+ ))) {
+ .SUCCESS => {},
+ else => |e| {
+ log.warn("read thread failed to set flags err=E{s}", .{@tagName(e)});
+ return false;
+ },
+ }
return true;
}
fn threadMainWindows(fd: posix.fd_t, io: *termio.Termio, quit: posix.fd_t) void {
// Always close our end of the pipe when we exit.
- defer posix.close(quit);
+ defer _ = posix.system.close(quit);
// Setup our crash metadata
crash.sentry.thread_state = .{
@@ -1766,8 +1789,8 @@ pub const ReadThread = struct {
while (true) {
while (true) {
var n: windows.DWORD = 0;
- if (windows.kernel32.ReadFile(fd, &buf, buf.len, &n, null) == 0) {
- const err = windows.kernel32.GetLastError();
+ if (windows.exp.kernel32.ReadFile(fd, &buf, buf.len, &n, null) == windows.FALSE) {
+ const err = windows.GetLastError();
switch (err) {
// Check for a quit signal
.OPERATION_ABORTED => break,
@@ -1784,12 +1807,12 @@ pub const ReadThread = struct {
// See threadMainPosix: hand the renderer state mutex
// off if the renderer is waiting, since this loop
// would otherwise starve it under heavy output.
- io.renderer_state.yieldToDemand();
+ io.renderer_state.yieldToDemand(global.io());
}
var quit_bytes: windows.DWORD = 0;
- if (windows.exp.kernel32.PeekNamedPipe(quit, null, 0, null, &quit_bytes, null) == 0) {
- const err = windows.kernel32.GetLastError();
+ if (windows.exp.kernel32.PeekNamedPipe(quit, null, 0, null, &quit_bytes, null) == windows.FALSE) {
+ const err = windows.GetLastError();
log.err("quit pipe reader error err={}", .{err});
unreachable;
}
@@ -1837,16 +1860,16 @@ fn execCommand(
};
const hush = if (passwd.home) |home| hush: {
- var dir = std.fs.openDirAbsolute(home, .{}) catch |err| {
+ var dir = std.Io.Dir.openDirAbsolute(global.io(), home, .{}) catch |err| {
log.warn(
"failed to open home dir, not checking for hushlogin err={}",
.{err},
);
break :hush false;
};
- defer dir.close();
+ defer dir.close(global.io());
- break :hush if (dir.access(".hushlogin", .{})) true else |_| false;
+ break :hush if (dir.access(global.io(), ".hushlogin", .{})) true else |_| false;
} else false;
// If we made it this far we're going to start building
@@ -1945,7 +1968,7 @@ fn execCommand(
return switch (command) {
// We need to clone the command since there's no guarantee the config remains valid.
- .direct => |_| (try command.clone(alloc)).direct,
+ .direct => (try command.clone(alloc)).direct,
.shell => |v| shell: {
var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 4);
@@ -1973,7 +1996,7 @@ fn execCommand(
// Other values are passed as-is and resolved by
// `internal_os.path.expand` in Command.startWindows.
const argv0 = if (std.ascii.eqlIgnoreCase(v, "cmd.exe"))
- std.process.getEnvVarOwned(alloc, "COMSPEC") catch
+ global.environ().getAlloc(alloc, "COMSPEC") catch
try alloc.dupe(u8, v)
else
try alloc.dupe(u8, v);
@@ -2002,6 +2025,37 @@ fn execCommand(
};
}
+/// Append a value to an environment variable such as PATH.
+/// The returned value is always allocated so it must be freed.
+fn appendEnv(
+ alloc: Allocator,
+ current: []const u8,
+ value: []const u8,
+) Allocator.Error![]u8 {
+ // If there is no prior value, we return it as-is
+ if (current.len == 0) return try alloc.dupe(u8, value);
+
+ // Otherwise we must prefix.
+ return try appendEnvAlways(alloc, current, value);
+}
+
+/// Always append value to environment, even when it is empty.
+/// This is useful because some env vars (like MANPATH) want there
+/// to be an empty prefix to preserve existing values.
+///
+/// The returned value is always allocated so it must be freed.
+fn appendEnvAlways(
+ alloc: Allocator,
+ current: []const u8,
+ value: []const u8,
+) Allocator.Error![]u8 {
+ return try std.fmt.allocPrint(alloc, "{s}{c}{s}", .{
+ current,
+ std.fs.path.delimiter,
+ value,
+ });
+}
+
/// Get information about the process(es) running within the backend. Returns
/// `null` if there was an error getting the information or the information is
/// not available on a particular platform.
@@ -2190,7 +2244,7 @@ test "execCommand windows: bare cmd.exe resolves via COMSPEC" {
try testing.expectEqual(1, result.len);
// Expect COMSPEC if available, otherwise the documented fallback.
- const expected = std.process.getEnvVarOwned(alloc, "COMSPEC") catch
+ const expected = testing.environ.getAlloc(alloc, "COMSPEC") catch
try alloc.dupe(u8, "C:\\Windows\\System32\\cmd.exe");
try testing.expectEqualStrings(expected, result[0]);
}