summaryrefslogtreecommitdiffstats
path: root/src/prepared_command.rs
blob: 8297095578c6aff48e81ea105211b0068f1e060d (plain)
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
use std::{
	ffi::{CString, OsStr},
	io::{Error, ErrorKind, Result},
	iter,
	os::unix::{ffi::*, io::RawFd},
	ptr,
};

use libc::{c_char, c_void};
use nix::{fcntl::OFlag, sys::wait, unistd};

use crate::util::ToIOResult;

#[derive(Clone, Debug)]
pub struct PreparedCommandBuilder {
	program: CString,
	args: Vec<CString>,
}

#[derive(Debug)]
pub struct PreparedCommand {
	child: unistd::Pid,
	pipew: RawFd,
}

impl Drop for PreparedCommand {
	fn drop(&mut self) {
		assert!(unistd::close(self.pipew).is_ok());
	}
}

fn os2c<S: AsRef<OsStr>>(s: S) -> CString {
	CString::new(s.as_ref().as_bytes()).unwrap()
}

impl PreparedCommandBuilder {
	pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
		self.args.push(os2c(arg));
		self
	}

	pub fn prepare(&mut self) -> Result<PreparedCommand> {
		let exe_p = self.program.as_ptr();

		let argv: Vec<*const c_char> = iter::once(exe_p)
			.chain(self.args.iter().map(|arg| arg.as_ptr()))
			.chain(iter::once(ptr::null()))
			.collect();

		let argv_p = argv.as_ptr();

		let (piper, pipew) = unistd::pipe2(OFlag::O_CLOEXEC).to_io_result()?;

		unsafe {
			match unistd::fork().to_io_result()? {
				unistd::ForkResult::Parent { child } => {
					let cmd = PreparedCommand { child, pipew };
					assert!(unistd::close(piper).is_ok());
					return Ok(cmd);
				}
				unistd::ForkResult::Child => {}
			}

			// Child process - only async-signal-safe calls allowed

			if libc::close(pipew) != 0 {
				libc::_exit(126);
			}

			// Wait for run trigger
			let mut buf = [0u8; 1];
			if libc::read(piper, buf.as_mut_ptr() as *mut c_void, buf.len()) != 1 {
				// PreparedCommand was dropped, or controlling process exited
				libc::_exit(126);
			}

			libc::execvp(exe_p, argv_p);

			// exec failed
			libc::_exit(127);
		}
	}
}

impl PreparedCommand {
	pub fn new<S: AsRef<OsStr>>(program: S) -> PreparedCommandBuilder {
		PreparedCommandBuilder {
			program: os2c(program),
			args: Vec::new(),
		}
	}

	pub fn run(self) -> Result<wait::WaitStatus> {
		if unistd::write(self.pipew, &[0]).to_io_result()? != 1 {
			return Err(Error::new(ErrorKind::Other, "command trigger write failed"));
		}

		wait::waitpid(Some(self.child), None).to_io_result()
	}
}