528 lines
21 KiB
C#
528 lines
21 KiB
C#
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<bool> DirectoryExistsAsync(UnixPath path, CancellationToken token = default)
|
|
{
|
|
return await lower.DirectoryExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token);
|
|
}
|
|
public override async Task<bool> 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<bool> 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<UnixPath> 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<UnixPath> EnumerateFileSystemEntriesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default)
|
|
{
|
|
List<UnixPath> entries = new List<UnixPath>();
|
|
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<bool> FileExistsAsync(UnixPath path, CancellationToken token = default)
|
|
{
|
|
return await lower.FileExistsAsync(path,token) || await upper.FileExistsAsync(path,token);
|
|
|
|
}
|
|
|
|
public override async Task<DateTime> 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<DateTime> 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<DateTime> 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<Stream> 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<int> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|