#[cfg(windows)]
use std::num::NonZeroU32;
use std::path::PathBuf;
use cros_async::ExecutorKind;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use crate::PciAddress;
pub mod asynchronous;
pub(crate) mod sys;
pub use asynchronous::BlockAsync;
fn block_option_sparse_default() -> bool {
true
}
fn block_option_lock_default() -> bool {
true
}
fn block_option_block_size_default() -> u32 {
512
}
#[cfg(windows)]
fn block_option_io_concurrency_default() -> NonZeroU32 {
NonZeroU32::new(1).unwrap()
}
pub const DISK_ID_LEN: usize = 20;
pub fn serialize_disk_id<S: Serializer>(
id: &Option<[u8; DISK_ID_LEN]>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match id {
None => serializer.serialize_none(),
Some(id) => {
let len = id.iter().position(|v| *v == 0).unwrap_or(DISK_ID_LEN);
serializer.serialize_some(
std::str::from_utf8(&id[0..len])
.map_err(|e| serde::ser::Error::custom(e.to_string()))?,
)
}
}
}
fn deserialize_disk_id<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
let id = Option::<String>::deserialize(deserializer)?;
match id {
None => Ok(None),
Some(id) => {
if id.len() > DISK_ID_LEN {
return Err(serde::de::Error::custom(format!(
"disk id must be {} or fewer characters",
DISK_ID_LEN
)));
}
let mut ret = [0u8; DISK_ID_LEN];
ret[..id.len()].copy_from_slice(id.as_bytes());
Ok(Some(ret))
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct DiskOption {
pub path: PathBuf,
#[serde(default, rename = "ro")]
pub read_only: bool,
#[serde(default)]
pub root: bool,
#[serde(default = "block_option_sparse_default")]
pub sparse: bool,
#[serde(default, alias = "o_direct")]
pub direct: bool,
#[serde(default = "block_option_lock_default")]
pub lock: bool,
#[serde(default = "block_option_block_size_default", alias = "block_size")]
pub block_size: u32,
#[serde(
default,
serialize_with = "serialize_disk_id",
deserialize_with = "deserialize_disk_id"
)]
pub id: Option<[u8; DISK_ID_LEN]>,
#[cfg(windows)]
#[serde(
default = "block_option_io_concurrency_default",
alias = "io_concurrency"
)]
pub io_concurrency: NonZeroU32,
#[serde(default)]
pub multiple_workers: bool,
#[serde(default, alias = "async_executor")]
pub async_executor: Option<ExecutorKind>,
#[serde(default)]
pub packed_queue: bool,
pub bootindex: Option<usize>,
pub pci_address: Option<PciAddress>,
}
impl Default for DiskOption {
fn default() -> Self {
Self {
path: PathBuf::new(),
read_only: false,
root: false,
sparse: block_option_sparse_default(),
direct: false,
lock: block_option_lock_default(),
block_size: block_option_block_size_default(),
id: None,
#[cfg(windows)]
io_concurrency: block_option_io_concurrency_default(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
}
}
#[cfg(test)]
mod tests {
#[cfg(any(target_os = "android", target_os = "linux"))]
use cros_async::sys::linux::ExecutorKindSys;
#[cfg(windows)]
use cros_async::sys::windows::ExecutorKindSys;
use serde_keyvalue::*;
use super::*;
fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
from_key_values(options)
}
#[test]
fn check_default_matches_from_key_values() {
let path = "/path/to/disk.img";
let disk = DiskOption {
path: PathBuf::from(path),
..DiskOption::default()
};
assert_eq!(disk, from_key_values(path).unwrap());
}
#[test]
fn params_from_key_values() {
let err = from_block_arg("").unwrap_err();
assert_eq!(
err,
ParseError {
kind: ErrorKind::SerdeError("missing field `path`".into()),
pos: 0,
}
);
let params = from_block_arg("/path/to/disk.img").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: Some(5),
pci_address: None,
}
);
let params = from_block_arg("path=/path/to/disk.img").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,ro").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: true,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,root").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: true,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,sparse").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,sparse=false").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: false,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,direct").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: true,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,o_direct").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: true,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,block-size=128").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 128,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,block_size=128").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 128,
id: None,
async_executor: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
#[cfg(windows)]
{
let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
io_concurrency: NonZeroU32::new(4).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params =
from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
.unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: Some(
ExecutorKindSys::Overlapped {
concurrency: Some(4)
}
.into()
),
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
}
let params = from_block_arg("/some/path.img,id=DISK").unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
assert_eq!(
err,
ParseError {
kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
pos: 0,
}
);
#[cfg(windows)]
let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
#[cfg(any(target_os = "android", target_os = "linux"))]
let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
let params =
from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: Some(ex_kind),
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: true,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: Some(PciAddress {
bus: 0,
dev: 1,
func: 1,
}),
}
);
let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
assert_eq!(
params,
DiskOption {
path: "/path/to/disk.img".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: false,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
}
);
let params = from_block_arg(&format!(
"/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
))
.unwrap();
assert_eq!(
params,
DiskOption {
path: "/some/path.img".into(),
read_only: true,
root: true,
sparse: false,
direct: true,
lock: true,
block_size: 256,
id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: Some(ex_kind),
packed_queue: false,
bootindex: None,
pci_address: Some(PciAddress {
bus: 0,
dev: 1,
func: 1,
}),
}
);
}
#[test]
fn diskoption_serialize_deserialize() {
let original = DiskOption {
path: "./rootfs".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: None,
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: None,
packed_queue: false,
bootindex: None,
pci_address: None,
};
let json = serde_json::to_string(&original).unwrap();
let deserialized = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
let original = DiskOption {
path: "./rootfs".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: Some(ExecutorKind::default()),
packed_queue: false,
bootindex: None,
pci_address: None,
};
let json = serde_json::to_string(&original).unwrap();
let deserialized = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
let original = DiskOption {
path: "./rootfs".into(),
read_only: false,
root: false,
sparse: true,
direct: false,
lock: true,
block_size: 512,
id: Some(*b"QWERTYUIOPASDFGHJKL:"),
#[cfg(windows)]
io_concurrency: NonZeroU32::new(1).unwrap(),
multiple_workers: false,
async_executor: Some(ExecutorKind::default()),
packed_queue: false,
bootindex: None,
pci_address: None,
};
let json = serde_json::to_string(&original).unwrap();
let deserialized = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
}