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: Src, filetype: FileType, recursive: bool, } impl> Bind { pub fn new(src: Src) -> Self { Self { src, filetype: FileType::Directory, recursive: false, } } pub fn with_type(mut self, filetype: FileType) -> Self { self.filetype = filetype; self } pub fn recursive(mut self, recursive: bool) -> Self { self.recursive = recursive; self } } impl + Send + Sync> FileSystem for Bind { async fn source(&self) -> Result>, Error> { Ok(Some(&self.src)) } fn extra_args(&self) -> impl IntoIterator> { [if self.recursive { "--rbind" } else { "--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::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()) } }