use core::{
any::Any,
sync::atomic::{AtomicBool, AtomicU32, Ordering},
};
use ax_errno::{AxError, AxResult, LinuxError};
use ax_fs::FileBackend;
use ax_sync::Mutex;
use axfs_ng_vfs::{DeviceId, NodeFlags, VfsResult};
use linux_raw_sys::{
ioctl::{BLKGETSIZE, BLKGETSIZE64, BLKRAGET, BLKRASET, BLKROGET, BLKROSET},
loop_device::{LOOP_CLR_FD, LOOP_GET_STATUS, LOOP_SET_FD, LOOP_SET_STATUS, loop_info},
};
use starry_vm::{VmMutPtr, VmPtr};
use crate::{
file::get_file_like,
pseudofs::{DeviceMmap, DeviceOps},
};
pub struct LoopDevice {
number: u32,
dev_id: DeviceId,
pub file: Mutex<Option<FileBackend>>,
pub ro: AtomicBool,
pub ra: AtomicU32,
}
impl LoopDevice {
pub(crate) fn new(number: u32, dev_id: DeviceId) -> Self {
Self {
number,
dev_id,
file: Mutex::new(None),
ro: AtomicBool::new(false),
ra: AtomicU32::new(512),
}
}
pub fn get_info(&self) -> AxResult<loop_info> {
if self.file.lock().is_none() {
return Err(AxError::from(LinuxError::ENXIO));
}
let mut res: loop_info = unsafe { core::mem::zeroed() };
res.lo_number = self.number as _;
res.lo_rdevice = self.dev_id.0 as _;
Ok(res)
}
pub fn set_info(&self, _src: loop_info) -> AxResult<()> {
Ok(())
}
pub fn clone_file(&self) -> VfsResult<FileBackend> {
let file = self.file.lock().clone();
file.ok_or(AxError::from(LinuxError::ENXIO))
}
}
impl DeviceOps for LoopDevice {
fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult<usize> {
let file = self.file.lock().clone();
file.ok_or(AxError::OperationNotPermitted)?
.read_at(buf, offset)
}
fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult<usize> {
if self.ro.load(Ordering::Relaxed) {
return Err(AxError::ReadOnlyFilesystem);
}
let file = self.file.lock().clone();
file.ok_or(AxError::OperationNotPermitted)?
.write_at(buf, offset)
}
fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult<usize> {
match cmd {
LOOP_SET_FD => {
let fd = arg as i32;
if fd < 0 {
return Err(AxError::BadFileDescriptor);
}
let f = get_file_like(fd)?;
let Some(file) = f.downcast_ref::<crate::file::File>() else {
return Err(AxError::InvalidInput);
};
let mut guard = self.file.lock();
if guard.is_some() {
return Err(AxError::ResourceBusy);
}
*guard = Some(file.inner().backend()?.clone());
}
LOOP_CLR_FD => {
let mut guard = self.file.lock();
if guard.is_none() {
return Err(AxError::from(LinuxError::ENXIO));
}
*guard = None;
}
LOOP_GET_STATUS => {
(arg as *mut loop_info).vm_write(self.get_info()?)?;
}
LOOP_SET_STATUS => {
let info = unsafe { (arg as *const loop_info).vm_read_uninit()?.assume_init() };
self.set_info(info)?;
}
BLKGETSIZE | BLKGETSIZE64 => {
let file = self.clone_file()?;
let sectors = file.location().len()? / 512;
if cmd == BLKGETSIZE {
(arg as *mut u32).vm_write(sectors as _)?;
} else {
(arg as *mut u64).vm_write(sectors * 512)?;
}
}
BLKROGET => {
(arg as *mut u32).vm_write(self.ro.load(Ordering::Relaxed) as u32)?;
}
BLKROSET => {
let ro = (arg as *const u32).vm_read()?;
if ro != 0 && ro != 1 {
return Err(AxError::InvalidInput);
}
self.ro.store(ro != 0, Ordering::Relaxed);
}
BLKRAGET => {
(arg as *mut u32).vm_write(self.ra.load(Ordering::Relaxed))?;
}
BLKRASET => {
self.ra
.store((arg as *const u32).vm_read()? as _, Ordering::Relaxed);
}
_ => {
warn!("unknown ioctl for loop device: {cmd}");
return Err(AxError::NotATty);
}
}
Ok(0)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mmap(&self, _offset: u64) -> DeviceMmap {
if let Some(FileBackend::Cached(cache)) = self.file.lock().as_ref() {
DeviceMmap::Cache(cache.clone())
} else {
DeviceMmap::None
}
}
fn flags(&self) -> NodeFlags {
NodeFlags::NON_CACHEABLE
}
}