diff --git a/backend/src/backup/backup_bulk.rs b/backend/src/backup/backup_bulk.rs index 9d560a46b..92aca651e 100644 --- a/backend/src/backup/backup_bulk.rs +++ b/backend/src/backup/backup_bulk.rs @@ -24,6 +24,7 @@ use crate::disk::mount::guard::TmpMountGuard; use crate::notifications::NotificationLevel; use crate::s9pk::manifest::PackageId; use crate::status::MainStatus; +use crate::util::io::dir_copy; use crate::util::serde::IoFormat; use crate::util::{display_none, Invoke}; use crate::version::VersionT; @@ -369,12 +370,7 @@ async fn perform_backup( } let luks_folder = Path::new("/media/embassy/config/luks"); if tokio::fs::metadata(&luks_folder).await.is_ok() { - Command::new("cp") - .arg("-r") - .arg(&luks_folder) - .arg(&luks_folder_bak) - .invoke(ErrorKind::Filesystem) - .await?; + dir_copy(&luks_folder, &luks_folder_bak).await?; } let timestamp = Some(Utc::now()); diff --git a/backend/src/util/io.rs b/backend/src/util/io.rs index de24dc24f..f96d7731a 100644 --- a/backend/src/util/io.rs +++ b/backend/src/util/io.rs @@ -1,11 +1,13 @@ use std::future::Future; use std::io::Cursor; +use std::os::unix::prelude::MetadataExt; use std::path::Path; use std::task::Poll; use futures::future::{BoxFuture, Fuse}; use futures::{AsyncSeek, FutureExt, TryStreamExt}; use helpers::NonDetachingJoinHandle; +use nix::unistd::{Gid, Uid}; use tokio::io::{ duplex, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, DuplexStream, ReadBuf, WriteHalf, }; @@ -416,3 +418,120 @@ impl AsyncWrite for BackTrackingReader { self.project().reader.poll_write_vectored(cx, bufs) } } + +pub fn dir_copy<'a, P0: AsRef + 'a + Send + Sync, P1: AsRef + 'a + Send + Sync>( + src: P0, + dst: P1, +) -> BoxFuture<'a, Result<(), crate::Error>> { + async move { + let m = tokio::fs::metadata(&src).await?; + let dst_path = dst.as_ref(); + tokio::fs::create_dir_all(&dst_path).await.with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("mkdir {}", dst_path.display()), + ) + })?; + tokio::fs::set_permissions(&dst_path, m.permissions()) + .await + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("chmod {}", dst_path.display()), + ) + })?; + let tmp_dst_path = dst_path.to_owned(); + tokio::task::spawn_blocking(move || { + nix::unistd::chown( + &tmp_dst_path, + Some(Uid::from_raw(m.uid())), + Some(Gid::from_raw(m.gid())), + ) + }) + .await + .with_kind(crate::ErrorKind::Unknown)? + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("chown {}", dst_path.display()), + ) + })?; + tokio_stream::wrappers::ReadDirStream::new(tokio::fs::read_dir(src.as_ref()).await?) + .map_err(|e| crate::Error::new(e, crate::ErrorKind::Filesystem)) + .try_for_each(|e| async move { + let m = e.metadata().await?; + let src_path = e.path(); + let dst_path = dst_path.join(e.file_name()); + if m.is_file() { + let len = m.len(); + let mut dst_file = + &mut tokio::fs::File::create(&dst_path).await.with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("create {}", dst_path.display()), + ) + })?; + tokio::io::copy( + &mut tokio::fs::File::open(&src_path).await.with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("open {}", src_path.display()), + ) + })?, + &mut dst_file, + ) + .await + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("cp {} -> {}", src_path.display(), dst_path.display()), + ) + })?; + dst_file.flush().await?; + dst_file.shutdown().await?; + dst_file.sync_all().await?; + drop(dst_file); + let tmp_dst_path = dst_path.clone(); + tokio::task::spawn_blocking(move || { + nix::unistd::chown( + &tmp_dst_path, + Some(Uid::from_raw(m.uid())), + Some(Gid::from_raw(m.gid())), + ) + }) + .await + .with_kind(crate::ErrorKind::Unknown)? + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("chown {}", dst_path.display()), + ) + })?; + } else if m.is_dir() { + dir_copy(src_path, dst_path).await?; + } else if m.file_type().is_symlink() { + tokio::fs::symlink( + tokio::fs::read_link(&src_path).await.with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("readlink {}", src_path.display()), + ) + })?, + &dst_path, + ) + .await + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("cp -P {} -> {}", src_path.display(), dst_path.display()), + ) + })?; + // Do not set permissions (see https://unix.stackexchange.com/questions/87200/change-permissions-for-a-symbolic-link) + } + Ok(()) + }) + .await?; + Ok(()) + } + .boxed() +}