1use std::collections::BTreeSet;
2use std::path::{Path, PathBuf};
3
4use eyre::Result;
5use floppy_disk::prelude::*;
6use tokio::io::AsyncWriteExt;
7use tracing::{error, trace, warn};
8
9pub struct DiskDrive<
10 'a,
11 'b,
12 F1: FloppyDisk<'a> + FloppyDiskUnixExt + Send + Sync + 'a,
13 F2: FloppyDisk<'b> + FloppyDiskUnixExt + Send + Sync + 'b,
14> where
15 <F1 as FloppyDisk<'a>>::Permissions: FloppyUnixPermissions,
16 <F1 as FloppyDisk<'a>>::Metadata: FloppyUnixMetadata,
17
18 <F1 as FloppyDisk<'a>>::DirBuilder: Send,
19 <F1 as FloppyDisk<'a>>::DirEntry: Send,
20 <F1 as FloppyDisk<'a>>::File: Send,
21 <F1 as FloppyDisk<'a>>::FileType: Send,
22 <F1 as FloppyDisk<'a>>::Metadata: Send,
23 <F1 as FloppyDisk<'a>>::OpenOptions: Send,
24 <F1 as FloppyDisk<'a>>::Permissions: Send,
25 <F1 as FloppyDisk<'a>>::ReadDir: Send,
26
27 <F2 as FloppyDisk<'b>>::Permissions: FloppyUnixPermissions,
28 <F2 as FloppyDisk<'b>>::Metadata: FloppyUnixMetadata,
29
30 <F2 as FloppyDisk<'b>>::DirBuilder: Send,
31 <F2 as FloppyDisk<'b>>::DirEntry: Send,
32 <F2 as FloppyDisk<'b>>::File: Send,
33 <F2 as FloppyDisk<'b>>::FileType: Send,
34 <F2 as FloppyDisk<'b>>::Metadata: Send,
35 <F2 as FloppyDisk<'b>>::OpenOptions: Send,
36 <F2 as FloppyDisk<'b>>::Permissions: Send,
37 <F2 as FloppyDisk<'b>>::ReadDir: Send,
38{
39 _f1: std::marker::PhantomData<&'a F1>,
40 _f2: std::marker::PhantomData<&'b F2>,
41}
42
43impl<
44 'a,
45 'b,
46 F1: FloppyDisk<'a> + FloppyDiskUnixExt + Send + Sync + 'a,
47 F2: FloppyDisk<'b> + FloppyDiskUnixExt + Send + Sync + 'b,
48 > DiskDrive<'a, 'b, F1, F2>
49where
50 <F1 as FloppyDisk<'a>>::Permissions: FloppyUnixPermissions,
51 <F1 as FloppyDisk<'a>>::Metadata: FloppyUnixMetadata,
52 <F2 as FloppyDisk<'b>>::Permissions: FloppyUnixPermissions,
53 <F2 as FloppyDisk<'b>>::Metadata: FloppyUnixMetadata,
54{
55 pub async fn copy_between(src: &'a F1, dest: &'b F2) -> Result<()> {
56 Self::do_copy(src, dest, None, None).await
57 }
58
59 pub async fn copy_from_src<P: Into<PathBuf>>(
60 src: &'a F1,
61 dest: &'b F2,
62 src_scope: P,
63 ) -> Result<()> {
64 let src_scope = src_scope.into();
65 let src_scope = if !src_scope.starts_with("/") {
66 PathBuf::from("/").join(src_scope)
67 } else {
68 src_scope
69 };
70 Self::do_copy(src, dest, Some(src_scope), None).await
71 }
72
73 pub async fn copy_to_dest<P: Into<PathBuf>>(
74 src: &'a F1,
75 dest: &'b F2,
76 dest_scope: P,
77 ) -> Result<()> {
78 let dest_scope = dest_scope.into();
79 let dest_scope = if !dest_scope.starts_with("/") {
80 PathBuf::from("/").join(dest_scope)
81 } else {
82 dest_scope
83 };
84 Self::do_copy(src, dest, None, Some(dest_scope)).await
85 }
86
87 pub async fn copy_from_src_to_dest<P: Into<PathBuf>, Q: Into<PathBuf>>(
88 src: &'a F1,
89 dest: &'b F2,
90 src_scope: P,
91 dest_scope: Q,
92 ) -> Result<()> {
93 let src_scope = src_scope.into();
94 let dest_scope = dest_scope.into();
95 let src_scope = if !src_scope.starts_with("/") {
96 PathBuf::from("/").join(src_scope)
97 } else {
98 src_scope
99 };
100 let dest_scope = if !dest_scope.starts_with("/") {
101 PathBuf::from("/").join(dest_scope)
102 } else {
103 dest_scope
104 };
105 Self::do_copy(src, dest, Some(src_scope), Some(dest_scope)).await
106 }
107
108 async fn do_copy(
109 src: &'a F1,
110 dest: &'b F2,
111 src_path: Option<PathBuf>,
112 dest_path: Option<PathBuf>,
113 ) -> Result<()> {
114 let src_path = src_path.unwrap_or_else(|| PathBuf::from("/"));
115 let dest_path = dest_path.unwrap_or_else(|| PathBuf::from("/"));
116 let paths = if src.metadata(&src_path).await?.is_file() {
117 trace!("copying src file {}", src_path.display());
118 let mut out = BTreeSet::new();
119 out.insert(src_path);
120 out
121 } else {
122 trace!("copying src dir {}", src_path.display());
123 nyoom::walk_ordered(src, src_path).await?
124 };
125 for src_path in paths {
126 trace!("processing src_path: {}", src_path.display());
127 match <F1 as FloppyDisk<'a>>::read_link(src, &src_path).await {
128 Ok(_) => {
129 trace!(
130 "copy symlink {} -> {}",
131 src_path.display(),
132 dest_path.display()
133 );
134 Self::add_symlink_to_memfs(src, dest, &src_path, &dest_path).await?;
135 }
136 Err(_) => {
137 let metadata = <F1 as FloppyDisk<'a>>::metadata(src, &src_path).await?;
138 let file_type = metadata.file_type();
139 if file_type.is_dir() {
140 trace!("copy dir {} -> {}", src_path.display(), dest_path.display());
141 Self::copy_dir_to_memfs(src, dest, &src_path, &dest_path).await?;
142 } else if file_type.is_file() {
143 trace!(
144 "copy file {} -> {}",
145 src_path.display(),
146 dest_path.display()
147 );
148 Self::copy_file_to_memfs(src, dest, &src_path, &dest_path).await?;
149 } else {
150 error!("unknown file type for source path {src_path:?}");
151 }
152 }
153 };
154 }
155
156 Ok(())
157 }
158
159 async fn copy_file_to_memfs(
160 src: &'a F1,
161 dest: &'b F2,
162 src_path: &Path,
163 dest_path: &Path,
164 ) -> Result<()> {
165 dest.create_dir_all("/").await?;
166 let dest_path = if !dest_path.starts_with("/") {
167 PathBuf::from("/").join(dest_path)
168 } else {
169 dest_path.to_path_buf()
170 };
171 let dest_metadata = <F2 as FloppyDisk>::metadata(dest, &dest_path).await;
173 let dest_path = if dest_metadata.is_ok() {
174 let dest_metadata = dest_metadata?;
175 if dest_metadata.is_dir() {
176 trace!("dest path is a dir, appending src name!");
177 let src_path = if src_path.starts_with("/") {
179 src_path.strip_prefix("/").unwrap()
180 } else {
181 src_path
182 };
183 dest_path.join(src_path)
184 } else {
185 dest_path.to_path_buf()
186 }
187 } else {
188 dest_path.to_path_buf()
189 };
190 let dest_path = dest_path.as_path();
191
192 trace!("creating file {dest_path:?}");
193 if let Some(memfs_parent) = dest_path.parent() {
194 trace!("creating parents: {}", memfs_parent.display());
195 dest.create_dir_all(memfs_parent).await?;
196 }
197
198 let mut src_handle: <F1 as FloppyDisk>::File = <F1::OpenOptions>::new()
199 .read(true)
200 .open(src, src_path)
201 .await?;
202 {
203 if let Some(parent) = dest_path.parent() {
204 trace!("creating dest file parents");
205 dest.create_dir_all(parent).await?;
206 }
207
208 let dest_metadata = <F2 as FloppyDisk>::metadata(dest, dest_path).await;
209 if dest_metadata.is_err() {
211 trace!("dest file {dest_path:?} doesn't exist, copying directly!");
212 let mut dest_handle: <F2 as FloppyDisk>::File = <F2::OpenOptions>::new()
213 .create(true)
214 .read(true)
215 .write(true)
216 .create_new(true)
217 .open(dest, dest_path)
218 .await?;
219 tokio::io::copy(&mut src_handle, &mut dest_handle).await?;
220
221 let src_metadata = src_handle.metadata().await?;
223 let src_permissions = src_metadata.permissions();
224 let mode = <<F1 as FloppyDisk<'_>>::Permissions as FloppyUnixPermissions>::mode(
225 &src_permissions,
226 );
227
228 let permissions = <<F2 as FloppyDisk>::Permissions>::from_mode(mode);
229 let uid = src_metadata.uid()?;
230 let gid = src_metadata.gid()?;
231
232 <F2 as FloppyDiskUnixExt>::chown(dest, dest_path, uid, gid).await?;
233 <F2 as FloppyDisk>::set_permissions(dest, dest_path, permissions).await?;
234
235 return Ok(());
236 }
237
238 let mut dest_handle: <F2 as FloppyDisk>::File = <F2::OpenOptions>::new()
239 .read(true)
240 .write(true)
241 .open(dest, dest_path)
242 .await?;
243
244 let dest_metadata = dest_metadata?;
246 if dest_metadata.is_dir() {
247 trace!("copying into dir {dest_path:?}");
248 let dest_path = dest_path.join(Path::new(src_path.file_name().unwrap()));
249 trace!("target path = {dest_path:?}");
250 let written = tokio::io::copy(&mut src_handle, &mut dest_handle).await?;
251 trace!("wrote {written} bytes");
252 dest_handle.flush().await?;
253
254 let src_metadata = src_handle.metadata().await?;
256 let src_permissions = src_metadata.permissions();
257 let mode = <<F1 as FloppyDisk<'_>>::Permissions as FloppyUnixPermissions>::mode(
258 &src_permissions,
259 );
260
261 let permissions = <<F2 as FloppyDisk>::Permissions>::from_mode(mode);
262 let uid = src_metadata.uid()?;
263 let gid = src_metadata.gid()?;
264
265 <F2 as FloppyDiskUnixExt>::chown(dest, &dest_path, uid, gid).await?;
266 <F2 as FloppyDisk>::set_permissions(dest, &dest_path, permissions).await?;
267
268 return Ok(());
269 }
270
271 if dest_metadata.is_file() {
273 trace!("overwriting dest file {dest_path:?}");
274 tokio::io::copy(&mut src_handle, &mut dest_handle).await?;
275
276 let src_metadata = src_handle.metadata().await?;
278 let src_permissions = src_metadata.permissions();
279 let mode = <<F1 as FloppyDisk<'_>>::Permissions as FloppyUnixPermissions>::mode(
280 &src_permissions,
281 );
282
283 let permissions = <<F2 as FloppyDisk>::Permissions>::from_mode(mode);
284 let uid = src_metadata.uid()?;
285 let gid = src_metadata.gid()?;
286
287 <F2 as FloppyDiskUnixExt>::chown(dest, dest_path, uid, gid).await?;
288 <F2 as FloppyDisk>::set_permissions(dest, dest_path, permissions).await?;
289
290 return Ok(());
291 }
292
293 if dest_metadata.is_symlink() {
295 warn!("dest file path {dest_path:?} is a symlink, skipping copy!");
296 return Ok(());
297 }
298 }
299
300 let src_metadata = src_handle.metadata().await?;
301 let src_permissions = src_metadata.permissions();
302 let mode =
303 <<F1 as FloppyDisk<'_>>::Permissions as FloppyUnixPermissions>::mode(&src_permissions);
304 let permissions = <<F2 as FloppyDisk>::Permissions>::from_mode(mode);
305 let uid = src_metadata.uid()?;
306 let gid = src_metadata.gid()?;
307 <F2 as FloppyDiskUnixExt>::chown(dest, dest_path, uid, gid).await?;
308 <F2 as FloppyDisk>::set_permissions(dest, dest_path, permissions).await?;
309
310 Ok(())
311 }
312
313 async fn copy_dir_to_memfs(
314 src: &'a F1,
315 dest: &'b F2,
316 src_path: &Path,
317 dest_path: &Path,
318 ) -> Result<()> {
319 let dest_path = dest_path.join(src_path);
320 let dest_path = dest_path.as_path();
321 trace!("creating dir {dest_path:?}");
322 dest.create_dir_all(dest_path).await?;
323
324 let src_metadata = src.metadata(src_path).await?;
325 let mode = src_metadata.permissions().mode();
326 let permissions = <F2 as FloppyDisk>::Permissions::from_mode(mode);
327 dest.set_permissions(dest_path, permissions).await?;
328 dest.chown(dest_path, src_metadata.uid()?, src_metadata.gid()?)
329 .await?;
330
331 Ok(())
332 }
333
334 async fn add_symlink_to_memfs(
335 src: &F1,
336 dest: &F2,
337 src_path: &Path,
338 dest_path: &Path,
339 ) -> Result<()> {
340 let dest_path = dest_path.join(src_path);
341 let dest_path = dest_path.as_path();
342 let link = src.read_link(src_path).await?;
343 trace!("linking {dest_path:?} to {link:?}");
344 dest.symlink(link, dest_path.into()).await?;
345
346 Ok(())
347 }
348}