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:
Erik Schilling 2023-05-05 15:13:26 +02:00 committed by Viresh Kumar
parent 4c8a2bc3ac
commit 3adff11c94
4 changed files with 211 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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();