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