On Linux prefer SCSI generic device if available.

This commit is contained in:
2026-07-07 03:44:11 +01:00
parent 12477b8a60
commit b3b2280522
2 changed files with 102 additions and 3 deletions

View File

@@ -54,7 +54,7 @@ partial class Device : Devices.Device, IDisposable
private nuint _capacity;
/// <summary>Gets the file handle representing this device</summary>
/// <value>The file handle</value>
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)
{

View File

@@ -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<DeviceInfo>(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;
}
}