virtual_fs/
overlay_fs.rs

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/// A primary filesystem and chain of secondary filesystems that are overlayed
21/// on top of each other.
22///
23/// # Precedence
24///
25/// The [`OverlayFileSystem`] will execute operations based on precedence.
26///
27///
28/// Most importantly, this means earlier filesystems can shadow files and
29/// directories that have a lower precedence.
30///
31///# Examples
32///
33/// Something useful to know is that the [`FileSystems`] trait is implemented
34/// for both arrays and tuples.
35///
36/// For example, if you want to create a [`crate::FileSystem`] which will
37/// create files in-memory while still being able to read from the host, you
38/// might do something like this:
39///
40/// ```rust
41/// use virtual_fs::{
42///     mem_fs::FileSystem as MemFS,
43///     host_fs::FileSystem as HostFS,
44///     OverlayFileSystem,
45/// };
46///
47/// let runtime = tokio::runtime::Builder::new_current_thread()
48///     .enable_all()
49///     .build()
50///     .unwrap();
51/// let _guard = runtime.enter();
52///
53/// let fs = OverlayFileSystem::new(MemFS::default(), [HostFS::new(tokio::runtime::Handle::current(), "/").unwrap()]);
54///
55/// // This also has the benefit of storing the two values in-line with no extra
56/// // overhead or indirection.
57/// assert_eq!(
58///     std::mem::size_of_val(&fs),
59///     std::mem::size_of::<(MemFS, HostFS)>(),
60/// );
61/// ```
62///
63/// A more complex example is
64#[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    /// Create a new [`FileSystem`] using a primary [`crate::FileSystem`] and a
76    /// chain of secondary [`FileSystems`].
77    pub fn new(primary: P, secondaries: S) -> Self {
78        OverlayFileSystem {
79            primary: Arc::new(primary),
80            secondaries,
81        }
82    }
83
84    /// Get a reference to the primary filesystem.
85    pub fn primary(&self) -> &P {
86        &self.primary
87    }
88
89    /// Get a reference to the secondary filesystems.
90    pub fn secondaries(&self) -> &S {
91        &self.secondaries
92    }
93
94    /// Get a mutable reference to the secondary filesystems.
95    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        // Whiteout files can not be read, they are just markers
118        if ops::is_white_out(path).is_some() {
119            return Err(FsError::EntryNotFound);
120        }
121
122        // Check if the file is in the primary
123        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        // There might be a whiteout, search for this
130        if ops::has_white_out(&self.primary, path) {
131            return Err(FsError::EntryNotFound);
132        }
133
134        // Otherwise scan the secondaries
135        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                        // White out entries block any later entries in the secondaries
160                        // unless the entry has comes before the white out, thus the order
161                        // that the file systems are parsed is important to this logic.
162                        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            // Make sure later entries are removed in favour of earlier ones.
188            // Note: this sort is guaranteed to be stable, meaning filesystems
189            // "higher up" the chain will be further towards the start and kept
190            // when deduplicating.
191            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        // You can not create directories that use the whiteout prefix
202        if ops::is_white_out(path).is_some() {
203            return Err(FsError::InvalidInput);
204        }
205
206        // It could be the case that the directory was earlier hidden in the secondaries
207        // by a whiteout file, hence we need to make sure those are cleared out.
208        ops::remove_white_out(self.primary.as_ref(), path);
209
210        // Make sure the parent tree is in place on the primary, this is to cover the
211        // scenario where the secondaries has a parent structure that is not yet in the
212        // primary and the primary needs it to create a sub-directory
213        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        // Create the directory in the primary
220        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        // Whiteout files can not be removed, instead the original directory
246        // must be removed or recreated.
247        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        // If the directory is contained in a secondary file system then we need to create a
256        // whiteout file so that it is suppressed and is no longer returned in `readdir` calls.
257
258        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        // Attempt to remove it from the primary, if this succeeds then we may have also
263        // added the whiteout file in the earlier step, but are required in this case to
264        // properly delete the directory.
265        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            // Whiteout files can not be renamed
281            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            // You can not rename a file into a whiteout file
290            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            // We attempt to rename the file or directory in the primary
300            // if this succeeds then we also need to ensure the white out
301            // files are created where we need them, so we do not immediately
302            // return until that is done
303            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 we have not yet renamed the file it may still reside in
313            // the secondaries, in which case we need to copy it to the
314            // primary rather than rename it
315            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 the rename operation was a success then we need to update any
326            // whiteout files on the primary before we return success.
327            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            // Otherwise we are in a failure scenario
343            self.permission_error_or_not_found(&from)
344        })
345    }
346
347    fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
348        // Whiteout files can not be read, they are just markers
349        if ops::is_white_out(path).is_some() {
350            return Err(FsError::EntryNotFound);
351        }
352
353        // Check if the file is in the primary
354        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        // There might be a whiteout, search for this
361        if ops::has_white_out(&self.primary, path) {
362            return Err(FsError::EntryNotFound);
363        }
364
365        // Otherwise scan the secondaries
366        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        // Whiteout files can not be read, they are just markers
378        if ops::is_white_out(path).is_some() {
379            return Err(FsError::EntryNotFound);
380        }
381
382        // Check if the file is in the primary
383        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        // There might be a whiteout, search for this
390        if ops::has_white_out(&self.primary, path) {
391            return Err(FsError::EntryNotFound);
392        }
393
394        // Otherwise scan the secondaries
395        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        // It is not possible to delete whiteout files directly, instead
407        // one must delete the original file
408        if ops::is_white_out(path).is_some() {
409            return Err(FsError::InvalidInput);
410        }
411
412        // If the file is contained in a secondary then then we need to create a
413        // whiteout file so that it is suppressed.
414        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        // Attempt to remove it from the primary
419        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        // Whiteout files can not be read, they are just markers
447        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        // Check if the file is in the primary (without actually creating it) as
457        // when the file is in the primary it takes preference over any of file
458        {
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        // In the scenario that we are creating the file then there is
469        // special handling that will ensure its setup correctly
470        if conf.create_new {
471            // When the secondary has the directory structure but the primary
472            // does not then we need to make sure we create all the structure
473            // in the primary
474            if let Some(parent) = path.parent() {
475                if ops::exists(self, parent) {
476                    // We create the directory structure on the primary so that
477                    // the new file can be created, this will make it override
478                    // whatever is in the primary
479                    ops::create_dir_all(&self.primary, parent)?;
480                } else {
481                    return Err(FsError::EntryNotFound);
482                }
483            }
484
485            // Remove any whiteout
486            ops::remove_white_out(&self.primary, path);
487
488            // Create the file in the primary
489            return self
490                .primary
491                .new_open_options()
492                .options(conf.clone())
493                .open(path);
494        }
495
496        // There might be a whiteout, search for this and if its found then
497        // we are done as the secondary file or directory has been earlier
498        // deleted via a white out (when the create flag is set then
499        // the white out marker is ignored)
500        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        // Determine if a mutation will be possible with the opened file
509        let require_mutations = conf.append || conf.write || conf.create_new | conf.truncate;
510
511        // If the file is on a secondary then we should open it
512        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                        // If the file was opened with the ability to mutate then we need
523                        // to return a copy on write emulation so that the file can be
524                        // copied from the secondary to the primary in the scenario that
525                        // it is edited
526                        return open_copy_on_write(path, conf, &self.primary, file);
527                    }
528                    other => return other,
529                }
530            }
531        }
532
533        // If we are creating the file then do so
534        if conf.create {
535            // Create the parent structure and remove any whiteouts
536            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            // Create the file in the primary
544            return self
545                .primary
546                .new_open_options()
547                .options(conf.clone())
548                .open(path);
549        }
550
551        // The file does not exist anywhere
552        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        // The original file is still open and can be accessed for all
575        // read operations
576        ReadOnly(Box<dyn VirtualFile + Send + Sync>),
577        // The copy has started but first we need to get the cursor
578        // position within the source file so that it can be restored
579        SeekingGet(Box<dyn VirtualFile + Send + Sync>),
580        // Now we have the original starting cursor location we need
581        // to move the position of the read to the start of the source
582        // file
583        SeekingSet {
584            original_offset: u64,
585            src: Box<dyn VirtualFile + Send + Sync>,
586        },
587        // We are now copying the data in parts held in the buffer piece
588        // by piece until the original file is completely copied
589        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        // After copying the file we need to seek the position back
597        // to its original location on the newly copied file
598        SeekingRestore {
599            dst: Box<dyn VirtualFile + Send + Sync>,
600        },
601        // We have copied the file and can use all the normal operations
602        Copied(Box<dyn VirtualFile + Send + Sync>),
603        // An error occurred during the copy operation and we are now in a
604        // failed state, after the error is consumed it will reset back
605        // to the original file
606        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            // Enter a loop until we go pending
642            let mut again = true;
643            while again {
644                again = false;
645
646                // The state machine is updated during the poll operation
647                replace_with_or_abort(&mut self.state, |state| match state {
648                    // We record the current position of the file so that it can be
649                    // restored after the copy-on-write is finished
650                    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                    // We complete the seek operation to the start of the source file
670                    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                                // Remove the whiteout, create the parent structure and open
677                                // the new file on the primary
678                                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                    // We are now copying all the data on blocks
723                    CowState::Copying {
724                        mut src,
725                        mut dst,
726                        mut buf,
727                        mut buf_pos,
728                        original_offset,
729                    } => {
730                        loop {
731                            // We are either copying more data from the source
732                            // or we are copying the data to the destination
733                            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                                            // When we append then we leave the cursor at the
756                                            // end of the file
757                                            return CowState::Copied(dst);
758                                        } else {
759                                            // No more data exists to be read so we now move on to
760                                            // restoring the cursor back to the original position
761                                            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                                        // There is more data to be processed
771                                        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                    // Now once the restoration of the seek position completes we set the copied state
790                    CowState::SeekingRestore { mut dst } => {
791                        match Pin::new(dst.as_mut()).poll_complete(cx) {
792                            Poll::Ready(_) => {
793                                // If we have changed the length then set it
794                                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            // Determine what response to give based off the state, when an error occurs
807            // this will be returned and the copy-on-write will be reset
808            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                    // in the scenario where the length is set but the file is not
880                    // polled then we need to make sure we create a file properly
881                    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            // Create the whiteout file in the primary
904            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            // Attempt to remove it from the primary first
910            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            // The file may actually be read-only and not support flush operations
986            // at all, and there's nothing to flush in read-only state anyway.
987            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            // Same deal as flush above
1003            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    // HACK: We shouldn't really be ignoring FsError::BaseNotDirectory, but
1127    // it's needed because the mem_fs::FileSystem doesn't return
1128    // FsError::EntryNotFound when an intermediate directory doesn't exist
1129    // (i.e. the "/path/to" in "/path/to/file.txt").
1130    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        // Delete a folder on the primary filesystem
1203        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        // Directory on the primary fs isn't empty
1212        assert_eq!(
1213            overlay.remove_dir(second).unwrap_err(),
1214            FsError::DirectoryNotEmpty,
1215        );
1216
1217        // Try to remove something on one of the overlay filesystems
1218        assert_eq!(overlay.remove_dir(third), Ok(()));
1219
1220        // It should no longer exist
1221        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        // Any new files will be created on the primary fs
1244        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        // You can open a file for reading and writing on the primary fs
1254        let _ = fs
1255            .new_open_options()
1256            .create(false)
1257            .write(true)
1258            .read(true)
1259            .open("/primary/write.txt")
1260            .unwrap();
1261
1262        // Files on the primary should always shadow the secondary
1263        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        // This second "secondary" filesystem should share the same folders as
1294        // the first one.
1295        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        // reading is fine
1333        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        // next we will write a new set of bytes
1338        f.set_len(0).unwrap();
1339        assert_eq!(f.write(b"Hi").await.unwrap(), 2);
1340        // Same with flushing
1341        assert_eq!(f.flush().await.unwrap(), ());
1342
1343        // if we now read it then the data should be different
1344        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        // including if we open it again
1351        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        // Now delete the file and make sure its not found
1375        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        // it should not be in the primary and nor should the secondary folder
1406        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        // Now lets check the file systems under
1442        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        // it should now exist in both the primary and secondary
1461        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        // Now create the file again after the unlink
1485        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        // The whiteout should be gone and new file exist
1497        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        // it should now exist in both the primary and secondary
1570        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        // Now delete the file and make sure its not found
1587        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    /// Make sure files that are never written are not copied to the primary,
1595    /// even when opened with write permissions.
1596    /// Regression test for https://github.com/wasmerio/wasmer/issues/5445
1597    #[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        // Primary should not have the file
1624        assert!(!ops::is_file(&fs.primary, "/secondary/file.txt"));
1625    }
1626
1627    // OLD tests that used WebcFileSystem.
1628    // Should be re-implemented with WebcVolumeFs
1629    // #[tokio::test]
1630    // async fn wasi_runner_use_case() {
1631    //     // Set up some dummy files on the host
1632    //     let temp = TempDir::new().unwrap();
1633    //     let first = temp.path().join("first");
1634    //     let file_txt = first.join("file.txt");
1635    //     let second = temp.path().join("second");
1636    //     std::fs::create_dir_all(&first).unwrap();
1637    //     std::fs::write(&file_txt, b"First!").unwrap();
1638    //     std::fs::create_dir_all(&second).unwrap();
1639    //     // configure the union FS so things are saved in memory by default
1640    //     // (initialized with a set of unix-like folders), but certain folders
1641    //     // are first to the host.
1642    //     let primary = RootFileSystemBuilder::new().build();
1643    //     let host_fs: Arc<dyn FileSystem + Send + Sync> =
1644    //         Arc::new(crate::host_fs::FileSystem::default());
1645    //     let first_dirs = [(&first, "/first"), (&second, "/second")];
1646    //     for (host, guest) in first_dirs {
1647    //         primary
1648    //             .mount(PathBuf::from(guest), &host_fs, host.clone())
1649    //             .unwrap();
1650    //     }
1651    //     // Set up the secondary file systems
1652    //     let webc = WebCOwned::parse(Bytes::from_static(PYTHON), &ParseOptions::default()).unwrap();
1653    //     let webc = WebcFileSystem::init_all(Arc::new(webc));
1654    //
1655    //     let fs = OverlayFileSystem::new(primary, [webc]);
1656    //
1657    //     // We should get all the normal directories from rootfs (primary)
1658    //     assert!(ops::is_dir(&fs, "/lib"));
1659    //     assert!(ops::is_dir(&fs, "/bin"));
1660    //     assert!(ops::is_file(&fs, "/dev/stdin"));
1661    //     assert!(ops::is_file(&fs, "/dev/stdout"));
1662    //     // We also want to see files from the WEBC volumes (secondary)
1663    //     assert!(ops::is_dir(&fs, "/lib/python3.6"));
1664    //     assert!(ops::is_file(&fs, "/lib/python3.6/collections/__init__.py"));
1665    //     #[cfg(never)]
1666    //     {
1667    //         // files on a secondary fs aren't writable
1668    //         // TODO(Michael-F-Bryan): re-enable this if/when we fix
1669    //         // open_readonly_file_hack()
1670    //         assert_eq!(
1671    //             fs.new_open_options()
1672    //                 .append(true)
1673    //                 .open("/lib/python3.6/collections/__init__.py")
1674    //                 .unwrap_err(),
1675    //             FsError::PermissionDenied,
1676    //         );
1677    //     }
1678    //     // you are allowed to create files that look like they are in a secondary
1679    //     // folder, though
1680    //     ops::touch(&fs, "/lib/python3.6/collections/something-else.py").unwrap();
1681    //     // But it'll be on the primary filesystem, not the secondary one
1682    //     assert!(ops::is_file(
1683    //         &fs.primary,
1684    //         "/lib/python3.6/collections/something-else.py"
1685    //     ));
1686    //     assert!(!ops::is_file(
1687    //         &fs.secondaries[0],
1688    //         "/lib/python3.6/collections/something-else.py"
1689    //     ));
1690    //     // You can do the same thing with folders
1691    //     fs.create_dir("/lib/python3.6/something-else".as_ref())
1692    //         .unwrap();
1693    //     assert!(ops::is_dir(&fs.primary, "/lib/python3.6/something-else"));
1694    //     assert!(!ops::is_dir(
1695    //         &fs.secondaries[0],
1696    //         "/lib/python3.6/something-else"
1697    //     ));
1698    //     // It only works when you are directly inside an existing directory
1699    //     // on the secondary filesystem, though
1700    //     assert_eq!(
1701    //         ops::touch(&fs, "/lib/python3.6/collections/this/doesnt/exist.txt").unwrap_err(),
1702    //         FsError::EntryNotFound
1703    //     );
1704    //     // you should also be able to read files mounted from the host
1705    //     assert!(ops::is_dir(&fs, "/first"));
1706    //     assert!(ops::is_file(&fs, "/first/file.txt"));
1707    //     assert_eq!(
1708    //         ops::read_to_string(&fs, "/first/file.txt").await.unwrap(),
1709    //         "First!"
1710    //     );
1711    //     // Overwriting them is fine and we'll see the changes on the host
1712    //     ops::write(&fs, "/first/file.txt", "Updated").await.unwrap();
1713    //     assert_eq!(std::fs::read_to_string(&file_txt).unwrap(), "Updated");
1714    //     // The filesystem will see changes on the host that happened after it was
1715    //     // set up
1716    //     let another = second.join("another.txt");
1717    //     std::fs::write(&another, "asdf").unwrap();
1718    //     assert_eq!(
1719    //         ops::read_to_string(&fs, "/second/another.txt")
1720    //             .await
1721    //             .unwrap(),
1722    //         "asdf"
1723    //     );
1724    // }
1725    //
1726    // #[tokio::test]
1727    // async fn absolute_and_relative_paths_are_passed_through() {
1728    //     let python = Arc::new(load_webc(PYTHON));
1729    //
1730    //     // The underlying filesystem doesn't care about absolute/relative paths
1731    //     assert_eq!(python.read_dir("/lib".as_ref()).unwrap().count(), 4);
1732    //     assert_eq!(python.read_dir("lib".as_ref()).unwrap().count(), 4);
1733    //
1734    //     // read_dir() should be passed through to the primary
1735    //     let webc_primary =
1736    //         OverlayFileSystem::new(Arc::clone(&python), [crate::EmptyFileSystem::default()]);
1737    //     assert_same_directory_contents(&python, "/lib", &webc_primary);
1738    //     assert_same_directory_contents(&python, "lib", &webc_primary);
1739    //
1740    //     // read_dir() should also be passed through to the secondary
1741    //     let webc_secondary =
1742    //         OverlayFileSystem::new(crate::EmptyFileSystem::default(), [Arc::clone(&python)]);
1743    //     assert_same_directory_contents(&python, "/lib", &webc_secondary);
1744    //     assert_same_directory_contents(&python, "lib", &webc_secondary);
1745    //
1746    //     // It should be fine to overlay the root fs on top of our webc file
1747    //     let overlay_rootfs = OverlayFileSystem::new(
1748    //         RootFileSystemBuilder::default().build(),
1749    //         [Arc::clone(&python)],
1750    //     );
1751    //     assert_same_directory_contents(&python, "/lib", &overlay_rootfs);
1752    //     assert_same_directory_contents(&python, "lib", &overlay_rootfs);
1753    // }
1754    // #[track_caller]
1755    // fn assert_same_directory_contents(
1756    //     original: &dyn FileSystem,
1757    //     path: impl AsRef<Path>,
1758    //     candidate: &dyn FileSystem,
1759    // ) {
1760    //     let path = path.as_ref();
1761    //
1762    //     let original_entries: Vec<_> = original
1763    //         .read_dir(path)
1764    //         .unwrap()
1765    //         .map(|r| r.unwrap())
1766    //         .collect();
1767    //     let candidate_entries: Vec<_> = candidate
1768    //         .read_dir(path)
1769    //         .unwrap()
1770    //         .map(|r| r.unwrap())
1771    //         .collect();
1772    //
1773    //     assert_eq!(original_entries, candidate_entries);
1774    // }
1775}