Files
start-os/core/src/disk/mount/filesystem/idmapped.rs
Aiden McClelland 96ae532879 Refactor/project structure (#3085)
* refactor project structure

* environment-based default registry

* fix tests

* update build container

* use docker platform for iso build emulation

* simplify compat

* Fix docker platform spec in run-compat.sh

* handle riscv compat

* fix bug with dep error exists attr

* undo removal of sorting

* use qemu for iso stage

---------

Co-authored-by: Mariusz Kogen <k0gen@pm.me>
Co-authored-by: Matt Hill <mattnine@protonmail.com>
2025-12-22 13:39:38 -07:00

157 lines
4.9 KiB
Rust

use std::ffi::OsStr;
use std::fmt::Display;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::str::FromStr;
use clap::Parser;
use clap::builder::ValueParserFactory;
use digest::generic_array::GenericArray;
use digest::{Digest, OutputSizeUser};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use tokio::process::Command;
use ts_rs::TS;
use super::FileSystem;
use crate::disk::mount::filesystem::MountType;
use crate::prelude::*;
use crate::util::{FromStrParser, Invoke};
#[derive(Clone, Copy, Debug, Deserialize, Serialize, Parser, TS)]
#[serde(rename_all = "camelCase")]
pub struct IdMap {
pub from_id: u32,
pub to_id: u32,
pub range: u32,
}
impl IdMap {
pub fn stack(a: Vec<IdMap>, b: Vec<IdMap>) -> Vec<IdMap> {
let mut res = Vec::with_capacity(a.len() + b.len());
res.extend_from_slice(&a);
for mut b in b {
for a in &a {
if a.from_id <= b.to_id && a.from_id + a.range > b.to_id {
b.to_id += a.to_id;
}
}
res.push(b);
}
res
}
}
impl FromStr for IdMap {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.splitn(3, ":").collect::<Vec<_>>();
if let Some([u, k, r]) = split.get(0..3) {
Ok(Self {
from_id: u.parse()?,
to_id: k.parse()?,
range: r.parse()?,
})
} else if let Some([u, k]) = split.get(0..2) {
Ok(Self {
from_id: u.parse()?,
to_id: k.parse()?,
range: 1,
})
} else {
Err(Error::new(
eyre!("{s} is not a valid idmap"),
ErrorKind::ParseNumber,
))
}
}
}
impl ValueParserFactory for IdMap {
type Parser = FromStrParser<IdMap>;
fn value_parser() -> Self::Parser {
<Self::Parser>::new()
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IdMapped<Fs: FileSystem> {
filesystem: Fs,
idmap: Vec<IdMap>,
}
impl<Fs: FileSystem> IdMapped<Fs> {
pub fn new(filesystem: Fs, idmap: Vec<IdMap>) -> Self {
Self { filesystem, idmap }
}
}
impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
fn mount_type(&self) -> Option<impl AsRef<str>> {
self.filesystem.mount_type()
}
fn extra_args(&self) -> impl IntoIterator<Item = impl AsRef<OsStr>> {
self.filesystem.extra_args()
}
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
self.filesystem
.mount_options()
.into_iter()
.map(|a| Box::new(a) as Box<dyn Display>)
.chain(if self.idmap.is_empty() {
None
} else {
use std::fmt::Write;
let mut option = "X-mount.idmap=".to_owned();
for i in &self.idmap {
write!(&mut option, "b:{}:{}:{} ", i.from_id, i.to_id, i.range).unwrap();
}
Some(Box::new(option) as Box<dyn Display>)
})
}
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
self.filesystem.source().await
}
async fn pre_mount(&self, mountpoint: &Path, mount_type: MountType) -> Result<(), Error> {
self.filesystem.pre_mount(mountpoint, mount_type).await?;
let info = tokio::fs::metadata(mountpoint).await?;
for i in &self.idmap {
let uid_in_range = i.from_id <= info.uid() && i.from_id + i.range > info.uid();
let gid_in_range = i.from_id <= info.gid() && i.from_id + i.range > info.gid();
if uid_in_range || gid_in_range {
Command::new("chown")
.arg(format!(
"{uid}:{gid}",
uid = if uid_in_range {
i.to_id + info.uid() - i.from_id
} else {
info.uid()
},
gid = if gid_in_range {
i.to_id + info.gid() - i.from_id
} else {
info.gid()
},
))
.arg(&mountpoint)
.invoke(crate::ErrorKind::Filesystem)
.await?;
}
}
Ok(())
}
async fn source_hash(
&self,
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
let mut sha = Sha256::new();
sha.update("IdMapped");
sha.update(self.filesystem.source_hash().await?);
sha.update(usize::to_be_bytes(self.idmap.len()));
for i in &self.idmap {
sha.update(u32::to_be_bytes(i.from_id));
sha.update(u32::to_be_bytes(i.to_id));
sha.update(u32::to_be_bytes(i.range));
}
Ok(sha.finalize())
}
}