Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions asusd/src/aura_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,31 @@ impl DeviceManager {
Ok(devices)
}

/// Resolve the `/dev/sgN` (scsi_generic) node backing a block device.
///
/// Walks up from the block device to its owning scsi_device and reads the
/// `scsi_generic/sgN` child. Works for whole-disk (`/dev/sda`) and
/// partition (`/dev/sda1`) nodes alike, since the scsi_device is a common
/// ancestor. Returns None if no sg node exists (e.g. the `sg` module is
/// not loaded).
fn sg_node_for_block(device: &Device) -> Option<String> {
let mut current = device.parent();
while let Some(d) = current {
if let Ok(entries) = std::fs::read_dir(d.syspath().join("scsi_generic")) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
let node = format!("/dev/{name}");
if std::path::Path::new(&node).exists() {
return Some(node);
}
}
}
}
current = d.parent();
}
None
}

async fn init_scsi(
connection: &Connection,
device: &Device,
Expand All @@ -289,8 +314,40 @@ impl DeviceManager {
.property_value("ID_MODEL_ID")
.unwrap_or_default()
.to_string_lossy();
if let Some(dev_str) = dev_node.as_os_str().to_str() {
if let Ok(dev_type) = DeviceHandle::maybe_scsi(dev_str, &prod_id).await {
// SG_IO with vendor commands on the block node (/dev/sdX)
// requires CAP_SYS_RAWIO, which the hardened asusd unit drops
// (every ioctl EPERMs and is silently swallowed by write_effect).
// The scsi_generic /dev/sgN node gates access at open() via
// file permissions instead, so it works with no capabilities,
// the same path sg3_utils / OpenRGB use.
//
// On hotplug the sg node can appear just after the block node,
// so retry briefly before falling back to the block device
// (which would EPERM). At startup the node already exists, so
// the first attempt succeeds with no delay.
let mut sg_node = None;
for attempt in 0..8u8 {
if let Some(sg) = Self::sg_node_for_block(device) {
sg_node = Some(sg);
break;
}
if attempt < 7 {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
let dev_str = match sg_node {
Some(sg) => Some(sg),
None => {
warn!(
"No /dev/sgN for SCSI device after retries; falling back to block \
node {:?} (SG_IO will EPERM unless asusd has CAP_SYS_RAWIO)",
dev_node
);
dev_node.as_os_str().to_str().map(|s| s.to_string())
}
};
if let Some(dev_str) = dev_str {
if let Ok(dev_type) = DeviceHandle::maybe_scsi(&dev_str, &prod_id).await {
if let DeviceHandle::Scsi(scsi) = dev_type.clone() {
let ctrl = ScsiZbus::new(scsi);
if ctrl
Expand Down
7 changes: 6 additions & 1 deletion asusd/src/aura_scsi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ impl ScsiAura {
pub async fn write_effect(&self, effect: &AuraEffect) -> Result<(), RogError> {
let mut tasks: Vec<Task> = effect.into();
for task in &mut tasks {
self.device.lock().await.perform(task).ok();
// Surface the ioctl errno instead of dropping it — an EPERM/EIO
// here was previously invisible, so asusd reported success while
// no SCSI traffic ever reached the device.
if let Err(e) = self.device.lock().await.perform(task) {
log::warn!("SCSI perform failed: {e}");
}
}
Ok(())
}
Expand Down