mirror of
https://github.com/ziglang/zig.git
synced 2024-11-24 13:20:14 +00:00
std.process.Child: detached process spawning
This commit is contained in:
parent
38e0f049c5
commit
f4e01b0e48
@ -9168,6 +9168,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 malloc(usize) ?*anyopaque;
|
||||
pub extern "c" fn realloc(?*anyopaque, usize) ?*anyopaque;
|
||||
@ -9351,6 +9352,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;
|
||||
|
||||
/// These are implementation defined but share identical values in at least musl and glibc:
|
||||
/// - https://git.musl-libc.org/cgit/musl/tree/include/locale.h?id=ab31e9d6a0fa7c5c408856c89df2dfb12c344039#n18
|
||||
|
@ -1581,6 +1581,10 @@ pub fn setsid() pid_t {
|
||||
return @bitCast(@as(u32, @truncate(syscall0(.setsid))));
|
||||
}
|
||||
|
||||
pub fn getsid(pid: pid_t) pid_t {
|
||||
return @bitCast(@as(u32, @truncate(syscall1(.getsid, @intCast(pid)))));
|
||||
}
|
||||
|
||||
pub fn getpid() pid_t {
|
||||
return @bitCast(@as(u32, @truncate(syscall0(.getpid))));
|
||||
}
|
||||
|
@ -154,6 +154,12 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
||||
}
|
||||
}
|
||||
|
||||
pub fn GetProcessId(Process: HANDLE) UnexpectedError!DWORD {
|
||||
const ret_code = kernel32.GetProcessId(Process);
|
||||
if (ret_code == 0) return unexpectedError(GetLastError());
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
pub fn GetCurrentProcess() HANDLE {
|
||||
const process_pseudo_handle: usize = @bitCast(@as(isize, -1));
|
||||
return @ptrFromInt(process_pseudo_handle);
|
||||
@ -1813,6 +1819,12 @@ pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwL
|
||||
return rc;
|
||||
}
|
||||
|
||||
pub fn GetConsoleProcessList(lpdwProcessList: []DWORD) UnexpectedError!DWORD {
|
||||
const ret_code = kernel32.GetConsoleProcessList(lpdwProcessList.ptr, @min(lpdwProcessList.len, math.maxInt(DWORD)));
|
||||
if (ret_code == 0) return unexpectedError(GetLastError());
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
pub const SetConsoleTextAttributeError = error{Unexpected};
|
||||
|
||||
pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void {
|
||||
@ -3727,6 +3739,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;
|
||||
|
@ -340,6 +340,8 @@ pub extern "kernel32" fn GetExitCodeProcess(
|
||||
// TODO: Already a wrapper for this, see `windows.GetCurrentProcess`.
|
||||
pub extern "kernel32" fn GetCurrentProcess() callconv(WINAPI) HANDLE;
|
||||
|
||||
pub extern "kernel32" fn GetProcessId(Process: HANDLE) callconv(WINAPI) DWORD;
|
||||
|
||||
// TODO: memcpy peb().ProcessParameters.Environment, mem.span(0). Requires locking the PEB.
|
||||
pub extern "kernel32" fn GetEnvironmentStringsW() callconv(WINAPI) ?LPWSTR;
|
||||
|
||||
@ -519,6 +521,11 @@ pub extern "kernel32" fn ReadConsoleOutputCharacterW(
|
||||
lpNumberOfCharsRead: *DWORD,
|
||||
) callconv(windows.WINAPI) BOOL;
|
||||
|
||||
pub extern "kernel32" fn GetConsoleProcessList(
|
||||
lpdwProcessList: [*]DWORD,
|
||||
dwProcessCount: DWORD,
|
||||
) callconv(WINAPI) DWORD;
|
||||
|
||||
// Memory Mapping/Allocation
|
||||
|
||||
// TODO: Wrapper around RtlCreateHeap.
|
||||
|
@ -3438,6 +3438,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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Test whether a file descriptor refers to a terminal.
|
||||
pub fn isatty(handle: fd_t) bool {
|
||||
if (native_os == .windows) {
|
||||
|
@ -57,6 +57,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,
|
||||
|
||||
@ -172,6 +175,7 @@ pub const SpawnError = error{
|
||||
posix.ExecveError ||
|
||||
posix.SetIdError ||
|
||||
posix.SetPgidError ||
|
||||
posix.SetSidError ||
|
||||
posix.ChangeCurDirError ||
|
||||
windows.CreateProcessError ||
|
||||
windows.GetProcessMemoryInfoError ||
|
||||
@ -215,6 +219,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,
|
||||
@ -658,6 +663,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);
|
||||
@ -844,6 +853,10 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
|
||||
.lpReserved2 = null,
|
||||
};
|
||||
var piProcInfo: windows.PROCESS_INFORMATION = undefined;
|
||||
var dwCreationFlags: windows.DWORD = windows.CREATE_UNICODE_ENVIRONMENT;
|
||||
if (self.detached) {
|
||||
dwCreationFlags |= windows.DETACHED_PROCESS;
|
||||
}
|
||||
|
||||
const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
|
||||
defer if (cwd_w) |cwd| self.allocator.free(cwd);
|
||||
@ -928,7 +941,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
|
||||
dir_buf.shrinkRetainingCapacity(normalized_len);
|
||||
}
|
||||
|
||||
windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
|
||||
windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo, dwCreationFlags) catch |no_path_err| {
|
||||
const original_err = switch (no_path_err) {
|
||||
// argv[0] contains unsupported characters that will never resolve to a valid exe.
|
||||
error.InvalidArg0 => return error.FileNotFound,
|
||||
@ -956,7 +969,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
|
||||
const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
|
||||
dir_buf.shrinkRetainingCapacity(normalized_len);
|
||||
|
||||
if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) {
|
||||
if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo, dwCreationFlags)) {
|
||||
break :run;
|
||||
} else |err| switch (err) {
|
||||
// argv[0] contains unsupported characters that will never resolve to a valid exe.
|
||||
@ -1057,6 +1070,7 @@ fn windowsCreateProcessPathExt(
|
||||
cwd_ptr: ?[*:0]u16,
|
||||
lpStartupInfo: *windows.STARTUPINFOW,
|
||||
lpProcessInformation: *windows.PROCESS_INFORMATION,
|
||||
dwCreationFlags: windows.DWORD,
|
||||
) !void {
|
||||
const app_name_len = app_buf.items.len;
|
||||
const dir_path_len = dir_buf.items.len;
|
||||
@ -1205,7 +1219,7 @@ fn windowsCreateProcessPathExt(
|
||||
else
|
||||
full_app_name;
|
||||
|
||||
if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
|
||||
if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation, dwCreationFlags)) |_| {
|
||||
return;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound,
|
||||
@ -1260,7 +1274,7 @@ fn windowsCreateProcessPathExt(
|
||||
else
|
||||
full_app_name;
|
||||
|
||||
if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
|
||||
if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation, dwCreationFlags)) |_| {
|
||||
return;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => continue,
|
||||
@ -1288,6 +1302,7 @@ fn windowsCreateProcess(
|
||||
cwd_ptr: ?[*:0]u16,
|
||||
lpStartupInfo: *windows.STARTUPINFOW,
|
||||
lpProcessInformation: *windows.PROCESS_INFORMATION,
|
||||
dwCreationFlags: windows.DWORD,
|
||||
) !void {
|
||||
// TODO the docs for environment pointer say:
|
||||
// > A pointer to the environment block for the new process. If this parameter
|
||||
@ -1312,7 +1327,7 @@ fn windowsCreateProcess(
|
||||
null,
|
||||
null,
|
||||
windows.TRUE,
|
||||
windows.CREATE_UNICODE_ENVIRONMENT,
|
||||
dwCreationFlags,
|
||||
@as(?*anyopaque, @ptrCast(envp_ptr)),
|
||||
cwd_ptr,
|
||||
lpStartupInfo,
|
||||
@ -1857,3 +1872,37 @@ fn argvToScriptCommandLineWindows(
|
||||
|
||||
return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
|
||||
}
|
||||
|
||||
test "detached child" {
|
||||
const cmd = if (native_os == .windows)
|
||||
&.{ "ping", "-n", "1", "-w", "10000", "127.255.255.255" }
|
||||
else &.{ "sleep", "10" };
|
||||
var child = ChildProcess.init(cmd, std.testing.allocator);
|
||||
child.stdin_behavior = .Ignore;
|
||||
child.stderr_behavior = .Ignore;
|
||||
child.stdout_behavior = .Pipe;
|
||||
child.detached = true;
|
||||
try child.spawn();
|
||||
|
||||
if (native_os == .windows) {
|
||||
const child_pid = try windows.GetProcessId(child.id);
|
||||
|
||||
// Give the process some time to actually start doing something.
|
||||
// If we check the process list immediately, we might be done before
|
||||
// the new process attaches to the console.
|
||||
var read_buffer: [1]u8 = undefined;
|
||||
try std.testing.expectEqual(1, try child.stdout.?.read(&read_buffer));
|
||||
|
||||
var proc_buffer: [16]windows.DWORD = undefined;
|
||||
const proc_count = try windows.GetConsoleProcessList(&proc_buffer);
|
||||
if (proc_count > 16) return error.ProcessBufferTooSmall;
|
||||
|
||||
for (proc_buffer[0..proc_count]) |proc| {
|
||||
if (proc == child_pid) return error.ProcessAttachedToConsole;
|
||||
}
|
||||
} else {
|
||||
const current_sid = try posix.getsid(0);
|
||||
const child_sid = try posix.getsid(child.id);
|
||||
try std.testing.expect(current_sid != child_sid);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user