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
20#[derive(Clone, PartialEq, Eq)]
65pub struct OverlayFileSystem<P, S> {
66 primary: Arc<P>,
67 secondaries: S,
68}
69
70impl<P, S> OverlayFileSystem<P, S>
71where
72 P: FileSystem + Send + Sync + 'static,
73 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
74{
75 pub fn new(primary: P, secondaries: S) -> Self {
78 OverlayFileSystem {
79 primary: Arc::new(primary),
80 secondaries,
81 }
82 }
83
84 pub fn primary(&self) -> &P {
86 &self.primary
87 }
88
89 pub fn secondaries(&self) -> &S {
91 &self.secondaries
92 }
93
94 pub fn secondaries_mut(&mut self) -> &mut S {
96 &mut self.secondaries
97 }
98
99 fn permission_error_or_not_found(&self, path: &Path) -> Result<(), FsError> {
100 for fs in self.secondaries.filesystems() {
101 if ops::exists(fs, path) {
102 return Err(FsError::PermissionDenied);
103 }
104 }
105
106 Err(FsError::EntryNotFound)
107 }
108}
109
110impl<P, S> FileSystem for OverlayFileSystem<P, S>
111where
112 P: FileSystem + Send + 'static,
113 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
114 for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
115{
116 fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
117 if ops::is_white_out(path).is_some() {
119 return Err(FsError::EntryNotFound);
120 }
121
122 match self.primary.readlink(path) {
124 Ok(meta) => return Ok(meta),
125 Err(e) if should_continue(e) => {}
126 Err(e) => return Err(e),
127 }
128
129 if ops::has_white_out(&self.primary, path) {
131 return Err(FsError::EntryNotFound);
132 }
133
134 for fs in self.secondaries.filesystems() {
136 match fs.readlink(path) {
137 Err(e) if should_continue(e) => continue,
138 other => return other,
139 }
140 }
141
142 Err(FsError::EntryNotFound)
143 }
144
145 fn read_dir(&self, path: &Path) -> Result<ReadDir, FsError> {
146 let mut entries = Vec::new();
147 let mut had_at_least_one_success = false;
148 let mut white_outs = HashSet::new();
149
150 let filesystems = std::iter::once(&self.primary as &(dyn FileSystem + Send))
151 .chain(self.secondaries().filesystems());
152
153 for fs in filesystems {
154 match fs.read_dir(path) {
155 Ok(r) => {
156 for entry in r {
157 let entry = entry?;
158
159 if let Some(path) = entry.is_white_out() {
163 tracing::trace!(
164 path=%path.display(),
165 "Found whiteout file",
166 );
167 white_outs.insert(path);
168 continue;
169 } else if white_outs.contains(&entry.path) {
170 tracing::trace!(
171 path=%path.display(),
172 "Skipping path because a whiteout exists",
173 );
174 continue;
175 }
176
177 entries.push(entry);
178 }
179 had_at_least_one_success = true;
180 }
181 Err(e) if should_continue(e) => continue,
182 Err(e) => return Err(e),
183 }
184 }
185
186 if had_at_least_one_success {
187 entries.sort_by(|a, b| a.path.cmp(&b.path));
192 entries.dedup_by(|a, b| a.path == b.path);
193
194 Ok(ReadDir::new(entries))
195 } else {
196 Err(FsError::BaseNotDirectory)
197 }
198 }
199
200 fn create_dir(&self, path: &Path) -> Result<(), FsError> {
201 if ops::is_white_out(path).is_some() {
203 return Err(FsError::InvalidInput);
204 }
205
206 ops::remove_white_out(self.primary.as_ref(), path);
209
210 if let Some(parent) = path.parent() {
214 if self.read_dir(parent).is_ok() {
215 ops::create_dir_all(&self.primary, parent).ok();
216 }
217 }
218
219 match self.primary.create_dir(path) {
221 Err(e) if should_continue(e) => {}
222 other => return other,
223 }
224
225 self.permission_error_or_not_found(path)
226 }
227
228 fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
229 if ops::is_white_out(path).is_some() {
232 tracing::trace!(
233 path=%path.display(),
234 "Unable to remove a whited out directory",
235 );
236 return Err(FsError::EntryNotFound);
237 }
238
239 let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
243 fs.read_dir(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
244 });
245
246 match self.primary.remove_dir(path) {
250 Err(e) if should_continue(e) => {}
251 other => return other,
252 }
253
254 if had_at_least_one_success {
255 return Ok(());
256 }
257 self.permission_error_or_not_found(path)
258 }
259
260 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
261 let from = from.to_owned();
262 let to = to.to_owned();
263 Box::pin(async move {
264 if ops::is_white_out(&from).is_some() {
266 tracing::trace!(
267 from=%from.display(),
268 to=%to.display(),
269 "Attempting to rename a file that was whited out"
270 );
271 return Err(FsError::EntryNotFound);
272 }
273 if ops::is_white_out(&to).is_some() {
275 tracing::trace!(
276 from=%from.display(),
277 to=%to.display(),
278 "Attempting to rename a file into a whiteout file"
279 );
280 return Err(FsError::InvalidInput);
281 }
282
283 let mut had_at_least_one_success = false;
288 match self.primary.rename(&from, &to).await {
289 Err(e) if should_continue(e) => {}
290 Ok(()) => {
291 had_at_least_one_success = true;
292 }
293 other => return other,
294 }
295
296 if !had_at_least_one_success {
300 for fs in self.secondaries.filesystems() {
301 if fs.metadata(&from).is_ok() {
302 ops::copy_reference_ext(fs, &self.primary, &from, &to).await?;
303 had_at_least_one_success = true;
304 break;
305 }
306 }
307 }
308
309 if had_at_least_one_success {
312 for fs in self.secondaries.filesystems() {
313 if fs.metadata(&from).is_ok() {
314 tracing::trace!(
315 path=%from.display(),
316 "Creating a whiteout for the file that was renamed",
317 );
318 ops::create_white_out(&self.primary, &from).ok();
319 break;
320 }
321 }
322 ops::remove_white_out(&self.primary, &to);
323 return Ok(());
324 }
325
326 self.permission_error_or_not_found(&from)
328 })
329 }
330
331 fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
332 if ops::is_white_out(path).is_some() {
334 return Err(FsError::EntryNotFound);
335 }
336
337 match self.primary.metadata(path) {
339 Ok(meta) => return Ok(meta),
340 Err(e) if should_continue(e) => {}
341 Err(e) => return Err(e),
342 }
343
344 if ops::has_white_out(&self.primary, path) {
346 return Err(FsError::EntryNotFound);
347 }
348
349 for fs in self.secondaries.filesystems() {
351 match fs.metadata(path) {
352 Err(e) if should_continue(e) => continue,
353 other => return other,
354 }
355 }
356
357 Err(FsError::EntryNotFound)
358 }
359
360 fn symlink_metadata(&self, path: &Path) -> crate::Result<Metadata> {
361 if ops::is_white_out(path).is_some() {
363 return Err(FsError::EntryNotFound);
364 }
365
366 match self.primary.symlink_metadata(path) {
368 Ok(meta) => return Ok(meta),
369 Err(e) if should_continue(e) => {}
370 Err(e) => return Err(e),
371 }
372
373 if ops::has_white_out(&self.primary, path) {
375 return Err(FsError::EntryNotFound);
376 }
377
378 for fs in self.secondaries.filesystems() {
380 match fs.symlink_metadata(path) {
381 Err(e) if should_continue(e) => continue,
382 other => return other,
383 }
384 }
385
386 Err(FsError::EntryNotFound)
387 }
388
389 fn remove_file(&self, path: &Path) -> Result<(), FsError> {
390 if ops::is_white_out(path).is_some() {
393 return Err(FsError::InvalidInput);
394 }
395
396 let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
399 fs.metadata(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
400 });
401
402 match self.primary.remove_file(path) {
404 Err(e) if should_continue(e) => {}
405 other => return other,
406 }
407
408 if had_at_least_one_success {
409 return Ok(());
410 }
411 self.permission_error_or_not_found(path)
412 }
413
414 fn new_open_options(&self) -> OpenOptions<'_> {
415 OpenOptions::new(self)
416 }
417
418 fn mount(
419 &self,
420 _name: String,
421 _path: &Path,
422 _fs: Box<dyn FileSystem + Send + Sync>,
423 ) -> Result<(), FsError> {
424 Err(FsError::Unsupported)
425 }
426}
427
428impl<P, S> FileOpener for OverlayFileSystem<P, S>
429where
430 P: FileSystem + Send + 'static,
431 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
432 for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
433{
434 fn open(
435 &self,
436 path: &Path,
437 conf: &OpenOptionsConfig,
438 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>, FsError> {
439 if ops::is_white_out(path).is_some() {
441 tracing::trace!(
442 path=%path.display(),
443 options=?conf,
444 "Whiteout files can't be opened",
445 );
446 return Err(FsError::InvalidInput);
447 }
448
449 {
452 let mut conf = conf.clone();
453 conf.create = false;
454 conf.create_new = false;
455 match self.primary.new_open_options().options(conf).open(path) {
456 Err(e) if should_continue(e) => {}
457 other => return other,
458 }
459 }
460
461 if conf.create_new {
464 if let Some(parent) = path.parent() {
468 if ops::exists(self, parent) {
469 ops::create_dir_all(&self.primary, parent)?;
473 } else {
474 return Err(FsError::EntryNotFound);
475 }
476 }
477
478 ops::remove_white_out(&self.primary, path);
480
481 return self
483 .primary
484 .new_open_options()
485 .options(conf.clone())
486 .open(path);
487 }
488
489 if !conf.create && ops::has_white_out(&self.primary, path) {
494 tracing::trace!(
495 path=%path.display(),
496 "The file has been whited out",
497 );
498 return Err(FsError::EntryNotFound);
499 }
500
501 let require_mutations = conf.append || conf.write || conf.create_new | conf.truncate;
503
504 if !ops::has_white_out(&self.primary, path) {
506 for fs in self.secondaries.filesystems() {
507 let mut sub_conf = conf.clone();
508 sub_conf.create = false;
509 sub_conf.create_new = false;
510 sub_conf.append = false;
511 sub_conf.truncate = false;
512 match fs.new_open_options().options(sub_conf.clone()).open(path) {
513 Err(e) if should_continue(e) => continue,
514 Ok(file) if require_mutations => {
515 return open_copy_on_write(path, conf, &self.primary, file);
520 }
521 other => return other,
522 }
523 }
524 }
525
526 if conf.create {
528 if let Some(parent) = path.parent() {
530 if ops::exists(self, parent) {
531 ops::create_dir_all(&self.primary, parent)?;
532 }
533 }
534 ops::remove_white_out(&self.primary, path);
535
536 return self
538 .primary
539 .new_open_options()
540 .options(conf.clone())
541 .open(path);
542 }
543
544 Err(FsError::EntryNotFound)
546 }
547}
548
549fn open_copy_on_write<P>(
550 path: &Path,
551 conf: &OpenOptionsConfig,
552 primary: &Arc<P>,
553 file: Box<dyn VirtualFile + Send + Sync>,
554) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError>
555where
556 P: FileSystem,
557{
558 struct CopyOnWriteFile<P> {
559 path: PathBuf,
560 primary: Arc<P>,
561 state: CowState,
562 readable: bool,
563 append: bool,
564 new_size: Option<u64>,
565 }
566 enum CowState {
567 ReadOnly(Box<dyn VirtualFile + Send + Sync>),
570 SeekingGet(Box<dyn VirtualFile + Send + Sync>),
573 SeekingSet {
577 original_offset: u64,
578 src: Box<dyn VirtualFile + Send + Sync>,
579 },
580 Copying {
583 original_offset: u64,
584 buf: Vec<u8>,
585 buf_pos: usize,
586 dst: Box<dyn VirtualFile + Send + Sync>,
587 src: Box<dyn VirtualFile + Send + Sync>,
588 },
589 SeekingRestore {
592 dst: Box<dyn VirtualFile + Send + Sync>,
593 },
594 Copied(Box<dyn VirtualFile + Send + Sync>),
596 Error {
600 err: io::Error,
601 src: Box<dyn VirtualFile + Send + Sync>,
602 },
603 }
604 impl CowState {
605 fn as_ref(&self) -> &(dyn VirtualFile + Send + Sync) {
606 match self {
607 Self::ReadOnly(inner) => inner.as_ref(),
608 Self::SeekingGet(inner) => inner.as_ref(),
609 Self::SeekingSet { src, .. } => src.as_ref(),
610 Self::Copying { src, .. } => src.as_ref(),
611 Self::SeekingRestore { dst, .. } => dst.as_ref(),
612 Self::Copied(inner) => inner.as_ref(),
613 Self::Error { src, .. } => src.as_ref(),
614 }
615 }
616 fn as_mut(&mut self) -> &mut (dyn VirtualFile + Send + Sync) {
617 match self {
618 Self::ReadOnly(inner) => inner.as_mut(),
619 Self::SeekingGet(inner) => inner.as_mut(),
620 Self::SeekingSet { src, .. } => src.as_mut(),
621 Self::Copying { src, .. } => src.as_mut(),
622 Self::SeekingRestore { dst, .. } => dst.as_mut(),
623 Self::Copied(inner) => inner.as_mut(),
624 Self::Error { src, .. } => src.as_mut(),
625 }
626 }
627 }
628
629 impl<P> CopyOnWriteFile<P>
630 where
631 P: FileSystem + 'static,
632 {
633 fn poll_copy_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
634 let mut again = true;
636 while again {
637 again = false;
638
639 replace_with_or_abort(&mut self.state, |state| match state {
641 CowState::SeekingGet(mut src) => {
644 match Pin::new(src.as_mut()).poll_complete(cx) {
645 Poll::Ready(Ok(offset)) => {
646 if let Err(err) =
647 Pin::new(src.as_mut()).start_seek(SeekFrom::Start(0))
648 {
649 return CowState::Error { err, src };
650 }
651 again = true;
652 CowState::SeekingSet {
653 original_offset: offset,
654 src,
655 }
656 }
657 Poll::Ready(Err(err)) => CowState::Error { err, src },
658 Poll::Pending => CowState::SeekingGet(src),
659 }
660 }
661
662 CowState::SeekingSet {
664 original_offset,
665 mut src,
666 } => {
667 match Pin::new(src.as_mut()).poll_complete(cx).map_ok(|_| ()) {
668 Poll::Ready(Ok(())) => {
669 if let Some(parent) = self.path.parent() {
672 ops::create_dir_all(&self.primary, parent).ok();
673 }
674 let mut had_white_out = false;
675 if ops::has_white_out(&self.primary, &self.path) {
676 ops::remove_white_out(&self.primary, &self.path);
677 had_white_out = true;
678 }
679 let dst = self
680 .primary
681 .new_open_options()
682 .create(true)
683 .read(self.readable)
684 .write(true)
685 .truncate(true)
686 .open(&self.path);
687 match dst {
688 Ok(dst) if had_white_out => {
689 again = true;
690 CowState::Copied(dst)
691 }
692 Ok(dst) => {
693 again = true;
694 CowState::Copying {
695 original_offset,
696 buf: Vec::new(),
697 buf_pos: 0,
698 src,
699 dst,
700 }
701 }
702 Err(err) => CowState::Error {
703 err: err.into(),
704 src,
705 },
706 }
707 }
708 Poll::Ready(Err(err)) => CowState::Error { err, src },
709 Poll::Pending => CowState::SeekingSet {
710 original_offset,
711 src,
712 },
713 }
714 }
715 CowState::Copying {
717 mut src,
718 mut dst,
719 mut buf,
720 mut buf_pos,
721 original_offset,
722 } => {
723 loop {
724 if buf_pos < buf.len() {
727 let dst_pinned = Pin::new(dst.as_mut());
728 match dst_pinned.poll_write(cx, &buf[buf_pos..]) {
729 Poll::Ready(Ok(0)) => {}
730 Poll::Ready(Ok(amt)) => {
731 buf_pos += amt;
732 continue;
733 }
734 Poll::Ready(Err(err)) => {
735 return CowState::Error { err, src };
736 }
737 Poll::Pending => {}
738 }
739 } else {
740 buf.resize_with(8192, || 0);
741 buf_pos = 8192;
742 let mut read_buf = ReadBuf::new(&mut buf);
743 match Pin::new(src.as_mut()).poll_read(cx, &mut read_buf) {
744 Poll::Ready(Ok(())) if read_buf.filled().is_empty() => {
745 again = true;
746
747 if self.append {
748 return CowState::Copied(dst);
751 } else {
752 if let Err(err) = Pin::new(dst.as_mut())
755 .start_seek(SeekFrom::Start(original_offset))
756 {
757 return CowState::Error { err, src };
758 }
759 return CowState::SeekingRestore { dst };
760 }
761 }
762 Poll::Ready(Ok(())) => {
763 let new_len = read_buf.filled().len();
765 unsafe { buf.set_len(new_len) };
766 buf_pos = 0;
767 continue;
768 }
769 Poll::Ready(Err(err)) => return CowState::Error { err, src },
770 Poll::Pending => {}
771 }
772 }
773 return CowState::Copying {
774 original_offset,
775 buf,
776 buf_pos,
777 src,
778 dst,
779 };
780 }
781 }
782 CowState::SeekingRestore { mut dst } => {
784 match Pin::new(dst.as_mut()).poll_complete(cx) {
785 Poll::Ready(_) => {
786 if let Some(new_size) = self.new_size.take() {
788 dst.set_len(new_size).ok();
789 }
790 CowState::Copied(dst)
791 }
792 Poll::Pending => CowState::SeekingRestore { dst },
793 }
794 }
795 s => s,
796 });
797 }
798
799 let mut ret = Poll::Pending;
802 replace_with_or_abort(&mut self.state, |state| match state {
803 CowState::ReadOnly(src) => {
804 ret = Poll::Ready(Ok(()));
805 CowState::ReadOnly(src)
806 }
807 CowState::Copied(src) => {
808 ret = Poll::Ready(Ok(()));
809 CowState::Copied(src)
810 }
811 CowState::Error { err, src } => {
812 ret = Poll::Ready(Err(err));
813 CowState::ReadOnly(src)
814 }
815 state => {
816 ret = Poll::Pending;
817 state
818 }
819 });
820 ret
821 }
822
823 fn poll_copy_start_and_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
824 replace_with_or_abort(&mut self.state, |state| match state {
825 CowState::ReadOnly(inner) => {
826 tracing::trace!("COW file touched, starting file clone");
827 CowState::SeekingGet(inner)
828 }
829 state => state,
830 });
831 self.poll_copy_progress(cx)
832 }
833 }
834
835 impl<P> Debug for CopyOnWriteFile<P>
836 where
837 P: FileSystem + 'static,
838 {
839 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
840 f.debug_struct("CopyOnWriteFile").finish()
841 }
842 }
843
844 impl<P> VirtualFile for CopyOnWriteFile<P>
845 where
846 P: FileSystem + 'static,
847 {
848 fn last_accessed(&self) -> u64 {
849 self.state.as_ref().last_accessed()
850 }
851
852 fn last_modified(&self) -> u64 {
853 self.state.as_ref().last_modified()
854 }
855
856 fn created_time(&self) -> u64 {
857 self.state.as_ref().created_time()
858 }
859
860 fn size(&self) -> u64 {
861 self.state.as_ref().size()
862 }
863
864 fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
865 self.new_size = Some(new_size);
866 replace_with_or_abort(&mut self.state, |state| match state {
867 CowState::Copied(mut file) => {
868 file.set_len(new_size).ok();
869 CowState::Copied(file)
870 }
871 state => {
872 if let Some(parent) = self.path.parent() {
875 ops::create_dir_all(&self.primary, parent).ok();
876 }
877 let dst = self
878 .primary
879 .new_open_options()
880 .create(true)
881 .write(true)
882 .open(&self.path);
883 if let Ok(mut file) = dst {
884 file.set_len(new_size).ok();
885 }
886 state
887 }
888 });
889 Ok(())
890 }
891
892 fn unlink(&mut self) -> crate::Result<()> {
893 let primary = self.primary.clone();
894 let path = self.path.clone();
895
896 let mut had_at_least_one_success = false;
898 if ops::create_white_out(&primary, &path).is_ok() {
899 had_at_least_one_success = true;
900 }
901
902 match primary.remove_file(&path) {
904 Err(e) if should_continue(e) => {}
905 other => return other,
906 }
907
908 if had_at_least_one_success {
909 return Ok(());
910 }
911 Err(FsError::PermissionDenied)
912 }
913
914 fn poll_read_ready(
915 mut self: Pin<&mut Self>,
916 cx: &mut std::task::Context<'_>,
917 ) -> Poll<std::io::Result<usize>> {
918 match self.poll_copy_progress(cx) {
919 Poll::Ready(Ok(())) => {}
920 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
921 Poll::Pending => return Poll::Pending,
922 }
923 Pin::new(self.state.as_mut()).poll_read_ready(cx)
924 }
925
926 fn poll_write_ready(
927 mut self: Pin<&mut Self>,
928 cx: &mut std::task::Context<'_>,
929 ) -> Poll<std::io::Result<usize>> {
930 match self.poll_copy_progress(cx) {
931 Poll::Ready(Ok(())) => {}
932 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
933 Poll::Pending => return Poll::Pending,
934 }
935 Pin::new(self.state.as_mut()).poll_write_ready(cx)
936 }
937 }
938
939 impl<P> AsyncWrite for CopyOnWriteFile<P>
940 where
941 P: FileSystem + 'static,
942 {
943 fn poll_write(
944 mut self: Pin<&mut Self>,
945 cx: &mut std::task::Context<'_>,
946 buf: &[u8],
947 ) -> Poll<Result<usize, std::io::Error>> {
948 match self.poll_copy_start_and_progress(cx) {
949 Poll::Pending => return Poll::Pending,
950 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
951 Poll::Ready(Ok(())) => {}
952 }
953 Pin::new(self.state.as_mut()).poll_write(cx, buf)
954 }
955
956 fn poll_write_vectored(
957 mut self: Pin<&mut Self>,
958 cx: &mut Context<'_>,
959 bufs: &[io::IoSlice<'_>],
960 ) -> Poll<Result<usize, io::Error>> {
961 match self.poll_copy_start_and_progress(cx) {
962 Poll::Pending => return Poll::Pending,
963 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
964 Poll::Ready(Ok(())) => {}
965 }
966 Pin::new(self.state.as_mut()).poll_write_vectored(cx, bufs)
967 }
968
969 fn poll_flush(
970 mut self: Pin<&mut Self>,
971 cx: &mut std::task::Context<'_>,
972 ) -> Poll<Result<(), std::io::Error>> {
973 match self.poll_copy_progress(cx) {
974 Poll::Ready(Ok(())) => {}
975 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
976 Poll::Pending => return Poll::Pending,
977 }
978 match self.state {
981 CowState::ReadOnly(_) => Poll::Ready(Ok(())),
982 _ => Pin::new(self.state.as_mut()).poll_flush(cx),
983 }
984 }
985
986 fn poll_shutdown(
987 mut self: Pin<&mut Self>,
988 cx: &mut std::task::Context<'_>,
989 ) -> Poll<Result<(), std::io::Error>> {
990 match self.poll_copy_progress(cx) {
991 Poll::Ready(Ok(())) => {}
992 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
993 Poll::Pending => return Poll::Pending,
994 }
995 match self.state {
997 CowState::ReadOnly(_) => Poll::Ready(Ok(())),
998 _ => Pin::new(self.state.as_mut()).poll_shutdown(cx),
999 }
1000 }
1001 }
1002
1003 impl<P> AsyncRead for CopyOnWriteFile<P>
1004 where
1005 P: FileSystem + 'static,
1006 {
1007 fn poll_read(
1008 mut self: Pin<&mut Self>,
1009 cx: &mut std::task::Context<'_>,
1010 buf: &mut tokio::io::ReadBuf<'_>,
1011 ) -> Poll<std::io::Result<()>> {
1012 match self.poll_copy_progress(cx) {
1013 Poll::Ready(Ok(())) => {}
1014 p => return p,
1015 }
1016 Pin::new(self.state.as_mut()).poll_read(cx, buf)
1017 }
1018 }
1019
1020 impl<P> AsyncSeek for CopyOnWriteFile<P>
1021 where
1022 P: FileSystem + 'static,
1023 {
1024 fn start_seek(
1025 mut self: Pin<&mut Self>,
1026 position: std::io::SeekFrom,
1027 ) -> std::io::Result<()> {
1028 match &mut self.state {
1029 CowState::ReadOnly(file)
1030 | CowState::SeekingGet(file)
1031 | CowState::Error { src: file, .. }
1032 | CowState::Copied(file)
1033 | CowState::SeekingRestore { dst: file, .. } => {
1034 Pin::new(file.as_mut()).start_seek(position)
1035 }
1036 CowState::SeekingSet {
1037 original_offset,
1038 src,
1039 ..
1040 }
1041 | CowState::Copying {
1042 original_offset,
1043 src,
1044 ..
1045 } => {
1046 *original_offset = match position {
1047 SeekFrom::Current(delta) => original_offset
1048 .checked_add_signed(delta)
1049 .unwrap_or(*original_offset),
1050 SeekFrom::Start(pos) => pos,
1051 SeekFrom::End(pos) => src
1052 .size()
1053 .checked_add_signed(pos)
1054 .unwrap_or(*original_offset),
1055 };
1056 Ok(())
1057 }
1058 }
1059 }
1060
1061 fn poll_complete(
1062 mut self: Pin<&mut Self>,
1063 cx: &mut std::task::Context<'_>,
1064 ) -> Poll<std::io::Result<u64>> {
1065 match self.poll_copy_progress(cx) {
1066 Poll::Ready(Ok(())) => {}
1067 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1068 Poll::Pending => return Poll::Pending,
1069 }
1070 Pin::new(self.state.as_mut()).poll_complete(cx)
1071 }
1072 }
1073
1074 tracing::trace!(
1075 path=%path.display(),
1076 options=?conf,
1077 "Opening the file in copy-on-write mode",
1078 );
1079 Ok(Box::new(CopyOnWriteFile::<P> {
1080 path: path.to_path_buf(),
1081 primary: primary.clone(),
1082 state: CowState::ReadOnly(file),
1083 readable: conf.read,
1084 append: conf.append,
1085 new_size: None,
1086 }))
1087}
1088
1089impl<P, S> Debug for OverlayFileSystem<P, S>
1090where
1091 P: FileSystem,
1092 S: for<'a> FileSystems<'a>,
1093{
1094 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1095 struct IterFilesystems<'a, S>(&'a S);
1096 impl<S> Debug for IterFilesystems<'_, S>
1097 where
1098 S: for<'b> FileSystems<'b>,
1099 {
1100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1101 let mut f = f.debug_list();
1102
1103 for fs in self.0.filesystems() {
1104 f.entry(&fs);
1105 }
1106
1107 f.finish()
1108 }
1109 }
1110
1111 f.debug_struct("OverlayFileSystem")
1112 .field("primary", &self.primary)
1113 .field("secondaries", &IterFilesystems(&self.secondaries))
1114 .finish()
1115 }
1116}
1117
1118fn should_continue(e: FsError) -> bool {
1119 matches!(
1124 e,
1125 FsError::EntryNotFound | FsError::InvalidInput | FsError::BaseNotDirectory
1126 )
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131 use std::path::PathBuf;
1132
1133 use super::*;
1134 use crate::mem_fs::FileSystem as MemFS;
1135 use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
1136
1137 #[tokio::test]
1138 async fn remove_directory() {
1139 let primary = MemFS::default();
1140 let secondary = MemFS::default();
1141 let first = Path::new("/first");
1142 let second = Path::new("/second");
1143 let file_txt = second.join("file.txt");
1144 let third = Path::new("/third");
1145 primary.create_dir(first).unwrap();
1146 primary.create_dir(second).unwrap();
1147 primary
1148 .new_open_options()
1149 .create(true)
1150 .write(true)
1151 .open(&file_txt)
1152 .unwrap()
1153 .write_all(b"Hello, World!")
1154 .await
1155 .unwrap();
1156 secondary.create_dir(third).unwrap();
1157
1158 let overlay = OverlayFileSystem::new(primary, [secondary]);
1159
1160 overlay.remove_dir(first).unwrap();
1162 assert_eq!(
1163 overlay.primary().metadata(first).unwrap_err(),
1164 FsError::EntryNotFound,
1165 "Deleted from primary"
1166 );
1167 assert!(!ops::exists(&overlay.secondaries[0], second));
1168
1169 assert_eq!(
1171 overlay.remove_dir(second).unwrap_err(),
1172 FsError::DirectoryNotEmpty,
1173 );
1174
1175 assert_eq!(overlay.remove_dir(third), Ok(()));
1177
1178 assert_eq!(overlay.metadata(third).unwrap_err(), FsError::EntryNotFound);
1180
1181 assert!(ops::exists(&overlay.secondaries[0], third));
1182 }
1183
1184 #[tokio::test]
1185 async fn open_files() {
1186 let primary = MemFS::default();
1187 let secondary = MemFS::default();
1188 ops::create_dir_all(&primary, "/primary").unwrap();
1189 ops::touch(&primary, "/primary/read.txt").unwrap();
1190 ops::touch(&primary, "/primary/write.txt").unwrap();
1191 ops::create_dir_all(&secondary, "/secondary").unwrap();
1192 ops::touch(&secondary, "/secondary/read.txt").unwrap();
1193 ops::touch(&secondary, "/secondary/write.txt").unwrap();
1194 ops::create_dir_all(&secondary, "/primary").unwrap();
1195 ops::write(&secondary, "/primary/read.txt", "This is shadowed")
1196 .await
1197 .unwrap();
1198
1199 let fs = OverlayFileSystem::new(primary, [secondary]);
1200
1201 let _ = fs
1203 .new_open_options()
1204 .create(true)
1205 .write(true)
1206 .open("/new.txt")
1207 .unwrap();
1208 assert!(ops::exists(&fs.primary, "/new.txt"));
1209 assert!(!ops::exists(&fs.secondaries[0], "/new.txt"));
1210
1211 let _ = fs
1213 .new_open_options()
1214 .create(false)
1215 .write(true)
1216 .read(true)
1217 .open("/primary/write.txt")
1218 .unwrap();
1219
1220 let content = ops::read_to_string(&fs, "/primary/read.txt").await.unwrap();
1222 assert_ne!(content, "This is shadowed");
1223 }
1224
1225 #[tokio::test]
1226 async fn create_file_that_looks_like_it_is_in_a_secondary_filesystem_folder() {
1227 let primary = MemFS::default();
1228 let secondary = MemFS::default();
1229 ops::create_dir_all(&secondary, "/path/to/").unwrap();
1230 assert!(!ops::is_dir(&primary, "/path/to/"));
1231 let fs = OverlayFileSystem::new(primary, [secondary]);
1232
1233 ops::touch(&fs, "/path/to/file.txt").unwrap();
1234
1235 assert!(ops::is_dir(&fs.primary, "/path/to/"));
1236 assert!(ops::is_file(&fs.primary, "/path/to/file.txt"));
1237 assert!(!ops::is_file(&fs.secondaries[0], "/path/to/file.txt"));
1238 }
1239
1240 #[tokio::test]
1241 async fn listed_files_appear_overlayed() {
1242 let primary = MemFS::default();
1243 let secondary = MemFS::default();
1244 let secondary_overlayed = MemFS::default();
1245 ops::create_dir_all(&primary, "/primary").unwrap();
1246 ops::touch(&primary, "/primary/read.txt").unwrap();
1247 ops::touch(&primary, "/primary/write.txt").unwrap();
1248 ops::create_dir_all(&secondary, "/secondary").unwrap();
1249 ops::touch(&secondary, "/secondary/read.txt").unwrap();
1250 ops::touch(&secondary, "/secondary/write.txt").unwrap();
1251 ops::create_dir_all(&secondary_overlayed, "/secondary").unwrap();
1254 ops::touch(&secondary_overlayed, "/secondary/overlayed.txt").unwrap();
1255
1256 let fs = OverlayFileSystem::new(primary, [secondary, secondary_overlayed]);
1257
1258 let paths: Vec<_> = ops::walk(&fs, "/").map(|entry| entry.path()).collect();
1259 assert_eq!(
1260 paths,
1261 vec![
1262 PathBuf::from("/secondary"),
1263 PathBuf::from("/secondary/write.txt"),
1264 PathBuf::from("/secondary/read.txt"),
1265 PathBuf::from("/secondary/overlayed.txt"),
1266 PathBuf::from("/primary"),
1267 PathBuf::from("/primary/write.txt"),
1268 PathBuf::from("/primary/read.txt"),
1269 ]
1270 );
1271 }
1272
1273 #[tokio::test]
1274 async fn open_secondary_fs_files_in_write_mode() {
1275 let primary = MemFS::default();
1276 let secondary = MemFS::default();
1277 ops::create_dir_all(&secondary, "/secondary").unwrap();
1278 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1279 .await
1280 .unwrap();
1281
1282 let fs = OverlayFileSystem::new(primary, [secondary]);
1283
1284 let mut f = fs
1285 .new_open_options()
1286 .write(true)
1287 .read(true)
1288 .open("/secondary/file.txt")
1289 .unwrap();
1290 let mut buf = String::new();
1292 f.read_to_string(&mut buf).await.unwrap();
1293 assert_eq!(buf, "Hello, World!");
1294 f.seek(SeekFrom::Start(0)).await.unwrap();
1295 f.set_len(0).unwrap();
1297 assert_eq!(f.write(b"Hi").await.unwrap(), 2);
1298 assert_eq!(f.flush().await.unwrap(), ());
1300
1301 buf = String::new();
1303 f.seek(SeekFrom::Start(0)).await.unwrap();
1304 f.read_to_string(&mut buf).await.unwrap();
1305 assert_eq!(buf, "Hi");
1306 drop(f);
1307
1308 let mut f = fs
1310 .new_open_options()
1311 .read(true)
1312 .open("/secondary/file.txt")
1313 .unwrap();
1314 buf = String::new();
1315 f.read_to_string(&mut buf).await.unwrap();
1316 assert_eq!(buf, "Hi");
1317 }
1318
1319 #[tokio::test]
1320 async fn open_secondary_fs_files_unlink() {
1321 let primary = MemFS::default();
1322 let secondary = MemFS::default();
1323 ops::create_dir_all(&secondary, "/secondary").unwrap();
1324 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1325 .await
1326 .unwrap();
1327
1328 let fs = OverlayFileSystem::new(primary, [secondary]);
1329
1330 fs.metadata(Path::new("/secondary/file.txt")).unwrap();
1331
1332 fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
1334 assert_eq!(
1335 fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
1336 FsError::EntryNotFound
1337 )
1338 }
1339
1340 #[tokio::test]
1341 async fn open_secondary_fs_without_cow() {
1342 let primary = MemFS::default();
1343 let secondary = MemFS::default();
1344 ops::create_dir_all(&secondary, "/secondary").unwrap();
1345 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1346 .await
1347 .unwrap();
1348
1349 let fs = OverlayFileSystem::new(primary, [secondary]);
1350
1351 let mut f = fs
1352 .new_open_options()
1353 .create(true)
1354 .read(true)
1355 .open(Path::new("/secondary/file.txt"))
1356 .unwrap();
1357 assert_eq!(f.size() as usize, 13);
1358
1359 let mut buf = String::new();
1360 f.read_to_string(&mut buf).await.unwrap();
1361 assert_eq!(buf, "Hello, World!");
1362
1363 assert!(!ops::is_dir(&fs.primary, "/secondary"));
1365 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1366 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1367 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1368 }
1369
1370 #[tokio::test]
1371 async fn create_and_append_secondary_fs_with_cow() {
1372 let primary = MemFS::default();
1373 let secondary = MemFS::default();
1374 ops::create_dir_all(&secondary, "/secondary").unwrap();
1375 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1376 .await
1377 .unwrap();
1378
1379 let fs = OverlayFileSystem::new(primary, [secondary]);
1380
1381 let mut f = fs
1382 .new_open_options()
1383 .create(true)
1384 .append(true)
1385 .read(true)
1386 .open(Path::new("/secondary/file.txt"))
1387 .unwrap();
1388 assert_eq!(f.size() as usize, 13);
1389
1390 f.write_all(b"asdf").await.unwrap();
1391 assert_eq!(f.size() as usize, 17);
1392
1393 f.seek(SeekFrom::Start(0)).await.unwrap();
1394
1395 let mut buf = String::new();
1396 f.read_to_string(&mut buf).await.unwrap();
1397 assert_eq!(buf, "Hello, World!asdf");
1398
1399 let f = fs
1401 .primary
1402 .new_open_options()
1403 .create(true)
1404 .append(true)
1405 .read(true)
1406 .open(Path::new("/secondary/file.txt"))
1407 .unwrap();
1408 assert_eq!(f.size() as usize, 17);
1409 let f = fs.secondaries[0]
1410 .new_open_options()
1411 .create(true)
1412 .append(true)
1413 .read(true)
1414 .open(Path::new("/secondary/file.txt"))
1415 .unwrap();
1416 assert_eq!(f.size() as usize, 13);
1417
1418 assert!(ops::is_dir(&fs.primary, "/secondary"));
1420 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1421 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1422 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1423 }
1424
1425 #[tokio::test]
1426 async fn unlink_file_from_secondary_fs() {
1427 let primary = MemFS::default();
1428 let secondary = MemFS::default();
1429 ops::create_dir_all(&secondary, "/secondary").unwrap();
1430 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1431 .await
1432 .unwrap();
1433
1434 let fs = OverlayFileSystem::new(primary, [secondary]);
1435
1436 fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
1437 assert_eq!(ops::exists(&fs, Path::new("/secondary/file.txt")), false);
1438
1439 assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1440 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1441
1442 let mut f = fs
1444 .new_open_options()
1445 .create(true)
1446 .write(true)
1447 .read(true)
1448 .open(Path::new("/secondary/file.txt"))
1449 .unwrap();
1450 assert_eq!(f.size() as usize, 0);
1451 f.write_all(b"asdf").await.unwrap();
1452 assert_eq!(f.size() as usize, 4);
1453
1454 assert!(!ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1456 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1457 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1458 }
1459
1460 #[tokio::test]
1461 async fn rmdir_from_secondary_fs() {
1462 let primary = MemFS::default();
1463 let secondary = MemFS::default();
1464 ops::create_dir_all(&secondary, "/secondary").unwrap();
1465
1466 let fs = OverlayFileSystem::new(primary, [secondary]);
1467
1468 assert!(ops::is_dir(&fs, "/secondary"));
1469 fs.remove_dir(Path::new("/secondary")).unwrap();
1470
1471 assert!(!ops::is_dir(&fs, "/secondary"));
1472 assert!(ops::is_file(&fs.primary, "/.wh.secondary"));
1473 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1474
1475 fs.create_dir(Path::new("/secondary")).unwrap();
1476 assert!(ops::is_dir(&fs, "/secondary"));
1477 assert!(ops::is_dir(&fs.primary, "/secondary"));
1478 assert!(!ops::is_file(&fs.primary, "/.wh.secondary"));
1479 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1480 }
1481
1482 #[tokio::test]
1483 async fn rmdir_sub_from_secondary_fs() {
1484 let primary = MemFS::default();
1485 let secondary = MemFS::default();
1486 ops::create_dir_all(&secondary, "/first/secondary").unwrap();
1487
1488 let fs = OverlayFileSystem::new(primary, [secondary]);
1489
1490 assert!(ops::is_dir(&fs, "/first/secondary"));
1491 fs.remove_dir(Path::new("/first/secondary")).unwrap();
1492
1493 assert!(!ops::is_dir(&fs, "/first/secondary"));
1494 assert!(ops::is_file(&fs.primary, "/first/.wh.secondary"));
1495 assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
1496
1497 fs.create_dir(Path::new("/first/secondary")).unwrap();
1498 assert!(ops::is_dir(&fs, "/first/secondary"));
1499 assert!(ops::is_dir(&fs.primary, "/first/secondary"));
1500 assert!(!ops::is_file(&fs.primary, "/first/.wh.secondary"));
1501 assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
1502 }
1503
1504 #[tokio::test]
1505 async fn create_new_secondary_fs_without_cow() {
1506 let primary = MemFS::default();
1507 let secondary = MemFS::default();
1508 ops::create_dir_all(&secondary, "/secondary").unwrap();
1509 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1510 .await
1511 .unwrap();
1512
1513 let fs = OverlayFileSystem::new(primary, [secondary]);
1514
1515 let mut f = fs
1516 .new_open_options()
1517 .create_new(true)
1518 .read(true)
1519 .open(Path::new("/secondary/file.txt"))
1520 .unwrap();
1521 assert_eq!(f.size() as usize, 0);
1522
1523 let mut buf = String::new();
1524 f.read_to_string(&mut buf).await.unwrap();
1525 assert_eq!(buf, "");
1526
1527 assert!(ops::is_dir(&fs.primary, "/secondary"));
1529 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1530 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1531 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1532 }
1533
1534 #[tokio::test]
1535 async fn open_secondary_fs_files_remove_dir() {
1536 let primary = MemFS::default();
1537 let secondary = MemFS::default();
1538 ops::create_dir_all(&secondary, "/secondary").unwrap();
1539
1540 let fs = OverlayFileSystem::new(primary, [secondary]);
1541
1542 fs.metadata(Path::new("/secondary")).unwrap();
1543
1544 fs.remove_dir(Path::new("/secondary")).unwrap();
1546 assert_eq!(
1547 fs.metadata(Path::new("/secondary")).unwrap_err(),
1548 FsError::EntryNotFound
1549 )
1550 }
1551
1552 #[tokio::test]
1556 async fn test_overlayfs_readonly_files_not_copied() {
1557 let primary = MemFS::default();
1558 let secondary = MemFS::default();
1559 ops::create_dir_all(&secondary, "/secondary").unwrap();
1560 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1561 .await
1562 .unwrap();
1563
1564 let fs = OverlayFileSystem::new(primary, [secondary]);
1565
1566 {
1567 let mut f = fs
1568 .new_open_options()
1569 .read(true)
1570 .write(true)
1571 .open(Path::new("/secondary/file.txt"))
1572 .unwrap();
1573 let mut s = String::new();
1574 f.read_to_string(&mut s).await.unwrap();
1575 assert_eq!(s, "Hello, World!");
1576
1577 f.flush().await.unwrap();
1578 f.shutdown().await.unwrap();
1579 }
1580
1581 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1583 }
1584
1585 }