using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Tesses.VirtualFilesystem.Filesystems { public class OverlayFilesystem : AsyncFileSystem { IVirtualFilesystem lower; IVirtualFilesystem upper; public OverlayFilesystem(IVirtualFilesystem lower,IVirtualFilesystem upper) { this.lower = lower; this.upper = upper; } public override async Task CreateDirectoryAsync(UnixPath directory, CancellationToken token = default) { await upper.CreateDirectoryAsync(directory,token); } public override async Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default) { if(await upper.DirectoryExistsAsync(path,token)) await upper.DeleteDirectoryAsync(path,token); } public override async Task DeleteFileAsync(UnixPath path, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token)) await upper.DeleteFileAsync(path,token); } public override async Task DirectoryExistsAsync(UnixPath path, CancellationToken token = default) { return await lower.DirectoryExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token); } public override async Task SymlinkExistsAsync(UnixPath file, CancellationToken token = default) { return await SymlinkExists2Async(lower,file,token) || await SymlinkExists2Async(upper,file,token); } public override bool CanHandleSymlinks(UnixPath path) { if(upper.FileExists(path) || upper.DirectoryExists(path) || (upper.CanHandleSymlinks(path) && upper.SymlinkExists(path))) { return upper.CanHandleSymlinks(path); } return lower.CanHandleSymlinks(path); } private async Task SymlinkExists2Async(IVirtualFilesystem fs,UnixPath p,CancellationToken token=default) { return fs.CanHandleSymlinks(p) && await fs.SymlinkExistsAsync(p,token); } public override async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default) { if(upper.CanHandleSymlinks(dest)) { await upper.CreateSymlinkAsync(src,dest,token); } } public override async Task CreateHardlinkAsync(UnixPath src,UnixPath dest,CancellationToken token=default) { if(await upper.FileExistsAsync(src,token)) { await upper.CreateHardlinkAsync(src,dest,token); } else { if(await upper.FileExistsAsync(dest,token) || !await lower.FileExistsAsync(src,token)) return; if(dest.ParentIsRoot) { await upper.CreateDirectoryAsync(dest.Parent); } using(var srcS = await lower.OpenAsync(src,FileMode.Open,FileAccess.Read,FileShare.Read)) { using(var destS = await upper.OpenAsync(dest,FileMode.Create,FileAccess.Write,FileShare.Read)) { await srcS.CopyToAsync(destS); } } } } public override async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token)) { await upper.SetAttributesAsync(path, attributes, token); } else { await lower.SetAttributesAsync(path, attributes,token); } } public override bool CanWatch(UnixPath path) { if(upper.DirectoryExists(path) && lower.DirectoryExists(path)) { return upper.CanWatch(path) || lower.CanWatch(path); } else if(upper.DirectoryExists(path)) { return upper.CanWatch(path); } else if(lower.DirectoryExists(path)) { return lower.CanWatch(path); } else { return false; } } public override IVirtualWatcher WatchDirectory(UnixPath dir) { if(upper.DirectoryExists(dir) && lower.DirectoryExists(dir)) { return new DoubleWatcher(lower.WatchDirectory(dir),upper.WatchDirectory(dir)); } else if(upper.DirectoryExists(dir)) { return upper.WatchDirectory(dir); } else if(lower.DirectoryExists(dir)) { return lower.WatchDirectory(dir); } else { return null; } } public override async Task ReadLinkAsync(UnixPath file, CancellationToken token = default) { if(await SymlinkExists2Async(upper,file,token)) { return await upper.ReadLinkAsync(file,token); } return await lower.ReadLinkAsync(file,token); } public override async IAsyncEnumerable EnumerateFileSystemEntriesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default) { List entries = new List(); if(await lower.DirectoryExistsAsync(path,token)) await foreach(var item in lower.EnumerateFileSystemEntriesAsync(path,token)) { entries.Add(item); yield return item; } if(await upper.DirectoryExistsAsync(path,token)) await foreach(var item in upper.EnumerateFileSystemEntriesAsync(path,token)) { bool canYield=true; foreach(var entry in entries) { if(entry.MyPathEquals(item)){canYield=false; break;}; } if(canYield) yield return item; } } public override async Task FileExistsAsync(UnixPath path, CancellationToken token = default) { return await lower.FileExistsAsync(path,token) || await upper.FileExistsAsync(path,token); } public override async Task GetCreationTimeAsync(UnixPath path, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token)) { return await upper.GetCreationTimeAsync(path,token); } return await lower.GetCreationTimeAsync(path,token); } public override async Task GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token)) { return await upper.GetLastAccessTimeAsync(path,token); } return await lower.GetLastAccessTimeAsync(path,token); } public override async Task GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token)) { return await upper.GetLastWriteTimeAsync(path,token); } return await lower.GetLastWriteTimeAsync(path,token); } public override async Task MoveDirectoryAsync(UnixPath src, UnixPath dest) { if(!dest.IsRoot) { await upper.CreateDirectoryAsync(dest); } await foreach(var dir in this.EnumerateDirectoriesAsync(src)) { await MoveDirectoryAsync(dir,dest / dir.Name); } await foreach(var file in this.EnumerateFilesAsync(src)) { await MoveFileAsync(file,dest / file.Name); } if(await upper.DirectoryExistsAsync(src)) { await upper.DeleteDirectoryAsync(src); } } public override async Task MoveFileAsync(UnixPath src, UnixPath dest) { if(!dest.ParentIsRoot) { await upper.CreateDirectoryAsync(dest.Parent); } if(await upper.FileExistsAsync(src)) { await upper.MoveFileAsync(src,dest); } else { using(var srcS = await lower.OpenAsync(src,FileMode.Open,FileAccess.Read,FileShare.Read)) { using(var destS = await upper.OpenAsync(dest,FileMode.Create,FileAccess.ReadWrite,FileShare.Read)) { await srcS.CopyToAsync(destS); } } } } public override async Task OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token)) { return await upper.OpenAsync(path,mode,access,share,token); } if(await lower.FileExistsAsync(path,token)) { if(access != FileAccess.Read && mode != FileMode.CreateNew && mode != FileMode.Truncate && mode != FileMode.Create) { var strm = await lower.OpenAsync(path,mode,access,share,token); return new CowStream(strm,path,lower,upper,mode,access,share); } return await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read); } return await upper.OpenAsync(path,mode,access,share,token); } public override async Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path) || await SymlinkExists2Async(upper,path,token)) { await upper.SetCreationTimeAsync(path,time,token); } else if(await lower.FileExistsAsync(path,token) || await lower.DirectoryExistsAsync(path) || await SymlinkExists2Async(lower,path,token)) { if(await lower.FileExistsAsync(path,token)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); using(var src = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token)) { using(var dest = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Read)) { await src.CopyToAsync(dest); } } await upper.SetCreationTimeAsync(path,time,token); } else if(await lower.DirectoryExistsAsync(path,token)) { await upper.CreateDirectoryAsync(path,token); await upper.SetCreationTimeAsync(path,time,token); } else if(await SymlinkExists2Async(lower,path,token)) { if(upper.CanHandleSymlinks(path)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); await upper.CreateSymlinkAsync(await lower.ReadLinkAsync(path,token),path,token); await upper.SetCreationTimeAsync(path,time,token); } } } else { await upper.SetCreationTimeAsync(path,time,token); } } public override async Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path) || await SymlinkExists2Async(upper,path,token)) { await upper.SetLastAccessTimeAsync(path,time,token); } else if(await lower.FileExistsAsync(path,token) || await lower.DirectoryExistsAsync(path) || await SymlinkExists2Async(lower,path,token)) { if(await lower.FileExistsAsync(path,token)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); using(var src = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token)) { using(var dest = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Read)) { await src.CopyToAsync(dest); } } await upper.SetLastAccessTimeAsync(path,time,token); } else if(await lower.DirectoryExistsAsync(path,token)) { await upper.CreateDirectoryAsync(path,token); await upper.SetLastAccessTimeAsync(path,time,token); } else if(await SymlinkExists2Async(lower,path,token)) { if(upper.CanHandleSymlinks(path)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); await upper.CreateSymlinkAsync(await lower.ReadLinkAsync(path,token),path,token); await upper.SetLastAccessTimeAsync(path,time,token); } } } else { await upper.SetLastAccessTimeAsync(path,time,token); } } public override async Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default) { if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path) || await SymlinkExists2Async(upper,path,token)) { await upper.SetLastWriteTimeAsync(path,time,token); } else if(await lower.FileExistsAsync(path,token) || await lower.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(lower,path,token)) { if(await lower.FileExistsAsync(path,token)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); using(var src = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token)) { using(var dest = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Read)) { await src.CopyToAsync(dest); } } await upper.SetLastWriteTimeAsync(path,time,token); } else if(await lower.DirectoryExistsAsync(path,token)) { await upper.CreateDirectoryAsync(path,token); await upper.SetLastWriteTimeAsync(path,time,token); } else if(await SymlinkExists2Async(lower,path,token)) { if(upper.CanHandleSymlinks(path)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); await upper.CreateSymlinkAsync(await lower.ReadLinkAsync(path,token),path,token); await upper.SetLastWriteTimeAsync(path,time,token); } } }else { await upper.SetLastWriteTimeAsync(path,time,token); } } private class CowStream : Stream { private FileMode mode; private FileAccess access; private FileShare share; private Stream strm; private UnixPath path; private IVirtualFilesystem upper; private IVirtualFilesystem lower; private bool inLower=true; public CowStream(Stream strm, UnixPath path,IVirtualFilesystem lower, IVirtualFilesystem upper,FileMode mode,FileAccess access,FileShare share) { this.lower=lower; this.strm = strm; this.path = path; this.upper = upper; this.mode = mode; this.access = access; this.share = share; } public override bool CanRead => strm.CanRead; public override bool CanSeek => strm.CanSeek; public override bool CanWrite => strm.CanWrite; public override long Length => strm.Length; public override long Position { get => strm.Position; set => strm.Position = value;} public override void Flush() { CowIt(); strm.Flush(); } public override int Read(byte[] buffer, int offset, int count) { return strm.Read(buffer,offset,count); } public override long Seek(long offset, SeekOrigin origin) { return strm.Seek(offset,origin); } public override void SetLength(long value) { CowIt(); strm.SetLength(value); } public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return await strm.ReadAsync(buffer, offset,count); //return base.ReadAsync(buffer, offset, count, cancellationToken); } public override int ReadByte() { return strm.ReadByte(); } public override void WriteByte(byte value) { CowIt(); strm.WriteByte(value); } public override void Write(byte[] buffer, int offset, int count) { CowIt(); strm.Write(buffer,offset,count); } public override async Task FlushAsync(CancellationToken cancellationToken) { await CowItAsync(cancellationToken); await strm.FlushAsync(cancellationToken); } public override async Task WriteAsync(byte[] buffer, int offset, int count,CancellationToken token) { await CowItAsync(token); await strm.WriteAsync(buffer,offset,count,token); } public override void Close() { strm.Close(); } private async Task CowItAsync(CancellationToken token) { if(inLower) { var pos = strm.Position; strm.Dispose(); using(var srcStrm = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token)) { if(!path.ParentIsRoot) await upper.CreateDirectoryAsync(path.Parent); using(var destStrm = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Write,token)) await srcStrm.CopyToAsync(destStrm,4096,token); } strm =await upper.OpenAsync(path,mode,access,share,token); strm.Position = pos; inLower=false; } } private void CowIt() { if(inLower) { var pos = strm.Position; strm.Dispose(); using(var srcStrm = lower.Open(path,FileMode.Open,FileAccess.Read,FileShare.Read)) { if(!path.ParentIsRoot) upper.CreateDirectory(path.Parent); using(var destStrm = upper.Open(path,FileMode.Create,FileAccess.Write,FileShare.Write)) srcStrm.CopyTo(destStrm); } strm =upper.Open(path,mode,access,share); strm.Position = pos; inLower=false; } } } } }