mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
This streamlines the code base to follow best practices for error handling in Rust: Each error struct implements std::error::Error (most due via thiserror::Error derive macro) and sets its source accordingly. This allows future work that nicely prints the error chains, for example. So far, the convention is that each error prints its sub error as part of its Display::fmt() impl. Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de> On-behalf-of: SAP philipp.schuster@sap.com
239 lines
6.8 KiB
Rust
239 lines
6.8 KiB
Rust
// Copyright © 2020 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
use std::io::{Read, Write};
|
|
use std::os::unix::io::RawFd;
|
|
|
|
use thiserror::Error;
|
|
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum Error {
|
|
#[error("Error writing to or reading from HTTP socket: {0}")]
|
|
Socket(#[source] std::io::Error),
|
|
#[error("Error sending file descriptors: {0}")]
|
|
SocketSendFds(#[source] vmm_sys_util::errno::Error),
|
|
#[error("Error parsing HTTP status code: {0}")]
|
|
StatusCodeParsing(#[source] std::num::ParseIntError),
|
|
#[error("HTTP output is missing protocol statement")]
|
|
MissingProtocol,
|
|
#[error("Error parsing HTTP Content-Length field: {0}")]
|
|
ContentLengthParsing(#[source] std::num::ParseIntError),
|
|
#[error("Server responded with an error: {0:?}: {1:?}")]
|
|
ServerResponse(StatusCode, Option<String>),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub enum StatusCode {
|
|
Continue,
|
|
Ok,
|
|
NoContent,
|
|
BadRequest,
|
|
NotFound,
|
|
TooManyRequests,
|
|
InternalServerError,
|
|
NotImplemented,
|
|
Unknown,
|
|
}
|
|
|
|
impl StatusCode {
|
|
fn from_raw(code: usize) -> StatusCode {
|
|
match code {
|
|
100 => StatusCode::Continue,
|
|
200 => StatusCode::Ok,
|
|
204 => StatusCode::NoContent,
|
|
400 => StatusCode::BadRequest,
|
|
404 => StatusCode::NotFound,
|
|
429 => StatusCode::TooManyRequests,
|
|
500 => StatusCode::InternalServerError,
|
|
501 => StatusCode::NotImplemented,
|
|
_ => StatusCode::Unknown,
|
|
}
|
|
}
|
|
|
|
fn parse(code: &str) -> Result<StatusCode, Error> {
|
|
Ok(StatusCode::from_raw(
|
|
code.trim().parse().map_err(Error::StatusCodeParsing)?,
|
|
))
|
|
}
|
|
|
|
fn is_server_error(self) -> bool {
|
|
!matches!(
|
|
self,
|
|
StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
|
|
)
|
|
}
|
|
}
|
|
|
|
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
|
let header_str = format!("{header}: ");
|
|
res.find(&header_str)
|
|
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
|
}
|
|
|
|
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
|
|
if let Some(o) = res.find("HTTP/1.1") {
|
|
Ok(StatusCode::parse(
|
|
&res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
|
|
)?)
|
|
} else {
|
|
Err(Error::MissingProtocol)
|
|
}
|
|
}
|
|
|
|
fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|
let mut res = String::new();
|
|
let mut body_offset = None;
|
|
let mut content_length: Option<usize> = None;
|
|
loop {
|
|
let mut bytes = vec![0; 256];
|
|
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
|
|
// If the return value is 0, the peer has performed an orderly shutdown.
|
|
if count == 0 {
|
|
break;
|
|
}
|
|
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
|
|
|
|
// End of headers
|
|
if let Some(o) = res.find("\r\n\r\n") {
|
|
body_offset = Some(o + "\r\n\r\n".len());
|
|
|
|
// With all headers available we can see if there is any body
|
|
content_length = if let Some(length) = get_header(&res, "Content-Length") {
|
|
Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if content_length.is_none() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if let Some(body_offset) = body_offset {
|
|
if let Some(content_length) = content_length {
|
|
if res.len() >= content_length + body_offset {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
|
let status_code = get_status_code(&res)?;
|
|
|
|
if status_code.is_server_error() {
|
|
Err(Error::ServerResponse(status_code, body_string))
|
|
} else {
|
|
Ok(body_string)
|
|
}
|
|
}
|
|
|
|
/// Make an API request using the fully qualified command name.
|
|
/// For example, full_command could be "vm.create" or "vmm.ping".
|
|
pub fn 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> {
|
|
socket
|
|
.send_with_fds(
|
|
&[format!(
|
|
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
|
)
|
|
.as_bytes()],
|
|
&request_fds,
|
|
)
|
|
.map_err(Error::SocketSendFds)?;
|
|
|
|
if let Some(request_body) = request_body {
|
|
socket
|
|
.write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
|
|
.map_err(Error::Socket)?;
|
|
}
|
|
|
|
socket.write_all(b"\r\n").map_err(Error::Socket)?;
|
|
|
|
if let Some(request_body) = request_body {
|
|
socket
|
|
.write_all(request_body.as_bytes())
|
|
.map_err(Error::Socket)?;
|
|
}
|
|
|
|
socket.flush().map_err(Error::Socket)?;
|
|
|
|
parse_http_response(socket)
|
|
}
|
|
|
|
pub fn 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> {
|
|
let response = simple_api_full_command_with_fds_and_response(
|
|
socket,
|
|
method,
|
|
full_command,
|
|
request_body,
|
|
request_fds,
|
|
)?;
|
|
|
|
if response.is_some() {
|
|
println!("{}", response.unwrap());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
|
|
socket: &mut T,
|
|
method: &str,
|
|
full_command: &str,
|
|
request_body: Option<&str>,
|
|
) -> Result<(), Error> {
|
|
simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
|
|
}
|
|
|
|
pub fn 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> {
|
|
simple_api_full_command_with_fds_and_response(
|
|
socket,
|
|
method,
|
|
full_command,
|
|
request_body,
|
|
Vec::new(),
|
|
)
|
|
}
|
|
|
|
pub fn 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> {
|
|
// Create the full VM command. For VMM commands, use
|
|
// simple_api_full_command().
|
|
let full_command = format!("vm.{c}");
|
|
|
|
simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
|
|
}
|
|
|
|
pub fn simple_api_command<T: Read + Write + ScmSocket>(
|
|
socket: &mut T,
|
|
method: &str,
|
|
c: &str,
|
|
request_body: Option<&str>,
|
|
) -> Result<(), Error> {
|
|
simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
|
|
}
|