1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT
//! Process-group isolation for the children this library starts itself.
//!
//! # Why a library-owned child needs its own group
//!
//! A host that has to clean up after a session cannot enumerate "my processes" safely: matching
//! by program name kills the user's own `lp`, and scanning the process table is a guess. What a
//! session *can* know exactly is the process groups it created, so that is what hosts use as the
//! ownership criterion — and a child this library spawns inside the host's group would be killed
//! by the host's cleanup as if the host had started it (a browser for [`open_external`], or a
//! spooler whose job the host knows nothing about).
//!
//! The reverse direction is the one the designer feedback reported: a child started here is
//! **not** reachable by the host's group cleanup, so a spooler that forks its own helpers left
//! grandchildren behind with nothing recording that they existed. This module answers both halves
//! with one rule: a child this library starts gets its own group, so "host processes" and
//! "library processes" are disjoint sets that neither side can kill by accident — and the
//! library ends what it started, descendants included.
//!
//! [`open_external`]: crate::platform::Platform::open_external
//!
//! # What is platform-specific here, and where it stays
//!
//! Process groups are a POSIX concept, so the calls are unix-only and a target that has no such
//! concept gets an honest no-op with the reason in the code rather than a `cfg` at every call
//! site (principles #36/#41: the difference is stated once, inside `src/platform/`). On Windows
//! the equivalent ownership unit is a job object, which this crate does not create; the boundary
//! is recorded in [`crate::platform::os_probes`] and here rather than implied.
/// Starts the child of `command` in its own process group, on targets that have process groups.
///
/// Returns the command so a call site reads as one builder chain. On targets without process
/// groups this is a documented no-op: the child then belongs to whatever the host's equivalent
/// ownership unit is, which is a fact a caller cannot change from here.
pub
pub
/// Ends `child` and, on unix, every process in the group it leads.
///
/// # Why the group and not the child
///
/// A spooler does not have to be a single process: `lp` hands the job to a filter chain, each
/// stage a child of its own. Killing only the direct child leaves that chain running with an open
/// handle on a job file the caller is about to delete, and nothing in the library would report
/// it. The group this library created with [`in_own_group`] is exactly the set to end.
///
/// The direct child is still reaped here, so no zombie is left for the rest of the session.
/// Callers keep whatever error reporting they had: this returns nothing, because "the process is
/// gone" is observable in the child's own status.
pub
pub