pub mod cmdline;
pub mod id_types;
use crate::util::{other_io_error, ResultErrorContext};
use btree_range_map::RangeMap;
pub use id_types::{GuestGid, GuestId, GuestUid, HostGid, HostId, HostUid, Id};
use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::ops::{Add, Range, Sub};
pub struct IdMap<Guest: GuestId<HostType = Host>, Host: HostId<GuestType = Guest>> {
guest_to_host: RangeMap<Guest::Inner, MapEntry<Guest, Host>>,
host_to_guest: RangeMap<Host::Inner, MapEntry<Host, Guest>>,
}
#[derive(Clone, Debug, PartialEq)]
enum MapEntry<Source: Id, Target: Id> {
Squash {
from: Range<Source>,
to: Target,
},
Range {
from: Range<Source>,
to_base: Target,
},
Fail {
from: Range<Source>,
},
}
#[derive(Clone, Debug)]
pub enum MapError<Source: Id> {
ExplicitFailMapping { id: Source },
}
impl<Guest, Host> IdMap<Guest, Host>
where
Guest: GuestId<HostType = Host>,
Host: HostId<GuestType = Guest>,
{
pub fn empty() -> Self {
IdMap {
guest_to_host: RangeMap::new(),
host_to_guest: RangeMap::new(),
}
}
pub fn map_guest(&self, guest_id: Guest) -> Result<Host, MapError<Guest>> {
self.guest_to_host
.get(guest_id.into_inner())
.map(|e| e.map(guest_id))
.unwrap_or(Ok(guest_id.id_mapped()))
}
pub fn map_host(&self, host_id: Host) -> Result<Guest, MapError<Host>> {
self.host_to_guest
.get(host_id.into_inner())
.map(|e| e.map(host_id))
.unwrap_or(Ok(host_id.id_mapped()))
}
fn do_push<Source, Target>(
map: &mut RangeMap<Source::Inner, MapEntry<Source, Target>>,
map_name: &str,
entry: MapEntry<Source, Target>,
) -> io::Result<()>
where
Source: Id + Sub<Source>,
Target: Id + Add<<Source as Sub>::Output, Output = Target>,
{
let wrapped_range = entry.source_range();
let inner_range = Range {
start: wrapped_range.start.into_inner(),
end: wrapped_range.end.into_inner(),
};
if map.intersects(inner_range.clone()) {
return Err(other_io_error(format!(
"{map_name} mapping '{entry}' intersects previously added entry"
)));
}
map.insert(inner_range, entry);
Ok(())
}
fn push_guest_to_host(&mut self, entry: MapEntry<Guest, Host>) -> io::Result<()> {
Self::do_push(&mut self.guest_to_host, "Guest-to-host", entry)
}
fn push_host_to_guest(&mut self, entry: MapEntry<Host, Guest>) -> io::Result<()> {
Self::do_push(&mut self.host_to_guest, "Host-to-guest", entry)
}
}
impl<Source: Id, Target: Id> MapEntry<Source, Target>
where
Source: Sub<Source>,
Target: Add<<Source as Sub>::Output, Output = Target>,
{
fn map(&self, id: Source) -> Result<Target, MapError<Source>> {
match self {
MapEntry::Squash { from, to } => {
assert!(from.contains(&id));
Ok(*to)
}
MapEntry::Range { from, to_base } => {
assert!(from.contains(&id));
Ok(*to_base + (id - from.start))
}
MapEntry::Fail { from } => {
assert!(from.contains(&id));
Err(MapError::ExplicitFailMapping { id })
}
}
}
fn source_range(&self) -> &Range<Source> {
match self {
MapEntry::Squash { from, to: _ } => from,
MapEntry::Range { from, to_base: _ } => from,
MapEntry::Fail { from } => from,
}
}
}
impl<Source: Id> Display for MapError<Source> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
MapError::ExplicitFailMapping { id } => {
write!(f, "Use of ID {id} has been configured to fail")
}
}
}
}
impl<Source: Id> std::error::Error for MapError<Source> {}
impl<Source: Id> From<MapError<Source>> for io::Error {
fn from(err: MapError<Source>) -> Self {
io::Error::new(io::ErrorKind::PermissionDenied, err)
}
}
impl<Source: Id, Target: Id> Display for MapEntry<Source, Target>
where
Source: Sub<Source>,
Target: Add<<Source as Sub>::Output, Output = Target>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
MapEntry::Squash { from, to } => {
write!(f, "squash [{}, {}) to {to}", from.start, from.end)
}
MapEntry::Range { from, to_base } => {
write!(
f,
"map [{}, {}) to [{to_base}, {})",
from.start,
from.end,
*to_base + (from.end - from.start)
)
}
MapEntry::Fail { from } => {
write!(f, "fail [{}, {})", from.start, from.end)
}
}
}
}
fn id_range_from_u32<I, P: Display>(base: u32, count: u32, param: P) -> io::Result<Range<I>>
where
u32: Into<I>,
{
let start: I = base.into();
let end: I = base
.checked_add(count)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("Parameter {param}: Range overflow"),
)
})?
.into();
Ok(start..end)
}
impl<Guest, Host> TryFrom<Vec<cmdline::IdMap>> for IdMap<Guest, Host>
where
Guest: GuestId<HostType = Host> + From<u32>,
Host: HostId<GuestType = Guest> + From<u32>,
{
type Error = io::Error;
fn try_from(cmdline: Vec<cmdline::IdMap>) -> io::Result<Self> {
let mut map = IdMap::empty();
for entry in cmdline {
match entry {
cmdline::IdMap::Guest {
from_guest,
to_host,
count,
} => map
.push_guest_to_host(MapEntry::Range {
from: id_range_from_u32(from_guest, count, &entry)?,
to_base: to_host.into(),
})
.err_context(|| entry)?,
cmdline::IdMap::Host {
from_host,
to_guest,
count,
} => map
.push_host_to_guest(MapEntry::Range {
from: id_range_from_u32(from_host, count, &entry)?,
to_base: to_guest.into(),
})
.err_context(|| entry)?,
cmdline::IdMap::SquashGuest {
from_guest,
to_host,
count,
} => map
.push_guest_to_host(MapEntry::Squash {
from: id_range_from_u32(from_guest, count, &entry)?,
to: to_host.into(),
})
.err_context(|| entry)?,
cmdline::IdMap::SquashHost {
from_host,
to_guest,
count,
} => map
.push_host_to_guest(MapEntry::Squash {
from: id_range_from_u32(from_host, count, &entry)?,
to: to_guest.into(),
})
.err_context(|| entry)?,
cmdline::IdMap::Bidirectional { guest, host, count } => {
map.push_guest_to_host(MapEntry::Range {
from: id_range_from_u32(guest, count, &entry)?,
to_base: host.into(),
})
.err_context(|| &entry)?;
map.push_host_to_guest(MapEntry::Range {
from: id_range_from_u32(host, count, &entry)?,
to_base: guest.into(),
})
.err_context(|| &entry)?;
}
cmdline::IdMap::ForbidGuest { from_guest, count } => {
map.push_guest_to_host(MapEntry::Fail {
from: (from_guest.into())..((from_guest + count).into()),
})
.err_context(|| &entry)?;
}
}
}
Ok(map)
}
}