1use std::{
2 collections::HashSet,
3 fmt::Debug,
4 io::{self, SeekFrom},
5 path::{Path, PathBuf},
6 pin::Pin,
7 sync::Arc,
8 task::{Context, Poll},
9};
10
11use futures::future::BoxFuture;
12use replace_with::replace_with_or_abort;
13use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
14
15use crate::{
16 FileOpener, FileSystem, FileSystems, FsError, Metadata, OpenOptions, OpenOptionsConfig,
17 ReadDir, VirtualFile, ops,
18};
19
20fn unlink_overlay_path<P>(primary: &Arc<P>, path: &Path) -> Result<(), FsError>
21where
22 P: FileSystem + ?Sized,
23{
24 match ops::create_white_out(primary, path) {
29 Ok(()) | Err(FsError::AlreadyExists) => {}
30 Err(e) => return Err(e),
31 }
32
33 match primary.remove_file(path) {
34 Err(e) if should_continue(e) => Ok(()),
37 other => other,
38 }
39}
40
41struct SecondaryFile<P> {
42 path: PathBuf,
43 primary: Arc<P>,
44 inner: Box<dyn VirtualFile + Send + Sync>,
45}
46
47impl<P> Debug for SecondaryFile<P>
48where
49 P: FileSystem + 'static,
50{
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("SecondaryFile").finish()
53 }
54}
55
56impl<P> VirtualFile for SecondaryFile<P>
57where
58 P: FileSystem + 'static,
59{
60 fn last_accessed(&self) -> u64 {
61 self.inner.last_accessed()
62 }
63
64 fn last_modified(&self) -> u64 {
65 self.inner.last_modified()
66 }
67
68 fn created_time(&self) -> u64 {
69 self.inner.created_time()
70 }
71
72 fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
73 self.inner.set_times(atime, mtime)
74 }
75
76 fn size(&self) -> u64 {
77 self.inner.size()
78 }
79
80 fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
81 self.inner.set_len(new_size)
82 }
83
84 fn unlink(&mut self) -> crate::Result<()> {
85 match ops::create_white_out(&self.primary, &self.path) {
90 Ok(()) => Ok(()),
91 Err(FsError::AlreadyExists) => Err(FsError::EntryNotFound),
94 Err(e) => Err(e),
95 }
96 }
97
98 fn is_open(&self) -> bool {
99 self.inner.is_open()
100 }
101
102 fn get_special_fd(&self) -> Option<u32> {
103 self.inner.get_special_fd()
104 }
105
106 fn write_from_mmap(&mut self, offset: u64, len: u64) -> std::io::Result<()> {
107 self.inner.write_from_mmap(offset, len)
108 }
109
110 fn as_owned_buffer(&self) -> Option<shared_buffer::OwnedBuffer> {
111 self.inner.as_owned_buffer()
112 }
113
114 fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
115 Pin::new(self.inner.as_mut()).poll_read_ready(cx)
116 }
117
118 fn poll_write_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
119 Pin::new(self.inner.as_mut()).poll_write_ready(cx)
120 }
121}
122
123impl<P> AsyncRead for SecondaryFile<P>
124where
125 P: FileSystem + 'static,
126{
127 fn poll_read(
128 mut self: Pin<&mut Self>,
129 cx: &mut Context<'_>,
130 buf: &mut ReadBuf<'_>,
131 ) -> Poll<io::Result<()>> {
132 Pin::new(self.inner.as_mut()).poll_read(cx, buf)
133 }
134}
135
136impl<P> AsyncWrite for SecondaryFile<P>
137where
138 P: FileSystem + 'static,
139{
140 fn poll_write(
141 mut self: Pin<&mut Self>,
142 cx: &mut Context<'_>,
143 buf: &[u8],
144 ) -> Poll<io::Result<usize>> {
145 Pin::new(self.inner.as_mut()).poll_write(cx, buf)
146 }
147
148 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
149 Pin::new(self.inner.as_mut()).poll_flush(cx)
150 }
151
152 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
153 Pin::new(self.inner.as_mut()).poll_shutdown(cx)
154 }
155
156 fn poll_write_vectored(
157 mut self: Pin<&mut Self>,
158 cx: &mut Context<'_>,
159 bufs: &[io::IoSlice<'_>],
160 ) -> Poll<io::Result<usize>> {
161 Pin::new(self.inner.as_mut()).poll_write_vectored(cx, bufs)
162 }
163
164 fn is_write_vectored(&self) -> bool {
165 self.inner.is_write_vectored()
166 }
167}
168
169impl<P> AsyncSeek for SecondaryFile<P>
170where
171 P: FileSystem + 'static,
172{
173 fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
174 Pin::new(self.inner.as_mut()).start_seek(position)
175 }
176
177 fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
178 Pin::new(self.inner.as_mut()).poll_complete(cx)
179 }
180}
181
182#[derive(Clone, PartialEq, Eq)]
227pub struct OverlayFileSystem<P, S> {
228 primary: Arc<P>,
229 secondaries: S,
230}
231
232impl<P, S> OverlayFileSystem<P, S>
233where
234 P: FileSystem + Send + Sync + 'static,
235 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
236{
237 pub fn new(primary: P, secondaries: S) -> Self {
240 OverlayFileSystem {
241 primary: Arc::new(primary),
242 secondaries,
243 }
244 }
245
246 pub fn primary(&self) -> &P {
248 &self.primary
249 }
250
251 pub fn secondaries(&self) -> &S {
253 &self.secondaries
254 }
255
256 pub fn secondaries_mut(&mut self) -> &mut S {
258 &mut self.secondaries
259 }
260
261 fn permission_error_or_not_found(&self, path: &Path) -> Result<(), FsError> {
262 for fs in self.secondaries.filesystems() {
263 if ops::exists(fs, path) {
264 return Err(FsError::PermissionDenied);
265 }
266 }
267
268 Err(FsError::EntryNotFound)
269 }
270}
271
272impl<P, S> FileSystem for OverlayFileSystem<P, S>
273where
274 P: FileSystem + Send + 'static,
275 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
276 for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
277{
278 fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
279 if ops::is_white_out(path).is_some() {
281 return Err(FsError::EntryNotFound);
282 }
283
284 match self.primary.readlink(path) {
286 Ok(meta) => return Ok(meta),
287 Err(e) if should_continue(e) => {}
288 Err(e) => return Err(e),
289 }
290
291 if ops::has_white_out(&self.primary, path) {
293 return Err(FsError::EntryNotFound);
294 }
295
296 for fs in self.secondaries.filesystems() {
298 match fs.readlink(path) {
299 Err(e) if should_continue(e) => continue,
300 other => return other,
301 }
302 }
303
304 Err(FsError::EntryNotFound)
305 }
306
307 fn read_dir(&self, path: &Path) -> Result<ReadDir, FsError> {
308 let mut entries = Vec::new();
309 let mut had_at_least_one_success = false;
310 let mut white_outs = HashSet::new();
311
312 let filesystems = std::iter::once(&self.primary as &(dyn FileSystem + Send))
313 .chain(self.secondaries().filesystems());
314
315 for fs in filesystems {
316 match fs.read_dir(path) {
317 Ok(r) => {
318 for entry in r {
319 let entry = entry?;
320
321 if let Some(path) = entry.is_white_out() {
325 tracing::trace!(
326 path=%path.display(),
327 "Found whiteout file",
328 );
329 white_outs.insert(path);
330 continue;
331 } else if white_outs.contains(&entry.path) {
332 tracing::trace!(
333 path=%path.display(),
334 "Skipping path because a whiteout exists",
335 );
336 continue;
337 }
338
339 entries.push(entry);
340 }
341 had_at_least_one_success = true;
342 }
343 Err(e) if should_continue(e) => continue,
344 Err(e) => return Err(e),
345 }
346 }
347
348 if had_at_least_one_success {
349 entries.sort_by(|a, b| a.path.cmp(&b.path));
354 entries.dedup_by(|a, b| a.path == b.path);
355
356 Ok(ReadDir::new(entries))
357 } else {
358 Err(FsError::BaseNotDirectory)
359 }
360 }
361
362 fn create_dir(&self, path: &Path) -> Result<(), FsError> {
363 if ops::is_white_out(path).is_some() {
365 return Err(FsError::InvalidInput);
366 }
367
368 ops::remove_white_out(self.primary.as_ref(), path);
371
372 if let Some(parent) = path.parent()
376 && self.read_dir(parent).is_ok()
377 {
378 ops::create_dir_all(&self.primary, parent).ok();
379 }
380
381 match self.primary.create_dir(path) {
383 Err(e) if should_continue(e) => {}
384 other => return other,
385 }
386
387 self.permission_error_or_not_found(path)
388 }
389
390 fn create_symlink(&self, source: &Path, target: &Path) -> Result<(), FsError> {
391 if ops::is_white_out(target).is_some() {
392 return Err(FsError::InvalidInput);
393 }
394
395 ops::remove_white_out(self.primary.as_ref(), target);
396
397 if let Some(parent) = target.parent()
398 && self.read_dir(parent).is_ok()
399 {
400 ops::create_dir_all(&self.primary, parent).ok();
401 }
402
403 self.primary.create_symlink(source, target)
404 }
405
406 fn hard_link(&self, source: &Path, target: &Path) -> Result<(), FsError> {
407 if ops::is_white_out(source).is_some() || ops::is_white_out(target).is_some() {
408 return Err(FsError::InvalidInput);
409 }
410
411 ops::remove_white_out(self.primary.as_ref(), target);
412
413 if let Some(parent) = target.parent()
414 && self.read_dir(parent).is_ok()
415 {
416 ops::create_dir_all(&self.primary, parent).ok();
417 }
418
419 self.primary.hard_link(source, target)
420 }
421
422 fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
423 if ops::is_white_out(path).is_some() {
426 tracing::trace!(
427 path=%path.display(),
428 "Unable to remove a whited out directory",
429 );
430 return Err(FsError::EntryNotFound);
431 }
432
433 let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
437 fs.read_dir(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
438 });
439
440 match self.primary.remove_dir(path) {
444 Err(e) if should_continue(e) => {}
445 other => return other,
446 }
447
448 if had_at_least_one_success {
449 return Ok(());
450 }
451 self.permission_error_or_not_found(path)
452 }
453
454 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
455 let from = from.to_owned();
456 let to = to.to_owned();
457 Box::pin(async move {
458 if ops::is_white_out(&from).is_some() {
460 tracing::trace!(
461 from=%from.display(),
462 to=%to.display(),
463 "Attempting to rename a file that was whited out"
464 );
465 return Err(FsError::EntryNotFound);
466 }
467 if ops::is_white_out(&to).is_some() {
469 tracing::trace!(
470 from=%from.display(),
471 to=%to.display(),
472 "Attempting to rename a file into a whiteout file"
473 );
474 return Err(FsError::InvalidInput);
475 }
476
477 let mut had_at_least_one_success = false;
482 match self.primary.rename(&from, &to).await {
483 Err(e) if should_continue(e) => {}
484 Ok(()) => {
485 had_at_least_one_success = true;
486 }
487 other => return other,
488 }
489
490 if !had_at_least_one_success {
494 for fs in self.secondaries.filesystems() {
495 if fs.metadata(&from).is_ok() {
496 ops::copy_reference_ext(fs, &self.primary, &from, &to).await?;
497 had_at_least_one_success = true;
498 break;
499 }
500 }
501 }
502
503 if had_at_least_one_success {
506 for fs in self.secondaries.filesystems() {
507 if fs.metadata(&from).is_ok() {
508 tracing::trace!(
509 path=%from.display(),
510 "Creating a whiteout for the file that was renamed",
511 );
512 ops::create_white_out(&self.primary, &from).ok();
513 break;
514 }
515 }
516 ops::remove_white_out(&self.primary, &to);
517 return Ok(());
518 }
519
520 self.permission_error_or_not_found(&from)
522 })
523 }
524
525 fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
526 if ops::is_white_out(path).is_some() {
528 return Err(FsError::EntryNotFound);
529 }
530
531 match self.primary.metadata(path) {
533 Ok(meta) => return Ok(meta),
534 Err(e) if should_continue(e) => {}
535 Err(e) => return Err(e),
536 }
537
538 if ops::has_white_out(&self.primary, path) {
540 return Err(FsError::EntryNotFound);
541 }
542
543 for fs in self.secondaries.filesystems() {
545 match fs.metadata(path) {
546 Err(e) if should_continue(e) => continue,
547 other => return other,
548 }
549 }
550
551 Err(FsError::EntryNotFound)
552 }
553
554 fn symlink_metadata(&self, path: &Path) -> crate::Result<Metadata> {
555 if ops::is_white_out(path).is_some() {
557 return Err(FsError::EntryNotFound);
558 }
559
560 match self.primary.symlink_metadata(path) {
562 Ok(meta) => return Ok(meta),
563 Err(e) if should_continue(e) => {}
564 Err(e) => return Err(e),
565 }
566
567 if ops::has_white_out(&self.primary, path) {
569 return Err(FsError::EntryNotFound);
570 }
571
572 for fs in self.secondaries.filesystems() {
574 match fs.symlink_metadata(path) {
575 Err(e) if should_continue(e) => continue,
576 other => return other,
577 }
578 }
579
580 Err(FsError::EntryNotFound)
581 }
582
583 fn remove_file(&self, path: &Path) -> Result<(), FsError> {
584 if ops::is_white_out(path).is_some() {
587 return Err(FsError::InvalidInput);
588 }
589
590 let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
593 fs.metadata(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
594 });
595
596 match self.primary.remove_file(path) {
598 Err(e) if should_continue(e) => {}
599 other => return other,
600 }
601
602 if had_at_least_one_success {
603 return Ok(());
604 }
605 self.permission_error_or_not_found(path)
606 }
607
608 fn new_open_options(&self) -> OpenOptions<'_> {
609 OpenOptions::new(self)
610 }
611}
612
613impl<P, S> FileOpener for OverlayFileSystem<P, S>
614where
615 P: FileSystem + Send + 'static,
616 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
617 for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
618{
619 fn open(
620 &self,
621 path: &Path,
622 conf: &OpenOptionsConfig,
623 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>, FsError> {
624 if ops::is_white_out(path).is_some() {
626 tracing::trace!(
627 path=%path.display(),
628 options=?conf,
629 "Whiteout files can't be opened",
630 );
631 return Err(FsError::InvalidInput);
632 }
633
634 {
637 let mut conf = conf.clone();
638 conf.create = false;
639 conf.create_new = false;
640 match self.primary.new_open_options().options(conf).open(path) {
641 Err(e) if should_continue(e) => {}
642 other => return other,
643 }
644 }
645
646 if conf.create_new {
649 if let Some(parent) = path.parent() {
653 if ops::exists(self, parent) {
654 ops::create_dir_all(&self.primary, parent)?;
658 } else {
659 return Err(FsError::EntryNotFound);
660 }
661 }
662
663 ops::remove_white_out(&self.primary, path);
665
666 return self
668 .primary
669 .new_open_options()
670 .options(conf.clone())
671 .open(path);
672 }
673
674 if !conf.create && ops::has_white_out(&self.primary, path) {
679 tracing::trace!(
680 path=%path.display(),
681 "The file has been whited out",
682 );
683 return Err(FsError::EntryNotFound);
684 }
685
686 let require_mutations = conf.append || conf.write || conf.create_new | conf.truncate;
688
689 if !ops::has_white_out(&self.primary, path) {
691 for fs in self.secondaries.filesystems() {
692 let mut sub_conf = conf.clone();
693 sub_conf.create = false;
694 sub_conf.create_new = false;
695 sub_conf.append = false;
696 sub_conf.truncate = false;
697 match fs.new_open_options().options(sub_conf.clone()).open(path) {
698 Err(e) if should_continue(e) => continue,
699 Ok(file) if require_mutations => {
700 return open_copy_on_write(path, conf, &self.primary, file);
705 }
706 Ok(file) => {
707 return Ok(Box::new(SecondaryFile {
708 path: path.to_path_buf(),
709 primary: Arc::clone(&self.primary),
710 inner: file,
711 }));
712 }
713 Err(err) => return Err(err),
714 }
715 }
716 }
717
718 if conf.create {
720 if let Some(parent) = path.parent()
722 && ops::exists(self, parent)
723 {
724 ops::create_dir_all(&self.primary, parent)?;
725 }
726 ops::remove_white_out(&self.primary, path);
727
728 return self
730 .primary
731 .new_open_options()
732 .options(conf.clone())
733 .open(path);
734 }
735
736 Err(FsError::EntryNotFound)
738 }
739}
740
741fn open_copy_on_write<P>(
742 path: &Path,
743 conf: &OpenOptionsConfig,
744 primary: &Arc<P>,
745 file: Box<dyn VirtualFile + Send + Sync>,
746) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError>
747where
748 P: FileSystem,
749{
750 struct CopyOnWriteFile<P> {
751 path: PathBuf,
752 primary: Arc<P>,
753 state: CowState,
754 readable: bool,
755 append: bool,
756 new_size: Option<u64>,
757 unlinked: bool,
758 }
759 enum CowState {
760 ReadOnly(Box<dyn VirtualFile + Send + Sync>),
763 SeekingGet(Box<dyn VirtualFile + Send + Sync>),
766 SeekingSet {
770 original_offset: u64,
771 src: Box<dyn VirtualFile + Send + Sync>,
772 },
773 Copying {
776 original_offset: u64,
777 buf: Vec<u8>,
778 buf_pos: usize,
779 dst: Box<dyn VirtualFile + Send + Sync>,
780 src: Box<dyn VirtualFile + Send + Sync>,
781 },
782 SeekingRestore {
785 dst: Box<dyn VirtualFile + Send + Sync>,
786 },
787 Copied(Box<dyn VirtualFile + Send + Sync>),
789 Error {
793 err: io::Error,
794 src: Box<dyn VirtualFile + Send + Sync>,
795 },
796 }
797 impl CowState {
798 fn as_ref(&self) -> &(dyn VirtualFile + Send + Sync) {
799 match self {
800 Self::ReadOnly(inner) => inner.as_ref(),
801 Self::SeekingGet(inner) => inner.as_ref(),
802 Self::SeekingSet { src, .. } => src.as_ref(),
803 Self::Copying { src, .. } => src.as_ref(),
804 Self::SeekingRestore { dst, .. } => dst.as_ref(),
805 Self::Copied(inner) => inner.as_ref(),
806 Self::Error { src, .. } => src.as_ref(),
807 }
808 }
809 fn as_mut(&mut self) -> &mut (dyn VirtualFile + Send + Sync) {
810 match self {
811 Self::ReadOnly(inner) => inner.as_mut(),
812 Self::SeekingGet(inner) => inner.as_mut(),
813 Self::SeekingSet { src, .. } => src.as_mut(),
814 Self::Copying { src, .. } => src.as_mut(),
815 Self::SeekingRestore { dst, .. } => dst.as_mut(),
816 Self::Copied(inner) => inner.as_mut(),
817 Self::Error { src, .. } => src.as_mut(),
818 }
819 }
820 }
821
822 impl<P> CopyOnWriteFile<P>
823 where
824 P: FileSystem + 'static,
825 {
826 fn poll_copy_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
827 let mut again = true;
829 while again {
830 again = false;
831
832 replace_with_or_abort(&mut self.state, |state| match state {
834 CowState::SeekingGet(mut src) => {
837 match Pin::new(src.as_mut()).poll_complete(cx) {
838 Poll::Ready(Ok(offset)) => {
839 if let Err(err) =
840 Pin::new(src.as_mut()).start_seek(SeekFrom::Start(0))
841 {
842 return CowState::Error { err, src };
843 }
844 again = true;
845 CowState::SeekingSet {
846 original_offset: offset,
847 src,
848 }
849 }
850 Poll::Ready(Err(err)) => CowState::Error { err, src },
851 Poll::Pending => CowState::SeekingGet(src),
852 }
853 }
854
855 CowState::SeekingSet {
857 original_offset,
858 mut src,
859 } => {
860 match Pin::new(src.as_mut()).poll_complete(cx).map_ok(|_| ()) {
861 Poll::Ready(Ok(())) => {
862 if self.unlinked {
863 again = true;
864 return CowState::Copying {
865 original_offset,
866 buf: Vec::new(),
867 buf_pos: 0,
868 dst: Box::new(crate::BufferFile::default()),
869 src,
870 };
871 }
872
873 if let Some(parent) = self.path.parent() {
876 ops::create_dir_all(&self.primary, parent).ok();
877 }
878 let mut had_white_out = false;
879 if ops::has_white_out(&self.primary, &self.path) {
880 ops::remove_white_out(&self.primary, &self.path);
881 had_white_out = true;
882 }
883 let dst = self
884 .primary
885 .new_open_options()
886 .create(true)
887 .read(self.readable)
888 .write(true)
889 .truncate(true)
890 .open(&self.path);
891 match dst {
892 Ok(dst) if had_white_out => {
893 again = true;
894 CowState::Copied(dst)
895 }
896 Ok(dst) => {
897 again = true;
898 CowState::Copying {
899 original_offset,
900 buf: Vec::new(),
901 buf_pos: 0,
902 src,
903 dst,
904 }
905 }
906 Err(err) => CowState::Error {
907 err: err.into(),
908 src,
909 },
910 }
911 }
912 Poll::Ready(Err(err)) => CowState::Error { err, src },
913 Poll::Pending => CowState::SeekingSet {
914 original_offset,
915 src,
916 },
917 }
918 }
919 CowState::Copying {
921 mut src,
922 mut dst,
923 mut buf,
924 mut buf_pos,
925 original_offset,
926 } => {
927 loop {
928 if buf_pos < buf.len() {
931 let dst_pinned = Pin::new(dst.as_mut());
932 match dst_pinned.poll_write(cx, &buf[buf_pos..]) {
933 Poll::Ready(Ok(0)) => {}
934 Poll::Ready(Ok(amt)) => {
935 buf_pos += amt;
936 continue;
937 }
938 Poll::Ready(Err(err)) => {
939 return CowState::Error { err, src };
940 }
941 Poll::Pending => {}
942 }
943 } else {
944 buf.resize_with(8192, || 0);
945 buf_pos = 8192;
946 let mut read_buf = ReadBuf::new(&mut buf);
947 match Pin::new(src.as_mut()).poll_read(cx, &mut read_buf) {
948 Poll::Ready(Ok(())) if read_buf.filled().is_empty() => {
949 again = true;
950
951 if self.append {
952 return CowState::Copied(dst);
955 } else {
956 if let Err(err) = Pin::new(dst.as_mut())
959 .start_seek(SeekFrom::Start(original_offset))
960 {
961 return CowState::Error { err, src };
962 }
963 return CowState::SeekingRestore { dst };
964 }
965 }
966 Poll::Ready(Ok(())) => {
967 let new_len = read_buf.filled().len();
969 unsafe { buf.set_len(new_len) };
970 buf_pos = 0;
971 continue;
972 }
973 Poll::Ready(Err(err)) => return CowState::Error { err, src },
974 Poll::Pending => {}
975 }
976 }
977 return CowState::Copying {
978 original_offset,
979 buf,
980 buf_pos,
981 src,
982 dst,
983 };
984 }
985 }
986 CowState::SeekingRestore { mut dst } => {
988 match Pin::new(dst.as_mut()).poll_complete(cx) {
989 Poll::Ready(_) => {
990 if let Some(new_size) = self.new_size.take() {
992 dst.set_len(new_size).ok();
993 }
994 CowState::Copied(dst)
995 }
996 Poll::Pending => CowState::SeekingRestore { dst },
997 }
998 }
999 s => s,
1000 });
1001 }
1002
1003 let mut ret = Poll::Pending;
1006 replace_with_or_abort(&mut self.state, |state| match state {
1007 CowState::ReadOnly(src) => {
1008 ret = Poll::Ready(Ok(()));
1009 CowState::ReadOnly(src)
1010 }
1011 CowState::Copied(src) => {
1012 ret = Poll::Ready(Ok(()));
1013 CowState::Copied(src)
1014 }
1015 CowState::Error { err, src } => {
1016 ret = Poll::Ready(Err(err));
1017 CowState::ReadOnly(src)
1018 }
1019 state => {
1020 ret = Poll::Pending;
1021 state
1022 }
1023 });
1024 ret
1025 }
1026
1027 fn poll_copy_start_and_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
1028 replace_with_or_abort(&mut self.state, |state| match state {
1029 CowState::ReadOnly(inner) => {
1030 tracing::trace!("COW file touched, starting file clone");
1031 CowState::SeekingGet(inner)
1032 }
1033 state => state,
1034 });
1035 self.poll_copy_progress(cx)
1036 }
1037 }
1038
1039 impl<P> Debug for CopyOnWriteFile<P>
1040 where
1041 P: FileSystem + 'static,
1042 {
1043 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1044 f.debug_struct("CopyOnWriteFile").finish()
1045 }
1046 }
1047
1048 impl<P> VirtualFile for CopyOnWriteFile<P>
1049 where
1050 P: FileSystem + 'static,
1051 {
1052 fn last_accessed(&self) -> u64 {
1053 self.state.as_ref().last_accessed()
1054 }
1055
1056 fn last_modified(&self) -> u64 {
1057 self.state.as_ref().last_modified()
1058 }
1059
1060 fn created_time(&self) -> u64 {
1061 self.state.as_ref().created_time()
1062 }
1063
1064 fn size(&self) -> u64 {
1065 self.state.as_ref().size()
1066 }
1067
1068 fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
1069 self.new_size = Some(new_size);
1070 replace_with_or_abort(&mut self.state, |state| match state {
1071 CowState::Copied(mut file) => {
1072 file.set_len(new_size).ok();
1073 CowState::Copied(file)
1074 }
1075 state => {
1076 if self.unlinked {
1077 return match state {
1078 CowState::ReadOnly(inner) => CowState::SeekingGet(inner),
1079 state => state,
1080 };
1081 }
1082
1083 if let Some(parent) = self.path.parent() {
1086 ops::create_dir_all(&self.primary, parent).ok();
1087 }
1088 let dst = self
1089 .primary
1090 .new_open_options()
1091 .create(true)
1092 .write(true)
1093 .open(&self.path);
1094 if let Ok(mut file) = dst {
1095 file.set_len(new_size).ok();
1096 }
1097 state
1098 }
1099 });
1100 Ok(())
1101 }
1102
1103 fn unlink(&mut self) -> crate::Result<()> {
1104 match &self.state {
1105 CowState::Copied(_) => {
1106 unlink_overlay_path(&self.primary, &self.path)?;
1109 }
1110 _ => {
1111 match ops::create_white_out(&self.primary, &self.path) {
1116 Ok(()) => {}
1117 Err(FsError::AlreadyExists) => return Err(FsError::EntryNotFound),
1120 Err(e) => return Err(e),
1121 }
1122 }
1123 }
1124 self.unlinked = true;
1125 Ok(())
1126 }
1127
1128 fn poll_read_ready(
1129 mut self: Pin<&mut Self>,
1130 cx: &mut std::task::Context<'_>,
1131 ) -> Poll<std::io::Result<usize>> {
1132 match self.poll_copy_progress(cx) {
1133 Poll::Ready(Ok(())) => {}
1134 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1135 Poll::Pending => return Poll::Pending,
1136 }
1137 Pin::new(self.state.as_mut()).poll_read_ready(cx)
1138 }
1139
1140 fn poll_write_ready(
1141 mut self: Pin<&mut Self>,
1142 cx: &mut std::task::Context<'_>,
1143 ) -> Poll<std::io::Result<usize>> {
1144 match self.poll_copy_progress(cx) {
1145 Poll::Ready(Ok(())) => {}
1146 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1147 Poll::Pending => return Poll::Pending,
1148 }
1149 Pin::new(self.state.as_mut()).poll_write_ready(cx)
1150 }
1151 }
1152
1153 impl<P> AsyncWrite for CopyOnWriteFile<P>
1154 where
1155 P: FileSystem + 'static,
1156 {
1157 fn poll_write(
1158 mut self: Pin<&mut Self>,
1159 cx: &mut std::task::Context<'_>,
1160 buf: &[u8],
1161 ) -> Poll<Result<usize, std::io::Error>> {
1162 match self.poll_copy_start_and_progress(cx) {
1163 Poll::Pending => return Poll::Pending,
1164 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1165 Poll::Ready(Ok(())) => {}
1166 }
1167 Pin::new(self.state.as_mut()).poll_write(cx, buf)
1168 }
1169
1170 fn poll_write_vectored(
1171 mut self: Pin<&mut Self>,
1172 cx: &mut Context<'_>,
1173 bufs: &[io::IoSlice<'_>],
1174 ) -> Poll<Result<usize, io::Error>> {
1175 match self.poll_copy_start_and_progress(cx) {
1176 Poll::Pending => return Poll::Pending,
1177 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1178 Poll::Ready(Ok(())) => {}
1179 }
1180 Pin::new(self.state.as_mut()).poll_write_vectored(cx, bufs)
1181 }
1182
1183 fn poll_flush(
1184 mut self: Pin<&mut Self>,
1185 cx: &mut std::task::Context<'_>,
1186 ) -> Poll<Result<(), std::io::Error>> {
1187 match self.poll_copy_progress(cx) {
1188 Poll::Ready(Ok(())) => {}
1189 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1190 Poll::Pending => return Poll::Pending,
1191 }
1192 match self.state {
1195 CowState::ReadOnly(_) => Poll::Ready(Ok(())),
1196 _ => Pin::new(self.state.as_mut()).poll_flush(cx),
1197 }
1198 }
1199
1200 fn poll_shutdown(
1201 mut self: Pin<&mut Self>,
1202 cx: &mut std::task::Context<'_>,
1203 ) -> Poll<Result<(), std::io::Error>> {
1204 match self.poll_copy_progress(cx) {
1205 Poll::Ready(Ok(())) => {}
1206 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1207 Poll::Pending => return Poll::Pending,
1208 }
1209 match self.state {
1211 CowState::ReadOnly(_) => Poll::Ready(Ok(())),
1212 _ => Pin::new(self.state.as_mut()).poll_shutdown(cx),
1213 }
1214 }
1215 }
1216
1217 impl<P> AsyncRead for CopyOnWriteFile<P>
1218 where
1219 P: FileSystem + 'static,
1220 {
1221 fn poll_read(
1222 mut self: Pin<&mut Self>,
1223 cx: &mut std::task::Context<'_>,
1224 buf: &mut tokio::io::ReadBuf<'_>,
1225 ) -> Poll<std::io::Result<()>> {
1226 match self.poll_copy_progress(cx) {
1227 Poll::Ready(Ok(())) => {}
1228 p => return p,
1229 }
1230 Pin::new(self.state.as_mut()).poll_read(cx, buf)
1231 }
1232 }
1233
1234 impl<P> AsyncSeek for CopyOnWriteFile<P>
1235 where
1236 P: FileSystem + 'static,
1237 {
1238 fn start_seek(
1239 mut self: Pin<&mut Self>,
1240 position: std::io::SeekFrom,
1241 ) -> std::io::Result<()> {
1242 match &mut self.state {
1243 CowState::ReadOnly(file)
1244 | CowState::SeekingGet(file)
1245 | CowState::Error { src: file, .. }
1246 | CowState::Copied(file)
1247 | CowState::SeekingRestore { dst: file, .. } => {
1248 Pin::new(file.as_mut()).start_seek(position)
1249 }
1250 CowState::SeekingSet {
1251 original_offset,
1252 src,
1253 ..
1254 }
1255 | CowState::Copying {
1256 original_offset,
1257 src,
1258 ..
1259 } => {
1260 *original_offset = match position {
1261 SeekFrom::Current(delta) => original_offset
1262 .checked_add_signed(delta)
1263 .unwrap_or(*original_offset),
1264 SeekFrom::Start(pos) => pos,
1265 SeekFrom::End(pos) => src
1266 .size()
1267 .checked_add_signed(pos)
1268 .unwrap_or(*original_offset),
1269 };
1270 Ok(())
1271 }
1272 }
1273 }
1274
1275 fn poll_complete(
1276 mut self: Pin<&mut Self>,
1277 cx: &mut std::task::Context<'_>,
1278 ) -> Poll<std::io::Result<u64>> {
1279 match self.poll_copy_progress(cx) {
1280 Poll::Ready(Ok(())) => {}
1281 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1282 Poll::Pending => return Poll::Pending,
1283 }
1284 Pin::new(self.state.as_mut()).poll_complete(cx)
1285 }
1286 }
1287
1288 tracing::trace!(
1289 path=%path.display(),
1290 options=?conf,
1291 "Opening the file in copy-on-write mode",
1292 );
1293 Ok(Box::new(CopyOnWriteFile::<P> {
1294 path: path.to_path_buf(),
1295 primary: primary.clone(),
1296 state: CowState::ReadOnly(file),
1297 readable: conf.read,
1298 append: conf.append,
1299 new_size: None,
1300 unlinked: false,
1301 }))
1302}
1303
1304impl<P, S> Debug for OverlayFileSystem<P, S>
1305where
1306 P: FileSystem,
1307 S: for<'a> FileSystems<'a>,
1308{
1309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1310 struct IterFilesystems<'a, S>(&'a S);
1311 impl<S> Debug for IterFilesystems<'_, S>
1312 where
1313 S: for<'b> FileSystems<'b>,
1314 {
1315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1316 let mut f = f.debug_list();
1317
1318 for fs in self.0.filesystems() {
1319 f.entry(&fs);
1320 }
1321
1322 f.finish()
1323 }
1324 }
1325
1326 f.debug_struct("OverlayFileSystem")
1327 .field("primary", &self.primary)
1328 .field("secondaries", &IterFilesystems(&self.secondaries))
1329 .finish()
1330 }
1331}
1332
1333fn should_continue(e: FsError) -> bool {
1334 matches!(
1339 e,
1340 FsError::EntryNotFound | FsError::InvalidInput | FsError::BaseNotDirectory
1341 )
1342}
1343
1344#[cfg(test)]
1345mod tests {
1346 use std::path::PathBuf;
1347
1348 use super::*;
1349 use crate::mem_fs::FileSystem as MemFS;
1350 use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
1351
1352 #[test]
1353 fn overlay_read_dir_rebases_mounted_entries() {
1354 let primary = MemFS::default();
1355 ops::create_dir_all(&primary, "/app").unwrap();
1356
1357 let volume = MemFS::default();
1358 ops::create_dir_all(&volume, "/themes/twentytwentyfour").unwrap();
1359
1360 let container = MemFS::default();
1361 ops::create_dir_all(&container, "/app/wp-content/themes/twentytwentyfour").unwrap();
1362
1363 let volume: Arc<dyn FileSystem + Send + Sync> = Arc::new(volume);
1364 primary
1365 .mount(
1366 PathBuf::from("/app/wp-content"),
1367 &volume,
1368 PathBuf::from("/"),
1369 )
1370 .unwrap();
1371
1372 let overlay = OverlayFileSystem::new(primary, [container]);
1373
1374 let mut entries: Vec<_> = overlay
1375 .read_dir(Path::new("/app/wp-content/themes"))
1376 .unwrap()
1377 .map(|entry| entry.unwrap().path)
1378 .collect();
1379 entries.sort();
1380
1381 assert_eq!(
1382 entries,
1383 vec![PathBuf::from("/app/wp-content/themes/twentytwentyfour")],
1384 );
1385 }
1386
1387 #[tokio::test]
1388 async fn remove_directory() {
1389 let primary = MemFS::default();
1390 let secondary = MemFS::default();
1391 let first = Path::new("/first");
1392 let second = Path::new("/second");
1393 let file_txt = second.join("file.txt");
1394 let third = Path::new("/third");
1395 primary.create_dir(first).unwrap();
1396 primary.create_dir(second).unwrap();
1397 primary
1398 .new_open_options()
1399 .create(true)
1400 .write(true)
1401 .open(&file_txt)
1402 .unwrap()
1403 .write_all(b"Hello, World!")
1404 .await
1405 .unwrap();
1406 secondary.create_dir(third).unwrap();
1407
1408 let overlay = OverlayFileSystem::new(primary, [secondary]);
1409
1410 overlay.remove_dir(first).unwrap();
1412 assert_eq!(
1413 overlay.primary().metadata(first).unwrap_err(),
1414 FsError::EntryNotFound,
1415 "Deleted from primary"
1416 );
1417 assert!(!ops::exists(&overlay.secondaries[0], second));
1418
1419 assert_eq!(
1421 overlay.remove_dir(second).unwrap_err(),
1422 FsError::DirectoryNotEmpty,
1423 );
1424
1425 assert_eq!(overlay.remove_dir(third), Ok(()));
1427
1428 assert_eq!(overlay.metadata(third).unwrap_err(), FsError::EntryNotFound);
1430
1431 assert!(ops::exists(&overlay.secondaries[0], third));
1432 }
1433
1434 #[tokio::test]
1435 async fn open_files() {
1436 let primary = MemFS::default();
1437 let secondary = MemFS::default();
1438 ops::create_dir_all(&primary, "/primary").unwrap();
1439 ops::touch(&primary, "/primary/read.txt").unwrap();
1440 ops::touch(&primary, "/primary/write.txt").unwrap();
1441 ops::create_dir_all(&secondary, "/secondary").unwrap();
1442 ops::touch(&secondary, "/secondary/read.txt").unwrap();
1443 ops::touch(&secondary, "/secondary/write.txt").unwrap();
1444 ops::create_dir_all(&secondary, "/primary").unwrap();
1445 ops::write(&secondary, "/primary/read.txt", "This is shadowed")
1446 .await
1447 .unwrap();
1448
1449 let fs = OverlayFileSystem::new(primary, [secondary]);
1450
1451 let _ = fs
1453 .new_open_options()
1454 .create(true)
1455 .write(true)
1456 .open("/new.txt")
1457 .unwrap();
1458 assert!(ops::exists(&fs.primary, "/new.txt"));
1459 assert!(!ops::exists(&fs.secondaries[0], "/new.txt"));
1460
1461 let _ = fs
1463 .new_open_options()
1464 .create(false)
1465 .write(true)
1466 .read(true)
1467 .open("/primary/write.txt")
1468 .unwrap();
1469
1470 let content = ops::read_to_string(&fs, "/primary/read.txt").await.unwrap();
1472 assert_ne!(content, "This is shadowed");
1473 }
1474
1475 #[tokio::test]
1476 async fn open_secondary_file_preserves_owned_buffer_access() {
1477 let primary = MemFS::default();
1478 let secondary = MemFS::default();
1479 ops::create_dir_all(&secondary, "/secondary").unwrap();
1480 secondary
1481 .insert_ro_file(
1482 "/secondary/buffer.txt".as_ref(),
1483 b"overlay-buffer".to_vec().into(),
1484 )
1485 .unwrap();
1486
1487 let fs = OverlayFileSystem::new(primary, [secondary]);
1488
1489 let file = fs
1490 .new_open_options()
1491 .read(true)
1492 .open("/secondary/buffer.txt")
1493 .unwrap();
1494
1495 let buffer = file
1496 .as_owned_buffer()
1497 .expect("secondary wrapper should preserve inner owned-buffer access");
1498 assert_eq!(buffer.as_slice(), b"overlay-buffer");
1499 }
1500
1501 #[tokio::test]
1502 async fn create_file_that_looks_like_it_is_in_a_secondary_filesystem_folder() {
1503 let primary = MemFS::default();
1504 let secondary = MemFS::default();
1505 ops::create_dir_all(&secondary, "/path/to/").unwrap();
1506 assert!(!ops::is_dir(&primary, "/path/to/"));
1507 let fs = OverlayFileSystem::new(primary, [secondary]);
1508
1509 ops::touch(&fs, "/path/to/file.txt").unwrap();
1510
1511 assert!(ops::is_dir(&fs.primary, "/path/to/"));
1512 assert!(ops::is_file(&fs.primary, "/path/to/file.txt"));
1513 assert!(!ops::is_file(&fs.secondaries[0], "/path/to/file.txt"));
1514 }
1515
1516 #[tokio::test]
1517 async fn listed_files_appear_overlaid() {
1518 let primary = MemFS::default();
1519 let secondary = MemFS::default();
1520 let secondary_overlaid = MemFS::default();
1521 ops::create_dir_all(&primary, "/primary").unwrap();
1522 ops::touch(&primary, "/primary/read.txt").unwrap();
1523 ops::touch(&primary, "/primary/write.txt").unwrap();
1524 ops::create_dir_all(&secondary, "/secondary").unwrap();
1525 ops::touch(&secondary, "/secondary/read.txt").unwrap();
1526 ops::touch(&secondary, "/secondary/write.txt").unwrap();
1527 ops::create_dir_all(&secondary_overlaid, "/secondary").unwrap();
1530 ops::touch(&secondary_overlaid, "/secondary/overlayed.txt").unwrap();
1531
1532 let fs = OverlayFileSystem::new(primary, [secondary, secondary_overlaid]);
1533
1534 let paths: Vec<_> = ops::walk(&fs, "/").map(|entry| entry.path()).collect();
1535 assert_eq!(
1536 paths,
1537 vec![
1538 PathBuf::from("/secondary"),
1539 PathBuf::from("/secondary/write.txt"),
1540 PathBuf::from("/secondary/read.txt"),
1541 PathBuf::from("/secondary/overlayed.txt"),
1542 PathBuf::from("/primary"),
1543 PathBuf::from("/primary/write.txt"),
1544 PathBuf::from("/primary/read.txt"),
1545 ]
1546 );
1547 }
1548
1549 #[tokio::test]
1550 async fn open_secondary_fs_files_in_write_mode() {
1551 let primary = MemFS::default();
1552 let secondary = MemFS::default();
1553 ops::create_dir_all(&secondary, "/secondary").unwrap();
1554 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1555 .await
1556 .unwrap();
1557
1558 let fs = OverlayFileSystem::new(primary, [secondary]);
1559
1560 let mut f = fs
1561 .new_open_options()
1562 .write(true)
1563 .read(true)
1564 .open("/secondary/file.txt")
1565 .unwrap();
1566 let mut buf = String::new();
1568 f.read_to_string(&mut buf).await.unwrap();
1569 assert_eq!(buf, "Hello, World!");
1570 f.seek(SeekFrom::Start(0)).await.unwrap();
1571 f.set_len(0).unwrap();
1573 assert_eq!(f.write(b"Hi").await.unwrap(), 2);
1574 assert_eq!(f.flush().await.unwrap(), ());
1576
1577 buf = String::new();
1579 f.seek(SeekFrom::Start(0)).await.unwrap();
1580 f.read_to_string(&mut buf).await.unwrap();
1581 assert_eq!(buf, "Hi");
1582 drop(f);
1583
1584 let mut f = fs
1586 .new_open_options()
1587 .read(true)
1588 .open("/secondary/file.txt")
1589 .unwrap();
1590 buf = String::new();
1591 f.read_to_string(&mut buf).await.unwrap();
1592 assert_eq!(buf, "Hi");
1593 }
1594
1595 #[tokio::test]
1596 async fn open_secondary_fs_files_unlink() {
1597 let primary = MemFS::default();
1598 let secondary = MemFS::default();
1599 ops::create_dir_all(&secondary, "/secondary").unwrap();
1600 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1601 .await
1602 .unwrap();
1603
1604 let fs = OverlayFileSystem::new(primary, [secondary]);
1605
1606 fs.metadata(Path::new("/secondary/file.txt")).unwrap();
1607
1608 fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
1610 assert_eq!(
1611 fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
1612 FsError::EntryNotFound
1613 )
1614 }
1615
1616 #[tokio::test]
1617 async fn open_secondary_fs_without_cow() {
1618 let primary = MemFS::default();
1619 let secondary = MemFS::default();
1620 ops::create_dir_all(&secondary, "/secondary").unwrap();
1621 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1622 .await
1623 .unwrap();
1624
1625 let fs = OverlayFileSystem::new(primary, [secondary]);
1626
1627 let mut f = fs
1628 .new_open_options()
1629 .create(true)
1630 .read(true)
1631 .open(Path::new("/secondary/file.txt"))
1632 .unwrap();
1633 assert_eq!(f.size() as usize, 13);
1634
1635 let mut buf = String::new();
1636 f.read_to_string(&mut buf).await.unwrap();
1637 assert_eq!(buf, "Hello, World!");
1638
1639 assert!(!ops::is_dir(&fs.primary, "/secondary"));
1641 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1642 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1643 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1644 }
1645
1646 #[tokio::test]
1647 async fn unlink_open_secondary_fs_without_cow_keeps_handle_alive() {
1648 let primary = MemFS::default();
1649 let secondary = MemFS::default();
1650 ops::create_dir_all(&secondary, "/secondary").unwrap();
1651 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1652 .await
1653 .unwrap();
1654
1655 let fs = OverlayFileSystem::new(primary, [secondary]);
1656
1657 let mut f = fs
1658 .new_open_options()
1659 .read(true)
1660 .open(Path::new("/secondary/file.txt"))
1661 .unwrap();
1662
1663 f.unlink().unwrap();
1664 assert_eq!(
1665 fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
1666 FsError::EntryNotFound
1667 );
1668 assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1669 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1670 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1671
1672 let mut buf = String::new();
1673 f.read_to_string(&mut buf).await.unwrap();
1674 assert_eq!(buf, "Hello, World!");
1675 }
1676
1677 #[tokio::test]
1678 async fn unlink_open_secondary_fs_without_cow_returns_not_found_when_whiteout_already_exists() {
1679 let primary = MemFS::default();
1680 let secondary = MemFS::default();
1681 ops::create_dir_all(&secondary, "/secondary").unwrap();
1682 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1683 .await
1684 .unwrap();
1685
1686 let fs = OverlayFileSystem::new(primary, [secondary]);
1687
1688 let mut f = fs
1689 .new_open_options()
1690 .read(true)
1691 .open(Path::new("/secondary/file.txt"))
1692 .unwrap();
1693
1694 ops::create_white_out(&fs.primary, "/secondary/file.txt").unwrap();
1697 assert_eq!(f.unlink(), Err(FsError::EntryNotFound));
1698 }
1699
1700 #[tokio::test]
1701 async fn unlink_open_secondary_fs_without_cow_preserves_whiteout_creation_error() {
1702 let primary = MemFS::default();
1703 let secondary = MemFS::default();
1704 ops::write(&primary, "/secondary", b"not a directory")
1705 .await
1706 .unwrap();
1707 ops::create_dir_all(&secondary, "/secondary").unwrap();
1708 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1709 .await
1710 .unwrap();
1711
1712 let fs = OverlayFileSystem::new(primary, [secondary]);
1713
1714 let mut f = fs
1715 .new_open_options()
1716 .read(true)
1717 .open(Path::new("/secondary/file.txt"))
1718 .unwrap();
1719
1720 assert_eq!(f.unlink(), Err(FsError::BaseNotDirectory));
1721 }
1722
1723 #[tokio::test]
1724 async fn create_and_append_secondary_fs_with_cow() {
1725 let primary = MemFS::default();
1726 let secondary = MemFS::default();
1727 ops::create_dir_all(&secondary, "/secondary").unwrap();
1728 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1729 .await
1730 .unwrap();
1731
1732 let fs = OverlayFileSystem::new(primary, [secondary]);
1733
1734 let mut f = fs
1735 .new_open_options()
1736 .create(true)
1737 .append(true)
1738 .read(true)
1739 .open(Path::new("/secondary/file.txt"))
1740 .unwrap();
1741 assert_eq!(f.size() as usize, 13);
1742
1743 f.write_all(b"asdf").await.unwrap();
1744 assert_eq!(f.size() as usize, 17);
1745
1746 f.seek(SeekFrom::Start(0)).await.unwrap();
1747
1748 let mut buf = String::new();
1749 f.read_to_string(&mut buf).await.unwrap();
1750 assert_eq!(buf, "Hello, World!asdf");
1751
1752 let f = fs
1754 .primary
1755 .new_open_options()
1756 .create(true)
1757 .append(true)
1758 .read(true)
1759 .open(Path::new("/secondary/file.txt"))
1760 .unwrap();
1761 assert_eq!(f.size() as usize, 17);
1762 let f = fs.secondaries[0]
1763 .new_open_options()
1764 .create(true)
1765 .append(true)
1766 .read(true)
1767 .open(Path::new("/secondary/file.txt"))
1768 .unwrap();
1769 assert_eq!(f.size() as usize, 13);
1770
1771 assert!(ops::is_dir(&fs.primary, "/secondary"));
1773 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1774 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1775 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1776 }
1777
1778 #[tokio::test]
1779 async fn unlink_open_secondary_fs_with_cow_does_not_recreate_path() {
1780 let primary = MemFS::default();
1781 let secondary = MemFS::default();
1782 ops::create_dir_all(&secondary, "/secondary").unwrap();
1783 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1784 .await
1785 .unwrap();
1786
1787 let fs = OverlayFileSystem::new(primary, [secondary]);
1788
1789 let mut f = fs
1790 .new_open_options()
1791 .append(true)
1792 .read(true)
1793 .open(Path::new("/secondary/file.txt"))
1794 .unwrap();
1795
1796 f.unlink().unwrap();
1797 assert_eq!(
1798 fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
1799 FsError::EntryNotFound
1800 );
1801 assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1802 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1803 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1804
1805 f.write_all(b"asdf").await.unwrap();
1806 f.seek(SeekFrom::Start(0)).await.unwrap();
1807
1808 let mut buf = String::new();
1809 f.read_to_string(&mut buf).await.unwrap();
1810 assert_eq!(buf, "Hello, World!asdf");
1811
1812 assert_eq!(
1813 fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
1814 FsError::EntryNotFound
1815 );
1816 assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1817 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1818 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1819 }
1820
1821 #[tokio::test]
1825 async fn unlink_secondary_handle_does_not_delete_later_primary_file() {
1826 let primary = MemFS::default();
1827 let secondary = MemFS::default();
1828 ops::create_dir_all(&secondary, "/dir").unwrap();
1829 ops::write(&secondary, "/dir/file.txt", b"secondary")
1830 .await
1831 .unwrap();
1832
1833 let fs = OverlayFileSystem::new(primary, [secondary]);
1834
1835 let mut f = fs
1837 .new_open_options()
1838 .read(true)
1839 .open(Path::new("/dir/file.txt"))
1840 .unwrap();
1841
1842 ops::create_dir_all(&fs.primary, "/dir").unwrap();
1845 ops::write(&fs.primary, "/dir/file.txt", b"primary replacement")
1846 .await
1847 .unwrap();
1848
1849 f.unlink().unwrap();
1852
1853 assert!(ops::is_file(&fs.primary, "/dir/.wh.file.txt"));
1855 assert!(ops::is_file(&fs.primary, "/dir/file.txt"));
1857 assert_eq!(
1858 ops::read_to_string(&fs.primary, "/dir/file.txt")
1859 .await
1860 .unwrap(),
1861 "primary replacement"
1862 );
1863 }
1864
1865 #[tokio::test]
1868 async fn unlink_cow_handle_does_not_delete_later_primary_file() {
1869 let primary = MemFS::default();
1870 let secondary = MemFS::default();
1871 ops::create_dir_all(&secondary, "/dir").unwrap();
1872 ops::write(&secondary, "/dir/file.txt", b"secondary")
1873 .await
1874 .unwrap();
1875
1876 let fs = OverlayFileSystem::new(primary, [secondary]);
1877
1878 let mut f = fs
1881 .new_open_options()
1882 .write(true)
1883 .read(true)
1884 .open(Path::new("/dir/file.txt"))
1885 .unwrap();
1886
1887 ops::create_dir_all(&fs.primary, "/dir").unwrap();
1890 ops::write(&fs.primary, "/dir/file.txt", b"primary replacement")
1891 .await
1892 .unwrap();
1893
1894 f.unlink().unwrap();
1897
1898 assert!(ops::is_file(&fs.primary, "/dir/.wh.file.txt"));
1899 assert!(ops::is_file(&fs.primary, "/dir/file.txt"));
1900 assert_eq!(
1901 ops::read_to_string(&fs.primary, "/dir/file.txt")
1902 .await
1903 .unwrap(),
1904 "primary replacement"
1905 );
1906 }
1907
1908 #[tokio::test]
1909 async fn unlink_file_from_secondary_fs() {
1910 let primary = MemFS::default();
1911 let secondary = MemFS::default();
1912 ops::create_dir_all(&secondary, "/secondary").unwrap();
1913 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1914 .await
1915 .unwrap();
1916
1917 let fs = OverlayFileSystem::new(primary, [secondary]);
1918
1919 fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
1920 assert_eq!(ops::exists(&fs, Path::new("/secondary/file.txt")), false);
1921
1922 assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1923 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1924
1925 let mut f = fs
1927 .new_open_options()
1928 .create(true)
1929 .write(true)
1930 .read(true)
1931 .open(Path::new("/secondary/file.txt"))
1932 .unwrap();
1933 assert_eq!(f.size() as usize, 0);
1934 f.write_all(b"asdf").await.unwrap();
1935 assert_eq!(f.size() as usize, 4);
1936
1937 assert!(!ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1939 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1940 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1941 }
1942
1943 #[tokio::test]
1944 async fn rmdir_from_secondary_fs() {
1945 let primary = MemFS::default();
1946 let secondary = MemFS::default();
1947 ops::create_dir_all(&secondary, "/secondary").unwrap();
1948
1949 let fs = OverlayFileSystem::new(primary, [secondary]);
1950
1951 assert!(ops::is_dir(&fs, "/secondary"));
1952 fs.remove_dir(Path::new("/secondary")).unwrap();
1953
1954 assert!(!ops::is_dir(&fs, "/secondary"));
1955 assert!(ops::is_file(&fs.primary, "/.wh.secondary"));
1956 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1957
1958 fs.create_dir(Path::new("/secondary")).unwrap();
1959 assert!(ops::is_dir(&fs, "/secondary"));
1960 assert!(ops::is_dir(&fs.primary, "/secondary"));
1961 assert!(!ops::is_file(&fs.primary, "/.wh.secondary"));
1962 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1963 }
1964
1965 #[tokio::test]
1966 async fn rmdir_sub_from_secondary_fs() {
1967 let primary = MemFS::default();
1968 let secondary = MemFS::default();
1969 ops::create_dir_all(&secondary, "/first/secondary").unwrap();
1970
1971 let fs = OverlayFileSystem::new(primary, [secondary]);
1972
1973 assert!(ops::is_dir(&fs, "/first/secondary"));
1974 fs.remove_dir(Path::new("/first/secondary")).unwrap();
1975
1976 assert!(!ops::is_dir(&fs, "/first/secondary"));
1977 assert!(ops::is_file(&fs.primary, "/first/.wh.secondary"));
1978 assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
1979
1980 fs.create_dir(Path::new("/first/secondary")).unwrap();
1981 assert!(ops::is_dir(&fs, "/first/secondary"));
1982 assert!(ops::is_dir(&fs.primary, "/first/secondary"));
1983 assert!(!ops::is_file(&fs.primary, "/first/.wh.secondary"));
1984 assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
1985 }
1986
1987 #[tokio::test]
1988 async fn create_new_secondary_fs_without_cow() {
1989 let primary = MemFS::default();
1990 let secondary = MemFS::default();
1991 ops::create_dir_all(&secondary, "/secondary").unwrap();
1992 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1993 .await
1994 .unwrap();
1995
1996 let fs = OverlayFileSystem::new(primary, [secondary]);
1997
1998 let mut f = fs
1999 .new_open_options()
2000 .create_new(true)
2001 .read(true)
2002 .open(Path::new("/secondary/file.txt"))
2003 .unwrap();
2004 assert_eq!(f.size() as usize, 0);
2005
2006 let mut buf = String::new();
2007 f.read_to_string(&mut buf).await.unwrap();
2008 assert_eq!(buf, "");
2009
2010 assert!(ops::is_dir(&fs.primary, "/secondary"));
2012 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
2013 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
2014 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
2015 }
2016
2017 #[tokio::test]
2018 async fn open_secondary_fs_files_remove_dir() {
2019 let primary = MemFS::default();
2020 let secondary = MemFS::default();
2021 ops::create_dir_all(&secondary, "/secondary").unwrap();
2022
2023 let fs = OverlayFileSystem::new(primary, [secondary]);
2024
2025 fs.metadata(Path::new("/secondary")).unwrap();
2026
2027 fs.remove_dir(Path::new("/secondary")).unwrap();
2029 assert_eq!(
2030 fs.metadata(Path::new("/secondary")).unwrap_err(),
2031 FsError::EntryNotFound
2032 )
2033 }
2034
2035 #[tokio::test]
2039 async fn test_overlayfs_readonly_files_not_copied() {
2040 let primary = MemFS::default();
2041 let secondary = MemFS::default();
2042 ops::create_dir_all(&secondary, "/secondary").unwrap();
2043 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
2044 .await
2045 .unwrap();
2046
2047 let fs = OverlayFileSystem::new(primary, [secondary]);
2048
2049 {
2050 let mut f = fs
2051 .new_open_options()
2052 .read(true)
2053 .write(true)
2054 .open(Path::new("/secondary/file.txt"))
2055 .unwrap();
2056 let mut s = String::new();
2057 f.read_to_string(&mut s).await.unwrap();
2058 assert_eq!(s, "Hello, World!");
2059
2060 f.flush().await.unwrap();
2061 f.shutdown().await.unwrap();
2062 }
2063
2064 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
2066 }
2067
2068 }