diff --git a/SabreTools.IO.Extensions.Test/DeviceExtensionsTests.cs b/SabreTools.IO.Extensions.Test/DeviceExtensionsTests.cs index b0d3616..d66cb1a 100644 --- a/SabreTools.IO.Extensions.Test/DeviceExtensionsTests.cs +++ b/SabreTools.IO.Extensions.Test/DeviceExtensionsTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Xunit; @@ -7,6 +6,84 @@ namespace SabreTools.IO.Extensions.Test { public class DeviceExtensionsTests { + #region IsExcludedUnixBlockDevice + + [Theory] + [InlineData("dm-0", true)] // Device-mapper (LVM/LUKS, etc.) virtual devices + [InlineData("fd0", true)] // Floppy drive + [InlineData("loop0", true)] // Loopback-mounted image + [InlineData("md0", true)] // Software RAID (mdadm) + [InlineData("nbd0", true)] // Network block device + [InlineData("ram0", true)] // Ramdisk + [InlineData("sda", false)] // Static storage + [InlineData("sg0", false)] // Generic SCSI + [InlineData("sr0", true)] // Optical drive + [InlineData("zd0", true)] // ZFS volume (zvol) + [InlineData("zram0", true)] // Compressed ramdisk] + public void IsExcludedUnixBlockDeviceTest(string name, bool expected) + { + bool actual = DeviceExtensions.IsExcludedUnixBlockDevice(name); + Assert.Equal(expected, actual); + } + + #endregion + + #region ReadUnixBlockDeviceSize + + [Theory] + [InlineData("nvme0n1", 2880)] // Not an sd* device + [InlineData("sda", 2880)] // Fixed disk of floppy size, not removable + [InlineData("sdb", 30310400)] // USB flash drive, not a floppy size + [InlineData("sdc", 0)] // Empty floppy drive (no media) + [InlineData("sdh", 2880)] // USB floppy, 1.44 MB + [InlineData("sdi", 1440)] // USB floppy, 720 KB (trailing newline) + public void ReadUnixBlockDeviceSizeTest(string device, long valueInFile) + { + long expected = valueInFile * 512; + string sysBlockEntry = Path.Combine(Environment.CurrentDirectory, "TestData", "sys", "block", device); + long actual = DeviceExtensions.ReadUnixBlockDeviceSize(sysBlockEntry); + Assert.Equal(expected, actual); + } + + #endregion + + #region ReadUnixBlockDeviceType + + [Theory] + [InlineData("sg0", 5)] + [InlineData("sg1", 0)] + [InlineData("sg2", 5)] + [InlineData("sg3", 1)] + [InlineData("sg10", 5)] + [InlineData("sga", 5)] + [InlineData("sgX", 5)] + public void ReadUnixBlockDeviceTypeTest(string device, int expected) + { + string sysGenericEntry = Path.Combine(Environment.CurrentDirectory, "TestData", "sys", "class", "scsi_generic", device); + int actual = DeviceExtensions.ReadUnixBlockDeviceType(sysGenericEntry); + Assert.Equal(expected, actual); + } + + #endregion + + #region ReadUnixRemovableFlag + + [Theory] + [InlineData("nvme0n1", true)] // Not an sd* device + [InlineData("sda", false)] // Fixed disk of floppy size, not removable + [InlineData("sdb", true)] // USB flash drive, not a floppy size + [InlineData("sdc", true)] // Empty floppy drive (no media) + [InlineData("sdh", true)] // USB floppy, 1.44 MB + [InlineData("sdi", true)] // USB floppy, 720 KB (trailing newline) + public void ReadUnixRemovableFlagTest(string device, bool expected) + { + string sysBlockEntry = Path.Combine(Environment.CurrentDirectory, "TestData", "sys", "block", device); + bool actual = DeviceExtensions.ReadUnixRemovableFlag(sysBlockEntry); + Assert.Equal(expected, actual); + } + + #endregion + #region HasDeviceIndexSuffix [Theory] @@ -25,6 +102,14 @@ namespace SabreTools.IO.Extensions.Test [InlineData("sr15", "sr", true)] [InlineData("src", "sr", false)] [InlineData("srm", "sr", false)] + // Generic block + [InlineData("sg0", "sg", true)] + [InlineData("sg1", "sg", true)] + [InlineData("sg2", "sg", true)] + [InlineData("sg3", "sg", true)] + [InlineData("sg10", "sg", true)] + [InlineData("sga", "sg", false)] + [InlineData("sgX", "sg", false)] public void HasDeviceIndexSuffixTest(string name, string prefix, bool expected) { bool actual = DeviceExtensions.HasDeviceIndexSuffix(name, prefix); @@ -32,125 +117,5 @@ namespace SabreTools.IO.Extensions.Test } #endregion - - #region EnumerateUnixOpticalGenericPaths - - [Fact] - public void EnumerateUnixOpticalGenericPaths_SysBlockRootNull_ReturnsEmpty() - { - var actual = DeviceExtensions.EnumerateUnixOpticalGenericPaths(null); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixOpticalGenericPaths_SysBlockRootEmpty_ReturnsEmpty() - { - var actual = DeviceExtensions.EnumerateUnixOpticalGenericPaths(string.Empty); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixOpticalGenericPaths_MissingDirectory_ReturnsEmpty() - { - string sysfsScsiGenericRoot = "INVALID"; - var actual = DeviceExtensions.EnumerateUnixOpticalGenericPaths(sysfsScsiGenericRoot); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixOpticalGenericPaths_OnlyMatchesOpticalSgNodes() - { - string sysfsScsiGenericRoot = Path.Combine(Environment.CurrentDirectory, "TestData", "sys", "class", "scsi_generic"); - - // sg0 - Optical, kept - // sg1 - Direct-access disk, skipped - // sg2 - Optical (trailing newline), kept - // sg3 - Sequential tape, skipped - // sg10 - Optical (multi-digit), kept - // sga - Name not all-digits, skipped - // sgX - Name not all-digits, skipped - List expected = ["sg0", "sg10", "sg2"]; - - var actual = DeviceExtensions.EnumerateUnixOpticalGenericPaths(sysfsScsiGenericRoot); - - var actualNames = new List(); - foreach (var p in actual) - { - actualNames.Add(Path.GetFileName(p)); - } - - actualNames.Sort(StringComparer.Ordinal); - - Assert.Equal(expected, actualNames); - } - - #endregion - - #region EnumerateUnixUsbFloppyBlockPaths - - [Fact] - public void EnumerateUnixUsbFloppyBlockPaths_DevRootNull_ReturnsEmpty() - { - var actual = DeviceExtensions.EnumerateUnixUsbFloppyBlockPaths(null, "/sys/block"); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixUsbFloppyBlockPaths_DevRootEmpty_ReturnsEmpty() - { - var actual = DeviceExtensions.EnumerateUnixUsbFloppyBlockPaths(string.Empty, "/sys/block"); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixUsbFloppyBlockPaths_SysBlockRootNull_ReturnsEmpty() - { - var actual = DeviceExtensions.EnumerateUnixUsbFloppyBlockPaths("/dev", null); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixUsbFloppyBlockPaths_SysBlockRootEmpty_ReturnsEmpty() - { - var actual = DeviceExtensions.EnumerateUnixUsbFloppyBlockPaths("/dev", string.Empty); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixUsbFloppyBlockPaths_MissingDirectory_ReturnsEmpty() - { - string sysBlockRoot = "INVALID"; - var actual = DeviceExtensions.EnumerateUnixUsbFloppyBlockPaths("/dev/", sysBlockRoot); - Assert.Empty(actual); - } - - [Fact] - public void EnumerateUnixUsbFloppyBlockPaths_OnlyMatchesRemovableFloppySizedDisks() - { - string devRoot = "/dev"; - string sysBlockRoot = Path.Combine(Environment.CurrentDirectory, "TestData", "sys", "block"); - - // nvme0n1 - Not an sd* device, not scanned - // sda - Fixed disk of floppy size, not removable, skipped - // sdb - USB flash drive, not a floppy size, skipped - // sdc - Empty floppy drive (no media), skipped - // sdh - USB floppy, 1.44 MB, matched - // sdi - USB floppy, 720 KB (trailing newline), matched - List expected = ["sdh", "sdi"]; - - var actual = DeviceExtensions.EnumerateUnixUsbFloppyBlockPaths(devRoot, sysBlockRoot); - - var actualNames = new List(); - foreach (var p in actual) - { - actualNames.Add(Path.GetFileName(p)); - } - - actualNames.Sort(StringComparer.Ordinal); - - Assert.Equal(expected, actualNames); - } - - #endregion } } diff --git a/SabreTools.IO.Extensions.Test/TestData/dev/sdb b/SabreTools.IO.Extensions.Test/TestData/dev/sdb deleted file mode 100644 index e69de29..0000000 diff --git a/SabreTools.IO.Extensions.Test/TestData/dev/serial0 b/SabreTools.IO.Extensions.Test/TestData/dev/serial0 deleted file mode 100644 index e69de29..0000000 diff --git a/SabreTools.IO.Extensions/DeviceExtensions.cs b/SabreTools.IO.Extensions/DeviceExtensions.cs index a1f4c2c..70c42a3 100644 --- a/SabreTools.IO.Extensions/DeviceExtensions.cs +++ b/SabreTools.IO.Extensions/DeviceExtensions.cs @@ -106,13 +106,12 @@ namespace SabreTools.IO.Extensions /// /// Enumerate Linux optical drives via their generic SCSI nodes (/dev/sg*). /// - /// sysfs class directory (typically "/sys/class/scsi_generic") /// Device paths, or an empty list when the sysfs directory is unreadable /// /// Unlike the block nodes, "sg" is not optical-specific, so each candidate is /// confirmed against sysfs: an optical drive reports SCSI peripheral device type 5. /// - public static List EnumerateUnixOpticalGenericPaths(string? sysfsScsiGenericRoot = "/sys/class/scsi_generic") + public static List EnumerateUnixOpticalGenericPaths() { // If the device root is invalid string devRoot = "/dev"; @@ -120,11 +119,12 @@ namespace SabreTools.IO.Extensions return []; // If the system generic root is invalid - if (string.IsNullOrEmpty(sysfsScsiGenericRoot) || !Directory.Exists(sysfsScsiGenericRoot)) + string sysfsScsiGenericRoot = "/sys/class/scsi_generic"; + if (!Directory.Exists(sysfsScsiGenericRoot)) return []; // Attempt to retrieve all matching devices - string[] entries = IOExtensions.SafeGetFileSystemEntries(sysfsScsiGenericRoot!, "sg*"); + string[] entries = IOExtensions.SafeGetFileSystemEntries(sysfsScsiGenericRoot, "sg*"); // Return only valid devices var result = new List(); @@ -149,8 +149,6 @@ namespace SabreTools.IO.Extensions /// Enumerate USB floppy drives, which the kernel exposes as ordinary SCSI disks /// (/dev/sd*) rather than /dev/fd* nodes. /// - /// Root directory device nodes live under (typically "/dev") - /// sysfs block directory (typically "/sys/block") /// Device paths, or an empty list when the directory is unreadable /// /// A disk is treated as a floppy when it reports itself removable and its media is one of @@ -158,18 +156,20 @@ namespace SabreTools.IO.Extensions /// inserted medium (Win32_LogicalDisk.MediaType) and, like that path, only recognizes the /// drive while floppy media is present. /// - public static List EnumerateUnixUsbFloppyBlockPaths(string? devRoot = "/dev", string? sysBlockRoot = "/sys/block") + public static List EnumerateUnixUsbFloppyBlockPaths() { // If the device root is invalid - if (string.IsNullOrEmpty(devRoot) || !Directory.Exists(devRoot)) + string devRoot = "/dev"; + if (!Directory.Exists(devRoot)) return []; // If the system block root is invalid - if (string.IsNullOrEmpty(sysBlockRoot) || !Directory.Exists(sysBlockRoot)) + string sysBlockRoot = "/sys/block"; + if (!Directory.Exists(sysBlockRoot)) return []; // Attempt to retrieve all matching devices - string[] entries = IOExtensions.SafeGetFileSystemEntries(sysBlockRoot!, "sd*"); + string[] entries = IOExtensions.SafeGetFileSystemEntries(sysBlockRoot, "sd*"); // Return only valid devices var result = new List(); @@ -342,7 +342,11 @@ namespace SabreTools.IO.Extensions /// The device size in bytes, 0 on error public static int ReadUnixBlockDeviceType(string sysGenericEntry) { +#if NET20 || NET35 string typePath = Path.Combine(Path.Combine(sysGenericEntry, "device"), "type"); +#else + string typePath = Path.Combine(sysGenericEntry, "device", "type"); +#endif try { if (!File.Exists(typePath))