Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/ByteSync.Client/Business/Inventories/FileSystemEntryKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ByteSync.Business.Inventories;

public enum FileSystemEntryKind
{
Unknown = 0,
RegularFile = 1,
Directory = 2,
BlockDevice = 3,
CharacterDevice = 4,
Fifo = 5,
Socket = 6,
Symlink = 7
}
511 changes: 256 additions & 255 deletions src/ByteSync.Client/ByteSync.Client.csproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using ByteSync.Business.Inventories;

namespace ByteSync.Interfaces.Controls.Inventories;

public interface IPosixFileTypeClassifier
{
FileSystemEntryKind ClassifyPosixEntry(string path);
}
39 changes: 36 additions & 3 deletions src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public InventoryBuilder(SessionMember sessionMember, DataNode dataNode, SessionS
IInventoryFileAnalyzer inventoryFileAnalyzer,
IInventorySaver inventorySaver,
IInventoryIndexer inventoryIndexer,
IFileSystemInspector? fileSystemInspector = null)
IFileSystemInspector? fileSystemInspector = null,
IPosixFileTypeClassifier? posixFileTypeClassifier = null)
{
_logger = logger;

Expand All @@ -45,6 +46,7 @@ public InventoryBuilder(SessionMember sessionMember, DataNode dataNode, SessionS

InventoryFileAnalyzer = inventoryFileAnalyzer;
FileSystemInspector = fileSystemInspector ?? new FileSystemInspector();
PosixFileTypeClassifier = posixFileTypeClassifier ?? new PosixFileTypeClassifier();
}

private Inventory InstantiateInventory()
Expand Down Expand Up @@ -87,6 +89,8 @@ private Inventory InstantiateInventory()

private IFileSystemInspector FileSystemInspector { get; }

private IPosixFileTypeClassifier PosixFileTypeClassifier { get; }

private bool IgnoreHidden
{
get { return SessionSettings is { ExcludeHiddenFiles: true }; }
Expand Down Expand Up @@ -305,7 +309,7 @@ private void AddInaccessibleDirectoryAndLog(InventoryPart inventoryPart, Directo
AddFileSystemDescription(inventoryPart, subDirectoryDescription);
_logger.LogWarning(ex, message, directoryInfo.FullName);
}

private bool IsRootPath(InventoryPart inventoryPart, FileSystemInfo fileSystemInfo)
{
var rootPath = NormalizePath(inventoryPart.RootPath);
Expand Down Expand Up @@ -359,6 +363,14 @@ private bool ShouldIgnoreHiddenDirectory(DirectoryInfo directoryInfo)
{
var isRoot = IsRootPath(inventoryPart, fileInfo);

var entryKind = PosixFileTypeClassifier.ClassifyPosixEntry(fileInfo.FullName);
if (IsPosixSpecialFile(entryKind))
{
AddPosixSpecialFileAndLog(inventoryPart, fileInfo, entryKind);

return;
}

if (!isRoot && ShouldIgnoreHiddenFile(fileInfo))
{
return;
Expand Down Expand Up @@ -527,6 +539,27 @@ private void AddInaccessibleFileAndLog(InventoryPart inventoryPart, FileInfo fil
_logger.LogWarning(ex, message, fileInfo.FullName);
}

private void AddPosixSpecialFileAndLog(InventoryPart inventoryPart, FileInfo fileInfo, FileSystemEntryKind entryKind)
{
inventoryPart.IsIncompleteDueToAccess = true;
var relativePath = BuildRelativePath(inventoryPart, fileInfo);
var fileDescription = new FileDescription(inventoryPart, relativePath)
{
IsAccessible = false
};
AddFileSystemDescription(inventoryPart, fileDescription);
_logger.LogWarning("File {File} is a POSIX special file ({EntryKind}) and will be skipped", fileInfo.FullName, entryKind);
}

private static bool IsPosixSpecialFile(FileSystemEntryKind entryKind)
{
return entryKind is
FileSystemEntryKind.BlockDevice or
FileSystemEntryKind.CharacterDevice or
FileSystemEntryKind.Fifo or
FileSystemEntryKind.Socket;
}

private string BuildRelativePath(InventoryPart inventoryPart, FileInfo fileInfo)
{
if (inventoryPart.InventoryPartType != FileSystemTypes.Directory)
Expand Down Expand Up @@ -584,4 +617,4 @@ private void AddFileSystemDescription(InventoryPart inventoryPart, FileSystemDes
}
}
}
}
}
150 changes: 150 additions & 0 deletions src/ByteSync.Client/Services/Inventories/PosixFileTypeClassifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using ByteSync.Business.Inventories;
using ByteSync.Interfaces.Controls.Inventories;
using Mono.Unix;
using Mono.Unix.Native;

namespace ByteSync.Services.Inventories;

public class PosixFileTypeClassifier : IPosixFileTypeClassifier
{
private readonly Func<string, UnixFileInfo> _unixFileInfoFactory;
private readonly Func<string, (bool Success, FilePermissions Type)> _tryGetMode;

public PosixFileTypeClassifier()
: this(path => new UnixFileInfo(path), TryGetModeDefault)
{
}

public PosixFileTypeClassifier(Func<string, UnixFileInfo> unixFileInfoFactory)
: this(unixFileInfoFactory, TryGetModeDefault)
{
}

public PosixFileTypeClassifier(Func<string, UnixFileInfo> unixFileInfoFactory,
Func<string, (bool Success, FilePermissions Type)> tryGetMode)
{
_unixFileInfoFactory = unixFileInfoFactory;
_tryGetMode = tryGetMode;
}

public FileSystemEntryKind ClassifyPosixEntry(string path)
{
if (OperatingSystem.IsWindows())
{
return FileSystemEntryKind.Unknown;
}

try
{
var modeResult = _tryGetMode(path);
if (!modeResult.Success)
{
return FileSystemEntryKind.Unknown;
}

var entryKind = MapFilePermissions(modeResult.Type);
if (entryKind == FileSystemEntryKind.RegularFile || entryKind == FileSystemEntryKind.Unknown)
{
var unixKind = TryClassifyWithUnixFileInfo(path);
if (unixKind != FileSystemEntryKind.Unknown)
{
return unixKind;
}
}

return entryKind;
}
catch (DllNotFoundException)
{
return FileSystemEntryKind.Unknown;
}
catch (EntryPointNotFoundException)
{
return FileSystemEntryKind.Unknown;
}
catch (PlatformNotSupportedException)
{
return FileSystemEntryKind.Unknown;
}
}

private static (bool Success, FilePermissions Type) TryGetModeDefault(string path)
{
if (Syscall.lstat(path, out var stat) != 0)
{
if (Syscall.stat(path, out stat) != 0)
{
return (false, 0);
}
}

var mode = stat.st_mode;
var type = mode & FilePermissions.S_IFMT;

return (true, type);
}

private static FileSystemEntryKind MapFilePermissions(FilePermissions type)
{
if (type == FilePermissions.S_IFREG)
{
return FileSystemEntryKind.RegularFile;
}

if (type == FilePermissions.S_IFDIR)
{
return FileSystemEntryKind.Directory;
}

if (type == FilePermissions.S_IFBLK)
{
return FileSystemEntryKind.BlockDevice;
}

if (type == FilePermissions.S_IFCHR)
{
return FileSystemEntryKind.CharacterDevice;
}

if (type == FilePermissions.S_IFIFO)
{
return FileSystemEntryKind.Fifo;
}

if (type == FilePermissions.S_IFSOCK)
{
return FileSystemEntryKind.Socket;
}

if (type == FilePermissions.S_IFLNK)
{
return FileSystemEntryKind.Symlink;
}

return FileSystemEntryKind.Unknown;
}

private FileSystemEntryKind TryClassifyWithUnixFileInfo(string path)
{
try
{
var info = _unixFileInfoFactory(path);

return info.FileType switch
{
FileTypes.BlockDevice => FileSystemEntryKind.BlockDevice,
FileTypes.CharacterDevice => FileSystemEntryKind.CharacterDevice,
FileTypes.Fifo => FileSystemEntryKind.Fifo,
FileTypes.Socket => FileSystemEntryKind.Socket,
FileTypes.Directory => FileSystemEntryKind.Directory,
FileTypes.RegularFile => FileSystemEntryKind.RegularFile,
FileTypes.SymbolicLink => FileSystemEntryKind.Symlink,
_ => FileSystemEntryKind.Unknown
};
}
catch (Exception)
{
return FileSystemEntryKind.Unknown;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using Autofac;
using ByteSync.Business;
using ByteSync.Business.DataNodes;
Expand Down Expand Up @@ -716,6 +717,60 @@ public async Task Test_ReparsePoint()
inventory.InventoryParts[0].DirectoryDescriptions.Count.Should().Be(1);
inventory.InventoryParts[0].FileDescriptions.Count.Should().Be(2);
}

[Test]
[Platform(Include = "Linux,MacOsX")]
public async Task Test_PosixFifo_IsSkipped()
{
InventoryBuilder inventoryBuilder;
Inventory inventory;

DirectoryInfo sourceA, unzipDir;
FileInfo fileInfo;

sourceA = new DirectoryInfo(IOUtils.Combine(_testDirectoryService.TestDirectory.FullName, "sourceA"));
sourceA.Create();
fileInfo = new FileInfo(_testDirectoryService.CreateFileInDirectory(sourceA.FullName, "fileA.txt", "fileAContent").FullName);

var fifoPath = IOUtils.Combine(sourceA.FullName, "pipeA");
CreateFifo(fifoPath);

var classifier = new PosixFileTypeClassifier();
var fifoKind = classifier.ClassifyPosixEntry(fifoPath);
if (fifoKind == FileSystemEntryKind.Unknown)
{
Assert.Ignore($"POSIX classification returned Unknown for '{fifoPath}'.");
}

var inventoryAFilePath = IOUtils.Combine(_testDirectoryService.TestDirectory.FullName, $"inventoryA.zip");

var sessionSettings = SessionSettings.BuildDefault();
var osPlatform = OperatingSystem.IsMacOS() ? OSPlatforms.MacOs : OSPlatforms.Linux;

inventoryBuilder = BuildInventoryBuilder(sessionSettings, null, null, osPlatform);
inventoryBuilder.AddInventoryPart(sourceA.FullName);
await inventoryBuilder.BuildBaseInventoryAsync(inventoryAFilePath);

File.Exists(inventoryAFilePath).Should().BeTrue();

unzipDir = new DirectoryInfo(IOUtils.Combine(_testDirectoryService.TestDirectory.FullName, "unzip"));
unzipDir.Create();

var fastZip = new FastZip();
fastZip.ExtractZip(inventoryAFilePath, unzipDir.FullName, null);

unzipDir.GetFiles("*", SearchOption.AllDirectories).Length.Should().Be(1);
File.Exists(IOUtils.Combine(unzipDir.FullName, $"inventory.json")).Should().BeTrue();

inventory = inventoryBuilder.Inventory!;
inventory.InventoryParts.Count.Should().Be(1);
inventory.InventoryParts[0].DirectoryDescriptions.Count.Should().Be(0);
inventory.InventoryParts[0].FileDescriptions.Count.Should().Be(2);

var fifoDescription = inventory.InventoryParts[0].FileDescriptions.Single(fd => fd.Name.Equals("pipeA"));
fifoDescription.IsAccessible.Should().BeFalse();
inventory.InventoryParts[0].IsIncompleteDueToAccess.Should().BeTrue();
}

[Test]
public async Task Test_GetBuildingStageData()
Expand Down Expand Up @@ -873,4 +928,21 @@ private InventoryBuilder BuildInventoryBuilder(SessionSettings? sessionSettings
saver,
new InventoryIndexer());
}

private static void CreateFifo(string path)
{
var startInfo = new ProcessStartInfo("mkfifo", path)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};

using var process = Process.Start(startInfo);
process.Should().NotBeNull();
process!.WaitForExit();

var error = process.StandardError.ReadToEnd();
process.ExitCode.Should().Be(0, error);
}
}
Loading
Loading