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