// Copyright (c) 2026 Duplicati Inc. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Proprietary.DiskImage.Filesystem; using Duplicati.Proprietary.DiskImage.Filesystem.Fat32; using Duplicati.Proprietary.DiskImage.Filesystem.Ntfs; using Duplicati.Proprietary.DiskImage.General; namespace Duplicati.Proprietary.DiskImage.SourceItems; /// /// Represents a filesystem as a source entry for backup operations. /// Acts as a container for files within a partition. /// internal class FilesystemSourceEntry(string parentPath, IFilesystem filesystem) : DiskImageEntryBase(System.IO.Path.Combine(parentPath, $"fs_{filesystem.Type}{System.IO.Path.DirectorySeparatorChar}")) { /// public override bool IsFolder => true; /// /// Gets the underlying filesystem instance. /// public IFilesystem Filesystem => filesystem; /// public override async IAsyncEnumerable Enumerate([EnumeratorCancellation] CancellationToken cancellationToken) { // Yield the files in the filesystem await foreach (var file in filesystem.ListFilesAsync(cancellationToken)) { yield return new FileSourceEntry(this.Path, filesystem, file); } } /// /// Gets the filesystem geometry metadata for this filesystem. /// /// Cancellation token. /// The filesystem geometry metadata, or null if not available. public async Task GetFilesystemGeometry(CancellationToken cancellationToken) { var fsMetadata = await filesystem.GetFilesystemMetadataAsync(cancellationToken); int blockSize = 1024 * 1024; // Default 1MB blocks if (fsMetadata is UnkownFilesystemMetadata unknownMeta) blockSize = unknownMeta.BlockSize; else if (fsMetadata is Fat32FilesystemMetadata fat32Meta) blockSize = fat32Meta.BlockSize; else if (fsMetadata is NtfsFilesystemMetadata ntfsMeta) blockSize = ntfsMeta.BlockSize; return new FilesystemGeometry { Type = filesystem.Type, PartitionNumber = filesystem.Partition.PartitionNumber, PartitionStartOffset = filesystem.Partition.StartOffset, BlockSize = blockSize, Metadata = fsMetadata != null ? JsonSerializer.Serialize(fsMetadata) : null }; } /// public override async Task> 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(); } // 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(); } // For NTFS, extract cluster and MFT information else if (fsMetadata is NtfsFilesystemMetadata ntfsMeta) { metadata["filesystem:BlockSize"] = ntfsMeta.BlockSize.ToString(); metadata["filesystem:ClusterSize"] = ntfsMeta.ClusterSize.ToString(); metadata["filesystem:TotalClusters"] = ntfsMeta.TotalClusters.ToString(); metadata["filesystem:AllocatedClusters"] = ntfsMeta.AllocatedClusters.ToString(); metadata["filesystem:FreeClusters"] = ntfsMeta.FreeClusters.ToString(); metadata["filesystem:MftRecordSize"] = ntfsMeta.MftRecordSize.ToString(); metadata["filesystem:HasJournal"] = ntfsMeta.HasJournal.ToString(); } } } catch { // Metadata not available } return metadata; } }