2026-02-05 11:14:01 +01:00
|
|
|
// Copyright (c) 2026 Duplicati Inc. All rights reserved.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
2026-02-06 06:12:55 +01:00
|
|
|
using System.Text.Json;
|
2026-02-05 11:14:01 +01:00
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Duplicati.Library.Interface;
|
|
|
|
|
using Duplicati.Proprietary.DiskImage.Filesystem;
|
2026-03-16 08:57:30 +01:00
|
|
|
using Duplicati.Proprietary.DiskImage.Filesystem.Fat32;
|
2026-03-05 15:30:36 +01:00
|
|
|
using Duplicati.Proprietary.DiskImage.General;
|
2026-02-05 11:14:01 +01:00
|
|
|
|
|
|
|
|
namespace Duplicati.Proprietary.DiskImage.SourceItems;
|
|
|
|
|
|
2026-02-15 12:45:59 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Represents a filesystem as a source entry for backup operations.
|
|
|
|
|
/// Acts as a container for files within a partition.
|
|
|
|
|
/// </summary>
|
2026-02-05 11:14:01 +01:00
|
|
|
internal class FilesystemSourceEntry(string parentPath, IFilesystem filesystem)
|
2026-02-12 16:54:35 +01:00
|
|
|
: DiskImageEntryBase(System.IO.Path.Combine(parentPath, $"fs_{filesystem.Type}{System.IO.Path.DirectorySeparatorChar}"))
|
2026-02-05 11:14:01 +01:00
|
|
|
{
|
2026-02-15 12:45:59 +01:00
|
|
|
/// <inheritdoc />
|
2026-02-05 11:14:01 +01:00
|
|
|
public override bool IsFolder => true;
|
|
|
|
|
|
2026-02-15 12:45:59 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the underlying filesystem instance.
|
|
|
|
|
/// </summary>
|
2026-02-09 11:36:15 +01:00
|
|
|
public IFilesystem Filesystem => filesystem;
|
|
|
|
|
|
2026-02-15 12:45:59 +01:00
|
|
|
/// <inheritdoc />
|
2026-02-05 11:14:01 +01:00
|
|
|
public override async IAsyncEnumerable<ISourceProviderEntry> Enumerate([EnumeratorCancellation] CancellationToken cancellationToken)
|
|
|
|
|
{
|
2026-02-09 11:36:15 +01:00
|
|
|
// Yield the files in the filesystem
|
2026-02-05 11:14:01 +01:00
|
|
|
await foreach (var file in filesystem.ListFilesAsync(cancellationToken))
|
|
|
|
|
{
|
|
|
|
|
yield return new FileSourceEntry(this.Path, filesystem, file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 11:36:15 +01:00
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the filesystem geometry metadata for this filesystem.
|
|
|
|
|
/// </summary>
|
2026-02-15 12:45:59 +01:00
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
|
|
|
/// <returns>The filesystem geometry metadata, or null if not available.</returns>
|
2026-02-09 11:36:15 +01:00
|
|
|
public async Task<FilesystemGeometry?> GetFilesystemGeometry(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var fsMetadata = await filesystem.GetFilesystemMetadataAsync(cancellationToken);
|
|
|
|
|
int blockSize = 1024 * 1024; // Default 1MB blocks
|
|
|
|
|
|
|
|
|
|
if (fsMetadata is UnkownFilesystemMetadata unknownMeta)
|
|
|
|
|
{
|
|
|
|
|
blockSize = unknownMeta.BlockSize;
|
|
|
|
|
}
|
2026-03-12 15:07:08 +01:00
|
|
|
else if (fsMetadata is Fat32FilesystemMetadata fat32Meta)
|
|
|
|
|
{
|
|
|
|
|
blockSize = fat32Meta.BlockSize;
|
|
|
|
|
}
|
2026-02-09 11:36:15 +01:00
|
|
|
|
|
|
|
|
return new FilesystemGeometry
|
|
|
|
|
{
|
|
|
|
|
Type = filesystem.Type,
|
|
|
|
|
PartitionNumber = filesystem.Partition.PartitionNumber,
|
|
|
|
|
PartitionStartOffset = filesystem.Partition.StartOffset,
|
|
|
|
|
BlockSize = blockSize,
|
|
|
|
|
Metadata = fsMetadata != null ? JsonSerializer.Serialize(fsMetadata) : null
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 12:45:59 +01:00
|
|
|
/// <inheritdoc />
|
2026-02-06 06:12:55 +01:00
|
|
|
public override async Task<Dictionary<string, string?>> GetMinorMetadata(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var metadata = await base.GetMinorMetadata(cancellationToken);
|
|
|
|
|
|
|
|
|
|
// Add filesystem-level metadata
|
|
|
|
|
metadata["diskimage:Type"] = "filesystem";
|
|
|
|
|
metadata["filesystem:Type"] = filesystem.Type.ToString();
|
|
|
|
|
metadata["filesystem:PartitionNumber"] = filesystem.Partition.PartitionNumber.ToString();
|
|
|
|
|
metadata["filesystem:PartitionStartOffset"] = filesystem.Partition.StartOffset.ToString();
|
|
|
|
|
|
|
|
|
|
// Get filesystem-specific metadata
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var fsMetadata = await filesystem.GetFilesystemMetadataAsync(cancellationToken);
|
|
|
|
|
if (fsMetadata != null)
|
|
|
|
|
{
|
|
|
|
|
metadata["filesystem:Metadata"] = JsonSerializer.Serialize(fsMetadata);
|
|
|
|
|
|
|
|
|
|
// For UnknownFilesystem, extract block size
|
|
|
|
|
if (fsMetadata is UnkownFilesystemMetadata unknownMeta)
|
|
|
|
|
{
|
|
|
|
|
metadata["filesystem:BlockSize"] = unknownMeta.BlockSize.ToString();
|
|
|
|
|
}
|
2026-03-12 15:07:08 +01:00
|
|
|
// For FAT32, extract cluster information
|
|
|
|
|
else if (fsMetadata is Fat32FilesystemMetadata fat32Meta)
|
|
|
|
|
{
|
|
|
|
|
metadata["filesystem:BlockSize"] = fat32Meta.BlockSize.ToString();
|
|
|
|
|
metadata["filesystem:ClusterSize"] = fat32Meta.ClusterSize.ToString();
|
|
|
|
|
metadata["filesystem:TotalClusters"] = fat32Meta.TotalClusters.ToString();
|
|
|
|
|
metadata["filesystem:AllocatedClusters"] = fat32Meta.AllocatedClusters.ToString();
|
|
|
|
|
metadata["filesystem:FreeClusters"] = fat32Meta.FreeClusters.ToString();
|
|
|
|
|
}
|
2026-02-06 06:12:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Metadata not available
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return metadata;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 11:14:01 +01:00
|
|
|
}
|