mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-28 08:01:04 +00:00
scsi: add support for WRITE(10)
This adds write support. Being very similar to READ(10) in structure, much of the code is very similar. Signed-off-by: Erik Schilling <erik.schilling@linaro.org>
This commit is contained in:
parent
4c8a2bc3ac
commit
3adff11c94
@ -70,7 +70,13 @@ fn create_backend(args: &ScsiArgs) -> Result<VhostUserScsiBackend> {
|
||||
}
|
||||
|
||||
for image in &args.images {
|
||||
let mut dev = BlockDevice::new(FileBackend::new(File::open(image).expect("Opening image")));
|
||||
let mut dev = BlockDevice::new(FileBackend::new(
|
||||
File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(image)
|
||||
.expect("Opening image"),
|
||||
));
|
||||
dev.set_write_protected(args.read_only);
|
||||
dev.set_solid_state(if args.solid_state {
|
||||
MediumRotationRate::NonRotating
|
||||
|
||||
@ -98,6 +98,7 @@ impl Mul<BlockSize> for BlockOffset {
|
||||
|
||||
pub(crate) trait BlockDeviceBackend: Send + Sync {
|
||||
fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> io::Result<()>;
|
||||
fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> io::Result<()>;
|
||||
fn size_in_blocks(&mut self) -> io::Result<BlockOffset>;
|
||||
fn block_size(&self) -> BlockSize;
|
||||
fn sync(&mut self) -> io::Result<()>;
|
||||
@ -122,6 +123,10 @@ impl BlockDeviceBackend for FileBackend {
|
||||
self.file.read_exact_at(buf, u64::from(offset))
|
||||
}
|
||||
|
||||
fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> io::Result<()> {
|
||||
self.file.write_all_at(buf, u64::from(offset))
|
||||
}
|
||||
|
||||
fn size_in_blocks(&mut self) -> io::Result<BlockOffset> {
|
||||
let len = ByteOffset::from(self.file.metadata()?.len());
|
||||
assert!(u64::from(len) % NonZeroU64::from(self.block_size.0) == 0);
|
||||
@ -168,6 +173,25 @@ impl<T: BlockDeviceBackend> BlockDevice<T> {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn write_blocks(
|
||||
&mut self,
|
||||
lba: BlockOffset,
|
||||
blocks: BlockOffset,
|
||||
reader: &mut dyn Read,
|
||||
) -> io::Result<()> {
|
||||
// TODO: Avoid the copies here.
|
||||
let mut buf = vec![
|
||||
0;
|
||||
usize::try_from(u64::from(blocks * self.backend.block_size()))
|
||||
.expect("block length in bytes should fit usize")
|
||||
];
|
||||
reader.read_exact(&mut buf)?;
|
||||
self.backend
|
||||
.write_exact_at(&buf, lba * self.backend.block_size())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_write_protected(&mut self, wp: bool) {
|
||||
self.write_protected = wp;
|
||||
}
|
||||
@ -181,7 +205,7 @@ impl<T: BlockDeviceBackend> LogicalUnit for BlockDevice<T> {
|
||||
fn execute_command(
|
||||
&mut self,
|
||||
data_in: &mut SilentlyTruncate<&mut dyn Write>,
|
||||
_data_out: &mut dyn Read,
|
||||
data_out: &mut dyn Read,
|
||||
req: LunRequest,
|
||||
command: LunSpecificCommand,
|
||||
) -> Result<CmdOutput, CmdError> {
|
||||
@ -415,6 +439,52 @@ impl<T: BlockDeviceBackend> LogicalUnit for BlockDevice<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::Write10 {
|
||||
dpo,
|
||||
fua,
|
||||
lba,
|
||||
transfer_length,
|
||||
} => {
|
||||
if dpo {
|
||||
// DPO is just a hint that the guest probably won't access
|
||||
// this any time soon, so we can ignore it
|
||||
debug!("Silently ignoring DPO flag");
|
||||
}
|
||||
|
||||
let size = match self.backend.size_in_blocks() {
|
||||
Ok(size) => size,
|
||||
Err(e) => {
|
||||
error!("Error getting image size for read: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
|
||||
}
|
||||
};
|
||||
|
||||
let lba = BlockOffset(lba.into());
|
||||
let transfer_length = BlockOffset(transfer_length.into());
|
||||
|
||||
if lba + transfer_length > size {
|
||||
return Ok(CmdOutput::check_condition(
|
||||
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
||||
));
|
||||
}
|
||||
|
||||
let write_result = self.write_blocks(lba, transfer_length, data_out);
|
||||
|
||||
if fua {
|
||||
if let Err(e) = self.backend.sync() {
|
||||
error!("Error syncing file: {}", e);
|
||||
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
|
||||
}
|
||||
}
|
||||
|
||||
match write_result {
|
||||
Ok(()) => Ok(CmdOutput::ok()),
|
||||
Err(e) => {
|
||||
error!("Error writing to block device: {}", e);
|
||||
Ok(CmdOutput::check_condition(sense::TARGET_FAILURE))
|
||||
}
|
||||
}
|
||||
}
|
||||
LunSpecificCommand::Inquiry(page_code) => {
|
||||
// top 3 bits 0: peripheral device code = exists and ready
|
||||
// bottom 5 bits 0: device type = block device
|
||||
|
||||
@ -174,6 +174,15 @@ pub(crate) enum LunSpecificCommand {
|
||||
lba: u32,
|
||||
transfer_length: u16,
|
||||
},
|
||||
Write10 {
|
||||
/// Disable page out (i.e. hint that this page won't be accessed again
|
||||
/// soon, so we shouldn't bother caching it)
|
||||
dpo: bool,
|
||||
/// Force unit access (i.e. bypass cache)
|
||||
fua: bool,
|
||||
lba: u32,
|
||||
transfer_length: u16,
|
||||
},
|
||||
ReadCapacity10,
|
||||
ReadCapacity16,
|
||||
ReportSupportedOperationCodes {
|
||||
@ -202,6 +211,7 @@ pub(crate) enum CommandType {
|
||||
ReportSupportedOperationCodes,
|
||||
RequestSense,
|
||||
TestUnitReady,
|
||||
Write10,
|
||||
}
|
||||
|
||||
pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
|
||||
@ -211,6 +221,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
|
||||
(CommandType::ModeSense6, (0x1a, None)),
|
||||
(CommandType::ReadCapacity10, (0x25, None)),
|
||||
(CommandType::Read10, (0x28, None)),
|
||||
(CommandType::Write10, (0x2a, None)),
|
||||
(CommandType::ReadCapacity16, (0x9e, Some(0x10))),
|
||||
(CommandType::ReportLuns, (0xa0, None)),
|
||||
(
|
||||
@ -374,6 +385,18 @@ impl CommandType {
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::Write10 => &[
|
||||
0x2A,
|
||||
0b1111_1100,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0011_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b0000_0100,
|
||||
],
|
||||
Self::Inquiry => &[
|
||||
0x12,
|
||||
0b0000_0001,
|
||||
@ -514,6 +537,24 @@ impl Cdb {
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::Write10 => {
|
||||
if cdb[1] & 0b1110_0000 != 0 {
|
||||
// Feature (protection) that we don't
|
||||
// support; the standard says to respond with INVALID
|
||||
// FIELD IN CDB for these if unsupported
|
||||
return Err(ParseError::InvalidField);
|
||||
}
|
||||
Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::Write10 {
|
||||
dpo: cdb[1] & 0b0001_0000 != 0,
|
||||
fua: cdb[1] & 0b0000_1000 != 0,
|
||||
lba: u32::from_be_bytes(cdb[2..6].try_into().unwrap()),
|
||||
transfer_length: u16::from_be_bytes(cdb[7..9].try_into().unwrap()),
|
||||
}),
|
||||
allocation_length: None,
|
||||
naca: (cdb[9] & 0b0000_0100) != 0,
|
||||
})
|
||||
}
|
||||
CommandType::ReadCapacity10 => Ok(Self {
|
||||
command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity10),
|
||||
allocation_length: None,
|
||||
|
||||
@ -6,12 +6,18 @@ mod bad_lun;
|
||||
mod generic;
|
||||
mod report_supported_operation_codes;
|
||||
|
||||
use std::{fs::File, io::Write};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Write,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use tempfile::tempfile;
|
||||
|
||||
use super::{
|
||||
block_device::{BlockDevice, FileBackend},
|
||||
block_device::{
|
||||
BlockDevice, BlockDeviceBackend, BlockOffset, BlockSize, ByteOffset, FileBackend,
|
||||
},
|
||||
target::EmulatedTarget,
|
||||
};
|
||||
use crate::scsi::{
|
||||
@ -19,6 +25,51 @@ use crate::scsi::{
|
||||
CmdOutput, Request, Target, TaskAttr,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestBackend {
|
||||
data: Arc<Mutex<[u8; 512 * 16]>>,
|
||||
}
|
||||
|
||||
impl TestBackend {
|
||||
fn new() -> Self {
|
||||
TestBackend {
|
||||
data: Arc::new(Mutex::new([0; 512 * 16])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockDeviceBackend for TestBackend {
|
||||
fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> std::io::Result<()> {
|
||||
let data = self.data.lock().unwrap();
|
||||
|
||||
let offset = usize::try_from(u64::from(offset)).expect("offset should fit usize");
|
||||
buf.copy_from_slice(&data[offset..(offset + buf.len())]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> std::io::Result<()> {
|
||||
let mut data = self.data.lock().unwrap();
|
||||
|
||||
let offset = usize::try_from(u64::from(offset)).expect("offset should fit usize");
|
||||
data[offset..(offset + buf.len())].copy_from_slice(buf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size_in_blocks(&mut self) -> std::io::Result<BlockOffset> {
|
||||
Ok(ByteOffset::from(
|
||||
u64::try_from(self.data.lock().unwrap().len()).expect("size_in_blocks should fit u64"),
|
||||
) / self.block_size())
|
||||
}
|
||||
|
||||
fn block_size(&self) -> BlockSize {
|
||||
BlockSize::try_from(512).expect("512 should be a valid BlockSize")
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn null_image() -> FileBackend {
|
||||
FileBackend::new(File::open("/dev/null").unwrap())
|
||||
}
|
||||
@ -103,6 +154,10 @@ fn do_command_fail(target: &mut EmulatedTarget, cdb: &[u8], expected_error: Sens
|
||||
do_command_fail_lun(target, 0, cdb, expected_error);
|
||||
}
|
||||
|
||||
fn block_size_512() -> BlockSize {
|
||||
BlockSize::try_from(512).expect("512 should be a valid block_size")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_unit_ready() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
@ -237,6 +292,41 @@ fn test_read_10_cross_out() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_10() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
let mut backend = TestBackend::new();
|
||||
let dev = BlockDevice::new(backend.clone());
|
||||
target.add_lun(Box::new(dev));
|
||||
|
||||
// TODO: this test relies on the default logical block size of 512. We should
|
||||
// make that explicit.
|
||||
|
||||
{
|
||||
let data_out = [b'w'; 512];
|
||||
|
||||
do_command_in(
|
||||
&mut target,
|
||||
&[
|
||||
0x2a, // WRITE (10)
|
||||
0, // flags
|
||||
0, 0, 0, 5, // LBA: 5
|
||||
0, // reserved, group #
|
||||
0, 1, // transfer length: 1
|
||||
0, // control
|
||||
],
|
||||
&data_out,
|
||||
&[],
|
||||
);
|
||||
|
||||
let mut buf = [0_u8; 512];
|
||||
backend
|
||||
.read_exact_at(&mut buf, BlockOffset::from(5) * block_size_512())
|
||||
.expect("Reading should work");
|
||||
assert_eq!(data_out, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_capacity_10() {
|
||||
let mut target = EmulatedTarget::new();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user