diff --git a/Aaru.Devices/Linux/Device.cs b/Aaru.Devices/Linux/Device.cs index e1ecdae4e..498f865fc 100644 --- a/Aaru.Devices/Linux/Device.cs +++ b/Aaru.Devices/Linux/Device.cs @@ -54,7 +54,7 @@ partial class Device : Devices.Device, IDisposable private nuint _capacity; /// Gets the file handle representing this device /// The file handle - int _fileDescriptor; + int _fileDescriptor; // Persistent, aligned native buffer private nuint _nativeBuffer; @@ -88,13 +88,33 @@ partial class Device : Devices.Device, IDisposable { errno = ErrorNumber.NoError; + // Prefer /dev/sg* over block devices (/dev/sr*, /dev/sd*, /dev/st*) so that the kernel + // does not apply SCSI command filtering, which blocks vendor-specific opcodes (e.g. OmniDrive + // 0xC0) without CAP_SYS_RAWIO. The sg device is looked up via sysfs and used only for + // opening; all other sysfs queries below continue to use the original devicePath. + string openPath = devicePath; + + if(devicePath.StartsWith("/dev/sr", StringComparison.Ordinal) || + devicePath.StartsWith("/dev/sd", StringComparison.Ordinal) || + devicePath.StartsWith("/dev/st", StringComparison.Ordinal)) + { + string sgDir = "/sys/block/" + Path.GetFileName(devicePath) + "/device/scsi_generic"; + + if(Directory.Exists(sgDir)) + { + string[] sgEntries = Directory.GetFileSystemEntries(sgDir, "*", SearchOption.TopDirectoryOnly); + + if(sgEntries.Length > 0) openPath = "/dev/" + Path.GetFileName(sgEntries[0]); + } + } + var dev = new Device { PlatformId = DetectOS.GetRealPlatformID(), Timeout = 15, Error = false, IsRemovable = false, - _fileDescriptor = Extern.open(devicePath, FileFlags.ReadWrite | FileFlags.NonBlocking | FileFlags.CreateNew) + _fileDescriptor = Extern.open(openPath, FileFlags.ReadWrite | FileFlags.NonBlocking | FileFlags.CreateNew) }; if(dev._fileDescriptor < 0) @@ -103,7 +123,7 @@ partial class Device : Devices.Device, IDisposable if(dev.LastError is 13 or 30) // EACCES or EROFS { - dev._fileDescriptor = Extern.open(devicePath, FileFlags.Readonly | FileFlags.NonBlocking); + dev._fileDescriptor = Extern.open(openPath, FileFlags.Readonly | FileFlags.NonBlocking); if(dev._fileDescriptor < 0) { diff --git a/Aaru.Devices/Linux/ListDevices.cs b/Aaru.Devices/Linux/ListDevices.cs index dac9b710f..05e3a3e51 100644 --- a/Aaru.Devices/Linux/ListDevices.cs +++ b/Aaru.Devices/Linux/ListDevices.cs @@ -31,6 +31,7 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.IO; using System.Runtime.Versioning; using System.Text; @@ -151,6 +152,84 @@ public static class ListDevices }; } + // Add /dev/sg* devices that have no block-device counterpart (sd*, sr*, st*). + // Devices that do have a counterpart will be opened via their sg node transparently + // by Device.Create(), so they should not appear twice in the list. + const string PATH_SYS_SG = "/sys/class/scsi_generic/"; + + if(Directory.Exists(PATH_SYS_SG)) + { + string[] sgDevs = Directory.GetFileSystemEntries(PATH_SYS_SG, "*", SearchOption.TopDirectoryOnly); + + var sgList = new List(devices); + + foreach(string sgSysPath in sgDevs) + { + string sgName = Path.GetFileName(sgSysPath); + string blockPath = Path.Combine(PATH_SYS_SG, sgName, "device", "block"); + + // Skip if this sg device has a block-device counterpart + if(Directory.Exists(blockPath) && + Directory.GetFileSystemEntries(blockPath, "*", SearchOption.TopDirectoryOnly).Length > 0) + continue; + + string sgDevPath = "/dev/" + sgName; + + var sgInfo = new DeviceInfo + { + Path = sgDevPath + }; + + if(hasUdev) + { + IntPtr udevDev = Extern.udev_device_new_from_subsystem_sysname(udev, "scsi_generic", sgName); + sgInfo.Vendor = Extern.udev_device_get_property_value(udevDev, "ID_VENDOR"); + sgInfo.Model = Extern.udev_device_get_property_value(udevDev, "ID_MODEL"); + + if(!string.IsNullOrEmpty(sgInfo.Model)) sgInfo.Model = sgInfo.Model.Replace('_', ' '); + + sgInfo.Serial = Extern.udev_device_get_property_value(udevDev, "ID_SCSI_SERIAL"); + + if(string.IsNullOrEmpty(sgInfo.Serial)) + sgInfo.Serial = Extern.udev_device_get_property_value(udevDev, "ID_SERIAL_SHORT"); + + sgInfo.Bus = Extern.udev_device_get_property_value(udevDev, "ID_BUS"); + } + + string sgDevicePath = Path.Combine(PATH_SYS_SG, sgName, "device"); + + if(File.Exists(Path.Combine(sgDevicePath, "vendor")) && string.IsNullOrEmpty(sgInfo.Vendor)) + { + var sr = new StreamReader(Path.Combine(sgDevicePath, "vendor"), Encoding.ASCII); + sgInfo.Vendor = sr.ReadLine()?.Trim(); + } + + if(File.Exists(Path.Combine(sgDevicePath, "model")) && string.IsNullOrEmpty(sgInfo.Model)) + { + var sr = new StreamReader(Path.Combine(sgDevicePath, "model"), Encoding.ASCII); + sgInfo.Model = sr.ReadLine()?.Trim(); + } + + if(File.Exists(Path.Combine(sgDevicePath, "serial")) && string.IsNullOrEmpty(sgInfo.Serial)) + { + var sr = new StreamReader(Path.Combine(sgDevicePath, "serial"), Encoding.ASCII); + sgInfo.Serial = sr.ReadLine()?.Trim(); + } + + if(!string.IsNullOrEmpty(sgInfo.Bus)) sgInfo.Bus = sgInfo.Bus.ToUpper(); + + sgInfo.Supported = sgInfo.Bus switch + { + "ATA" or "ATAPI" or "SCSI" or "USB" or "PCMCIA" or "FireWire" => true, + _ => sgInfo.Supported + }; + + sgList.Add(sgInfo); + } + + devices = [.. sgList]; + } + return devices; } } \ No newline at end of file