1
1
use std:: {
2
2
ffi:: c_int,
3
- io,
4
- os:: fd:: OwnedFd ,
5
- process:: { exit, Child , Command } ,
3
+ io:: { self , Read , Write } ,
4
+ os:: {
5
+ fd:: OwnedFd ,
6
+ unix:: { net:: UnixStream , process:: CommandExt } ,
7
+ } ,
8
+ process:: { exit, Command } ,
6
9
time:: Duration ,
7
10
} ;
8
11
9
12
use crate :: log:: { dev_info, dev_warn} ;
10
13
use crate :: system:: {
11
- getpgid, interface:: ProcessId , kill, setpgid, setsid, signal:: SignalInfo ,
12
- term:: set_controlling_terminal,
14
+ fork, getpgid,
15
+ interface:: ProcessId ,
16
+ kill, setpgid, setsid,
17
+ signal:: SignalInfo ,
18
+ term:: { set_controlling_terminal, tcgetpgrp, tcsetpgrp} ,
19
+ wait:: { waitpid, WaitError , WaitOptions } ,
13
20
} ;
14
21
15
22
use signal_hook:: consts:: * ;
@@ -24,7 +31,7 @@ use super::{cond_fmt, signal_fmt};
24
31
// FIXME: This should return `io::Result<!>` but `!` is not stable yet.
25
32
pub ( super ) fn exec_monitor (
26
33
pty_follower : OwnedFd ,
27
- mut command : Command ,
34
+ command : Command ,
28
35
backchannel : & mut MonitorBackchannel ,
29
36
) -> io:: Result < ( ) > {
30
37
let mut dispatcher = EventDispatcher :: < MonitorClosure > :: new ( ) ?;
@@ -46,6 +53,9 @@ pub(super) fn exec_monitor(
46
53
err
47
54
} ) ?;
48
55
56
+ // Use a pipe to get the IO error if `exec_command` fails.
57
+ let ( mut errpipe_tx, errpipe_rx) = UnixStream :: pair ( ) ?;
58
+
49
59
// Wait for the parent to give us green light before spawning the command. This avoids race
50
60
// conditions when the command exits quickly.
51
61
let event = retry_while_interrupted ( || backchannel. recv ( ) ) . map_err ( |err| {
@@ -58,22 +68,35 @@ pub(super) fn exec_monitor(
58
68
59
69
// FIXME (ogsudo): Some extra config happens here if selinux is available.
60
70
61
- // FIXME (ogsudo): Do any additional configuration that needs to be run after `fork` but before `exec`.
62
-
63
- // spawn the command.
64
- let command = command. spawn ( ) . map_err ( |err| {
65
- dev_warn ! ( "cannot spawn command: {err}" ) ;
71
+ let command_pid = fork ( ) . map_err ( |err| {
72
+ dev_warn ! ( "unable to fork command process: {err}" ) ;
66
73
err
67
74
} ) ?;
68
75
69
- let command_pid = command. id ( ) as ProcessId ;
76
+ if command_pid == 0 {
77
+ drop ( errpipe_rx) ;
78
+
79
+ let err = exec_command ( command, pty_follower) ;
80
+ dev_warn ! ( "failed to execute command: {err}" ) ;
81
+ // If `exec_command` returns, it means that executing the command failed. Send the error to
82
+ // the monitor using the pipe.
83
+ if let Some ( error_code) = err. raw_os_error ( ) {
84
+ errpipe_tx. write_all ( & error_code. to_ne_bytes ( ) ) . ok ( ) ;
85
+ }
86
+ drop ( errpipe_tx) ;
87
+ // FIXME: Calling `exit` doesn't run any destructors, clean everything up.
88
+ exit ( 1 )
89
+ }
70
90
71
91
// Send the command's PID to the parent.
72
92
if let Err ( err) = backchannel. send ( & ParentMessage :: CommandPid ( command_pid) ) {
73
93
dev_warn ! ( "cannot send command PID to parent: {err}" ) ;
74
94
}
75
95
76
- let mut closure = MonitorClosure :: new ( command, command_pid, backchannel, & mut dispatcher) ;
96
+ let mut closure = MonitorClosure :: new ( command_pid, errpipe_rx, backchannel, & mut dispatcher) ;
97
+
98
+ // Set the foreground group for the pty follower.
99
+ tcsetpgrp ( & pty_follower, closure. command_pgrp ) . ok ( ) ;
77
100
78
101
// FIXME (ogsudo): Here's where the signal mask is removed because the handlers for the signals
79
102
// have been setup after initializing the closure.
@@ -91,25 +114,45 @@ pub(super) fn exec_monitor(
91
114
exit ( 1 )
92
115
}
93
116
117
+ // FIXME: This should return `io::Result<!>` but `!` is not stable yet.
118
+ fn exec_command ( mut command : Command , pty_follower : OwnedFd ) -> io:: Error {
119
+ // FIXME (ogsudo): Do any additional configuration that needs to be run after `fork` but before `exec`
120
+ let command_pid = std:: process:: id ( ) as ProcessId ;
121
+
122
+ setpgid ( 0 , command_pid) . ok ( ) ;
123
+
124
+ // Wait for the monitor to set us as the foreground group for the pty.
125
+ while !tcgetpgrp ( & pty_follower) . is_ok_and ( |pid| pid == command_pid) {
126
+ std:: thread:: sleep ( std:: time:: Duration :: from_micros ( 1 ) ) ;
127
+ }
128
+
129
+ command. exec ( )
130
+ }
131
+
94
132
struct MonitorClosure < ' a > {
95
- command : Child ,
96
133
/// The command PID.
97
134
///
98
135
/// This is `Some` iff the process is still running.
99
136
command_pid : Option < ProcessId > ,
100
137
command_pgrp : ProcessId ,
138
+ errpipe_rx : UnixStream ,
101
139
backchannel : & ' a mut MonitorBackchannel ,
102
140
}
103
141
104
142
impl < ' a > MonitorClosure < ' a > {
105
143
fn new (
106
- command : Child ,
107
144
command_pid : ProcessId ,
145
+ errpipe_rx : UnixStream ,
108
146
backchannel : & ' a mut MonitorBackchannel ,
109
147
dispatcher : & mut EventDispatcher < Self > ,
110
148
) -> Self {
111
149
// FIXME (ogsudo): Store the pgid of the monitor.
112
150
151
+ // Register the callback to receive the IO error if the command fails to execute.
152
+ dispatcher. set_read_callback ( & errpipe_rx, |monitor, dispatcher| {
153
+ monitor. read_errpipe ( dispatcher)
154
+ } ) ;
155
+
113
156
// Register the callback to receive events from the backchannel
114
157
dispatcher. set_read_callback ( backchannel, |monitor, dispatcher| {
115
158
monitor. read_backchannel ( dispatcher)
@@ -122,9 +165,9 @@ impl<'a> MonitorClosure<'a> {
122
165
} ;
123
166
124
167
Self {
125
- command,
126
168
command_pid : Some ( command_pid) ,
127
169
command_pgrp,
170
+ errpipe_rx,
128
171
backchannel,
129
172
}
130
173
}
@@ -154,7 +197,67 @@ impl<'a> MonitorClosure<'a> {
154
197
}
155
198
}
156
199
}
200
+
201
+ fn handle_sigchld ( & mut self , command_pid : ProcessId ) {
202
+ let status = loop {
203
+ match waitpid ( command_pid, WaitOptions :: new ( ) . untraced ( ) . no_hang ( ) ) {
204
+ Ok ( ( _pid, status) ) => break status,
205
+ Err ( WaitError :: Io ( err) ) if was_interrupted ( & err) => { }
206
+ Err ( _) => return ,
207
+ }
208
+ } ;
209
+
210
+ if let Some ( exit_code) = status. exit_status ( ) {
211
+ dev_info ! ( "command ({command_pid}) exited with status code {exit_code}" ) ;
212
+ // The command did exit, set it's PID to `None`.
213
+ self . command_pid = None ;
214
+ self . backchannel
215
+ . send ( & ParentMessage :: CommandExit ( exit_code) )
216
+ . ok ( ) ;
217
+ } else if let Some ( signal) = status. term_signal ( ) {
218
+ dev_info ! (
219
+ "command ({command_pid}) was terminated by {}" ,
220
+ signal_fmt( signal) ,
221
+ ) ;
222
+ // The command was terminated, set it's PID to `None`.
223
+ self . command_pid = None ;
224
+ self . backchannel
225
+ . send ( & ParentMessage :: CommandSignal ( signal) )
226
+ . ok ( ) ;
227
+ } else if let Some ( _signal) = status. stop_signal ( ) {
228
+ // FIXME: we should save the foreground process group ID so we can restore it later.
229
+ dev_info ! (
230
+ "command ({command_pid}) was stopped by {}" ,
231
+ signal_fmt( _signal) ,
232
+ ) ;
233
+ } else if status. did_continue ( ) {
234
+ dev_info ! ( "command ({command_pid}) continued execution" ) ;
235
+ } else {
236
+ dev_warn ! ( "unexpected wait status for command ({command_pid})" )
237
+ }
238
+ }
239
+
240
+ fn read_errpipe ( & mut self , dispatcher : & mut EventDispatcher < Self > ) {
241
+ let mut buf = 0i32 . to_ne_bytes ( ) ;
242
+ match self . errpipe_rx . read_exact ( & mut buf) {
243
+ Err ( err) if was_interrupted ( & err) => { /* Retry later */ }
244
+ Err ( err) => {
245
+ // Could not read from the pipe, report error to the parent.
246
+ // FIXME: Maybe we should have a different variant for this.
247
+ self . backchannel . send ( & err. into ( ) ) . ok ( ) ;
248
+ dispatcher. set_break ( ( ) ) ;
249
+ }
250
+ Ok ( _) => {
251
+ // Received error code from the command, forward it to the parent.
252
+ let error_code = i32:: from_ne_bytes ( buf) ;
253
+ self . backchannel
254
+ . send ( & ParentMessage :: IoError ( error_code) )
255
+ . ok ( ) ;
256
+ }
257
+ }
258
+ }
157
259
}
260
+
158
261
/// Send a signal to the command.
159
262
fn send_signal ( signal : c_int , command_pid : ProcessId , from_parent : bool ) {
160
263
dev_info ! (
@@ -221,17 +324,12 @@ impl<'a> EventClosure for MonitorClosure<'a> {
221
324
} ;
222
325
223
326
match info. signal ( ) {
224
- // FIXME: check `mon_handle_sigchld`
225
327
SIGCHLD => {
226
- if let Ok ( Some ( exit_status) ) = self . command . try_wait ( ) {
227
- dev_info ! (
228
- "command ({command_pid}) exited with status: {}" ,
229
- exit_status
230
- ) ;
231
- // The command has terminated, set it's PID to `None`.
232
- self . command_pid = None ;
328
+ self . handle_sigchld ( command_pid) ;
329
+ if self . command_pid . is_none ( ) {
330
+ // FIXME: This is what ogsudo calls a `loopexit`. Meaning that we should still
331
+ // dispatch the remaining events to their callbacks and exit nicely.
233
332
dispatcher. set_break ( ( ) ) ;
234
- self . backchannel . send ( & exit_status. into ( ) ) . unwrap ( ) ;
235
333
}
236
334
}
237
335
// Skip the signal if it was sent by the user and it is self-terminating.
0 commit comments