]> git.proxmox.com Git - pve-lxc-syscalld.git/blob - src/fork.rs
blocking fixup, and actually recvmsg on recvmsg
[pve-lxc-syscalld.git] / src / fork.rs
1 //! Fork helper.
2 //!
3 //! Note that forking in rust can be dangerous. A fork must consider all mutexes to be in a broken
4 //! state, and cannot rely on any of its reference life times, so we be careful what kind of data
5 //! we continue to work with.
6
7 use std::io;
8 use std::os::raw::c_int;
9 use std::os::unix::io::{FromRawFd, IntoRawFd};
10 use std::panic::UnwindSafe;
11
12 use crate::io::pipe::{self, Pipe};
13 use crate::syscall::SyscallStatus;
14 use crate::tools::Fd;
15
16 pub async fn forking_syscall<F>(func: F) -> io::Result<SyscallStatus>
17 where
18 F: FnOnce() -> io::Result<SyscallStatus> + UnwindSafe,
19 {
20 let mut fork = Fork::new(func)?;
21 let result = fork.get_result().await?;
22 fork.wait()?;
23 Ok(result)
24 }
25
26 pub struct Fork {
27 pid: Option<libc::pid_t>,
28 // FIXME: abuse! tokio-fs is not updated to futures@0.3 yet, but a TcpStream does the same
29 // thing as a file when it's already open anyway...
30 out: Pipe<pipe::Read>,
31 }
32
33 impl Drop for Fork {
34 fn drop(&mut self) {
35 if self.pid.is_some() {
36 let _ = self.wait();
37 }
38 }
39 }
40
41 #[repr(C, packed)]
42 struct Data {
43 val: i64,
44 error: i32,
45 failure: i32,
46 }
47
48 impl Fork {
49 pub fn new<F>(func: F) -> io::Result<Self>
50 where
51 F: FnOnce() -> io::Result<SyscallStatus> + UnwindSafe,
52 {
53 let (pipe_r, pipe_w) = pipe::pipe()?;
54
55 let pid = c_try!(unsafe { libc::fork() });
56 if pid == 0 {
57 drop(pipe_r);
58 let mut pipe_w = unsafe { Fd::from_raw_fd(pipe_w.into_raw_fd()) };
59 let _ = std::panic::catch_unwind(move || {
60 pipe_w.set_nonblocking(false).unwrap();
61 let mut pipe_w = unsafe { std::fs::File::from_raw_fd(pipe_w.into_raw_fd()) };
62 let out = match func() {
63 Ok(SyscallStatus::Ok(val)) => Data {
64 val,
65 error: 0,
66 failure: 0,
67 },
68 Ok(SyscallStatus::Err(error)) => Data {
69 val: -1,
70 error: error as _,
71 failure: 0,
72 },
73 Err(err) => Data {
74 val: -1,
75 error: -1,
76 failure: err.raw_os_error().unwrap_or(libc::EFAULT),
77 },
78 };
79
80 let slice = unsafe {
81 std::slice::from_raw_parts(
82 &out as *const Data as *const u8,
83 std::mem::size_of::<Data>(),
84 )
85 };
86
87 use std::io::Write;
88 match pipe_w.write_all(slice) {
89 Ok(()) => unsafe { libc::_exit(0) },
90 Err(_) => unsafe { libc::_exit(1) },
91 }
92 });
93 unsafe {
94 libc::_exit(-1);
95 }
96 }
97 drop(pipe_w);
98
99 Ok(Self {
100 pid: Some(pid),
101 out: pipe_r,
102 })
103 }
104
105 pub fn wait(&mut self) -> io::Result<()> {
106 let my_pid = self.pid.take().unwrap();
107 let mut status: c_int = -1;
108
109 loop {
110 match c_result!(unsafe { libc::waitpid(my_pid, &mut status, 0) }) {
111 Ok(pid) if pid == my_pid => break,
112 Ok(_other) => continue,
113 Err(ref err) if err.kind() == io::ErrorKind::Interrupted => continue,
114 Err(other) => return Err(other),
115 }
116 }
117
118 if status != 0 {
119 Err(io::Error::new(
120 io::ErrorKind::Other,
121 "error in child process",
122 ))
123 } else {
124 Ok(())
125 }
126 }
127
128 pub async fn get_result(&mut self) -> io::Result<SyscallStatus> {
129 let mut data: Data = unsafe { std::mem::zeroed() };
130 // Compiler bug: we currently need to put the slice into a temporary variable...
131 let dataslice: &mut [u8] = unsafe {
132 std::slice::from_raw_parts_mut(
133 &mut data as *mut Data as *mut u8,
134 std::mem::size_of::<Data>(),
135 )
136 };
137 self.out.read_exact(dataslice).await?;
138 //self.read_exact(unsafe {
139 // std::slice::from_raw_parts_mut(
140 // &mut data as *mut Data as *mut u8,
141 // std::mem::size_of::<Data>(),
142 // )
143 //})
144 //.await?;
145 if data.failure != 0 {
146 Err(io::Error::from_raw_os_error(data.failure))
147 } else if data.error == 0 {
148 Ok(SyscallStatus::Ok(data.val))
149 } else {
150 Ok(SyscallStatus::Err(data.error))
151 }
152 }
153 }