This commit is contained in:
antosikv 2025-11-24 16:13:03 -05:00 committed by GitHub
commit 27e7210b7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 270 additions and 1 deletions

View file

@ -10857,6 +10857,7 @@ pub extern "c" fn setregid(rgid: gid_t, egid: gid_t) c_int;
pub extern "c" fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) c_int;
pub extern "c" fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) c_int;
pub extern "c" fn setpgid(pid: pid_t, pgid: pid_t) c_int;
pub extern "c" fn setsid() pid_t;
pub extern "c" fn getuid() uid_t;
pub extern "c" fn getgid() gid_t;
pub extern "c" fn geteuid() uid_t;
@ -11071,6 +11072,7 @@ pub extern "c" fn setlogmask(maskpri: c_int) c_int;
pub extern "c" fn if_nametoindex([*:0]const u8) c_int;
pub extern "c" fn getpid() pid_t;
pub extern "c" fn getsid(pid: pid_t) pid_t;
pub extern "c" fn getppid() pid_t;
pub extern "c" fn setsid() pid_t;

View file

@ -1903,6 +1903,10 @@ pub fn setsid() usize {
return syscall0(.setsid);
}
pub fn getsid(pid: pid_t) pid_t {
return @bitCast(@as(u32, @truncate(syscall1(.getsid, @intCast(pid)))));
}
pub fn getpid() pid_t {
// Casts result to a pid_t, safety-checking >= 0, because getpid() cannot fail
return @intCast(@as(u32, @truncate(syscall0(.getpid))));

View file

@ -3661,6 +3661,7 @@ pub const COORD = extern struct {
Y: SHORT,
};
pub const DETACHED_PROCESS = 8;
pub const CREATE_UNICODE_ENVIRONMENT = 1024;
pub const TLS_OUT_OF_INDEXES = 4294967295;

View file

@ -3141,6 +3141,29 @@ pub fn setpgid(pid: pid_t, pgid: pid_t) SetPgidError!void {
}
}
pub const SetSidError = error{PermissionDenied} || UnexpectedError;
pub fn setsid() SetSidError!pid_t {
const res = system.setsid();
switch (errno(@as(isize, res))) {
.SUCCESS => return res,
.PERM => return error.PermissionDenied,
else => |err| return unexpectedErrno(err),
}
}
pub const GetSidError = error{ProcessNotFound} || SetSidError;
pub fn getsid(pid: pid_t) GetSidError!pid_t {
const res = system.getsid(pid);
switch (errno(@as(isize, res))) {
.SUCCESS => return res,
.PERM => return error.PermissionDenied,
.SRCH => return error.ProcessNotFound,
else => |err| return unexpectedErrno(err),
}
}
pub fn getuid() uid_t {
return system.getuid();
}

View file

@ -62,6 +62,9 @@ stdin_behavior: StdIo,
stdout_behavior: StdIo,
stderr_behavior: StdIo,
/// Set to spawn a detached process.
detached: bool,
/// Set to change the user id when spawning the child process.
uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t,
@ -181,6 +184,7 @@ pub const SpawnError = error{
posix.ExecveError ||
posix.SetIdError ||
posix.SetPgidError ||
posix.SetSidError ||
posix.ChangeCurDirError ||
windows.CreateProcessError ||
windows.GetProcessMemoryInfoError ||
@ -224,6 +228,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
.term = null,
.env_map = null,
.cwd = null,
.detached = false,
.uid = if (native_os == .windows or native_os == .wasi) {} else null,
.gid = if (native_os == .windows or native_os == .wasi) {} else null,
.pgid = if (native_os == .windows or native_os == .wasi) {} else null,
@ -237,13 +242,25 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
};
}
/// Call this if you have no intention of calling `kill` or `wait` to properly
/// dispose of any resources related to the child process.
pub fn deinit(self: *ChildProcess) void {
if (native_os == .windows) {
posix.close(self.thread_handle);
posix.close(self.id);
}
self.cleanupStreams();
}
pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
const user_info = try process.getUserInfo(name);
self.uid = user_info.uid;
self.gid = user_info.gid;
}
/// On success must call `kill` or `wait`.
/// On success must call `kill` or `wait`. In the case of a detached process,
/// consider using `deinit` instead if you have no intention of synchronizing
/// with the child.
/// After spawning the `id` is available.
pub fn spawn(self: *ChildProcess) SpawnError!void {
if (!process.can_spawn) {
@ -643,6 +660,10 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
const pid_result = try posix.fork();
if (pid_result == 0) {
// we are the child
if (self.detached) {
_ = posix.setsid() catch |err| forkChildErrReport(err_pipe[1], err);
}
setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
@ -891,6 +912,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
.create_suspended = self.start_suspended,
.create_unicode_environment = true,
.create_no_window = self.create_no_window,
.detached_process = self.detached,
};
run: {

View file

@ -63,6 +63,12 @@
.child_process = .{
.path = "child_process",
},
.detached_child = .{
.path = "detached_child",
},
.child_spawn_fail = .{
.path = "child_spawn_fail",
},
.embed_generated_file = .{
.path = "embed_generated_file",
},

View file

@ -0,0 +1,32 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
const optimize: std.builtin.OptimizeMode = .Debug;
const target = b.graph.host;
if (builtin.os.tag == .wasi) return;
const child = b.addExecutable(.{
.name = "child",
.root_source_file = b.path("child.zig"),
.optimize = optimize,
.target = target,
});
const main = b.addExecutable(.{
.name = "main",
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = target,
});
const run = b.addRunArtifact(main);
run.addArtifactArg(child);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}

View file

@ -0,0 +1,19 @@
const std = @import("std");
pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer if (gpa_state.deinit() == .leak) @panic("leaks were detected");
const gpa = gpa_state.allocator();
var args = try std.process.argsWithAllocator(gpa);
defer args.deinit();
_ = args.next() orelse unreachable; // skip executable name
const sleep_seconds = try std.fmt.parseInt(u32, args.next() orelse unreachable, 0);
const stdout = std.io.getStdOut();
_ = try stdout.write("started");
const end_time = std.time.timestamp() + sleep_seconds;
while (std.time.timestamp() < end_time) {
std.time.sleep(@max(end_time - std.time.timestamp(), 0) * 1_000_000_000);
}
}

View file

@ -0,0 +1,38 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer if (gpa_state.deinit() == .leak) @panic("memory leak detected");
const gpa = gpa_state.allocator();
var args = try std.process.argsWithAllocator(gpa);
defer args.deinit();
_ = args.next() orelse unreachable; // skip executable name
const child_path = args.next() orelse unreachable;
const argv = &.{""};
var child = std.process.Child.init(argv, gpa);
child.stdin_behavior = .Ignore;
child.stderr_behavior = .Ignore;
child.stdout_behavior = .Pipe;
child.detached = true;
child.pgid = if (builtin.os.tag == .windows) void{} else try std.posix.getsid(0);
defer {
_ = child.kill() catch {};
}
if (child.spawn()) {
if (child.waitForSpawn()) {
return error.SpawnSilencedError;
} else |_| {}
} else |_| {}
child = std.process.Child.init(&.{ child_path, "30" }, gpa);
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Inherit;
// this spawn should succeed and return without an error
try child.spawn();
}

