From 4267fffa95e6e22fccd25bc225668d9ce6e8c8d5 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Fri, 13 Mar 2026 12:48:21 +0100 Subject: [PATCH] Have auto detection of file system on partition when the type cannot be determined from the partition table --- proprietary/DiskImage/Filesystem/Fat32.cs | 31 ++++++++++++++++ proprietary/DiskImage/Partition/GPT.cs | 43 +++++++++-------------- proprietary/DiskImage/Partition/MBR.cs | 24 ++++++++++--- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/proprietary/DiskImage/Filesystem/Fat32.cs b/proprietary/DiskImage/Filesystem/Fat32.cs index a306ae492..860347574 100644 --- a/proprietary/DiskImage/Filesystem/Fat32.cs +++ b/proprietary/DiskImage/Filesystem/Fat32.cs @@ -270,6 +270,37 @@ internal class Fat32Filesystem : IFilesystem return metadata; } + /// + /// Tries to detect whether a given partition has a FAT32 file system. + /// + /// The raw disk to check for the filesystem. + /// The offset on the disk where the partition starts. + /// The size of the partition. + /// Cancellation token. + /// A task that resolves to true if a valid FAT32 filesystem is detected, otherwise false. + public static async Task DetectAsync(IRawDisk disk, long offset, int size, CancellationToken cancellationToken) + { + try + { + var bootSectorData = new byte[512]; + using var stream = await disk.ReadBytesAsync(offset, size, cancellationToken); + var bytesRead = await stream.ReadAsync(bootSectorData, cancellationToken); + if (bytesRead < 512) + return false; + + // Try FAT32: the constructor validates the boot sector signature and + // the "FAT32" filesystem-type string at offset 0x52. + _ = new Fat32BootSector(bootSectorData); + return true; + } + catch + { + // Unable to read the partition + } + + return false; + } + /// public void Dispose() { diff --git a/proprietary/DiskImage/Partition/GPT.cs b/proprietary/DiskImage/Partition/GPT.cs index a0ac98cf9..9d46bb836 100644 --- a/proprietary/DiskImage/Partition/GPT.cs +++ b/proprietary/DiskImage/Partition/GPT.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using Duplicati.Library.Utility; using Duplicati.Proprietary.DiskImage.Disk; +using Duplicati.Proprietary.DiskImage.Filesystem; using Duplicati.Proprietary.DiskImage.General; namespace Duplicati.Proprietary.DiskImage.Partition; @@ -391,7 +393,7 @@ internal class GPT : IPartitionTable PartitionType partitionType = DeterminePartitionType(typeGuid); // Determine filesystem type based on partition name and known patterns - FileSystemType fsType = DetermineFilesystemType(name, typeGuid); + FileSystemType fsType = DetermineFilesystemType(startOffset, (int)size, typeGuid.ToString(), CancellationToken.None); return new BasePartition { @@ -421,40 +423,27 @@ internal class GPT : IPartitionTable } /// - /// Determines the filesystem type based on the partition name and type GUID. - /// Uses heuristics based on common naming patterns and known GUIDs. + /// Determines the filesystem type based on the partition type GUID and partition data. /// /// The partition name. /// The partition type GUID. /// The corresponding . - private static FileSystemType DetermineFilesystemType(string name, Guid typeGuid) + private FileSystemType DetermineFilesystemType(long offset, int size, string typeGuid, CancellationToken cancellationToken) { - if (!string.IsNullOrEmpty(name)) + // First, we try to look at the GUID + if (typeGuid == "48465300-0000-11AA-AA11-00306543ECAC") + return FileSystemType.HFSPlus; + + if (typeGuid == "7C3457EF-0000-11AA-AA11-00306543ECAC") + return FileSystemType.APFS; + + if (m_rawDisk is not null) { - var upperName = name.ToUpperInvariant(); - if (upperName.Contains("NTFS")) return FileSystemType.NTFS; - if (upperName.Contains("FAT32")) return FileSystemType.FAT32; - if (upperName.Contains("FAT16")) return FileSystemType.FAT16; - if (upperName.Contains("FAT12")) return FileSystemType.FAT12; - if (upperName.Contains("EXFAT")) return FileSystemType.ExFAT; - if (upperName.Contains("HFS")) return FileSystemType.HFSPlus; - if (upperName.Contains("APFS")) return FileSystemType.APFS; - if (upperName.Contains("EXT4")) return FileSystemType.Ext4; - if (upperName.Contains("EXT3")) return FileSystemType.Ext3; - if (upperName.Contains("EXT2")) return FileSystemType.Ext2; - if (upperName.Contains("XFS")) return FileSystemType.XFS; - if (upperName.Contains("BTRFS")) return FileSystemType.Btrfs; - if (upperName.Contains("ZFS")) return FileSystemType.ZFS; - if (upperName.Contains("REFS")) return FileSystemType.ReFS; + if (Fat32Filesystem.DetectAsync(m_rawDisk, offset, size, cancellationToken).Await()) + return FileSystemType.FAT32; } - // Fallback based on GUID - return typeGuid.ToString().ToUpper() switch - { - "48465300-0000-11AA-AA11-00306543ECAC" => FileSystemType.HFSPlus, - "7C3457EF-0000-11AA-AA11-00306543ECAC" => FileSystemType.APFS, - _ => FileSystemType.Unknown - }; + return FileSystemType.Unknown; } // GPT-specific properties diff --git a/proprietary/DiskImage/Partition/MBR.cs b/proprietary/DiskImage/Partition/MBR.cs index ba8d7010c..6fd2ad194 100644 --- a/proprietary/DiskImage/Partition/MBR.cs +++ b/proprietary/DiskImage/Partition/MBR.cs @@ -4,7 +4,9 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using Duplicati.Library.Utility; using Duplicati.Proprietary.DiskImage.Disk; +using Duplicati.Proprietary.DiskImage.Filesystem; using Duplicati.Proprietary.DiskImage.General; namespace Duplicati.Proprietary.DiskImage.Partition; @@ -135,7 +137,7 @@ internal class MBR : IPartitionTable StartOffset = entry.StartLBA * m_bytesPerSector, Size = entry.SizeInSectors * m_bytesPerSector, Name = $"Partition {i + 1}", - FilesystemType = DetermineFilesystemType(entry), + FilesystemType = entry.FilesystemType, VolumeGuid = null, RawDisk = m_rawDisk, StartingLba = entry.StartLBA, @@ -185,6 +187,7 @@ internal class MBR : IPartitionTable { EntryNumber = entryNumber, PartitionType = DeterminePartitionType(partitionType), + FilesystemType = DetermineFilesystemType(partitionType, startLBA * m_bytesPerSector, (int)(sizeInSectors * m_bytesPerSector)), PartitionTypeByte = partitionType, StartLBA = startLBA, SizeInSectors = sizeInSectors, @@ -210,11 +213,21 @@ internal class MBR : IPartitionTable /// /// Determines the filesystem type based on the partition type byte. /// - /// The MBR partition entry. + /// The partition type byte from the MBR. + /// The offset of the partition within the disk. + /// The size of the partition in bytes. /// The determined filesystem type. - private static FileSystemType DetermineFilesystemType(MBRPartitionEntry entry) + private FileSystemType DetermineFilesystemType(byte typeByte, long offset, int size) { - return MbrPartitionTypes.ToFilesystemType(entry.PartitionTypeByte); + var typeByByte = MbrPartitionTypes.ToFilesystemType(typeByte); + if (typeByByte is FileSystemType.Unknown && m_rawDisk != null) + { + // If the partition type byte is unknown, attempt to detect the filesystem by reading the partition data + if (Fat32Filesystem.DetectAsync(m_rawDisk, offset, size, CancellationToken.None).Await()) + return FileSystemType.FAT32; + } + + return typeByByte; } // MBR-specific properties @@ -318,6 +331,9 @@ internal class MBR : IPartitionTable /// Gets the partition type. public PartitionType PartitionType { get; init; } + /// Gets the filesystem type. + public FileSystemType FilesystemType { get; init; } + /// Gets the raw partition type byte. public byte PartitionTypeByte { get; init; }