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 && self.read_dir(parent).is_ok()
215 {
216 ops::create_dir_all(&self.primary, parent).ok();
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 create_symlink(&self, source: &Path, target: &Path) -> Result<(), FsError> {
229 if ops::is_white_out(target).is_some() {
230 return Err(FsError::InvalidInput);
231 }
232
233 ops::remove_white_out(self.primary.as_ref(), target);
234
235 if let Some(parent) = target.parent()
236 && self.read_dir(parent).is_ok()
237 {
238 ops::create_dir_all(&self.primary, parent).ok();
239 }
240
241 self.primary.create_symlink(source, target)
242 }
243
244 fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
245 if ops::is_white_out(path).is_some() {
248 tracing::trace!(
249 path=%path.display(),
250 "Unable to remove a whited out directory",
251 );
252 return Err(FsError::EntryNotFound);
253 }
254
255 let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
259 fs.read_dir(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
260 });
261
262 match self.primary.remove_dir(path) {
266 Err(e) if should_continue(e) => {}
267 other => return other,
268 }
269
270 if had_at_least_one_success {
271 return Ok(());
272 }
273 self.permission_error_or_not_found(path)
274 }
275
276 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
277 let from = from.to_owned();
278 let to = to.to_owned();
279 Box::pin(async move {
280 if ops::is_white_out(&from).is_some() {
282 tracing::trace!(
283 from=%from.display(),
284 to=%to.display(),
285 "Attempting to rename a file that was whited out"
286 );
287 return Err(FsError::EntryNotFound);
288 }
289 if ops::is_white_out(&to).is_some() {
291 tracing::trace!(
292 from=%from.display(),
293 to=%to.display(),
294 "Attempting to rename a file into a whiteout file"
295 );
296 return Err(FsError::InvalidInput);
297 }
298
299 let mut had_at_least_one_success = false;
304 match self.primary.rename(&from, &to).await {
305 Err(e) if should_continue(e) => {}
306 Ok(()) => {
307 had_at_least_one_success = true;
308 }
309 other => return other,
310 }
311
312 if !had_at_least_one_success {
316 for fs in self.secondaries.filesystems() {
317 if fs.metadata(&from).is_ok() {
318 ops::copy_reference_ext(fs, &self.primary, &from, &to).await?;
319 had_at_least_one_success = true;
320 break;
321 }
322 }
323 }
324
325 if had_at_least_one_success {
328 for fs in self.secondaries.filesystems() {
329 if fs.metadata(&from).is_ok() {
330 tracing::trace!(
331 path=%from.display(),
332 "Creating a whiteout for the file that was renamed",
333 );
334 ops::create_white_out(&self.primary, &from).ok();
335 break;
336 }
337 }
338 ops::remove_white_out(&self.primary, &to);
339 return Ok(());
340 }
341
342 self.permission_error_or_not_found(&from)
344 })
345 }
346
347 fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
348 if ops::is_white_out(path).is_some() {
350 return Err(FsError::EntryNotFound);
351 }
352
353 match self.primary.metadata(path) {
355 Ok(meta) => return Ok(meta),
356 Err(e) if should_continue(e) => {}
357 Err(e) => return Err(e),
358 }
359
360 if ops::has_white_out(&self.primary, path) {
362 return Err(FsError::EntryNotFound);
363 }
364
365 for fs in self.secondaries.filesystems() {
367 match fs.metadata(path) {
368 Err(e) if should_continue(e) => continue,
369 other => return other,
370 }
371 }
372
373 Err(FsError::EntryNotFound)
374 }
375
376 fn symlink_metadata(&self, path: &Path) -> crate::Result<Metadata> {
377 if ops::is_white_out(path).is_some() {
379 return Err(FsError::EntryNotFound);
380 }
381
382 match self.primary.symlink_metadata(path) {
384 Ok(meta) => return Ok(meta),
385 Err(e) if should_continue(e) => {}
386 Err(e) => return Err(e),
387 }
388
389 if ops::has_white_out(&self.primary, path) {
391 return Err(FsError::EntryNotFound);
392 }
393
394 for fs in self.secondaries.filesystems() {
396 match fs.symlink_metadata(path) {
397 Err(e) if should_continue(e) => continue,
398 other => return other,
399 }
400 }
401
402 Err(FsError::EntryNotFound)
403 }
404
405 fn remove_file(&self, path: &Path) -> Result<(), FsError> {
406 if ops::is_white_out(path).is_some() {
409 return Err(FsError::InvalidInput);
410 }
411
412 let had_at_least_one_success = self.secondaries.filesystems().into_iter().any(|fs| {
415 fs.metadata(path).is_ok() && ops::create_white_out(&self.primary, path).is_ok()
416 });
417
418 match self.primary.remove_file(path) {
420 Err(e) if should_continue(e) => {}
421 other => return other,
422 }
423
424 if had_at_least_one_success {
425 return Ok(());
426 }
427 self.permission_error_or_not_found(path)
428 }
429
430 fn new_open_options(&self) -> OpenOptions<'_> {
431 OpenOptions::new(self)
432 }
433}
434
435impl<P, S> FileOpener for OverlayFileSystem<P, S>
436where
437 P: FileSystem + Send + 'static,
438 S: for<'a> FileSystems<'a> + Send + Sync + 'static,
439 for<'a> <<S as FileSystems<'a>>::Iter as IntoIterator>::IntoIter: Send,
440{
441 fn open(
442 &self,
443 path: &Path,
444 conf: &OpenOptionsConfig,
445 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>, FsError> {
446 if ops::is_white_out(path).is_some() {
448 tracing::trace!(
449 path=%path.display(),
450 options=?conf,
451 "Whiteout files can't be opened",
452 );
453 return Err(FsError::InvalidInput);
454 }
455
456 {
459 let mut conf = conf.clone();
460 conf.create = false;
461 conf.create_new = false;
462 match self.primary.new_open_options().options(conf).open(path) {
463 Err(e) if should_continue(e) => {}
464 other => return other,
465 }
466 }
467
468 if conf.create_new {
471 if let Some(parent) = path.parent() {
475 if ops::exists(self, parent) {
476 ops::create_dir_all(&self.primary, parent)?;
480 } else {
481 return Err(FsError::EntryNotFound);
482 }
483 }
484
485 ops::remove_white_out(&self.primary, path);
487
488 return self
490 .primary
491 .new_open_options()
492 .options(conf.clone())
493 .open(path);
494 }
495
496 if !conf.create && ops::has_white_out(&self.primary, path) {
501 tracing::trace!(
502 path=%path.display(),
503 "The file has been whited out",
504 );
505 return Err(FsError::EntryNotFound);
506 }
507
508 let require_mutations = conf.append || conf.write || conf.create_new | conf.truncate;
510
511 if !ops::has_white_out(&self.primary, path) {
513 for fs in self.secondaries.filesystems() {
514 let mut sub_conf = conf.clone();
515 sub_conf.create = false;
516 sub_conf.create_new = false;
517 sub_conf.append = false;
518 sub_conf.truncate = false;
519 match fs.new_open_options().options(sub_conf.clone()).open(path) {
520 Err(e) if should_continue(e) => continue,
521 Ok(file) if require_mutations => {
522 return open_copy_on_write(path, conf, &self.primary, file);
527 }
528 other => return other,
529 }
530 }
531 }
532
533 if conf.create {
535 if let Some(parent) = path.parent()
537 && ops::exists(self, parent)
538 {
539 ops::create_dir_all(&self.primary, parent)?;
540 }
541 ops::remove_white_out(&self.primary, path);
542
543 return self
545 .primary
546 .new_open_options()
547 .options(conf.clone())
548 .open(path);
549 }
550
551 Err(FsError::EntryNotFound)
553 }
554}
555
556fn open_copy_on_write<P>(
557 path: &Path,
558 conf: &OpenOptionsConfig,
559 primary: &Arc<P>,
560 file: Box<dyn VirtualFile + Send + Sync>,
561) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError>
562where
563 P: FileSystem,
564{
565 struct CopyOnWriteFile<P> {
566 path: PathBuf,
567 primary: Arc<P>,
568 state: CowState,
569 readable: bool,
570 append: bool,
571 new_size: Option<u64>,
572 }
573 enum CowState {
574 ReadOnly(Box<dyn VirtualFile + Send + Sync>),
577 SeekingGet(Box<dyn VirtualFile + Send + Sync>),
580 SeekingSet {
584 original_offset: u64,
585 src: Box<dyn VirtualFile + Send + Sync>,
586 },
587 Copying {
590 original_offset: u64,
591 buf: Vec<u8>,
592 buf_pos: usize,
593 dst: Box<dyn VirtualFile + Send + Sync>,
594 src: Box<dyn VirtualFile + Send + Sync>,
595 },
596 SeekingRestore {
599 dst: Box<dyn VirtualFile + Send + Sync>,
600 },
601 Copied(Box<dyn VirtualFile + Send + Sync>),
603 Error {
607 err: io::Error,
608 src: Box<dyn VirtualFile + Send + Sync>,
609 },
610 }
611 impl CowState {
612 fn as_ref(&self) -> &(dyn VirtualFile + Send + Sync) {
613 match self {
614 Self::ReadOnly(inner) => inner.as_ref(),
615 Self::SeekingGet(inner) => inner.as_ref(),
616 Self::SeekingSet { src, .. } => src.as_ref(),
617 Self::Copying { src, .. } => src.as_ref(),
618 Self::SeekingRestore { dst, .. } => dst.as_ref(),
619 Self::Copied(inner) => inner.as_ref(),
620 Self::Error { src, .. } => src.as_ref(),
621 }
622 }
623 fn as_mut(&mut self) -> &mut (dyn VirtualFile + Send + Sync) {
624 match self {
625 Self::ReadOnly(inner) => inner.as_mut(),
626 Self::SeekingGet(inner) => inner.as_mut(),
627 Self::SeekingSet { src, .. } => src.as_mut(),
628 Self::Copying { src, .. } => src.as_mut(),
629 Self::SeekingRestore { dst, .. } => dst.as_mut(),
630 Self::Copied(inner) => inner.as_mut(),
631 Self::Error { src, .. } => src.as_mut(),
632 }
633 }
634 }
635
636 impl<P> CopyOnWriteFile<P>
637 where
638 P: FileSystem + 'static,
639 {
640 fn poll_copy_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
641 let mut again = true;
643 while again {
644 again = false;
645
646 replace_with_or_abort(&mut self.state, |state| match state {
648 CowState::SeekingGet(mut src) => {
651 match Pin::new(src.as_mut()).poll_complete(cx) {
652 Poll::Ready(Ok(offset)) => {
653 if let Err(err) =
654 Pin::new(src.as_mut()).start_seek(SeekFrom::Start(0))
655 {
656 return CowState::Error { err, src };
657 }
658 again = true;
659 CowState::SeekingSet {
660 original_offset: offset,
661 src,
662 }
663 }
664 Poll::Ready(Err(err)) => CowState::Error { err, src },
665 Poll::Pending => CowState::SeekingGet(src),
666 }
667 }
668
669 CowState::SeekingSet {
671 original_offset,
672 mut src,
673 } => {
674 match Pin::new(src.as_mut()).poll_complete(cx).map_ok(|_| ()) {
675 Poll::Ready(Ok(())) => {
676 if let Some(parent) = self.path.parent() {
679 ops::create_dir_all(&self.primary, parent).ok();
680 }
681 let mut had_white_out = false;
682 if ops::has_white_out(&self.primary, &self.path) {
683 ops::remove_white_out(&self.primary, &self.path);
684 had_white_out = true;
685 }
686 let dst = self
687 .primary
688 .new_open_options()
689 .create(true)
690 .read(self.readable)
691 .write(true)
692 .truncate(true)
693 .open(&self.path);
694 match dst {
695 Ok(dst) if had_white_out => {
696 again = true;
697 CowState::Copied(dst)
698 }
699 Ok(dst) => {
700 again = true;
701 CowState::Copying {
702 original_offset,
703 buf: Vec::new(),
704 buf_pos: 0,
705 src,
706 dst,
707 }
708 }
709 Err(err) => CowState::Error {
710 err: err.into(),
711 src,
712 },
713 }
714 }
715 Poll::Ready(Err(err)) => CowState::Error { err, src },
716 Poll::Pending => CowState::SeekingSet {
717 original_offset,
718 src,
719 },
720 }
721 }
722 CowState::Copying {
724 mut src,
725 mut dst,
726 mut buf,
727 mut buf_pos,
728 original_offset,
729 } => {
730 loop {
731 if buf_pos < buf.len() {
734 let dst_pinned = Pin::new(dst.as_mut());
735 match dst_pinned.poll_write(cx, &buf[buf_pos..]) {
736 Poll::Ready(Ok(0)) => {}
737 Poll::Ready(Ok(amt)) => {
738 buf_pos += amt;
739 continue;
740 }
741 Poll::Ready(Err(err)) => {
742 return CowState::Error { err, src };
743 }
744 Poll::Pending => {}
745 }
746 } else {
747 buf.resize_with(8192, || 0);
748 buf_pos = 8192;
749 let mut read_buf = ReadBuf::new(&mut buf);
750 match Pin::new(src.as_mut()).poll_read(cx, &mut read_buf) {
751 Poll::Ready(Ok(())) if read_buf.filled().is_empty() => {
752 again = true;
753
754 if self.append {
755 return CowState::Copied(dst);
758 } else {
759 if let Err(err) = Pin::new(dst.as_mut())
762 .start_seek(SeekFrom::Start(original_offset))
763 {
764 return CowState::Error { err, src };
765 }
766 return CowState::SeekingRestore { dst };
767 }
768 }
769 Poll::Ready(Ok(())) => {
770 let new_len = read_buf.filled().len();
772 unsafe { buf.set_len(new_len) };
773 buf_pos = 0;
774 continue;
775 }
776 Poll::Ready(Err(err)) => return CowState::Error { err, src },
777 Poll::Pending => {}
778 }
779 }
780 return CowState::Copying {
781 original_offset,
782 buf,
783 buf_pos,
784 src,
785 dst,
786 };
787 }
788 }
789 CowState::SeekingRestore { mut dst } => {
791 match Pin::new(dst.as_mut()).poll_complete(cx) {
792 Poll::Ready(_) => {
793 if let Some(new_size) = self.new_size.take() {
795 dst.set_len(new_size).ok();
796 }
797 CowState::Copied(dst)
798 }
799 Poll::Pending => CowState::SeekingRestore { dst },
800 }
801 }
802 s => s,
803 });
804 }
805
806 let mut ret = Poll::Pending;
809 replace_with_or_abort(&mut self.state, |state| match state {
810 CowState::ReadOnly(src) => {
811 ret = Poll::Ready(Ok(()));
812 CowState::ReadOnly(src)
813 }
814 CowState::Copied(src) => {
815 ret = Poll::Ready(Ok(()));
816 CowState::Copied(src)
817 }
818 CowState::Error { err, src } => {
819 ret = Poll::Ready(Err(err));
820 CowState::ReadOnly(src)
821 }
822 state => {
823 ret = Poll::Pending;
824 state
825 }
826 });
827 ret
828 }
829
830 fn poll_copy_start_and_progress(&mut self, cx: &mut Context) -> Poll<io::Result<()>> {
831 replace_with_or_abort(&mut self.state, |state| match state {
832 CowState::ReadOnly(inner) => {
833 tracing::trace!("COW file touched, starting file clone");
834 CowState::SeekingGet(inner)
835 }
836 state => state,
837 });
838 self.poll_copy_progress(cx)
839 }
840 }
841
842 impl<P> Debug for CopyOnWriteFile<P>
843 where
844 P: FileSystem + 'static,
845 {
846 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
847 f.debug_struct("CopyOnWriteFile").finish()
848 }
849 }
850
851 impl<P> VirtualFile for CopyOnWriteFile<P>
852 where
853 P: FileSystem + 'static,
854 {
855 fn last_accessed(&self) -> u64 {
856 self.state.as_ref().last_accessed()
857 }
858
859 fn last_modified(&self) -> u64 {
860 self.state.as_ref().last_modified()
861 }
862
863 fn created_time(&self) -> u64 {
864 self.state.as_ref().created_time()
865 }
866
867 fn size(&self) -> u64 {
868 self.state.as_ref().size()
869 }
870
871 fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
872 self.new_size = Some(new_size);
873 replace_with_or_abort(&mut self.state, |state| match state {
874 CowState::Copied(mut file) => {
875 file.set_len(new_size).ok();
876 CowState::Copied(file)
877 }
878 state => {
879 if let Some(parent) = self.path.parent() {
882 ops::create_dir_all(&self.primary, parent).ok();
883 }
884 let dst = self
885 .primary
886 .new_open_options()
887 .create(true)
888 .write(true)
889 .open(&self.path);
890 if let Ok(mut file) = dst {
891 file.set_len(new_size).ok();
892 }
893 state
894 }
895 });
896 Ok(())
897 }
898
899 fn unlink(&mut self) -> crate::Result<()> {
900 let primary = self.primary.clone();
901 let path = self.path.clone();
902
903 let mut had_at_least_one_success = false;
905 if ops::create_white_out(&primary, &path).is_ok() {
906 had_at_least_one_success = true;
907 }
908
909 match primary.remove_file(&path) {
911 Err(e) if should_continue(e) => {}
912 other => return other,
913 }
914
915 if had_at_least_one_success {
916 return Ok(());
917 }
918 Err(FsError::PermissionDenied)
919 }
920
921 fn poll_read_ready(
922 mut self: Pin<&mut Self>,
923 cx: &mut std::task::Context<'_>,
924 ) -> Poll<std::io::Result<usize>> {
925 match self.poll_copy_progress(cx) {
926 Poll::Ready(Ok(())) => {}
927 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
928 Poll::Pending => return Poll::Pending,
929 }
930 Pin::new(self.state.as_mut()).poll_read_ready(cx)
931 }
932
933 fn poll_write_ready(
934 mut self: Pin<&mut Self>,
935 cx: &mut std::task::Context<'_>,
936 ) -> Poll<std::io::Result<usize>> {
937 match self.poll_copy_progress(cx) {
938 Poll::Ready(Ok(())) => {}
939 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
940 Poll::Pending => return Poll::Pending,
941 }
942 Pin::new(self.state.as_mut()).poll_write_ready(cx)
943 }
944 }
945
946 impl<P> AsyncWrite for CopyOnWriteFile<P>
947 where
948 P: FileSystem + 'static,
949 {
950 fn poll_write(
951 mut self: Pin<&mut Self>,
952 cx: &mut std::task::Context<'_>,
953 buf: &[u8],
954 ) -> Poll<Result<usize, std::io::Error>> {
955 match self.poll_copy_start_and_progress(cx) {
956 Poll::Pending => return Poll::Pending,
957 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
958 Poll::Ready(Ok(())) => {}
959 }
960 Pin::new(self.state.as_mut()).poll_write(cx, buf)
961 }
962
963 fn poll_write_vectored(
964 mut self: Pin<&mut Self>,
965 cx: &mut Context<'_>,
966 bufs: &[io::IoSlice<'_>],
967 ) -> Poll<Result<usize, io::Error>> {
968 match self.poll_copy_start_and_progress(cx) {
969 Poll::Pending => return Poll::Pending,
970 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
971 Poll::Ready(Ok(())) => {}
972 }
973 Pin::new(self.state.as_mut()).poll_write_vectored(cx, bufs)
974 }
975
976 fn poll_flush(
977 mut self: Pin<&mut Self>,
978 cx: &mut std::task::Context<'_>,
979 ) -> Poll<Result<(), std::io::Error>> {
980 match self.poll_copy_progress(cx) {
981 Poll::Ready(Ok(())) => {}
982 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
983 Poll::Pending => return Poll::Pending,
984 }
985 match self.state {
988 CowState::ReadOnly(_) => Poll::Ready(Ok(())),
989 _ => Pin::new(self.state.as_mut()).poll_flush(cx),
990 }
991 }
992
993 fn poll_shutdown(
994 mut self: Pin<&mut Self>,
995 cx: &mut std::task::Context<'_>,
996 ) -> Poll<Result<(), std::io::Error>> {
997 match self.poll_copy_progress(cx) {
998 Poll::Ready(Ok(())) => {}
999 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1000 Poll::Pending => return Poll::Pending,
1001 }
1002 match self.state {
1004 CowState::ReadOnly(_) => Poll::Ready(Ok(())),
1005 _ => Pin::new(self.state.as_mut()).poll_shutdown(cx),
1006 }
1007 }
1008 }
1009
1010 impl<P> AsyncRead for CopyOnWriteFile<P>
1011 where
1012 P: FileSystem + 'static,
1013 {
1014 fn poll_read(
1015 mut self: Pin<&mut Self>,
1016 cx: &mut std::task::Context<'_>,
1017 buf: &mut tokio::io::ReadBuf<'_>,
1018 ) -> Poll<std::io::Result<()>> {
1019 match self.poll_copy_progress(cx) {
1020 Poll::Ready(Ok(())) => {}
1021 p => return p,
1022 }
1023 Pin::new(self.state.as_mut()).poll_read(cx, buf)
1024 }
1025 }
1026
1027 impl<P> AsyncSeek for CopyOnWriteFile<P>
1028 where
1029 P: FileSystem + 'static,
1030 {
1031 fn start_seek(
1032 mut self: Pin<&mut Self>,
1033 position: std::io::SeekFrom,
1034 ) -> std::io::Result<()> {
1035 match &mut self.state {
1036 CowState::ReadOnly(file)
1037 | CowState::SeekingGet(file)
1038 | CowState::Error { src: file, .. }
1039 | CowState::Copied(file)
1040 | CowState::SeekingRestore { dst: file, .. } => {
1041 Pin::new(file.as_mut()).start_seek(position)
1042 }
1043 CowState::SeekingSet {
1044 original_offset,
1045 src,
1046 ..
1047 }
1048 | CowState::Copying {
1049 original_offset,
1050 src,
1051 ..
1052 } => {
1053 *original_offset = match position {
1054 SeekFrom::Current(delta) => original_offset
1055 .checked_add_signed(delta)
1056 .unwrap_or(*original_offset),
1057 SeekFrom::Start(pos) => pos,
1058 SeekFrom::End(pos) => src
1059 .size()
1060 .checked_add_signed(pos)
1061 .unwrap_or(*original_offset),
1062 };
1063 Ok(())
1064 }
1065 }
1066 }
1067
1068 fn poll_complete(
1069 mut self: Pin<&mut Self>,
1070 cx: &mut std::task::Context<'_>,
1071 ) -> Poll<std::io::Result<u64>> {
1072 match self.poll_copy_progress(cx) {
1073 Poll::Ready(Ok(())) => {}
1074 Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1075 Poll::Pending => return Poll::Pending,
1076 }
1077 Pin::new(self.state.as_mut()).poll_complete(cx)
1078 }
1079 }
1080
1081 tracing::trace!(
1082 path=%path.display(),
1083 options=?conf,
1084 "Opening the file in copy-on-write mode",
1085 );
1086 Ok(Box::new(CopyOnWriteFile::<P> {
1087 path: path.to_path_buf(),
1088 primary: primary.clone(),
1089 state: CowState::ReadOnly(file),
1090 readable: conf.read,
1091 append: conf.append,
1092 new_size: None,
1093 }))
1094}
1095
1096impl<P, S> Debug for OverlayFileSystem<P, S>
1097where
1098 P: FileSystem,
1099 S: for<'a> FileSystems<'a>,
1100{
1101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1102 struct IterFilesystems<'a, S>(&'a S);
1103 impl<S> Debug for IterFilesystems<'_, S>
1104 where
1105 S: for<'b> FileSystems<'b>,
1106 {
1107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1108 let mut f = f.debug_list();
1109
1110 for fs in self.0.filesystems() {
1111 f.entry(&fs);
1112 }
1113
1114 f.finish()
1115 }
1116 }
1117
1118 f.debug_struct("OverlayFileSystem")
1119 .field("primary", &self.primary)
1120 .field("secondaries", &IterFilesystems(&self.secondaries))
1121 .finish()
1122 }
1123}
1124
1125fn should_continue(e: FsError) -> bool {
1126 matches!(
1131 e,
1132 FsError::EntryNotFound | FsError::InvalidInput | FsError::BaseNotDirectory
1133 )
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use std::path::PathBuf;
1139
1140 use super::*;
1141 use crate::mem_fs::FileSystem as MemFS;
1142 use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
1143
1144 #[test]
1145 fn overlay_read_dir_rebases_mounted_entries() {
1146 let primary = MemFS::default();
1147 ops::create_dir_all(&primary, "/app").unwrap();
1148
1149 let volume = MemFS::default();
1150 ops::create_dir_all(&volume, "/themes/twentytwentyfour").unwrap();
1151
1152 let container = MemFS::default();
1153 ops::create_dir_all(&container, "/app/wp-content/themes/twentytwentyfour").unwrap();
1154
1155 let volume: Arc<dyn FileSystem + Send + Sync> = Arc::new(volume);
1156 primary
1157 .mount(
1158 PathBuf::from("/app/wp-content"),
1159 &volume,
1160 PathBuf::from("/"),
1161 )
1162 .unwrap();
1163
1164 let overlay = OverlayFileSystem::new(primary, [container]);
1165
1166 let mut entries: Vec<_> = overlay
1167 .read_dir(Path::new("/app/wp-content/themes"))
1168 .unwrap()
1169 .map(|entry| entry.unwrap().path)
1170 .collect();
1171 entries.sort();
1172
1173 assert_eq!(
1174 entries,
1175 vec![PathBuf::from("/app/wp-content/themes/twentytwentyfour")],
1176 );
1177 }
1178
1179 #[tokio::test]
1180 async fn remove_directory() {
1181 let primary = MemFS::default();
1182 let secondary = MemFS::default();
1183 let first = Path::new("/first");
1184 let second = Path::new("/second");
1185 let file_txt = second.join("file.txt");
1186 let third = Path::new("/third");
1187 primary.create_dir(first).unwrap();
1188 primary.create_dir(second).unwrap();
1189 primary
1190 .new_open_options()
1191 .create(true)
1192 .write(true)
1193 .open(&file_txt)
1194 .unwrap()
1195 .write_all(b"Hello, World!")
1196 .await
1197 .unwrap();
1198 secondary.create_dir(third).unwrap();
1199
1200 let overlay = OverlayFileSystem::new(primary, [secondary]);
1201
1202 overlay.remove_dir(first).unwrap();
1204 assert_eq!(
1205 overlay.primary().metadata(first).unwrap_err(),
1206 FsError::EntryNotFound,
1207 "Deleted from primary"
1208 );
1209 assert!(!ops::exists(&overlay.secondaries[0], second));
1210
1211 assert_eq!(
1213 overlay.remove_dir(second).unwrap_err(),
1214 FsError::DirectoryNotEmpty,
1215 );
1216
1217 assert_eq!(overlay.remove_dir(third), Ok(()));
1219
1220 assert_eq!(overlay.metadata(third).unwrap_err(), FsError::EntryNotFound);
1222
1223 assert!(ops::exists(&overlay.secondaries[0], third));
1224 }
1225
1226 #[tokio::test]
1227 async fn open_files() {
1228 let primary = MemFS::default();
1229 let secondary = MemFS::default();
1230 ops::create_dir_all(&primary, "/primary").unwrap();
1231 ops::touch(&primary, "/primary/read.txt").unwrap();
1232 ops::touch(&primary, "/primary/write.txt").unwrap();
1233 ops::create_dir_all(&secondary, "/secondary").unwrap();
1234 ops::touch(&secondary, "/secondary/read.txt").unwrap();
1235 ops::touch(&secondary, "/secondary/write.txt").unwrap();
1236 ops::create_dir_all(&secondary, "/primary").unwrap();
1237 ops::write(&secondary, "/primary/read.txt", "This is shadowed")
1238 .await
1239 .unwrap();
1240
1241 let fs = OverlayFileSystem::new(primary, [secondary]);
1242
1243 let _ = fs
1245 .new_open_options()
1246 .create(true)
1247 .write(true)
1248 .open("/new.txt")
1249 .unwrap();
1250 assert!(ops::exists(&fs.primary, "/new.txt"));
1251 assert!(!ops::exists(&fs.secondaries[0], "/new.txt"));
1252
1253 let _ = fs
1255 .new_open_options()
1256 .create(false)
1257 .write(true)
1258 .read(true)
1259 .open("/primary/write.txt")
1260 .unwrap();
1261
1262 let content = ops::read_to_string(&fs, "/primary/read.txt").await.unwrap();
1264 assert_ne!(content, "This is shadowed");
1265 }
1266
1267 #[tokio::test]
1268 async fn create_file_that_looks_like_it_is_in_a_secondary_filesystem_folder() {
1269 let primary = MemFS::default();
1270 let secondary = MemFS::default();
1271 ops::create_dir_all(&secondary, "/path/to/").unwrap();
1272 assert!(!ops::is_dir(&primary, "/path/to/"));
1273 let fs = OverlayFileSystem::new(primary, [secondary]);
1274
1275 ops::touch(&fs, "/path/to/file.txt").unwrap();
1276
1277 assert!(ops::is_dir(&fs.primary, "/path/to/"));
1278 assert!(ops::is_file(&fs.primary, "/path/to/file.txt"));
1279 assert!(!ops::is_file(&fs.secondaries[0], "/path/to/file.txt"));
1280 }
1281
1282 #[tokio::test]
1283 async fn listed_files_appear_overlayed() {
1284 let primary = MemFS::default();
1285 let secondary = MemFS::default();
1286 let secondary_overlayed = MemFS::default();
1287 ops::create_dir_all(&primary, "/primary").unwrap();
1288 ops::touch(&primary, "/primary/read.txt").unwrap();
1289 ops::touch(&primary, "/primary/write.txt").unwrap();
1290 ops::create_dir_all(&secondary, "/secondary").unwrap();
1291 ops::touch(&secondary, "/secondary/read.txt").unwrap();
1292 ops::touch(&secondary, "/secondary/write.txt").unwrap();
1293 ops::create_dir_all(&secondary_overlayed, "/secondary").unwrap();
1296 ops::touch(&secondary_overlayed, "/secondary/overlayed.txt").unwrap();
1297
1298 let fs = OverlayFileSystem::new(primary, [secondary, secondary_overlayed]);
1299
1300 let paths: Vec<_> = ops::walk(&fs, "/").map(|entry| entry.path()).collect();
1301 assert_eq!(
1302 paths,
1303 vec![
1304 PathBuf::from("/secondary"),
1305 PathBuf::from("/secondary/write.txt"),
1306 PathBuf::from("/secondary/read.txt"),
1307 PathBuf::from("/secondary/overlayed.txt"),
1308 PathBuf::from("/primary"),
1309 PathBuf::from("/primary/write.txt"),
1310 PathBuf::from("/primary/read.txt"),
1311 ]
1312 );
1313 }
1314
1315 #[tokio::test]
1316 async fn open_secondary_fs_files_in_write_mode() {
1317 let primary = MemFS::default();
1318 let secondary = MemFS::default();
1319 ops::create_dir_all(&secondary, "/secondary").unwrap();
1320 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1321 .await
1322 .unwrap();
1323
1324 let fs = OverlayFileSystem::new(primary, [secondary]);
1325
1326 let mut f = fs
1327 .new_open_options()
1328 .write(true)
1329 .read(true)
1330 .open("/secondary/file.txt")
1331 .unwrap();
1332 let mut buf = String::new();
1334 f.read_to_string(&mut buf).await.unwrap();
1335 assert_eq!(buf, "Hello, World!");
1336 f.seek(SeekFrom::Start(0)).await.unwrap();
1337 f.set_len(0).unwrap();
1339 assert_eq!(f.write(b"Hi").await.unwrap(), 2);
1340 assert_eq!(f.flush().await.unwrap(), ());
1342
1343 buf = String::new();
1345 f.seek(SeekFrom::Start(0)).await.unwrap();
1346 f.read_to_string(&mut buf).await.unwrap();
1347 assert_eq!(buf, "Hi");
1348 drop(f);
1349
1350 let mut f = fs
1352 .new_open_options()
1353 .read(true)
1354 .open("/secondary/file.txt")
1355 .unwrap();
1356 buf = String::new();
1357 f.read_to_string(&mut buf).await.unwrap();
1358 assert_eq!(buf, "Hi");
1359 }
1360
1361 #[tokio::test]
1362 async fn open_secondary_fs_files_unlink() {
1363 let primary = MemFS::default();
1364 let secondary = MemFS::default();
1365 ops::create_dir_all(&secondary, "/secondary").unwrap();
1366 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1367 .await
1368 .unwrap();
1369
1370 let fs = OverlayFileSystem::new(primary, [secondary]);
1371
1372 fs.metadata(Path::new("/secondary/file.txt")).unwrap();
1373
1374 fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
1376 assert_eq!(
1377 fs.metadata(Path::new("/secondary/file.txt")).unwrap_err(),
1378 FsError::EntryNotFound
1379 )
1380 }
1381
1382 #[tokio::test]
1383 async fn open_secondary_fs_without_cow() {
1384 let primary = MemFS::default();
1385 let secondary = MemFS::default();
1386 ops::create_dir_all(&secondary, "/secondary").unwrap();
1387 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1388 .await
1389 .unwrap();
1390
1391 let fs = OverlayFileSystem::new(primary, [secondary]);
1392
1393 let mut f = fs
1394 .new_open_options()
1395 .create(true)
1396 .read(true)
1397 .open(Path::new("/secondary/file.txt"))
1398 .unwrap();
1399 assert_eq!(f.size() as usize, 13);
1400
1401 let mut buf = String::new();
1402 f.read_to_string(&mut buf).await.unwrap();
1403 assert_eq!(buf, "Hello, World!");
1404
1405 assert!(!ops::is_dir(&fs.primary, "/secondary"));
1407 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1408 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1409 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1410 }
1411
1412 #[tokio::test]
1413 async fn create_and_append_secondary_fs_with_cow() {
1414 let primary = MemFS::default();
1415 let secondary = MemFS::default();
1416 ops::create_dir_all(&secondary, "/secondary").unwrap();
1417 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1418 .await
1419 .unwrap();
1420
1421 let fs = OverlayFileSystem::new(primary, [secondary]);
1422
1423 let mut f = fs
1424 .new_open_options()
1425 .create(true)
1426 .append(true)
1427 .read(true)
1428 .open(Path::new("/secondary/file.txt"))
1429 .unwrap();
1430 assert_eq!(f.size() as usize, 13);
1431
1432 f.write_all(b"asdf").await.unwrap();
1433 assert_eq!(f.size() as usize, 17);
1434
1435 f.seek(SeekFrom::Start(0)).await.unwrap();
1436
1437 let mut buf = String::new();
1438 f.read_to_string(&mut buf).await.unwrap();
1439 assert_eq!(buf, "Hello, World!asdf");
1440
1441 let f = fs
1443 .primary
1444 .new_open_options()
1445 .create(true)
1446 .append(true)
1447 .read(true)
1448 .open(Path::new("/secondary/file.txt"))
1449 .unwrap();
1450 assert_eq!(f.size() as usize, 17);
1451 let f = fs.secondaries[0]
1452 .new_open_options()
1453 .create(true)
1454 .append(true)
1455 .read(true)
1456 .open(Path::new("/secondary/file.txt"))
1457 .unwrap();
1458 assert_eq!(f.size() as usize, 13);
1459
1460 assert!(ops::is_dir(&fs.primary, "/secondary"));
1462 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1463 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1464 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1465 }
1466
1467 #[tokio::test]
1468 async fn unlink_file_from_secondary_fs() {
1469 let primary = MemFS::default();
1470 let secondary = MemFS::default();
1471 ops::create_dir_all(&secondary, "/secondary").unwrap();
1472 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1473 .await
1474 .unwrap();
1475
1476 let fs = OverlayFileSystem::new(primary, [secondary]);
1477
1478 fs.remove_file(Path::new("/secondary/file.txt")).unwrap();
1479 assert_eq!(ops::exists(&fs, Path::new("/secondary/file.txt")), false);
1480
1481 assert!(ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1482 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1483
1484 let mut f = fs
1486 .new_open_options()
1487 .create(true)
1488 .write(true)
1489 .read(true)
1490 .open(Path::new("/secondary/file.txt"))
1491 .unwrap();
1492 assert_eq!(f.size() as usize, 0);
1493 f.write_all(b"asdf").await.unwrap();
1494 assert_eq!(f.size() as usize, 4);
1495
1496 assert!(!ops::is_file(&fs.primary, "/secondary/.wh.file.txt"));
1498 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1499 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1500 }
1501
1502 #[tokio::test]
1503 async fn rmdir_from_secondary_fs() {
1504 let primary = MemFS::default();
1505 let secondary = MemFS::default();
1506 ops::create_dir_all(&secondary, "/secondary").unwrap();
1507
1508 let fs = OverlayFileSystem::new(primary, [secondary]);
1509
1510 assert!(ops::is_dir(&fs, "/secondary"));
1511 fs.remove_dir(Path::new("/secondary")).unwrap();
1512
1513 assert!(!ops::is_dir(&fs, "/secondary"));
1514 assert!(ops::is_file(&fs.primary, "/.wh.secondary"));
1515 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1516
1517 fs.create_dir(Path::new("/secondary")).unwrap();
1518 assert!(ops::is_dir(&fs, "/secondary"));
1519 assert!(ops::is_dir(&fs.primary, "/secondary"));
1520 assert!(!ops::is_file(&fs.primary, "/.wh.secondary"));
1521 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1522 }
1523
1524 #[tokio::test]
1525 async fn rmdir_sub_from_secondary_fs() {
1526 let primary = MemFS::default();
1527 let secondary = MemFS::default();
1528 ops::create_dir_all(&secondary, "/first/secondary").unwrap();
1529
1530 let fs = OverlayFileSystem::new(primary, [secondary]);
1531
1532 assert!(ops::is_dir(&fs, "/first/secondary"));
1533 fs.remove_dir(Path::new("/first/secondary")).unwrap();
1534
1535 assert!(!ops::is_dir(&fs, "/first/secondary"));
1536 assert!(ops::is_file(&fs.primary, "/first/.wh.secondary"));
1537 assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
1538
1539 fs.create_dir(Path::new("/first/secondary")).unwrap();
1540 assert!(ops::is_dir(&fs, "/first/secondary"));
1541 assert!(ops::is_dir(&fs.primary, "/first/secondary"));
1542 assert!(!ops::is_file(&fs.primary, "/first/.wh.secondary"));
1543 assert!(ops::is_dir(&fs.secondaries[0], "/first/secondary"));
1544 }
1545
1546 #[tokio::test]
1547 async fn create_new_secondary_fs_without_cow() {
1548 let primary = MemFS::default();
1549 let secondary = MemFS::default();
1550 ops::create_dir_all(&secondary, "/secondary").unwrap();
1551 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1552 .await
1553 .unwrap();
1554
1555 let fs = OverlayFileSystem::new(primary, [secondary]);
1556
1557 let mut f = fs
1558 .new_open_options()
1559 .create_new(true)
1560 .read(true)
1561 .open(Path::new("/secondary/file.txt"))
1562 .unwrap();
1563 assert_eq!(f.size() as usize, 0);
1564
1565 let mut buf = String::new();
1566 f.read_to_string(&mut buf).await.unwrap();
1567 assert_eq!(buf, "");
1568
1569 assert!(ops::is_dir(&fs.primary, "/secondary"));
1571 assert!(ops::is_file(&fs.primary, "/secondary/file.txt"));
1572 assert!(ops::is_dir(&fs.secondaries[0], "/secondary"));
1573 assert!(ops::is_file(&fs.secondaries[0], "/secondary/file.txt"));
1574 }
1575
1576 #[tokio::test]
1577 async fn open_secondary_fs_files_remove_dir() {
1578 let primary = MemFS::default();
1579 let secondary = MemFS::default();
1580 ops::create_dir_all(&secondary, "/secondary").unwrap();
1581
1582 let fs = OverlayFileSystem::new(primary, [secondary]);
1583
1584 fs.metadata(Path::new("/secondary")).unwrap();
1585
1586 fs.remove_dir(Path::new("/secondary")).unwrap();
1588 assert_eq!(
1589 fs.metadata(Path::new("/secondary")).unwrap_err(),
1590 FsError::EntryNotFound
1591 )
1592 }
1593
1594 #[tokio::test]
1598 async fn test_overlayfs_readonly_files_not_copied() {
1599 let primary = MemFS::default();
1600 let secondary = MemFS::default();
1601 ops::create_dir_all(&secondary, "/secondary").unwrap();
1602 ops::write(&secondary, "/secondary/file.txt", b"Hello, World!")
1603 .await
1604 .unwrap();
1605
1606 let fs = OverlayFileSystem::new(primary, [secondary]);
1607
1608 {
1609 let mut f = fs
1610 .new_open_options()
1611 .read(true)
1612 .write(true)
1613 .open(Path::new("/secondary/file.txt"))
1614 .unwrap();
1615 let mut s = String::new();
1616 f.read_to_string(&mut s).await.unwrap();
1617 assert_eq!(s, "Hello, World!");
1618
1619 f.flush().await.unwrap();
1620 f.shutdown().await.unwrap();
1621 }
1622
1623 assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1625 }
1626
1627 }