Have auto detection of file system on partition when the type cannot be determined from the partition table

This commit is contained in:
Carl Johnsen
2026-03-13 12:48:21 +01:00
parent f5e7adf19e
commit 4267fffa95
3 changed files with 67 additions and 31 deletions
+31
View File
@@ -270,6 +270,37 @@ internal class Fat32Filesystem : IFilesystem
return metadata;
}
/// <summary>
/// Tries to detect whether a given partition has a FAT32 file system.
/// </summary>
/// <param name="disk">The raw disk to check for the filesystem.</param>
/// <param name="offset">The offset on the disk where the partition starts.</param>
/// <param name="size">The size of the partition.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to true if a valid FAT32 filesystem is detected, otherwise false.</returns>
public static async Task<bool> 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;
}
/// <inheritdoc />
public void Dispose()
{
+16 -27
View File
@@ -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
}
/// <summary>
/// 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.
/// </summary>
/// <param name="name">The partition name.</param>
/// <param name="typeGuid">The partition type GUID.</param>
/// <returns>The corresponding <see cref="FileSystemType"/>.</returns>
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
+20 -4
View File
@@ -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
/// <summary>
/// Determines the filesystem type based on the partition type byte.
/// </summary>
/// <param name="entry">The MBR partition entry.</param>
/// <param name="typeByte">The partition type byte from the MBR.</param>
/// <param name="offset">The offset of the partition within the disk.</param>
/// <param name="size">The size of the partition in bytes.</param>
/// <returns>The determined filesystem type.</returns>
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
/// <summary>Gets the partition type.</summary>
public PartitionType PartitionType { get; init; }
/// <summary>Gets the filesystem type.</summary>
public FileSystemType FilesystemType { get; init; }
/// <summary>Gets the raw partition type byte.</summary>
public byte PartitionTypeByte { get; init; }