1 // Copyright 2021 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::fs::File;
6 use std::io::IoSlice;
7 use std::io::IoSliceMut;
8 use std::io::Seek;
9 use std::io::SeekFrom;
10 use std::os::unix::io::AsRawFd;
11 use std::os::unix::prelude::AsFd;
12
13 use libc::O_ACCMODE;
14 use libc::O_WRONLY;
15 use nix::cmsg_space;
16 use nix::fcntl::fcntl;
17 use nix::fcntl::FcntlArg;
18 use nix::sys::epoll::EpollCreateFlags;
19 use nix::sys::epoll::EpollFlags;
20 use nix::sys::eventfd::EfdFlags;
21 use nix::sys::eventfd::EventFd;
22 use nix::sys::socket::connect;
23 use nix::sys::socket::recvmsg;
24 use nix::sys::socket::sendmsg;
25 use nix::sys::socket::socket;
26 use nix::sys::socket::AddressFamily;
27 use nix::sys::socket::ControlMessage;
28 use nix::sys::socket::ControlMessageOwned;
29 use nix::sys::socket::MsgFlags;
30 use nix::sys::socket::SockFlag;
31 use nix::sys::socket::SockType;
32 use nix::sys::socket::UnixAddr;
33 use nix::unistd::pipe;
34 use nix::unistd::read;
35 use nix::unistd::write;
36
37 use super::super::add_item;
38 use super::super::cross_domain_protocol::CrossDomainSendReceive;
39 use super::super::cross_domain_protocol::CROSS_DOMAIN_ID_TYPE_READ_PIPE;
40 use super::super::cross_domain_protocol::CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB;
41 use super::super::cross_domain_protocol::CROSS_DOMAIN_ID_TYPE_WRITE_PIPE;
42 use super::super::cross_domain_protocol::CROSS_DOMAIN_MAX_IDENTIFIERS;
43 use super::super::CrossDomainContext;
44 use super::super::CrossDomainItem;
45 use super::super::CrossDomainJob;
46 use super::super::CrossDomainState;
47 use super::epoll_internal::Epoll;
48 use super::epoll_internal::EpollEvent;
49 use crate::cross_domain::cross_domain_protocol::CrossDomainInit;
50 use crate::cross_domain::CrossDomainEvent;
51 use crate::cross_domain::CrossDomainToken;
52 use crate::cross_domain::WAIT_CONTEXT_MAX;
53 use crate::rutabaga_os::AsRawDescriptor;
54 use crate::rutabaga_os::FromRawDescriptor;
55 use crate::rutabaga_os::RawDescriptor;
56 use crate::RutabagaError;
57 use crate::RutabagaResult;
58
59 pub type SystemStream = File;
60
61 // Determine type of OS-specific descriptor. See `from_file` in wl.rs for explantation on the
62 // current, Linux-based method.
descriptor_analysis( descriptor: &mut File, descriptor_type: &mut u32, size: &mut u32, ) -> RutabagaResult<()>63 pub fn descriptor_analysis(
64 descriptor: &mut File,
65 descriptor_type: &mut u32,
66 size: &mut u32,
67 ) -> RutabagaResult<()> {
68 match descriptor.seek(SeekFrom::End(0)) {
69 Ok(seek_size) => {
70 *descriptor_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB;
71 *size = seek_size.try_into()?;
72 Ok(())
73 }
74 _ => {
75 let flags = fcntl(descriptor.as_raw_descriptor(), FcntlArg::F_GETFL)?;
76 *descriptor_type = match flags & O_ACCMODE {
77 O_WRONLY => CROSS_DOMAIN_ID_TYPE_WRITE_PIPE,
78 _ => return Err(RutabagaError::InvalidCrossDomainItemType),
79 };
80
81 Ok(())
82 }
83 }
84 }
85
86 impl CrossDomainState {
send_msg(&self, opaque_data: &[u8], descriptors: &[RawDescriptor]) -> RutabagaResult<usize>87 fn send_msg(&self, opaque_data: &[u8], descriptors: &[RawDescriptor]) -> RutabagaResult<usize> {
88 let cmsg = ControlMessage::ScmRights(descriptors);
89 if let Some(connection) = &self.connection {
90 let bytes_sent = sendmsg::<()>(
91 connection.as_raw_descriptor(),
92 &[IoSlice::new(opaque_data)],
93 &[cmsg],
94 MsgFlags::empty(),
95 None,
96 )?;
97
98 return Ok(bytes_sent);
99 }
100
101 Err(RutabagaError::InvalidCrossDomainChannel)
102 }
103
receive_msg(&self, opaque_data: &mut [u8]) -> RutabagaResult<(usize, Vec<File>)>104 pub(crate) fn receive_msg(&self, opaque_data: &mut [u8]) -> RutabagaResult<(usize, Vec<File>)> {
105 // If any errors happen, the socket will get dropped, preventing more reading.
106 let mut iovecs = [IoSliceMut::new(opaque_data)];
107 let mut cmsgspace = cmsg_space!([RawDescriptor; CROSS_DOMAIN_MAX_IDENTIFIERS]);
108 let flags = MsgFlags::empty();
109
110 if let Some(connection) = &self.connection {
111 let r = recvmsg::<()>(
112 connection.as_raw_descriptor(),
113 &mut iovecs,
114 Some(&mut cmsgspace),
115 flags,
116 )?;
117 let len = r.bytes;
118
119 let files = match r.cmsgs().next() {
120 Some(ControlMessageOwned::ScmRights(fds)) => {
121 fds.into_iter()
122 .map(|fd| {
123 // SAFETY:
124 // Safe since the descriptors from recv_with_fds(..) are owned by us and
125 // valid.
126 unsafe { File::from_raw_descriptor(fd) }
127 })
128 .collect()
129 }
130 Some(_) => return Err(RutabagaError::Unsupported),
131 None => Vec::new(),
132 };
133
134 Ok((len, files))
135 } else {
136 Err(RutabagaError::InvalidCrossDomainChannel)
137 }
138 }
139 }
140
141 impl CrossDomainContext {
get_connection( &mut self, cmd_init: &CrossDomainInit, ) -> RutabagaResult<Option<SystemStream>>142 pub(crate) fn get_connection(
143 &mut self,
144 cmd_init: &CrossDomainInit,
145 ) -> RutabagaResult<Option<SystemStream>> {
146 let channels = self
147 .channels
148 .take()
149 .ok_or(RutabagaError::InvalidCrossDomainChannel)?;
150 let base_channel = &channels
151 .iter()
152 .find(|channel| channel.channel_type == cmd_init.channel_type)
153 .ok_or(RutabagaError::InvalidCrossDomainChannel)?
154 .base_channel;
155
156 let socket_fd = socket(
157 AddressFamily::Unix,
158 SockType::Stream,
159 SockFlag::SOCK_CLOEXEC,
160 None,
161 )?;
162
163 let unix_addr = UnixAddr::new(base_channel)?;
164 connect(socket_fd.as_raw_fd(), &unix_addr)?;
165 let stream = socket_fd.into();
166 Ok(Some(stream))
167 }
168
send( &self, cmd_send: &CrossDomainSendReceive, opaque_data: &[u8], ) -> RutabagaResult<()>169 pub(crate) fn send(
170 &self,
171 cmd_send: &CrossDomainSendReceive,
172 opaque_data: &[u8],
173 ) -> RutabagaResult<()> {
174 let mut descriptors = [0; CROSS_DOMAIN_MAX_IDENTIFIERS];
175
176 let mut write_pipe_opt: Option<File> = None;
177 let mut read_pipe_id_opt: Option<u32> = None;
178
179 let num_identifiers = cmd_send.num_identifiers.try_into()?;
180
181 if num_identifiers > CROSS_DOMAIN_MAX_IDENTIFIERS {
182 return Err(RutabagaError::SpecViolation(
183 "max cross domain identifiers exceeded",
184 ));
185 }
186
187 let iter = cmd_send
188 .identifiers
189 .iter()
190 .zip(cmd_send.identifier_types.iter())
191 .zip(descriptors.iter_mut())
192 .take(num_identifiers);
193
194 for ((identifier, identifier_type), descriptor) in iter {
195 if *identifier_type == CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB {
196 let context_resources = self.context_resources.lock().unwrap();
197
198 let context_resource = context_resources
199 .get(identifier)
200 .ok_or(RutabagaError::InvalidResourceId)?;
201
202 if let Some(ref handle) = context_resource.handle {
203 *descriptor = handle.os_handle.as_raw_descriptor();
204 } else {
205 return Err(RutabagaError::InvalidRutabagaHandle);
206 }
207 } else if *identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE {
208 // In practice, just 1 pipe pair per send is observed. If we encounter
209 // more, this can be changed later.
210 if write_pipe_opt.is_some() {
211 return Err(RutabagaError::SpecViolation("expected just one pipe pair"));
212 }
213
214 let (raw_read_pipe, raw_write_pipe) = pipe()?;
215 let read_pipe = File::from(raw_read_pipe);
216 let write_pipe = File::from(raw_write_pipe);
217
218 *descriptor = write_pipe.as_raw_descriptor();
219 let read_pipe_id: u32 = add_item(
220 &self.item_state,
221 CrossDomainItem::WaylandReadPipe(read_pipe),
222 );
223
224 // For Wayland read pipes, the guest guesses which identifier the host will use to
225 // avoid waiting for the host to generate one. Validate guess here. This works
226 // because of the way Sommelier copy + paste works. If the Sommelier sequence of
227 // events changes, it's always possible to wait for the host
228 // response.
229 if read_pipe_id != *identifier {
230 return Err(RutabagaError::InvalidCrossDomainItemId);
231 }
232
233 // The write pipe needs to be dropped after the send_msg(..) call is complete, so
234 // the read pipe can receive subsequent hang-up events.
235 write_pipe_opt = Some(write_pipe);
236 read_pipe_id_opt = Some(read_pipe_id);
237 } else {
238 // Don't know how to handle anything else yet.
239 return Err(RutabagaError::InvalidCrossDomainItemType);
240 }
241 }
242
243 if let (Some(state), Some(resample_evt)) = (&self.state, &self.resample_evt) {
244 state.send_msg(opaque_data, &descriptors[..num_identifiers])?;
245
246 if let Some(read_pipe_id) = read_pipe_id_opt {
247 state.add_job(CrossDomainJob::AddReadPipe(read_pipe_id));
248 channel_signal(resample_evt)?;
249 }
250 } else {
251 return Err(RutabagaError::InvalidCrossDomainState);
252 }
253
254 Ok(())
255 }
256 }
257
258 pub type Sender = EventFd;
259 // TODO: Receiver should be EventFd as well, but there is no way to clone a nix EventFd.
260 pub type Receiver = File;
261
channel_signal(sender: &Sender) -> RutabagaResult<()>262 pub fn channel_signal(sender: &Sender) -> RutabagaResult<()> {
263 sender.write(1)?;
264 Ok(())
265 }
266
channel_wait(receiver: &Receiver) -> RutabagaResult<()>267 pub fn channel_wait(receiver: &Receiver) -> RutabagaResult<()> {
268 read(receiver.as_raw_fd(), &mut 1u64.to_ne_bytes())?;
269 Ok(())
270 }
271
read_volatile(file: &File, opaque_data: &mut [u8]) -> RutabagaResult<usize>272 pub fn read_volatile(file: &File, opaque_data: &mut [u8]) -> RutabagaResult<usize> {
273 let bytes_read = read(file.as_raw_fd(), opaque_data)?;
274 Ok(bytes_read)
275 }
276
write_volatile(file: &File, opaque_data: &[u8]) -> RutabagaResult<()>277 pub fn write_volatile(file: &File, opaque_data: &[u8]) -> RutabagaResult<()> {
278 write(file.as_fd(), opaque_data)?;
279 Ok(())
280 }
281
channel() -> RutabagaResult<(Sender, Receiver)>282 pub fn channel() -> RutabagaResult<(Sender, Receiver)> {
283 let sender = EventFd::from_flags(EfdFlags::empty())?;
284 let receiver = sender.as_fd().try_clone_to_owned()?.into();
285 Ok((sender, receiver))
286 }
287
288 pub struct WaitContext {
289 epoll_ctx: Epoll,
290 data: u64,
291 vec: Vec<(u64, CrossDomainToken)>,
292 }
293
294 impl WaitContext {
new() -> RutabagaResult<WaitContext>295 pub fn new() -> RutabagaResult<WaitContext> {
296 let epoll = Epoll::new(EpollCreateFlags::empty())?;
297 Ok(WaitContext {
298 epoll_ctx: epoll,
299 data: 0,
300 vec: Default::default(),
301 })
302 }
303
add<Waitable: AsFd>( &mut self, token: CrossDomainToken, waitable: Waitable, ) -> RutabagaResult<()>304 pub fn add<Waitable: AsFd>(
305 &mut self,
306 token: CrossDomainToken,
307 waitable: Waitable,
308 ) -> RutabagaResult<()> {
309 self.data += 1;
310 self.epoll_ctx
311 .add(waitable, EpollEvent::new(EpollFlags::EPOLLIN, self.data))?;
312 self.vec.push((self.data, token));
313 Ok(())
314 }
315
calculate_token(&self, data: u64) -> RutabagaResult<CrossDomainToken>316 fn calculate_token(&self, data: u64) -> RutabagaResult<CrossDomainToken> {
317 if let Some(item) = self.vec.iter().find(|item| item.0 == data) {
318 return Ok(item.1);
319 }
320
321 Err(RutabagaError::SpecViolation("unable to find token"))
322 }
323
wait(&mut self) -> RutabagaResult<Vec<CrossDomainEvent>>324 pub fn wait(&mut self) -> RutabagaResult<Vec<CrossDomainEvent>> {
325 let mut events = [EpollEvent::empty(); WAIT_CONTEXT_MAX];
326 let count = self.epoll_ctx.wait(&mut events, isize::MAX)?;
327 let events = events[0..count]
328 .iter()
329 .map(|e| CrossDomainEvent {
330 token: self.calculate_token(e.data()).unwrap(),
331 readable: e.events() & EpollFlags::EPOLLIN == EpollFlags::EPOLLIN,
332 hung_up: e.events() & EpollFlags::EPOLLHUP == EpollFlags::EPOLLHUP
333 || e.events() & EpollFlags::EPOLLRDHUP != EpollFlags::EPOLLRDHUP,
334 })
335 .collect();
336
337 Ok(events)
338 }
339
delete<Waitable: AsFd>( &mut self, token: CrossDomainToken, waitable: Waitable, ) -> RutabagaResult<()>340 pub fn delete<Waitable: AsFd>(
341 &mut self,
342 token: CrossDomainToken,
343 waitable: Waitable,
344 ) -> RutabagaResult<()> {
345 self.epoll_ctx.delete(waitable)?;
346 self.vec.retain(|item| item.1 != token);
347 Ok(())
348 }
349 }
350