update comments
parent
5156723070
commit
17775fa857
|
|
@ -13,10 +13,24 @@ pub const Options = struct {
|
|||
};
|
||||
|
||||
/// The `_macos-disclaim` command is an internal-only Ghostty command that
|
||||
/// is only available on macOS. It uses private posix_spawn APIs to
|
||||
/// make the child process the "responssible process" in macOS so it is
|
||||
/// in charge of its own TCC (permissions like Downloads folder access or
|
||||
/// camera) and resource accounting rather than Ghostty.
|
||||
/// is only available on macOS. It acts as a trampoline that uses private
|
||||
/// posix_spawn APIs to make the child process the "responsible process" in
|
||||
/// macOS so it is in charge of its own TCC (permissions like Downloads folder
|
||||
/// access or camera) and resource accounting rather than Ghostty.
|
||||
///
|
||||
/// ## Responsible Process Concept
|
||||
///
|
||||
/// In macOS, the "responsible process" is a system-level attribution mechanism
|
||||
/// used for TCC (Transparency, Consent, and Control) permissions and resource
|
||||
/// accounting (energy, memory, etc.). When a process spawns children, those
|
||||
/// children normally inherit the parent's responsible process designation. This
|
||||
/// means when programs launched from Ghostty request permissions or consume
|
||||
/// resources, macOS attributes those actions to Ghostty itself.
|
||||
///
|
||||
/// By using the private `responsibility_spawnattrs_setdisclaim` API, we make
|
||||
/// the spawned process responsible for itself rather than being attributed to
|
||||
/// Ghostty. This ensures that TCC prompts and resource accounting are correctly
|
||||
/// associated with the actual program being run, not the terminal emulator.
|
||||
pub fn run(alloc: Allocator) !u8 {
|
||||
// This helper is only for Apple systems. POSIX in general has posix_spawn
|
||||
// but we only use it on Apple platforms because it lets us shed our
|
||||
|
|
@ -33,7 +47,7 @@ pub fn run(alloc: Allocator) !u8 {
|
|||
_ = arg_iter.skip();
|
||||
_ = arg_iter.skip();
|
||||
|
||||
// Collect remaining args for exec
|
||||
// Collect remaining args for exec.
|
||||
var args: std.ArrayList(?[*:0]const u8) = .empty;
|
||||
defer args.deinit(alloc);
|
||||
while (arg_iter.next()) |arg| try args.append(alloc, arg);
|
||||
|
|
@ -47,7 +61,9 @@ pub fn run(alloc: Allocator) !u8 {
|
|||
defer posix_spawn.spawn_attr.destroy(&attrs);
|
||||
{
|
||||
try posix_spawn.spawn_attr.setflags(&attrs, .{
|
||||
// Act like exec(): replace this process.
|
||||
// POSIX_SPAWN_SETEXEC is a macOS extension that makes posix_spawn
|
||||
// behave like exec(), replacing the current process image with the
|
||||
// spawned program.
|
||||
.setexec = true,
|
||||
});
|
||||
|
||||
|
|
@ -57,6 +73,8 @@ pub fn run(alloc: Allocator) !u8 {
|
|||
try posix_spawn.spawn_attr.disclaim(&attrs, true);
|
||||
}
|
||||
|
||||
// On success, this call DOES NOT RETURN because POSIX_SPAWN_SETEXEC
|
||||
// replaces this process image. On failure, we log and return 1.
|
||||
_ = posix_spawn.spawnp(
|
||||
std.mem.span(args.items[0].?),
|
||||
null,
|
||||
|
|
@ -71,6 +89,8 @@ pub fn run(alloc: Allocator) !u8 {
|
|||
return 1;
|
||||
};
|
||||
|
||||
// We set the exec flag so we can't reach this point.
|
||||
// Unreachable because POSIX_SPAWN_SETEXEC replaces this process on success.
|
||||
// If we reach here, either the spawn failed (handled above) or the platform
|
||||
// didn't honor SETEXEC (which would be a serious bug).
|
||||
unreachable;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,34 @@ const errno = std.posix.errno;
|
|||
const fd_t = std.posix.fd_t;
|
||||
const pid_t = std.posix.pid_t;
|
||||
|
||||
// Zig's standard library doesn't yet wrap posix_spawnattr_setsigdefault,
|
||||
// so we declare it here. This sets the signals that will be set to SIG_DFL
|
||||
// in the spawned child process. See man 3 posix_spawnattr_setsigdefault
|
||||
// for details. Only takes effect when used with POSIX_SPAWN_SETSIGDEF flag.
|
||||
extern "c" fn posix_spawnattr_setsigdefault(
|
||||
attr: *c.posix_spawnattr_t,
|
||||
sigdefault: *const std.posix.sigset_t,
|
||||
) c_int;
|
||||
|
||||
// This function is not part of any public Apple header and is not documented
|
||||
// in man pages. It controls whether a spawned process inherits the "responsible
|
||||
// process" designation from its parent for purposes of TCC (Transparency, Consent,
|
||||
// and Control) permissions and resource accounting.
|
||||
//
|
||||
// When `disclaim` is true, the spawned process becomes responsible for itself
|
||||
// rather than being attributed to the spawning process. This is critical for
|
||||
// terminal emulators to avoid having all child processes' permission requests
|
||||
// and resource usage attributed to the terminal itself.
|
||||
//
|
||||
// References:
|
||||
// - https://www.qt.io/blog/the-curious-case-of-the-responsible-process
|
||||
// - Reverse-engineered from various open source projects
|
||||
extern "c" fn responsibility_spawnattrs_setdisclaim(
|
||||
attrs: *const c.posix_spawnattr_t,
|
||||
disclaim: bool,
|
||||
) c_int;
|
||||
|
||||
/// Spawn a new process using PATH resolution.
|
||||
pub fn spawnp(
|
||||
path: [:0]const u8,
|
||||
actions: ?*file_actions.T,
|
||||
|
|
@ -91,6 +110,10 @@ pub const file_actions = struct {
|
|||
};
|
||||
}
|
||||
|
||||
/// Change working directory in the spawned process.
|
||||
///
|
||||
/// Uses the non-portable (_np suffix) addchdir function which is available
|
||||
/// on Darwin and some other platforms.
|
||||
pub fn chdir(
|
||||
actions: *T,
|
||||
path: [*:0]const u8,
|
||||
|
|
@ -136,6 +159,14 @@ pub const spawn_attr = struct {
|
|||
};
|
||||
}
|
||||
|
||||
/// Set signals to default (SIG_DFL) in the spawned process.
|
||||
///
|
||||
/// This function sets which signals should be reset to their default
|
||||
/// handlers in the child process. Only takes effect when the
|
||||
/// POSIX_SPAWN_SETSIGDEF flag is set in the spawn attributes.
|
||||
///
|
||||
/// This is typically paired with Flags.setsigdef = true to ensure
|
||||
/// the child doesn't inherit custom signal handlers from the parent.
|
||||
pub fn setsigdefault(attr: *T, sigdefault: *const std.posix.sigset_t) UnexpectedError!void {
|
||||
return switch (errno(posix_spawnattr_setsigdefault(
|
||||
attr,
|
||||
|
|
@ -146,8 +177,9 @@ pub const spawn_attr = struct {
|
|||
};
|
||||
}
|
||||
|
||||
/// This is undocumented, private API, so I'll link to some resources
|
||||
/// here: https://www.qt.io/blog/the-curious-case-of-the-responsible-process
|
||||
/// Set the "disclaim" flag for macOS responsible process handling.
|
||||
///
|
||||
/// See: https://www.qt.io/blog/the-curious-case-of-the-responsible-process
|
||||
pub fn disclaim(attr: *T, v: bool) UnexpectedError!void {
|
||||
return switch (errno(responsibility_spawnattrs_setdisclaim(
|
||||
attr,
|
||||
|
|
@ -159,20 +191,24 @@ pub const spawn_attr = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// POSIX spawn flags with Apple/Darwin extensions.
|
||||
///
|
||||
/// Note: Several fields are Apple-specific extensions and will not work on
|
||||
/// other POSIX systems.
|
||||
pub const Flags = packed struct(c_short) {
|
||||
resetids: bool = false,
|
||||
setpgroup: bool = false,
|
||||
setsigdef: bool = false,
|
||||
setsigmask: bool = false,
|
||||
resetids: bool = false, // Reset effective UID/GID to real UID/GID
|
||||
setpgroup: bool = false, // Set process group
|
||||
setsigdef: bool = false, // Reset signals to SIG_DFL (see setsigdefault)
|
||||
setsigmask: bool = false, // Set signal mask in child
|
||||
_pad1: u2 = 0,
|
||||
setexec: bool = false,
|
||||
start_suspended: bool = false,
|
||||
disable_aslr: bool = false,
|
||||
setexec: bool = false, // Replace current process image (like exec)
|
||||
start_suspended: bool = false, // Start process suspended (debugging)
|
||||
disable_aslr: bool = false, // Disable ASLR for spawned process
|
||||
_pad2: u1 = 0,
|
||||
setsid: bool = false,
|
||||
reslide: bool = false,
|
||||
setsid: bool = false, // Create new session (process becomes session leader)
|
||||
reslide: bool = false, // Re-randomize ASLR slide
|
||||
_pad3: u2 = 0,
|
||||
cloexec_default: bool = false,
|
||||
cloexec_default: bool = false, // Default file descriptors to close-on-exec
|
||||
_pad4: u1 = 0,
|
||||
|
||||
/// Integer value of this struct.
|
||||
|
|
@ -223,6 +259,10 @@ test "spawn_attr.setsigdefault" {
|
|||
}
|
||||
|
||||
test "spawn_attr.disclaim" {
|
||||
// This test uses the private macOS API and will only pass on Darwin
|
||||
const builtin = @import("builtin");
|
||||
if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
|
||||
|
||||
var attr = try spawn_attr.create();
|
||||
defer spawn_attr.destroy(&attr);
|
||||
try spawn_attr.disclaim(&attr, true);
|
||||
|
|
|
|||
|
|
@ -1408,8 +1408,17 @@ fn execCommand(
|
|||
var args: std.ArrayList([:0]const u8) = .empty;
|
||||
defer args.deinit(alloc);
|
||||
|
||||
// If we're on macOS, we use the posix_spawn trampoline no matter
|
||||
// what, so prepend that.
|
||||
// On macOS, we ALWAYS route child processes through our internal
|
||||
// +_macos-disclaim trampoline command. This trampoline uses posix_spawn
|
||||
// with POSIX_SPAWN_SETEXEC and the private responsibility_spawnattrs_setdisclaim
|
||||
// API to ensure the final process is not attributed to Ghostty for TCC
|
||||
// permissions or resource accounting.
|
||||
//
|
||||
// The trampoline immediately replaces itself with the target command, so
|
||||
// no intermediate process remains. The argv structure becomes:
|
||||
// [ghostty_path, "+_macos-disclaim", actual_command, actual_args...]
|
||||
//
|
||||
// See src/cli/macos_disclaim.zig for the trampoline implementation.
|
||||
if (comptime builtin.target.os.tag == .macos) {
|
||||
var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const exe_bin_path = std.fs.selfExePath(&exe_buf) catch |err| {
|
||||
|
|
|
|||
Loading…
Reference in New Issue