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; }