mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
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>
This commit is contained in:
68
core/src/disk/mount/filesystem/backupfs.rs
Normal file
68
core/src/disk/mount/filesystem/backupfs.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct BackupFS<DataDir: AsRef<Path>, Password: fmt::Display> {
|
||||
data_dir: DataDir,
|
||||
password: Password,
|
||||
idmapped_root: Vec<(u32, u32)>,
|
||||
}
|
||||
impl<DataDir: AsRef<Path>, Password: fmt::Display> BackupFS<DataDir, Password> {
|
||||
pub fn new(data_dir: DataDir, password: Password, idmapped_root: Vec<(u32, u32)>) -> Self {
|
||||
BackupFS {
|
||||
data_dir,
|
||||
password,
|
||||
idmapped_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<DataDir: AsRef<Path> + Send + Sync, Password: fmt::Display + Send + Sync> FileSystem
|
||||
for BackupFS<DataDir, Password>
|
||||
{
|
||||
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
||||
Some("backup-fs")
|
||||
}
|
||||
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
|
||||
[
|
||||
Cow::Owned(format!("password={}", self.password)),
|
||||
Cow::Borrowed("file-size-padding=0.05"),
|
||||
Cow::Borrowed("allow_other"),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
self.idmapped_root
|
||||
.iter()
|
||||
.map(|(root, range)| Cow::Owned(format!("idmapped-root={root}:{range}"))),
|
||||
)
|
||||
}
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some(&self.data_dir))
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("BackupFS");
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.data_dir.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.data_dir.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
100
core/src/disk/mount/filesystem/bind.rs
Normal file
100
core/src/disk/mount/filesystem/bind.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::disk::mount::filesystem::MountType;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::create_file;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum FileType {
|
||||
File,
|
||||
Directory,
|
||||
Infer,
|
||||
}
|
||||
impl Default for FileType {
|
||||
fn default() -> Self {
|
||||
FileType::Directory
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bind<Src: AsRef<Path>> {
|
||||
src: Src,
|
||||
filetype: FileType,
|
||||
}
|
||||
impl<Src: AsRef<Path>> Bind<Src> {
|
||||
pub fn new(src: Src) -> Self {
|
||||
Self {
|
||||
src,
|
||||
filetype: FileType::Directory,
|
||||
}
|
||||
}
|
||||
pub fn with_type(mut self, filetype: FileType) -> Self {
|
||||
self.filetype = filetype;
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<Src: AsRef<Path> + Send + Sync> FileSystem for Bind<Src> {
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some(&self.src))
|
||||
}
|
||||
fn extra_args(&self) -> impl IntoIterator<Item = impl AsRef<std::ffi::OsStr>> {
|
||||
["--bind"]
|
||||
}
|
||||
async fn pre_mount(&self, mountpoint: &Path, mount_type: MountType) -> Result<(), Error> {
|
||||
let from_meta = tokio::fs::metadata(&self.src).await.ok();
|
||||
let to_meta = tokio::fs::metadata(&mountpoint).await.ok();
|
||||
if matches!(self.filetype, FileType::File)
|
||||
|| (matches!(self.filetype, FileType::Infer)
|
||||
&& from_meta.as_ref().map_or(false, |m| m.is_file()))
|
||||
{
|
||||
if to_meta.as_ref().map_or(false, |m| m.is_dir()) {
|
||||
tokio::fs::remove_dir(mountpoint).await?;
|
||||
}
|
||||
if from_meta.is_none() && mount_type == MountType::ReadWrite {
|
||||
create_file(self.src.as_ref()).await?.sync_all().await?;
|
||||
}
|
||||
if to_meta.is_none() {
|
||||
create_file(mountpoint).await?.sync_all().await?;
|
||||
}
|
||||
} else {
|
||||
if to_meta.as_ref().map_or(false, |m| m.is_file()) {
|
||||
tokio::fs::remove_file(mountpoint).await?;
|
||||
}
|
||||
if from_meta.is_none() && mount_type == MountType::ReadWrite {
|
||||
tokio::fs::create_dir_all(self.src.as_ref()).await?;
|
||||
}
|
||||
if to_meta.is_none() {
|
||||
tokio::fs::create_dir_all(mountpoint).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("Bind");
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.src.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.src.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
47
core/src/disk/mount/filesystem/block_dev.rs
Normal file
47
core/src/disk/mount/filesystem/block_dev.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(concrete(LogicalName = std::path::PathBuf))]
|
||||
pub struct BlockDev<LogicalName: AsRef<Path>> {
|
||||
logicalname: LogicalName,
|
||||
}
|
||||
impl<LogicalName: AsRef<Path>> BlockDev<LogicalName> {
|
||||
pub fn new(logicalname: LogicalName) -> Self {
|
||||
BlockDev { logicalname }
|
||||
}
|
||||
}
|
||||
impl<LogicalName: AsRef<Path> + Send + Sync> FileSystem for BlockDev<LogicalName> {
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some(&self.logicalname))
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("BlockDev");
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.logicalname.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.logicalname.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
106
core/src/disk/mount/filesystem/cifs.rs
Normal file
106
core/src/disk/mount/filesystem/cifs.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use std::net::IpAddr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use super::{FileSystem, MountType, ReadOnly};
|
||||
use crate::Error;
|
||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||
use crate::util::Invoke;
|
||||
|
||||
async fn resolve_hostname(hostname: &str) -> Result<IpAddr, Error> {
|
||||
if let Ok(addr) = hostname.parse() {
|
||||
return Ok(addr);
|
||||
}
|
||||
if hostname.ends_with(".local") {
|
||||
return Ok(IpAddr::V4(crate::net::mdns::resolve_mdns(hostname).await?));
|
||||
}
|
||||
Ok(String::from_utf8(
|
||||
Command::new("nmblookup")
|
||||
.arg(hostname)
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?,
|
||||
)?
|
||||
.split(" ")
|
||||
.next()
|
||||
.unwrap()
|
||||
.trim()
|
||||
.parse()?)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn mount_cifs(
|
||||
hostname: &str,
|
||||
path: impl AsRef<Path>,
|
||||
username: &str,
|
||||
password: Option<&str>,
|
||||
mountpoint: impl AsRef<Path>,
|
||||
mount_type: MountType,
|
||||
) -> Result<(), Error> {
|
||||
tokio::fs::create_dir_all(mountpoint.as_ref()).await?;
|
||||
let ip: IpAddr = resolve_hostname(hostname).await?;
|
||||
let absolute_path = Path::new("/").join(path.as_ref());
|
||||
let mut cmd = Command::new("mount");
|
||||
cmd.arg("-t")
|
||||
.arg("cifs")
|
||||
.env("USER", username)
|
||||
.env("PASSWD", password.unwrap_or_default())
|
||||
.arg(format!("//{}{}", ip, absolute_path.display()))
|
||||
.arg(mountpoint.as_ref());
|
||||
if mount_type == ReadOnly {
|
||||
cmd.arg("-o").arg("ro,noserverino");
|
||||
} else {
|
||||
cmd.arg("-o").arg("noserverino");
|
||||
}
|
||||
cmd.invoke(crate::ErrorKind::Filesystem).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Cifs {
|
||||
pub hostname: String,
|
||||
pub path: PathBuf,
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
impl Cifs {
|
||||
pub async fn mountable(&self) -> Result<(), Error> {
|
||||
let guard = TmpMountGuard::mount(self, ReadOnly).await?;
|
||||
guard.unmount().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FileSystem for Cifs {
|
||||
async fn mount<P: AsRef<std::path::Path> + Send>(
|
||||
&self,
|
||||
mountpoint: P,
|
||||
mount_type: MountType,
|
||||
) -> Result<(), Error> {
|
||||
mount_cifs(
|
||||
&self.hostname,
|
||||
&self.path,
|
||||
&self.username,
|
||||
self.password.as_ref().map(|p| p.as_str()),
|
||||
mountpoint,
|
||||
mount_type,
|
||||
)
|
||||
.await
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("Cifs");
|
||||
sha.update(self.hostname.as_bytes());
|
||||
sha.update(self.path.as_os_str().as_bytes());
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
83
core/src/disk/mount/filesystem/ecryptfs.rs
Normal file
83
core/src/disk/mount/filesystem/ecryptfs.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::fmt::Display;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use lazy_format::lazy_format;
|
||||
use sha2::Sha256;
|
||||
use tokio::process::Command;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::disk::mount::filesystem::default_mount_command;
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub struct EcryptFS<EncryptedDir: AsRef<Path>, Key: AsRef<str>> {
|
||||
encrypted_dir: EncryptedDir,
|
||||
key: Key,
|
||||
}
|
||||
impl<EncryptedDir: AsRef<Path>, Key: AsRef<str>> EcryptFS<EncryptedDir, Key> {
|
||||
pub fn new(encrypted_dir: EncryptedDir, key: Key) -> Self {
|
||||
EcryptFS { encrypted_dir, key }
|
||||
}
|
||||
}
|
||||
impl<EncryptedDir: AsRef<Path> + Send + Sync, Key: AsRef<str> + Send + Sync> FileSystem
|
||||
for EcryptFS<EncryptedDir, Key>
|
||||
{
|
||||
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
||||
Some("ecryptfs")
|
||||
}
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some(&self.encrypted_dir))
|
||||
}
|
||||
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
|
||||
[
|
||||
Box::new(lazy_format!(
|
||||
"key=passphrase:passphrase_passwd={}",
|
||||
self.key.as_ref()
|
||||
)) as Box<dyn Display>,
|
||||
Box::new("ecryptfs_cipher=aes"),
|
||||
Box::new("ecryptfs_key_bytes=32"),
|
||||
Box::new("ecryptfs_passthrough=n"),
|
||||
Box::new("ecryptfs_enable_filename_crypto=y"),
|
||||
Box::new("no_sig_cache"),
|
||||
]
|
||||
}
|
||||
async fn mount<P: AsRef<Path> + Send>(
|
||||
&self,
|
||||
mountpoint: P,
|
||||
mount_type: super::MountType,
|
||||
) -> Result<(), Error> {
|
||||
self.pre_mount(mountpoint.as_ref(), mount_type).await?;
|
||||
Command::new("mount")
|
||||
.args(
|
||||
default_mount_command(self, mountpoint, mount_type)
|
||||
.await?
|
||||
.get_args(),
|
||||
)
|
||||
.input(Some(&mut std::io::Cursor::new(b"\n")))
|
||||
.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("EcryptFS");
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.encrypted_dir.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.encrypted_dir.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
25
core/src/disk/mount/filesystem/efivarfs.rs
Normal file
25
core/src/disk/mount/filesystem/efivarfs.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct EfiVarFs;
|
||||
impl FileSystem for EfiVarFs {
|
||||
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
||||
Some("efivarfs")
|
||||
}
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some("efivarfs"))
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("EfiVarFs");
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
50
core/src/disk/mount/filesystem/httpdirfs.rs
Normal file
50
core/src/disk/mount/filesystem/httpdirfs.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::{FileSystem, MountType};
|
||||
use crate::Error;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub async fn mount_httpdirfs(url: &Url, mountpoint: impl AsRef<Path>) -> Result<(), Error> {
|
||||
tokio::fs::create_dir_all(mountpoint.as_ref()).await?;
|
||||
let mut cmd = tokio::process::Command::new("httpdirfs");
|
||||
cmd.arg("--cache")
|
||||
.arg("--single-file-mode")
|
||||
.arg(url.as_str())
|
||||
.arg(mountpoint.as_ref());
|
||||
cmd.invoke(crate::ErrorKind::Filesystem).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HttpDirFS {
|
||||
url: Url,
|
||||
}
|
||||
impl HttpDirFS {
|
||||
pub fn new(url: Url) -> Self {
|
||||
HttpDirFS { url }
|
||||
}
|
||||
}
|
||||
impl FileSystem for HttpDirFS {
|
||||
async fn mount<P: AsRef<Path> + Send>(
|
||||
&self,
|
||||
mountpoint: P,
|
||||
_mount_type: MountType,
|
||||
) -> Result<(), Error> {
|
||||
mount_httpdirfs(&self.url, mountpoint).await
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("HttpDirFS");
|
||||
sha.update(self.url.as_str());
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
156
core/src/disk/mount/filesystem/idmapped.rs
Normal file
156
core/src/disk/mount/filesystem/idmapped.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
33
core/src/disk/mount/filesystem/label.rs
Normal file
33
core/src/disk/mount/filesystem/label.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Label<S: AsRef<str>> {
|
||||
label: S,
|
||||
}
|
||||
impl<S: AsRef<str>> Label<S> {
|
||||
pub fn new(label: S) -> Self {
|
||||
Label { label }
|
||||
}
|
||||
}
|
||||
impl<S: AsRef<str> + Send + Sync> FileSystem for Label<S> {
|
||||
fn extra_args(&self) -> impl IntoIterator<Item = impl AsRef<std::ffi::OsStr>> {
|
||||
["-L", self.label.as_ref()]
|
||||
}
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(None::<&Path>)
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("Label");
|
||||
sha.update(self.label.as_ref().as_bytes());
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
63
core/src/disk/mount/filesystem/loop_dev.rs
Normal file
63
core/src/disk/mount/filesystem/loop_dev.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::fmt::Display;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use lazy_format::lazy_format;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::FileSystem;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoopDev<LogicalName: AsRef<Path>> {
|
||||
logicalname: LogicalName,
|
||||
offset: u64,
|
||||
size: u64,
|
||||
}
|
||||
impl<LogicalName: AsRef<Path>> LoopDev<LogicalName> {
|
||||
pub fn new(logicalname: LogicalName, offset: u64, size: u64) -> Self {
|
||||
Self {
|
||||
logicalname,
|
||||
offset,
|
||||
size,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<LogicalName: AsRef<Path> + Send + Sync> FileSystem for LoopDev<LogicalName> {
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some(
|
||||
tokio::fs::canonicalize(self.logicalname.as_ref()).await?,
|
||||
))
|
||||
}
|
||||
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
|
||||
[
|
||||
Box::new("loop") as Box<dyn Display>,
|
||||
Box::new(lazy_format!("offset={}", self.offset)),
|
||||
Box::new(lazy_format!("sizelimit={}", self.size)),
|
||||
]
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("LoopDev");
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.logicalname.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.logicalname.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
sha.update(&u64::to_be_bytes(self.offset)[..]);
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
116
core/src/disk/mount/filesystem/mod.rs
Normal file
116
core/src/disk/mount/filesystem/mod.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{Display, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use digest::OutputSizeUser;
|
||||
use digest::generic_array::GenericArray;
|
||||
use futures::Future;
|
||||
use sha2::Sha256;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
pub mod backupfs;
|
||||
pub mod bind;
|
||||
pub mod block_dev;
|
||||
pub mod cifs;
|
||||
pub mod ecryptfs;
|
||||
pub mod efivarfs;
|
||||
pub mod httpdirfs;
|
||||
pub mod idmapped;
|
||||
pub mod label;
|
||||
pub mod loop_dev;
|
||||
pub mod overlayfs;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MountType {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
pub use MountType::*;
|
||||
|
||||
pub(self) async fn default_mount_command(
|
||||
fs: &(impl FileSystem + ?Sized),
|
||||
mountpoint: impl AsRef<Path> + Send,
|
||||
mount_type: MountType,
|
||||
) -> Result<std::process::Command, Error> {
|
||||
let mut cmd = std::process::Command::new("mount");
|
||||
if mount_type == ReadOnly {
|
||||
cmd.arg("-r");
|
||||
}
|
||||
cmd.args(fs.extra_args());
|
||||
if let Some(ty) = fs.mount_type() {
|
||||
cmd.arg("-t").arg(ty.as_ref());
|
||||
}
|
||||
if let Some(options) = fs
|
||||
.mount_options()
|
||||
.into_iter()
|
||||
.fold(None, |acc: Option<String>, x| match acc {
|
||||
Some(mut s) => {
|
||||
write!(s, ",{}", x).unwrap();
|
||||
Some(s)
|
||||
}
|
||||
None => Some(x.to_string()),
|
||||
})
|
||||
{
|
||||
cmd.arg("-o").arg(options);
|
||||
}
|
||||
if let Some(source) = fs.source().await? {
|
||||
cmd.arg(source.as_ref());
|
||||
}
|
||||
cmd.arg(mountpoint.as_ref());
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
pub(self) async fn default_mount_impl(
|
||||
fs: &(impl FileSystem + ?Sized),
|
||||
mountpoint: impl AsRef<Path> + Send,
|
||||
mount_type: MountType,
|
||||
) -> Result<(), Error> {
|
||||
fs.pre_mount(mountpoint.as_ref(), mount_type).await?;
|
||||
Command::from(default_mount_command(fs, mountpoint, mount_type).await?)
|
||||
.capture(false)
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait FileSystem: Send + Sync {
|
||||
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
||||
None::<&str>
|
||||
}
|
||||
fn extra_args(&self) -> impl IntoIterator<Item = impl AsRef<OsStr>> {
|
||||
[] as [&str; 0]
|
||||
}
|
||||
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
|
||||
[] as [&str; 0]
|
||||
}
|
||||
fn source(&self) -> impl Future<Output = Result<Option<impl AsRef<Path>>, Error>> + Send {
|
||||
async { Ok(None::<&Path>) }
|
||||
}
|
||||
fn pre_mount(
|
||||
&self,
|
||||
mountpoint: &Path,
|
||||
#[allow(unused_variables)] mount_type: MountType,
|
||||
) -> impl Future<Output = Result<(), Error>> + Send {
|
||||
async move {
|
||||
tokio::fs::create_dir_all(mountpoint).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn mount<P: AsRef<Path> + Send>(
|
||||
&self,
|
||||
mountpoint: P,
|
||||
mount_type: MountType,
|
||||
) -> impl Future<Output = Result<(), Error>> + Send {
|
||||
default_mount_impl(self, mountpoint, mount_type)
|
||||
}
|
||||
fn source_hash(
|
||||
&self,
|
||||
) -> impl Future<
|
||||
Output = Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error>,
|
||||
> + Send;
|
||||
}
|
||||
162
core/src/disk/mount/filesystem/overlayfs.rs
Normal file
162
core/src/disk/mount/filesystem/overlayfs.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use std::fmt::Display;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
||||
use crate::disk::mount::guard::{GenericMountGuard, MountGuard};
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::TmpDir;
|
||||
|
||||
pub struct OverlayFs<P0: AsRef<Path>, P1: AsRef<Path>, P2: AsRef<Path>> {
|
||||
lower: P0,
|
||||
upper: P1,
|
||||
work: P2,
|
||||
}
|
||||
impl<P0: AsRef<Path>, P1: AsRef<Path>, P2: AsRef<Path>> OverlayFs<P0, P1, P2> {
|
||||
pub fn new(lower: P0, upper: P1, work: P2) -> Self {
|
||||
Self { lower, upper, work }
|
||||
}
|
||||
}
|
||||
impl<P0: AsRef<Path> + Send + Sync, P1: AsRef<Path> + Send + Sync, P2: AsRef<Path> + Send + Sync>
|
||||
FileSystem for OverlayFs<P0, P1, P2>
|
||||
{
|
||||
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
||||
Some("overlay")
|
||||
}
|
||||
async fn source(&self) -> Result<Option<impl AsRef<Path>>, Error> {
|
||||
Ok(Some("overlay"))
|
||||
}
|
||||
fn mount_options(&self) -> impl IntoIterator<Item = impl Display> {
|
||||
[
|
||||
Box::new(lazy_format!("lowerdir={}", self.lower.as_ref().display()))
|
||||
as Box<dyn Display>,
|
||||
Box::new(lazy_format!("upperdir={}", self.upper.as_ref().display())),
|
||||
Box::new(lazy_format!("workdir={}", self.work.as_ref().display())),
|
||||
]
|
||||
}
|
||||
async fn pre_mount(&self, mountpoint: &Path, _: MountType) -> Result<(), Error> {
|
||||
tokio::fs::create_dir_all(self.upper.as_ref()).await?;
|
||||
tokio::fs::create_dir_all(self.work.as_ref()).await?;
|
||||
tokio::fs::create_dir_all(mountpoint).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn source_hash(
|
||||
&self,
|
||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||
tokio::fs::create_dir_all(self.upper.as_ref()).await?;
|
||||
tokio::fs::create_dir_all(self.work.as_ref()).await?;
|
||||
let mut sha = Sha256::new();
|
||||
sha.update("OverlayFs");
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.lower.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.lower.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.upper.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.upper.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
sha.update(
|
||||
tokio::fs::canonicalize(self.work.as_ref())
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
self.upper.as_ref().display().to_string(),
|
||||
)
|
||||
})?
|
||||
.as_os_str()
|
||||
.as_bytes(),
|
||||
);
|
||||
Ok(sha.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OverlayGuard<G: GenericMountGuard> {
|
||||
lower: Option<G>,
|
||||
upper: Option<TmpDir>,
|
||||
inner_guard: MountGuard,
|
||||
}
|
||||
impl<G: GenericMountGuard> OverlayGuard<G> {
|
||||
pub async fn mount(lower: G, mountpoint: impl AsRef<Path>) -> Result<Self, Error> {
|
||||
let upper = TmpDir::new().await?;
|
||||
let inner_guard = MountGuard::mount(
|
||||
&OverlayFs::new(
|
||||
lower.path(),
|
||||
upper.as_ref().join("upper"),
|
||||
upper.as_ref().join("work"),
|
||||
),
|
||||
mountpoint,
|
||||
ReadWrite,
|
||||
)
|
||||
.await?;
|
||||
Ok(Self {
|
||||
lower: Some(lower),
|
||||
upper: Some(upper),
|
||||
inner_guard,
|
||||
})
|
||||
}
|
||||
pub async fn unmount(mut self, delete_mountpoint: bool) -> Result<(), Error> {
|
||||
self.inner_guard.take().unmount(delete_mountpoint).await?;
|
||||
if let Some(lower) = self.lower.take() {
|
||||
lower.unmount().await?;
|
||||
}
|
||||
if let Some(upper) = self.upper.take() {
|
||||
upper.delete().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn take(&mut self) -> Self {
|
||||
Self {
|
||||
lower: self.lower.take(),
|
||||
upper: self.upper.take(),
|
||||
inner_guard: self.inner_guard.take(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<G: GenericMountGuard> GenericMountGuard for OverlayGuard<G> {
|
||||
fn path(&self) -> &Path {
|
||||
self.inner_guard.path()
|
||||
}
|
||||
async fn unmount(self) -> Result<(), Error> {
|
||||
self.unmount(false).await
|
||||
}
|
||||
}
|
||||
impl<G: GenericMountGuard> Drop for OverlayGuard<G> {
|
||||
fn drop(&mut self) {
|
||||
let lower = self.lower.take();
|
||||
let upper = self.upper.take();
|
||||
let guard = self.inner_guard.take();
|
||||
if lower.is_some() || upper.is_some() || guard.mounted {
|
||||
tokio::spawn(async move {
|
||||
guard.unmount(false).await.log_err();
|
||||
if let Some(lower) = lower {
|
||||
lower.unmount().await.log_err();
|
||||
}
|
||||
if let Some(upper) = upper {
|
||||
upper.delete().await.log_err();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user