diff --git a/proprietary/DiskImage/Disk/Interfaces.cs b/proprietary/DiskImage/Disk/Interfaces.cs
index 6003440ef..5140477db 100644
--- a/proprietary/DiskImage/Disk/Interfaces.cs
+++ b/proprietary/DiskImage/Disk/Interfaces.cs
@@ -12,6 +12,11 @@ namespace Duplicati.Proprietary.DiskImage.Disk;
///
internal interface IRawDisk : IDisposable
{
+ ///
+ /// Gets the platform-specific prefix for disk entries (e.g., "\\.\" on Windows, "/dev/" on Unix).
+ ///
+ static abstract string Prefix { get; }
+
///
/// Gets the disk identifier (e.g., "\\.\PhysicalDrive0" or "/dev/sda")
///
diff --git a/proprietary/DiskImage/Disk/Mac.cs b/proprietary/DiskImage/Disk/Mac.cs
index 60d486261..0f65ddf21 100644
--- a/proprietary/DiskImage/Disk/Mac.cs
+++ b/proprietary/DiskImage/Disk/Mac.cs
@@ -42,8 +42,12 @@ namespace Duplicati.Proprietary.DiskImage.Disk
private uint m_sectorSize = 0;
private long m_size = 0;
private bool m_shouldFlush = false;
+ private const string DEVICE_PREFIX = "/dev/";
private readonly SemaphoreSlim m_ioLock = new(1, 1);
+ ///
+ public static string Prefix => "/dev/";
+
///
public string DevicePath { get { return m_devicePath; } }
@@ -74,9 +78,9 @@ namespace Duplicati.Proprietary.DiskImage.Disk
return devicePath;
// Convert /dev/diskN to /dev/rdiskN for raw/character device access
- if (devicePath.StartsWith("/dev/disk") && !devicePath.StartsWith("/dev/rdisk"))
+ if (devicePath.StartsWith($"{DEVICE_PREFIX}disk") && !devicePath.StartsWith($"{DEVICE_PREFIX}rdisk"))
{
- return devicePath.Replace("/dev/disk", "/dev/rdisk");
+ return devicePath.Replace($"{DEVICE_PREFIX}disk", $"{DEVICE_PREFIX}rdisk");
}
return devicePath;
@@ -122,7 +126,7 @@ namespace Duplicati.Proprietary.DiskImage.Disk
public async Task AutoUnmountAsync(CancellationToken cancellationToken)
{
// Extract disk number from device path
- string blockDevicePath = m_devicePath.Replace("/dev/rdisk", "/dev/disk");
+ string blockDevicePath = m_devicePath.Replace($"{DEVICE_PREFIX}rdisk", $"{DEVICE_PREFIX}disk");
// Use diskutil to unmount all volumes on the disk
var unmount = await ProcessRunner.RunProcessAsync("diskutil", $"unmountDisk {blockDevicePath}", 30_000, cancellationToken);
@@ -495,7 +499,7 @@ namespace Duplicati.Proprietary.DiskImage.Disk
if (string.IsNullOrWhiteSpace(list.Output))
yield break;
- var devices = list.Output.Split("/dev/disk", StringSplitOptions.RemoveEmptyEntries);
+ var devices = list.Output.Split($"{DEVICE_PREFIX}disk", StringSplitOptions.RemoveEmptyEntries);
foreach (var device in devices)
{
@@ -510,7 +514,7 @@ namespace Duplicati.Proprietary.DiskImage.Disk
if (string.IsNullOrWhiteSpace(identifier))
continue; // Skip if we can't determine an identifier
- var path = $"/dev/{identifier}";
+ var path = $"{DEVICE_PREFIX}{identifier}";
var size = mainLine[^3..^1]; // Size is typically the third-to-last token alongside the unit (e.g., "500 GB")
size[0] = size[0].Trim()[1..]; // Remove any leading/trailing whitespace, and the leading + or *
var sizeinBytes = Library.Utility.Sizeparser.ParseSize(string.Join(' ', size));
@@ -529,7 +533,7 @@ namespace Duplicati.Proprietary.DiskImage.Disk
var driveInfo = new PhysicalDriveInfo
{
- Number = identifier["/dev/disk".Length..],
+ Number = identifier[$"{DEVICE_PREFIX}disk".Length..],
Path = path,
Size = (ulong)sizeinBytes,
DisplayName = displayName,
diff --git a/proprietary/DiskImage/Disk/Windows.cs b/proprietary/DiskImage/Disk/Windows.cs
index 94786c1b3..81839c14e 100644
--- a/proprietary/DiskImage/Disk/Windows.cs
+++ b/proprietary/DiskImage/Disk/Windows.cs
@@ -34,12 +34,16 @@ namespace Duplicati.Proprietary.DiskImage.Disk
private uint m_sectorSize = 0;
private long m_size = 0;
private bool m_shouldFlush = false;
+ private const string DEVICE_PREFIX = @"\\.\PHYSICALDRIVE";
// Reusable aligned native buffer for zero-copy I/O
private SafeHGlobalHandle? m_alignedBuffer;
private int m_alignedBufferSize = 0;
private readonly SemaphoreSlim m_ioLock = new(1, 1);
+ ///
+ public static string Prefix => @"\\.\";
+
///
public string DevicePath { get { return m_devicePath; } }
@@ -537,9 +541,9 @@ $diskInfo | ConvertTo-Json -Depth 4
foreach (var drive in drives)
{
var number = -1;
- if (drive.Path.StartsWith("\\\\.\\PHYSICALDRIVE", StringComparison.OrdinalIgnoreCase))
+ if (drive.Path.StartsWith(DEVICE_PREFIX, StringComparison.OrdinalIgnoreCase))
{
- int.TryParse(drive.Path.AsSpan("\\\\.\\PHYSICALDRIVE".Length), out number);
+ int.TryParse(drive.Path.AsSpan(DEVICE_PREFIX.Length), out number);
}
drive.Number = number.ToString();
@@ -563,9 +567,9 @@ $diskInfo | ConvertTo-Json -Depth 4
{
// Extract drive number from path (e.g., \\.\PHYSICALDRIVE0 -> 0)
var number = -1;
- if (drive.Path.StartsWith("\\\\.\\PHYSICALDRIVE", StringComparison.OrdinalIgnoreCase))
+ if (drive.Path.StartsWith(DEVICE_PREFIX, StringComparison.OrdinalIgnoreCase))
{
- int.TryParse(drive.Path.AsSpan("\\\\.\\PHYSICALDRIVE".Length), out number);
+ int.TryParse(drive.Path.AsSpan(DEVICE_PREFIX.Length), out number);
}
drive.Number = number.ToString();
yield return drive;
diff --git a/proprietary/DiskImage/SourceProvider.cs b/proprietary/DiskImage/SourceProvider.cs
index 1db3cfa4a..737df0c2d 100644
--- a/proprietary/DiskImage/SourceProvider.cs
+++ b/proprietary/DiskImage/SourceProvider.cs
@@ -186,6 +186,21 @@ public sealed class SourceProvider : ISourceProviderModule, IDisposable
throw new PlatformNotSupportedException(Strings.PlatformNotSupported);
}
+ ///
+ /// Gets the platform-specific prefix for disk entries (e.g., "\\.\" on Windows, "/dev/" on Unix). This is used to construct entry paths correctly based on the underlying platform.
+ ///
+ /// The platform-specific prefix for disk entries.
+ /// Thrown when the platform is not supported.
+ public static string GetDevicePrefix()
+ {
+ if (OperatingSystem.IsWindows())
+ return Windows.Prefix;
+ else if (OperatingSystem.IsMacOS())
+ return Mac.Prefix;
+ else
+ throw new PlatformNotSupportedException(Strings.PlatformNotSupported);
+ }
+
///
public void Dispose()
{
diff --git a/proprietary/DiskImage/WebModule.cs b/proprietary/DiskImage/WebModule.cs
index e4e1fd86b..423d30734 100644
--- a/proprietary/DiskImage/WebModule.cs
+++ b/proprietary/DiskImage/WebModule.cs
@@ -58,14 +58,7 @@ public class WebModule : IWebModule
return res;
}
- string prefix = "";
- if (OperatingSystem.IsWindows())
- prefix = "\\\\.\\";
- else if (OperatingSystem.IsMacOS())
- prefix = "/dev/";
- else
- throw new PlatformNotSupportedException(Strings.PlatformNotSupported);
-
+ var prefix = SourceProvider.GetDevicePrefix();
List parts = [.. path[prefix.Length..].Split(Path.DirectorySeparatorChar, 2)];
var physicalDrivePath = prefix + parts.First();
var subpath = parts.Last();