File size: 2,885 Bytes
fda8632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
use super::{BashJob, BashTable};
use crate::agent::web_tools::io_detail;
use std::ffi::CString;
use std::fs::File;
use std::os::fd::FromRawFd;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::time::Instant;

unsafe extern "C" {
    fn mkstemp(template: *mut std::ffi::c_char) -> i32;
}

pub(super) fn spawn_job(
    table: &mut BashTable,
    command: &str,
    timeout: i32,
) -> Result<usize, String> {
    let (path, file) = create_output_file()?;
    let stdout = file.try_clone().map_err(|error| {
        format!(
            "failed to create temporary output file: {}",
            io_detail(&error)
        )
    })?;
    let stderr = file.try_clone().map_err(|error| {
        format!(
            "failed to create temporary output file: {}",
            io_detail(&error)
        )
    })?;
    drop(file);
    let child = Command::new("/bin/sh")
        .arg("-c")
        .arg(command)
        .stdin(Stdio::inherit())
        .stdout(Stdio::from(stdout))
        .stderr(Stdio::from(stderr))
        .process_group(0)
        .spawn()
        .map_err(|error| format!("failed to fork: {}", io_detail(&error)))?;
    let pid = child.id() as i32;
    if table.next_id <= 0 {
        table.next_id = 1;
    }
    let id = table.next_id;
    table.next_id += 1;
    table.jobs.insert(
        0,
        BashJob {
            id,
            pid,
            path,
            child: Some(child),
            start: Instant::now(),
            timeout_sec: f64::from(timeout),
            observed_once: false,
            exit_status: -1,
            running: true,
            timed_out: false,
        },
    );
    Ok(0)
}

fn create_output_file() -> Result<(PathBuf, File), String> {
    let mut tmpl = CString::new("/tmp/ds4_agent_output_XXXXXX")
        .map_err(|_| "failed to create temporary output file: invalid template".to_string())?
        .into_bytes_with_nul();
    // SAFETY: [Category 8 — FFI] `tmpl` is an exclusive CString buffer ending
    // in `XXXXXX\0`; `mkstemp` rewrites those six bytes in place and does not
    // retain the pointer.
    let fd = unsafe { mkstemp(tmpl.as_mut_ptr().cast()) };
    if fd < 0 {
        return Err(format!(
            "failed to create temporary output file: {}",
            io_detail(&std::io::Error::last_os_error())
        ));
    }
    let path = CString::from_vec_with_nul(tmpl)
        .map_err(|_| "failed to create temporary output file: invalid path".to_string())?
        .into_string()
        .map_err(|_| "failed to create temporary output file: invalid path".to_string())?;
    // SAFETY: [Category 13 — Library / unsafe contract] `fd` is a live exclusive
    // descriptor from `mkstemp` (fd >= 0 checked). `File` takes ownership; no
    // other close path exists.
    Ok((PathBuf::from(path), unsafe { File::from_raw_fd(fd) }))
}