| 1 | module os |
| 2 | |
| 3 | // The kind of the pipe file descriptor, that is used for communicating with the child process |
| 4 | pub enum ChildProcessPipeKind { |
| 5 | stdin |
| 6 | stdout |
| 7 | stderr |
| 8 | } |
| 9 | |
| 10 | // signal_kill kills the process, after that it is no longer running. |
| 11 | pub fn (mut p Process) signal_kill() { |
| 12 | if p.status !in [.running, .stopped] { |
| 13 | return |
| 14 | } |
| 15 | p._signal_kill() |
| 16 | p.status = .aborted |
| 17 | } |
| 18 | |
| 19 | // signal_term terminates the process. |
| 20 | pub fn (mut p Process) signal_term() { |
| 21 | if p.status !in [.running, .stopped] { |
| 22 | return |
| 23 | } |
| 24 | p._signal_term() |
| 25 | } |
| 26 | |
| 27 | // signal_pgkill kills the whole process group. |
| 28 | pub fn (mut p Process) signal_pgkill() { |
| 29 | if p.status !in [.running, .stopped] { |
| 30 | return |
| 31 | } |
| 32 | p._signal_pgkill() |
| 33 | } |
| 34 | |
| 35 | // signal_stop stops the process, you can resume it with p.signal_continue(). |
| 36 | pub fn (mut p Process) signal_stop() { |
| 37 | if p.status != .running { |
| 38 | return |
| 39 | } |
| 40 | p._signal_stop() |
| 41 | p.status = .stopped |
| 42 | } |
| 43 | |
| 44 | // signal_continue tell a stopped process to continue/resume its work. |
| 45 | pub fn (mut p Process) signal_continue() { |
| 46 | if p.status != .stopped { |
| 47 | return |
| 48 | } |
| 49 | p._signal_continue() |
| 50 | p.status = .running |
| 51 | } |
| 52 | |
| 53 | // wait for a process to finish. |
| 54 | // Note: You have to call p.wait(), otherwise a finished process |
| 55 | // would get to a zombie state, and its resources will not get |
| 56 | // released fully, until its parent process exits. |
| 57 | // Note: This call will block the calling process until the child |
| 58 | // process is finished. |
| 59 | pub fn (mut p Process) wait() { |
| 60 | if p.status == .not_started { |
| 61 | p._spawn() |
| 62 | } |
| 63 | if p.status !in [.running, .stopped] { |
| 64 | return |
| 65 | } |
| 66 | p._wait() |
| 67 | } |
| 68 | |
| 69 | // close frees the OS resources associated with the process. |
| 70 | // It can be called multiple times, but will free the resources just once. |
| 71 | // If the process has already finished, this sets the process state to |
| 72 | // .closed, which is final. |
| 73 | pub fn (mut p Process) close() { |
| 74 | if p.status in [.not_started, .closed] { |
| 75 | return |
| 76 | } |
| 77 | $if !windows { |
| 78 | for i in 0 .. 3 { |
| 79 | if p.stdio_fd[i] != -1 { |
| 80 | fd_close(p.stdio_fd[i]) |
| 81 | p.stdio_fd[i] = -1 |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | if p.status !in [.running, .stopped] { |
| 86 | p.status = .closed |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | @[unsafe] |
| 91 | pub fn (mut p Process) free() { |
| 92 | p.close() |
| 93 | unsafe { |
| 94 | p.filename.free() |
| 95 | p.err.free() |
| 96 | p.args.free() |
| 97 | p.env.free() |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | // _spawn should not be called directly, but only by p.run()/p.wait(). |
| 102 | // It encapsulates the fork/execve mechanism that allows the |
| 103 | // asynchronous starting of the new child process. |
| 104 | fn (mut p Process) _spawn() int { |
| 105 | if !p.env_is_custom { |
| 106 | p.env = []string{} |
| 107 | current_environment := environ() |
| 108 | for k, v in current_environment { |
| 109 | p.env << '${k}=${v}' |
| 110 | } |
| 111 | } |
| 112 | mut pid := 0 |
| 113 | $if windows { |
| 114 | pid = p.win_spawn_process() |
| 115 | } $else { |
| 116 | pid = p.unix_spawn_process() |
| 117 | } |
| 118 | p.pid = pid |
| 119 | p.status = .running |
| 120 | return 0 |
| 121 | } |
| 122 | |
| 123 | // is_alive query whether the process is still alive. |
| 124 | pub fn (mut p Process) is_alive() bool { |
| 125 | mut res := false |
| 126 | if p.status in [.running, .stopped] { |
| 127 | res = p._is_alive() |
| 128 | } |
| 129 | $if trace_process_is_alive ? { |
| 130 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, res: ${res}') |
| 131 | } |
| 132 | return res |
| 133 | } |
| 134 | |
| 135 | // |
| 136 | pub fn (mut p Process) set_redirect_stdio() { |
| 137 | p.use_stdio_ctl = true |
| 138 | $if trace_process_pipes ? { |
| 139 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}') |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // stdin_write will write the string `s`, to the stdin pipe of the child process. |
| 144 | pub fn (mut p Process) stdin_write(s string) { |
| 145 | p._check_redirection_call(@METHOD) |
| 146 | $if trace_process_pipes ? { |
| 147 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, s.len: ${s.len}, s: `${s}`') |
| 148 | } |
| 149 | p._write_to(.stdin, s) |
| 150 | } |
| 151 | |
| 152 | // stdout_slurp will read from the stdout pipe. |
| 153 | // It will block until it either reads all the data, or until the pipe is closed (end of file). |
| 154 | pub fn (mut p Process) stdout_slurp() string { |
| 155 | p._check_redirection_call(@METHOD) |
| 156 | res := p._slurp_from(.stdout) |
| 157 | $if trace_process_pipes ? { |
| 158 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, res.len: ${res.len}, res: `${res}`') |
| 159 | } |
| 160 | return res |
| 161 | } |
| 162 | |
| 163 | // stderr_slurp will read from the stderr pipe. |
| 164 | // It will block until it either reads all the data, or until the pipe is closed (end of file). |
| 165 | pub fn (mut p Process) stderr_slurp() string { |
| 166 | p._check_redirection_call(@METHOD) |
| 167 | res := p._slurp_from(.stderr) |
| 168 | $if trace_process_pipes ? { |
| 169 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, res.len: ${res.len}, res: `${res}`') |
| 170 | } |
| 171 | return res |
| 172 | } |
| 173 | |
| 174 | // stdout_read reads a block of data from the child process stdout pipe. |
| 175 | // It returns `''` immediately when there is currently no data to be read. |
| 176 | pub fn (mut p Process) stdout_read() string { |
| 177 | p._check_redirection_call(@METHOD) |
| 178 | if !p._is_pending(.stdout) { |
| 179 | return '' |
| 180 | } |
| 181 | res := p._read_from(.stdout) |
| 182 | $if trace_process_pipes ? { |
| 183 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, res.len: ${res.len}, res: `${res}`') |
| 184 | } |
| 185 | return res |
| 186 | } |
| 187 | |
| 188 | // stderr_read reads a block of data from the child process stderr pipe. |
| 189 | // It returns `''` immediately when there is currently no data to be read. |
| 190 | pub fn (mut p Process) stderr_read() string { |
| 191 | p._check_redirection_call(@METHOD) |
| 192 | if !p._is_pending(.stderr) { |
| 193 | return '' |
| 194 | } |
| 195 | res := p._read_from(.stderr) |
| 196 | $if trace_process_pipes ? { |
| 197 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, res.len: ${res.len}, res: `${res}`') |
| 198 | } |
| 199 | return res |
| 200 | } |
| 201 | |
| 202 | // pipe_read reads a block of data, from the given pipe of the child process. |
| 203 | // It will return `none`, if there is no data to be read, *without blocking*. |
| 204 | pub fn (mut p Process) pipe_read(pkind ChildProcessPipeKind) ?string { |
| 205 | p._check_redirection_call(@METHOD) |
| 206 | if !p._is_pending(pkind) { |
| 207 | $if trace_process_pipes ? { |
| 208 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, no pending data') |
| 209 | } |
| 210 | return none |
| 211 | } |
| 212 | res := p._read_from(pkind) |
| 213 | if res.len == 0 { |
| 214 | return none |
| 215 | } |
| 216 | $if trace_process_pipes ? { |
| 217 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, res.len: ${res.len}, res: `${res}`') |
| 218 | } |
| 219 | return res |
| 220 | } |
| 221 | |
| 222 | // is_pending returns whether there is data to be read from child process's pipe corresponding to `pkind`. |
| 223 | // For example `if p.is_pending(.stdout) { dump( p.stdout_read() ) }` will not block indefinitely. |
| 224 | pub fn (mut p Process) is_pending(pkind ChildProcessPipeKind) bool { |
| 225 | p._check_redirection_call(@METHOD) |
| 226 | res := p._is_pending(pkind) |
| 227 | $if trace_process_pipes ? { |
| 228 | eprintln('${@LOCATION}, pid: ${p.pid}, status: ${p.status}, pkind: ${pkind}, res: ${res}') |
| 229 | } |
| 230 | return res |
| 231 | } |
| 232 | |
| 233 | // _read_from should be called only from .stdout_read/0, .stderr_read/0 and .pipe_read/1. |
| 234 | fn (mut p Process) _read_from(pkind ChildProcessPipeKind) string { |
| 235 | $if windows { |
| 236 | s, _ := p.win_read_string(int(pkind), 4096) |
| 237 | return s |
| 238 | } $else { |
| 239 | s, _ := fd_read(p.stdio_fd[pkind], 4096) |
| 240 | return s |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | // _slurp_from should be called only from stdout_slurp() and stderr_slurp(). |
| 245 | fn (mut p Process) _slurp_from(pkind ChildProcessPipeKind) string { |
| 246 | $if windows { |
| 247 | return p.win_slurp(int(pkind)) |
| 248 | } $else { |
| 249 | return fd_slurp(p.stdio_fd[pkind]).join('') |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | // _write_to should be called only from stdin_write(). |
| 254 | fn (mut p Process) _write_to(pkind ChildProcessPipeKind, s string) { |
| 255 | $if windows { |
| 256 | p.win_write_string(int(pkind), s) |
| 257 | } $else { |
| 258 | fd_write(p.stdio_fd[pkind], s) |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | // _is_pending should be called only from is_pending(). |
| 263 | fn (mut p Process) _is_pending(pkind ChildProcessPipeKind) bool { |
| 264 | $if windows { |
| 265 | return p.win_is_pending(int(pkind)) |
| 266 | } $else { |
| 267 | return fd_is_pending(p.stdio_fd[pkind]) |
| 268 | } |
| 269 | return false |
| 270 | } |
| 271 | |
| 272 | // _check_redirection_call should be called just by stdxxx methods. |
| 273 | fn (mut p Process) _check_redirection_call(fn_name string) { |
| 274 | if !p.use_stdio_ctl { |
| 275 | panic('Call p.set_redirect_stdio() before calling p.' + fn_name) |
| 276 | } |
| 277 | if p.status == .not_started { |
| 278 | panic('Call p.' + fn_name + '() after you have called p.run()') |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | // _signal_stop should not be called directly, except by p.signal_stop. |
| 283 | fn (mut p Process) _signal_stop() { |
| 284 | $if windows { |
| 285 | p.win_stop_process() |
| 286 | } $else { |
| 287 | p.unix_stop_process() |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | // _signal_continue should not be called directly, just by p.signal_continue. |
| 292 | fn (mut p Process) _signal_continue() { |
| 293 | $if windows { |
| 294 | p.win_resume_process() |
| 295 | } $else { |
| 296 | p.unix_resume_process() |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | // _signal_kill should not be called directly, except by p.signal_kill. |
| 301 | fn (mut p Process) _signal_kill() { |
| 302 | $if windows { |
| 303 | p.win_kill_process() |
| 304 | } $else { |
| 305 | p.unix_kill_process() |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | // _signal_term should not be called directly, except by p.signal_term. |
| 310 | fn (mut p Process) _signal_term() { |
| 311 | $if windows { |
| 312 | p.win_term_process() |
| 313 | } $else { |
| 314 | p.unix_term_process() |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | // _signal_pgkill should not be called directly, except by p.signal_pgkill. |
| 319 | fn (mut p Process) _signal_pgkill() { |
| 320 | $if windows { |
| 321 | p.win_kill_pgroup() |
| 322 | } $else { |
| 323 | p.unix_kill_pgroup() |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | // _wait should not be called directly, except by p.wait(). |
| 328 | fn (mut p Process) _wait() { |
| 329 | $if windows { |
| 330 | p.win_wait() |
| 331 | } $else { |
| 332 | p.unix_wait() |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | // _is_alive should not be called directly, except by p.is_alive(). |
| 337 | fn (mut p Process) _is_alive() bool { |
| 338 | $if windows { |
| 339 | return p.win_is_alive() |
| 340 | } $else { |
| 341 | return p.unix_is_alive() |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | // run starts the new process. |
| 346 | pub fn (mut p Process) run() { |
| 347 | if p.status != .not_started { |
| 348 | return |
| 349 | } |
| 350 | p._spawn() |
| 351 | } |
| 352 | |