@@ -18,9 +18,100 @@ fn set_exit_with_parent() {
1818
1919#[ cfg( target_os = "macos" ) ]
2020fn set_exit_with_parent ( ) {
21- // macOS does not have the `prctl` function, so we do nothing.
22- // This means that the child process will not be terminated when
23- // the parent process exits.
21+ // macOS lacks `prctl`, so we use `kqueue`/`kevent` with `EVFILT_PROC`/`NOTE_EXIT`
22+ // to watch for the parent process exiting.
23+ //
24+ // Because this runs in `pre_exec` (after fork, before exec), any thread we spawn
25+ // would be killed by the subsequent `exec` call. Instead, we double-fork a detached
26+ // watcher process: the intermediate process exits immediately (so the watcher is
27+ // re-parented to PID 1), and the watcher blocks on `kevent` until either the parent
28+ // or the child exits.
29+ use libc:: { c_int, close, fork, getpid, getppid, kevent, kqueue, pid_t, waitpid} ;
30+ use libc:: { EVFILT_PROC , EV_ADD , EV_ENABLE , NOTE_EXIT , SIGTERM } ;
31+ use std:: mem;
32+
33+ unsafe {
34+ let child_pid: pid_t = getpid ( ) ;
35+ let parent_pid: pid_t = getppid ( ) ;
36+
37+ let intermediate = fork ( ) ;
38+ if intermediate < 0 {
39+ return ;
40+ }
41+
42+ if intermediate == 0 {
43+ // Intermediate process: fork the watcher and exit immediately so the
44+ // watcher is re-parented to init/launchd and won't become a zombie.
45+ let watcher = fork ( ) ;
46+ if watcher < 0 {
47+ libc:: _exit ( 1 ) ;
48+ }
49+ if watcher == 0 {
50+ // Watcher process: use kqueue to watch parent_pid and child_pid.
51+ let kq = kqueue ( ) ;
52+ if kq == -1 {
53+ libc:: _exit ( 1 ) ;
54+ }
55+
56+ let changes: [ libc:: kevent ; 2 ] = [
57+ libc:: kevent {
58+ ident : parent_pid as libc:: uintptr_t ,
59+ filter : EVFILT_PROC ,
60+ flags : EV_ADD | EV_ENABLE ,
61+ fflags : NOTE_EXIT ,
62+ data : 0 ,
63+ udata : std:: ptr:: null_mut ( ) ,
64+ } ,
65+ libc:: kevent {
66+ ident : child_pid as libc:: uintptr_t ,
67+ filter : EVFILT_PROC ,
68+ flags : EV_ADD | EV_ENABLE ,
69+ fflags : NOTE_EXIT ,
70+ data : 0 ,
71+ udata : std:: ptr:: null_mut ( ) ,
72+ } ,
73+ ] ;
74+
75+ let r = kevent (
76+ kq,
77+ changes. as_ptr ( ) ,
78+ 2 ,
79+ std:: ptr:: null_mut ( ) ,
80+ 0 ,
81+ std:: ptr:: null ( ) ,
82+ ) ;
83+ if r == -1 {
84+ close ( kq) ;
85+ libc:: _exit ( 1 ) ;
86+ }
87+
88+ loop {
89+ let mut res: libc:: kevent = mem:: zeroed ( ) ;
90+ let n = kevent ( kq, std:: ptr:: null ( ) , 0 , & mut res, 1 , std:: ptr:: null ( ) ) ;
91+ if n <= 0 {
92+ break ;
93+ }
94+ if res. ident == parent_pid as libc:: uintptr_t {
95+ // Parent exited: terminate the child.
96+ libc:: kill ( child_pid, SIGTERM ) ;
97+ break ;
98+ } else if res. ident == child_pid as libc:: uintptr_t {
99+ // Child exited on its own: nothing to do.
100+ break ;
101+ }
102+ }
103+
104+ close ( kq) ;
105+ libc:: _exit ( 0 ) ;
106+ } else {
107+ libc:: _exit ( 0 ) ;
108+ }
109+ } else {
110+ // Original child: reap the intermediate process, then continue to exec.
111+ let mut status: c_int = 0 ;
112+ waitpid ( intermediate, & mut status, 0 ) ;
113+ }
114+ }
24115}
25116
26117pub fn exit_with_parent ( ) -> Result < ( ) , std:: io:: Error > {
0 commit comments