View file

@ -0,0 +1,32 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
const optimize: std.builtin.OptimizeMode = .Debug;
const target = b.graph.host;
if (builtin.os.tag == .wasi) return;
const child = b.addExecutable(.{
.name = "child",
.root_source_file = b.path("child.zig"),
.optimize = optimize,
.target = target,
});
const main = b.addExecutable(.{
.name = "main",
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = target,
});
const run = b.addRunArtifact(main);
run.addArtifactArg(child);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}

View file

@ -0,0 +1,19 @@
const std = @import("std");
pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer if (gpa_state.deinit() == .leak) @panic("leaks were detected");
const gpa = gpa_state.allocator();
var args = try std.process.argsWithAllocator(gpa);
defer args.deinit();
_ = args.next() orelse unreachable; // skip executable name
const sleep_seconds = try std.fmt.parseInt(u32, args.next() orelse unreachable, 0);
const stdout = std.io.getStdOut();
_ = try stdout.write("started");
const end_time = std.time.timestamp() + sleep_seconds;
while (std.time.timestamp() < end_time) {
std.time.sleep(@max(end_time - std.time.timestamp(), 0) * 1_000_000_000);
}
}

View file

@ -0,0 +1,71 @@
const std = @import("std");
const builtin = @import("builtin");
const windows = std.os.windows;
extern "kernel32" fn GetProcessId(Process: windows.HANDLE) callconv(windows.WINAPI) windows.DWORD;
extern "kernel32" fn GetConsoleProcessList(
lpdwProcessList: [*]windows.DWORD,
dwProcessCount: windows.DWORD,
) callconv(windows.WINAPI) windows.DWORD;
pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
defer if (gpa_state.deinit() == .leak) @panic("memory leak detected");
const gpa = gpa_state.allocator();
var args = try std.process.argsWithAllocator(gpa);
defer args.deinit();
_ = args.next() orelse unreachable; // skip executable name
const child_path = args.next() orelse unreachable;
var child = std.process.Child.init(&.{ child_path, "30" }, gpa);
child.stdin_behavior = .Ignore;
child.stderr_behavior = .Inherit;
child.stdout_behavior = .Pipe;
child.detached = true;
try child.spawn();
try child.waitForSpawn();
defer {
_ = child.kill() catch {};
}
// Give the process some time to actually start doing something before
// checking if it properly detached.
var read_buffer: [1]u8 = undefined;
if (try child.stdout.?.read(&read_buffer) != 1) {
return error.OutputReadFailed;
}
switch (builtin.os.tag) {
.windows => {
const child_pid = GetProcessId(child.id);
if (child_pid == 0) return error.GetProcessIdFailed;
var proc_buffer: []windows.DWORD = undefined;
var proc_count: windows.DWORD = 16;
while (true) {
proc_buffer = try gpa.alloc(windows.DWORD, proc_count);
defer gpa.free(proc_buffer);
proc_count = GetConsoleProcessList(proc_buffer.ptr, @min(proc_buffer.len, std.math.maxInt(windows.DWORD)));
if (proc_count == 0) return error.ConsoleProcessListFailed;
if (proc_count <= proc_buffer.len) {
for (proc_buffer[0..proc_count]) |proc| {
if (proc == child_pid) return error.ProcessAttachedToConsole;
}
break;
}
}
},
else => {
const posix = std.posix;
const current_sid = try posix.getsid(0);
const child_sid = try posix.getsid(child.id);
if (current_sid == child_sid) {
return error.SameChildSession;
}
},
}
}