• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2020 Alibaba Cloud. All rights reserved.
2 // SPDX-License-Identifier: Apache-2.0
3 
4 use std::mem;
5 use std::string::ToString;
6 
7 use base::AsRawDescriptor;
8 use base::RawDescriptor;
9 use zerocopy::Immutable;
10 use zerocopy::IntoBytes;
11 
12 use crate::message::*;
13 use crate::BackendReq;
14 use crate::Connection;
15 use crate::Error;
16 use crate::Frontend;
17 use crate::HandlerResult;
18 use crate::Result;
19 
20 /// Client for a vhost-user frontend. Allows a backend to send requests to the frontend.
21 pub struct FrontendClient {
22     sock: Connection<BackendReq>,
23 
24     // Protocol feature VHOST_USER_PROTOCOL_F_REPLY_ACK has been negotiated.
25     reply_ack_negotiated: bool,
26 
27     // whether the connection has encountered any failure
28     error: Option<i32>,
29 }
30 
31 impl FrontendClient {
32     /// Create a new instance from the given connection.
new(ep: Connection<BackendReq>) -> Self33     pub fn new(ep: Connection<BackendReq>) -> Self {
34         FrontendClient {
35             sock: ep,
36             reply_ack_negotiated: false,
37             error: None,
38         }
39     }
40 
send_message<T>( &mut self, request: BackendReq, msg: &T, fds: Option<&[RawDescriptor]>, ) -> HandlerResult<u64> where T: IntoBytes + Immutable,41     fn send_message<T>(
42         &mut self,
43         request: BackendReq,
44         msg: &T,
45         fds: Option<&[RawDescriptor]>,
46     ) -> HandlerResult<u64>
47     where
48         T: IntoBytes + Immutable,
49     {
50         let len = mem::size_of::<T>();
51         let mut hdr = VhostUserMsgHeader::new(request, 0, len as u32);
52         if self.reply_ack_negotiated {
53             hdr.set_need_reply(true);
54         }
55         self.sock
56             .send_message(&hdr, msg, fds)
57             .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
58 
59         self.wait_for_reply(&hdr)
60             .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
61     }
62 
wait_for_reply(&mut self, hdr: &VhostUserMsgHeader<BackendReq>) -> Result<u64>63     fn wait_for_reply(&mut self, hdr: &VhostUserMsgHeader<BackendReq>) -> Result<u64> {
64         let code = hdr.get_code().map_err(|_| Error::InvalidMessage)?;
65         if code != BackendReq::SHMEM_MAP
66             && code != BackendReq::SHMEM_UNMAP
67             && code != BackendReq::GPU_MAP
68             && code != BackendReq::EXTERNAL_MAP
69             && !self.reply_ack_negotiated
70         {
71             return Ok(0);
72         }
73 
74         let (reply, body, rfds) = self.sock.recv_message::<VhostUserU64>()?;
75         if !reply.is_reply_for(hdr) || !rfds.is_empty() || !body.is_valid() {
76             return Err(Error::InvalidMessage);
77         }
78         if body.value != 0 {
79             return Err(Error::FrontendInternalError);
80         }
81 
82         Ok(body.value)
83     }
84 
85     /// Set the negotiation state of the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature.
86     ///
87     /// When the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature has been negotiated, the
88     /// "REPLY_ACK" flag will be set in the message header for every backend to frontend request
89     /// message.
set_reply_ack_flag(&mut self, enable: bool)90     pub fn set_reply_ack_flag(&mut self, enable: bool) {
91         self.reply_ack_negotiated = enable;
92     }
93 
94     /// Mark connection as failed with specified error code.
set_failed(&mut self, error: i32)95     pub fn set_failed(&mut self, error: i32) {
96         self.error = Some(error);
97     }
98 }
99 
100 impl Frontend for FrontendClient {
101     /// Handle shared memory region mapping requests.
shmem_map( &mut self, req: &VhostUserShmemMapMsg, fd: &dyn AsRawDescriptor, ) -> HandlerResult<u64>102     fn shmem_map(
103         &mut self,
104         req: &VhostUserShmemMapMsg,
105         fd: &dyn AsRawDescriptor,
106     ) -> HandlerResult<u64> {
107         self.send_message(BackendReq::SHMEM_MAP, req, Some(&[fd.as_raw_descriptor()]))
108     }
109 
110     /// Handle shared memory region unmapping requests.
shmem_unmap(&mut self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64>111     fn shmem_unmap(&mut self, req: &VhostUserShmemUnmapMsg) -> HandlerResult<u64> {
112         self.send_message(BackendReq::SHMEM_UNMAP, req, None)
113     }
114 
115     /// Handle config change requests.
handle_config_change(&mut self) -> HandlerResult<u64>116     fn handle_config_change(&mut self) -> HandlerResult<u64> {
117         self.send_message(BackendReq::CONFIG_CHANGE_MSG, &VhostUserEmptyMessage, None)
118     }
119 
120     /// Handle GPU shared memory region mapping requests.
gpu_map( &mut self, req: &VhostUserGpuMapMsg, descriptor: &dyn AsRawDescriptor, ) -> HandlerResult<u64>121     fn gpu_map(
122         &mut self,
123         req: &VhostUserGpuMapMsg,
124         descriptor: &dyn AsRawDescriptor,
125     ) -> HandlerResult<u64> {
126         self.send_message(
127             BackendReq::GPU_MAP,
128             req,
129             Some(&[descriptor.as_raw_descriptor()]),
130         )
131     }
132 
133     /// Handle external memory region mapping requests.
external_map(&mut self, req: &VhostUserExternalMapMsg) -> HandlerResult<u64>134     fn external_map(&mut self, req: &VhostUserExternalMapMsg) -> HandlerResult<u64> {
135         self.send_message(BackendReq::EXTERNAL_MAP, req, None)
136     }
137 }
138 
139 #[cfg(test)]
140 mod tests {
141     use super::*;
142 
143     #[test]
test_backend_req_set_failed()144     fn test_backend_req_set_failed() {
145         let (p1, _p2) = Connection::pair().unwrap();
146         let mut frontend_client = FrontendClient::new(p1);
147 
148         assert!(frontend_client.error.is_none());
149         frontend_client.set_failed(libc::EAGAIN);
150         assert_eq!(frontend_client.error, Some(libc::EAGAIN));
151     }
152 }
153