diff --git a/crates/scsi/src/scsi/emulation/block_device.rs b/crates/scsi/src/scsi/emulation/block_device.rs index b21c833..117c66c 100644 --- a/crates/scsi/src/scsi/emulation/block_device.rs +++ b/crates/scsi/src/scsi/emulation/block_device.rs @@ -192,6 +192,20 @@ impl BlockDevice { Ok(()) } + fn write_same_block( + &mut self, + lba_start: BlockOffset, + block_count: BlockOffset, + buf: &[u8], + ) -> io::Result<()> { + let block_size = self.backend.block_size(); + for lba in u64::from(lba_start)..u64::from(lba_start + block_count) { + let lba = BlockOffset(lba); + self.backend.write_exact_at(buf, lba * block_size)?; + } + Ok(()) + } + pub fn set_write_protected(&mut self, wp: bool) { self.write_protected = wp; } @@ -485,6 +499,58 @@ impl LogicalUnit for BlockDevice { } } } + LunSpecificCommand::WriteSame16 { + lba, + number_of_logical_blocks, + anchor, + } => { + // We do not support block provisioning + if anchor { + return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB)); + } + + // This command can be used to unmap/discard a region of blocks... + // TODO: Do something smarter and punch holes into the backend, + // for now we will just write A LOT of zeros in a very inefficient way. + + 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::UNRECOVERED_READ_ERROR)); + } + }; + + let lba = BlockOffset(lba); + let number_of_logical_blocks = BlockOffset(number_of_logical_blocks.into()); + + if lba + number_of_logical_blocks > size { + return Ok(CmdOutput::check_condition( + sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + )); + } + + let mut buf = vec![ + 0; + usize::try_from(u32::from(self.backend.block_size())) + .expect("block_size should fit usize") + ]; + let read_result = data_out.read_exact(&mut buf); + if let Err(e) = read_result { + error!("Error reading from data_out: {}", e); + return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE)); + } + + let write_result = self.write_same_block(lba, number_of_logical_blocks, &buf); + + 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 diff --git a/crates/scsi/src/scsi/emulation/command.rs b/crates/scsi/src/scsi/emulation/command.rs index bdfed33..39fed99 100644 --- a/crates/scsi/src/scsi/emulation/command.rs +++ b/crates/scsi/src/scsi/emulation/command.rs @@ -183,6 +183,11 @@ pub(crate) enum LunSpecificCommand { lba: u32, transfer_length: u16, }, + WriteSame16 { + lba: u64, + number_of_logical_blocks: u32, + anchor: bool, + }, ReadCapacity10, ReadCapacity16, ReportSupportedOperationCodes { @@ -212,6 +217,7 @@ pub(crate) enum CommandType { RequestSense, TestUnitReady, Write10, + WriteSame16, } pub(crate) const OPCODES: &[(CommandType, (u8, Option))] = &[ @@ -222,6 +228,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option))] = &[ (CommandType::ReadCapacity10, (0x25, None)), (CommandType::Read10, (0x28, None)), (CommandType::Write10, (0x2a, None)), + (CommandType::WriteSame16, (0x93, None)), (CommandType::ReadCapacity16, (0x9e, Some(0x10))), (CommandType::ReportLuns, (0xa0, None)), ( @@ -397,6 +404,24 @@ impl CommandType { 0b1111_1111, 0b0000_0100, ], + Self::WriteSame16 => &[ + 0x93, + 0b1111_1001, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b0011_1111, + 0b0000_0100, + ], Self::Inquiry => &[ 0x12, 0b0000_0001, @@ -555,6 +580,24 @@ impl Cdb { naca: (cdb[9] & 0b0000_0100) != 0, }) } + CommandType::WriteSame16 => { + if cdb[1] & 0b1110_0001 != 0 { + warn!("Unsupported field in WriteSame16"); + // We neither support protections nor logical block provisioning + return Err(ParseError::InvalidField); + } + Ok(Self { + command: Command::LunSpecificCommand(LunSpecificCommand::WriteSame16 { + lba: u64::from_be_bytes(cdb[2..10].try_into().expect("lba should fit u64")), + number_of_logical_blocks: u32::from_be_bytes( + cdb[10..14].try_into().expect("block count should fit u32"), + ), + anchor: (cdb[1] & 0b0001_0000) != 0, + }), + allocation_length: None, + naca: (cdb[15] & 0b0000_0100) != 0, + }) + } CommandType::ReadCapacity10 => Ok(Self { command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity10), allocation_length: None, diff --git a/crates/scsi/src/scsi/emulation/tests/mod.rs b/crates/scsi/src/scsi/emulation/tests/mod.rs index f23e01a..8e8ffc1 100644 --- a/crates/scsi/src/scsi/emulation/tests/mod.rs +++ b/crates/scsi/src/scsi/emulation/tests/mod.rs @@ -327,6 +327,52 @@ fn test_write_10() { } } +#[test] +fn test_write_same_16() { + 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. + + backend + .write_exact_at(&[0xff; 512 * 6], BlockOffset::from(5) * block_size_512()) + .expect("Write should succeed"); + + let data_out = [0_u8; 512]; + + do_command_in( + &mut target, + &[ + 0x93, // WRITE SAME (16) + 0, // flags + 0, 0, 0, 0, 0, 0, 0, 5, // LBA: 5 + 0, 0, 0, 5, // tnumber of blocks: 5 + 0, // reserved, group # + 0, // control + ], + &data_out, + &[], + ); + + let mut buf = [0_u8; 512 * 5]; + backend + .read_exact_at(&mut buf, BlockOffset::from(5) * block_size_512()) + .expect("Reading should work"); + assert_eq!([0_u8; 512 * 5], buf, "5 sectors should have been zero'd"); + + let mut buf = [0_u8; 512]; + backend + .read_exact_at(&mut buf, BlockOffset::from(10) * block_size_512()) + .expect("Reading should work"); + assert_eq!( + [0xff_u8; 512], buf, + "sector after write should be left untouched" + ); +} + #[test] fn test_read_capacity_10() { let mut target = EmulatedTarget::new();