xref: /cloud-hypervisor/api_client/src/lib.rs (revision 5e52729453cb62edbe4fb3a4aa24f8cca31e667e)
1 // Copyright © 2020 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use std::fmt;
7 use std::io::{Read, Write};
8 use std::os::unix::io::RawFd;
9 use vmm_sys_util::sock_ctrl_msg::ScmSocket;
10 
11 #[derive(Debug)]
12 pub enum Error {
13     Socket(std::io::Error),
14     SocketSendFds(vmm_sys_util::errno::Error),
15     StatusCodeParsing(std::num::ParseIntError),
16     MissingProtocol,
17     ContentLengthParsing(std::num::ParseIntError),
18     ServerResponse(StatusCode, Option<String>),
19 }
20 
21 impl fmt::Display for Error {
22     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23         use Error::*;
24         match self {
25             Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
26             SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
27             StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
28             MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
29             ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
30             ServerResponse(s, o) => {
31                 if let Some(o) = o {
32                     write!(f, "Server responded with an error: {s:?}: {o}")
33                 } else {
34                     write!(f, "Server responded with an error: {s:?}")
35                 }
36             }
37         }
38     }
39 }
40 
41 #[derive(Clone, Copy, Debug)]
42 pub enum StatusCode {
43     Continue,
44     Ok,
45     NoContent,
46     BadRequest,
47     NotFound,
48     InternalServerError,
49     NotImplemented,
50     Unknown,
51 }
52 
53 impl StatusCode {
54     fn from_raw(code: usize) -> StatusCode {
55         match code {
56             100 => StatusCode::Continue,
57             200 => StatusCode::Ok,
58             204 => StatusCode::NoContent,
59             400 => StatusCode::BadRequest,
60             404 => StatusCode::NotFound,
61             500 => StatusCode::InternalServerError,
62             501 => StatusCode::NotImplemented,
63             _ => StatusCode::Unknown,
64         }
65     }
66 
67     fn parse(code: &str) -> Result<StatusCode, Error> {
68         Ok(StatusCode::from_raw(
69             code.trim().parse().map_err(Error::StatusCodeParsing)?,
70         ))
71     }
72 
73     fn is_server_error(self) -> bool {
74         !matches!(
75             self,
76             StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
77         )
78     }
79 }
80 
81 fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
82     let header_str = format!("{header}: ");
83     res.find(&header_str)
84         .map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
85 }
86 
87 fn get_status_code(res: &str) -> Result<StatusCode, Error> {
88     if let Some(o) = res.find("HTTP/1.1") {
89         Ok(StatusCode::parse(
90             &res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
91         )?)
92     } else {
93         Err(Error::MissingProtocol)
94     }
95 }
96 
97 fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
98     let mut res = String::new();
99     let mut body_offset = None;
100     let mut content_length: Option<usize> = None;
101     loop {
102         let mut bytes = vec![0; 256];
103         let count = socket.read(&mut bytes).map_err(Error::Socket)?;
104         // If the return value is 0, the peer has performed an orderly shutdown.
105         if count == 0 {
106             break;
107         }
108         res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
109 
110         // End of headers
111         if let Some(o) = res.find("\r\n\r\n") {
112             body_offset = Some(o + "\r\n\r\n".len());
113 
114             // With all headers available we can see if there is any body
115             content_length = if let Some(length) = get_header(&res, "Content-Length") {
116                 Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
117             } else {
118                 None
119             };
120 
121             if content_length.is_none() {
122                 break;
123             }
124         }
125 
126         if let Some(body_offset) = body_offset {
127             if let Some(content_length) = content_length {
128                 if res.len() >= content_length + body_offset {
129                     break;
130                 }
131             }
132         }
133     }
134     let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
135     let status_code = get_status_code(&res)?;
136 
137     if status_code.is_server_error() {
138         Err(Error::ServerResponse(status_code, body_string))
139     } else {
140         Ok(body_string)
141     }
142 }
143 
144 /// Make an API request using the fully qualified command name.
145 /// For example, full_command could be "vm.create" or "vmm.ping".
146 pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
147     socket: &mut T,
148     method: &str,
149     full_command: &str,
150     request_body: Option<&str>,
151     request_fds: Vec<RawFd>,
152 ) -> Result<Option<String>, Error> {
153     socket
154         .send_with_fds(
155             &[format!(
156                 "{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
157             )
158             .as_bytes()],
159             &request_fds,
160         )
161         .map_err(Error::SocketSendFds)?;
162 
163     if let Some(request_body) = request_body {
164         socket
165             .write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
166             .map_err(Error::Socket)?;
167     }
168 
169     socket.write_all(b"\r\n").map_err(Error::Socket)?;
170 
171     if let Some(request_body) = request_body {
172         socket
173             .write_all(request_body.as_bytes())
174             .map_err(Error::Socket)?;
175     }
176 
177     socket.flush().map_err(Error::Socket)?;
178 
179     parse_http_response(socket)
180 }
181 
182 pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
183     socket: &mut T,
184     method: &str,
185     full_command: &str,
186     request_body: Option<&str>,
187     request_fds: Vec<RawFd>,
188 ) -> Result<(), Error> {
189     let response = simple_api_full_command_with_fds_and_response(
190         socket,
191         method,
192         full_command,
193         request_body,
194         request_fds,
195     )?;
196 
197     if response.is_some() {
198         println!("{}", response.unwrap());
199     }
200 
201     Ok(())
202 }
203 
204 pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
205     socket: &mut T,
206     method: &str,
207     full_command: &str,
208     request_body: Option<&str>,
209 ) -> Result<(), Error> {
210     simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
211 }
212 
213 pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
214     socket: &mut T,
215     method: &str,
216     full_command: &str,
217     request_body: Option<&str>,
218 ) -> Result<Option<String>, Error> {
219     simple_api_full_command_with_fds_and_response(
220         socket,
221         method,
222         full_command,
223         request_body,
224         Vec::new(),
225     )
226 }
227 
228 pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
229     socket: &mut T,
230     method: &str,
231     c: &str,
232     request_body: Option<&str>,
233     request_fds: Vec<RawFd>,
234 ) -> Result<(), Error> {
235     // Create the full VM command. For VMM commands, use
236     // simple_api_full_command().
237     let full_command = format!("vm.{c}");
238 
239     simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
240 }
241 
242 pub fn simple_api_command<T: Read + Write + ScmSocket>(
243     socket: &mut T,
244     method: &str,
245     c: &str,
246     request_body: Option<&str>,
247 ) -> Result<(), Error> {
248     simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
249 }
250