• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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