scsi: Add an file-based target implementation

This implements the previously defined interface by emulating the commands
against a file-backed block device.

The vast majority of this work was done by Gaelan Steele as part of a
GSoC project [1][2].

[1] https://github.com/rust-vmm/vhost-device/pull/4
[2] https://gist.github.com/Gaelan/febec4e4606e1320026a0924c3bf74d0

Co-developed-by: Erik Schilling <erik.schilling@linaro.org>
Signed-off-by: Erik Schilling <erik.schilling@linaro.org>
Signed-off-by: Gaelan Steele <gbs@canishe.com>
This commit is contained in:
Gaelan Steele 2023-03-08 16:00:10 +01:00 committed by Viresh Kumar
parent a72a0a74e0
commit be1eaf3f79
10 changed files with 1586 additions and 1 deletions

View File

@ -1 +1,3 @@
pub mod scsi;
// We do not use any of this yet
#[allow(dead_code)]
mod scsi;

View File

@ -0,0 +1,632 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{
convert::{TryFrom, TryInto},
fs::File,
io::{self, Read, Write},
num::{NonZeroU32, NonZeroU64, TryFromIntError},
ops::{Add, Div, Mul, Sub},
os::unix::prelude::*,
};
use log::{debug, error, warn};
use super::{
command::{
parse_opcode, CommandType, LunSpecificCommand, ModePageSelection, ModeSensePageControl,
ParseOpcodeResult, ReportSupportedOpCodesMode, SenseFormat, VpdPage, OPCODES,
},
mode_page::ModePage,
response_data::{respond_standard_inquiry_data, SilentlyTruncate},
target::{LogicalUnit, LunRequest},
};
use crate::scsi::{sense, CmdError, CmdOutput, TaskAttr};
pub(crate) enum MediumRotationRate {
Unreported,
NonRotating,
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub(crate) struct ByteOffset(u64);
impl From<u64> for ByteOffset {
fn from(value: u64) -> Self {
ByteOffset(value)
}
}
impl From<ByteOffset> for u64 {
fn from(value: ByteOffset) -> Self {
value.0
}
}
impl Div<BlockSize> for ByteOffset {
type Output = BlockOffset;
fn div(self, rhs: BlockSize) -> Self::Output {
BlockOffset(self.0 / NonZeroU64::from(rhs.0))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub(crate) struct BlockSize(NonZeroU32);
impl From<BlockSize> for u32 {
fn from(value: BlockSize) -> Self {
u32::from(value.0)
}
}
impl TryFrom<u32> for BlockSize {
type Error = TryFromIntError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(BlockSize(NonZeroU32::try_from(value)?))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub(crate) struct BlockOffset(u64);
impl From<BlockOffset> for u64 {
fn from(value: BlockOffset) -> Self {
value.0
}
}
impl From<u64> for BlockOffset {
fn from(value: u64) -> Self {
BlockOffset(value)
}
}
impl Add<BlockOffset> for BlockOffset {
type Output = BlockOffset;
fn add(self, rhs: BlockOffset) -> Self::Output {
BlockOffset(self.0 + rhs.0)
}
}
impl Sub<BlockOffset> for BlockOffset {
type Output = Self;
fn sub(self, rhs: BlockOffset) -> Self::Output {
BlockOffset(self.0 - rhs.0)
}
}
impl Mul<BlockSize> for BlockOffset {
type Output = ByteOffset;
fn mul(self, rhs: BlockSize) -> Self::Output {
ByteOffset(self.0 * u64::from(NonZeroU64::from(rhs.0)))
}
}
pub(crate) trait BlockDeviceBackend: Send + Sync {
fn read_exact_at(&mut self, buf: &mut [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<()>;
}
pub(crate) struct FileBackend {
file: File,
block_size: BlockSize,
}
impl FileBackend {
pub fn new(file: File) -> Self {
Self {
file,
block_size: BlockSize::try_from(512).expect("512 is valid BlockSize"),
}
}
}
impl BlockDeviceBackend for FileBackend {
fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> io::Result<()> {
self.file.read_exact_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);
Ok(len / self.block_size)
}
fn block_size(&self) -> BlockSize {
self.block_size
}
fn sync(&mut self) -> io::Result<()> {
self.file.sync_data()
}
}
pub(crate) struct BlockDevice<T: BlockDeviceBackend> {
backend: T,
write_protected: bool,
rotation_rate: MediumRotationRate,
}
impl<T: BlockDeviceBackend> BlockDevice<T> {
pub(crate) const fn new(backend: T) -> Self {
Self {
backend,
write_protected: false,
rotation_rate: MediumRotationRate::Unreported,
}
}
fn read_blocks(&mut self, lba: BlockOffset, blocks: BlockOffset) -> io::Result<Vec<u8>> {
// TODO: Ideally, this would be a read_vectored directly into guest
// address space. Instead, we have an allocation and several copies.
let mut ret = vec![
0;
usize::try_from(u64::from(blocks * self.backend.block_size()))
.expect("block length in bytes should fit usize")
];
self.backend
.read_exact_at(&mut ret[..], lba * self.backend.block_size())?;
Ok(ret)
}
pub fn set_write_protected(&mut self, wp: bool) {
self.write_protected = wp;
}
pub fn set_solid_state(&mut self, rotation_rate: MediumRotationRate) {
self.rotation_rate = rotation_rate;
}
}
impl<T: BlockDeviceBackend> LogicalUnit for BlockDevice<T> {
fn execute_command(
&mut self,
data_in: &mut SilentlyTruncate<&mut dyn Write>,
_data_out: &mut dyn Read,
req: LunRequest,
command: LunSpecificCommand,
) -> Result<CmdOutput, CmdError> {
if req.crn != 0 {
// CRN is a weird bit of the protocol we wouldn't ever expect to be used over
// virtio-scsi; but it's allowed to set it non-zero
warn!("Received non-zero CRN: {}", req.crn);
}
if req.task_attr != TaskAttr::Simple {
// virtio-scsi spec allows us to treat all task attrs as SIMPLE.
warn!("Ignoring non-simple task attr of {:?}", req.task_attr);
}
if req.prio != 0 {
// My reading of SAM-6 is that priority is purely advisory, so it's fine to
// ignore it.
warn!("Ignoring non-zero priority of {}.", req.prio);
}
if req.naca {
// We don't support NACA, and say as much in our INQUIRY data, so if
// we get it that's an error.
warn!("Driver set NACA bit, which is unsupported.");
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
}
debug!("Incoming command: {:?}", command);
match command {
LunSpecificCommand::TestUnitReady => Ok(CmdOutput::ok()),
LunSpecificCommand::ReadCapacity10 => {
match self.backend.size_in_blocks() {
Ok(size) => {
// READ CAPACITY (10) returns a 32-bit LBA, which may not be enough. If it
// isn't, we're supposed to return 0xffff_ffff and hope the driver gets the
// memo and uses the newer READ CAPACITY (16).
// n.b. this is the last block, ie (length-1), not length
let final_block: u32 = u64::from(size - BlockOffset(1))
.try_into()
.unwrap_or(0xffff_ffff);
let block_size: u32 = u32::from(self.backend.block_size());
data_in
.write_all(&u32::to_be_bytes(final_block))
.map_err(CmdError::DataIn)?;
data_in
.write_all(&u32::to_be_bytes(block_size))
.map_err(CmdError::DataIn)?;
Ok(CmdOutput::ok())
}
Err(e) => {
error!("Error getting image size: {}", e);
// TODO: Is this a reasonable sense code to send?
Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR))
}
}
}
LunSpecificCommand::ReadCapacity16 => {
match self.backend.size_in_blocks() {
Ok(size) => {
// n.b. this is the last block, ie (length-1), not length
let final_block = u64::from(size - BlockOffset(1));
let block_size = u32::from(self.backend.block_size());
data_in
.write_all(&u64::to_be_bytes(final_block))
.map_err(CmdError::DataIn)?;
data_in
.write_all(&u32::to_be_bytes(block_size))
.map_err(CmdError::DataIn)?;
// no protection stuff; 1-to-1 logical/physical blocks
data_in.write_all(&[0, 0]).map_err(CmdError::DataIn)?;
// top 2 bits: thin provisioning stuff; other 14 bits are lowest
// aligned LBA, which is zero
data_in
.write_all(&[0b1100_0000, 0])
.map_err(CmdError::DataIn)?;
// reserved
data_in.write_all(&[0; 16]).map_err(CmdError::DataIn)?;
Ok(CmdOutput::ok())
}
Err(e) => {
error!("Error getting image size: {}", e);
// TODO: Is this a reasonable sense code to send?
Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR))
}
}
}
LunSpecificCommand::ModeSense6 { mode_page, pc, dbd } => {
// we use this for the pages array if we only need a single element; lifetime
// rules mean it has to be declared here
let single_page_array: [ModePage; 1];
let pages = match mode_page {
ModePageSelection::Single(x) => {
single_page_array = [x];
&single_page_array
}
ModePageSelection::AllPageZeros => ModePage::ALL_ZERO,
};
let pages_len: u32 = pages.iter().map(|x| u32::from(x.page_length() + 2)).sum();
// SPC-6r05, 7.5.6: "Logical units that support more than 256 bytes of block
// descriptors and mode pages should implement ten-byte mode commands. The MODE
// DATA LENGTH field in the six-byte CDB header limits the transferred data to
// 256 bytes."
// Unclear what exactly we're supposed to do if we have more than 256 bytes of
// mode pages and get sent a MODE SENSE (6). In any case, we don't at the
// moment; if we ever get that much, this unwrap() will start
// crashing us and we can figure out what to do.
let pages_len = u8::try_from(pages_len).unwrap();
// mode parameter header
data_in
.write_all(&[
pages_len + 3, // size in bytes after this one
0, // medium type - 0 for SBC
if self.write_protected {
0b1001_0000 // WP, support DPOFUA
} else {
0b0001_0000 // support DPOFUA
},
0, // block desc length
])
.map_err(CmdError::DataIn)?;
if !dbd {
// TODO: Block descriptors are optional, so we currently
// don't provide them. Does any driver
// actually use them?
}
for page in pages {
match pc {
ModeSensePageControl::Current | ModeSensePageControl::Default => {
page.write(data_in).map_err(CmdError::DataIn)?;
}
ModeSensePageControl::Changeable => {
// SPC-6 6.14.3: "If the logical unit does not
// implement changeable parameters mode pages and
// the device server receives a MODE SENSE command
// with 01b in the PC field, then the device server
// shall terminate the command with CHECK CONDITION
// status, with the sense key set to ILLEGAL
// REQUEST, and the additional sense code set to
// INVALID FIELD IN CDB."
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
}
ModeSensePageControl::Saved => {
return Ok(CmdOutput::check_condition(
sense::SAVING_PARAMETERS_NOT_SUPPORTED,
))
}
}
}
Ok(CmdOutput::ok())
}
LunSpecificCommand::Read10 {
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");
}
if fua {
// Somewhat weirdly, SCSI supports FUA on reads. Here's the
// key bit: "A force unit access (FUA) bit set to one
// specifies that the device server shall read the logical
// blocks from… the medium. If the FUA bit is set to one
// and a volatile cache contains a more recent version of a
// logical block than… the medium, then, before reading the
// logical block, the device server shall write the logical
// block to… the medium."
// I guess the idea is that you can read something back, and
// be absolutely sure what you just read will persist.
// So for our purposes, we need to make sure whatever we
// return has been saved to disk. fsync()ing the whole image
// is a bit blunt, but does the trick.
if let Err(e) = self.backend.sync() {
error!("Error syncing file: {}", e);
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
}
}
// Ignore group number: AFAICT, it's for separating reads from different
// workloads in performance metrics, and we don't report anything like that
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.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 read_result = self.read_blocks(lba, transfer_length);
match read_result {
Ok(bytes) => {
data_in.write_all(&bytes[..]).map_err(CmdError::DataIn)?;
Ok(CmdOutput::ok())
}
Err(e) => {
error!("Error reading image: {}", e);
Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR))
}
}
}
LunSpecificCommand::Inquiry(page_code) => {
// top 3 bits 0: peripheral device code = exists and ready
// bottom 5 bits 0: device type = block device
data_in.write_all(&[0]).map_err(CmdError::DataIn)?;
if let Some(code) = page_code {
let mut out = vec![];
match code {
VpdPage::SupportedVpdPages => {
out.push(VpdPage::SupportedVpdPages.into());
out.push(VpdPage::BlockDeviceCharacteristics.into());
out.push(VpdPage::LogicalBlockProvisioning.into());
}
VpdPage::BlockDeviceCharacteristics => {
let rotation_rate: u16 = match self.rotation_rate {
MediumRotationRate::Unreported => 0,
MediumRotationRate::NonRotating => 1,
};
out.extend_from_slice(&rotation_rate.to_be_bytes());
// nothing worth setting in the rest
out.extend_from_slice(&[0; 58]);
}
VpdPage::LogicalBlockProvisioning => {
out.push(0); // don't support threshold sets
out.push(0b1110_0100); // support unmapping w/ UNMAP
// and WRITE SAME (10 & 16),
// don't support anchored
// LBAs or group descriptors
out.push(0b0000_0010); // thin provisioned
out.push(0); // no threshold % support
}
_ => return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB)),
}
data_in
.write_all(&[code.into()])
.map_err(CmdError::DataIn)?;
data_in
.write_all(
&u16::try_from(out.len())
.expect("VPD page < 2^16 bits")
.to_be_bytes(),
)
.map_err(CmdError::DataIn)?;
data_in.write_all(&out).map_err(CmdError::DataIn)?;
} else {
respond_standard_inquiry_data(data_in).map_err(CmdError::DataIn)?;
}
Ok(CmdOutput::ok())
}
LunSpecificCommand::ReportSupportedOperationCodes { rctd, mode } => {
// helpers for output data format
fn one_command_supported(
data_in: &mut impl Write,
ty: CommandType,
) -> io::Result<()> {
data_in.write_all(&[0])?; // unused flags
data_in.write_all(&[0b0000_0011])?; // supported, don't set a bunch of flags
let tpl = ty.cdb_template();
data_in.write_all(
&u16::try_from(tpl.len())
.expect("length of TPL to be same as CDB")
.to_be_bytes(),
)?;
data_in.write_all(tpl)?;
Ok(())
}
fn one_command_not_supported(data_in: &mut impl Write) -> io::Result<()> {
data_in.write_all(&[0])?; // unused flags
data_in.write_all(&[0b0000_0001])?; // not supported
data_in.write_all(&[0; 2])?; // cdb len
Ok(())
}
fn timeout_descriptor(data_in: &mut impl Write) -> io::Result<()> {
// timeout descriptor
data_in.write_all(&0xa_u16.to_be_bytes())?; // len
data_in.write_all(&[0, 0])?; // reserved, cmd specific
data_in.write_all(&0_u32.to_be_bytes())?;
data_in.write_all(&0_u32.to_be_bytes())?;
Ok(())
}
match mode {
ReportSupportedOpCodesMode::All => {
let cmd_len = if rctd { 20 } else { 8 };
let len = u32::try_from(OPCODES.len() * cmd_len)
.expect("less than (2^32 / 20) ~= 2^27 opcodes");
data_in
.write_all(&len.to_be_bytes())
.map_err(CmdError::DataIn)?;
for &(ty, (opcode, sa)) in OPCODES {
data_in.write_all(&[opcode]).map_err(CmdError::DataIn)?;
data_in.write_all(&[0]).map_err(CmdError::DataIn)?; // reserved
data_in
.write_all(&sa.unwrap_or(0).to_be_bytes())
.map_err(CmdError::DataIn)?;
data_in.write_all(&[0]).map_err(CmdError::DataIn)?; // reserved
let ctdp: u8 = if rctd { 0b10 } else { 0b00 };
let servactv = u8::from(sa.is_some());
data_in
.write_all(&[ctdp | servactv])
.map_err(CmdError::DataIn)?;
data_in
.write_all(
&u16::try_from(ty.cdb_template().len())
.expect("length of TPL to be same as CDB")
.to_be_bytes(),
)
.map_err(CmdError::DataIn)?;
if rctd {
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
}
}
}
ReportSupportedOpCodesMode::OneCommand(opcode) => match parse_opcode(opcode) {
ParseOpcodeResult::Command(ty) => {
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
if rctd {
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
}
}
ParseOpcodeResult::ServiceAction(_) => {
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
}
ParseOpcodeResult::Invalid => {
warn!("Reporting that we don't support command {:#2x}. It might be worth adding.", opcode);
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
}
},
ReportSupportedOpCodesMode::OneServiceAction(opcode, sa) => {
match parse_opcode(opcode) {
ParseOpcodeResult::Command(_) => {
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
}
ParseOpcodeResult::ServiceAction(unparsed_sa) => {
if let Some(ty) = unparsed_sa.parse(sa) {
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
if rctd {
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
}
} else {
warn!("Reporting that we don't support command {:#2x}/{:#2x}. It might be worth adding.", opcode, sa);
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
}
}
ParseOpcodeResult::Invalid => {
// the spec isn't super clear what we're supposed to do here, but I
// think an invalid opcode is one for which our implementation
// "does not implement service actions", so we say invalid field in
// CDB
warn!("Reporting that we don't support command {:#2x}/{:#2x}. It might be worth adding.", opcode, sa);
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
}
}
}
ReportSupportedOpCodesMode::OneCommandOrServiceAction(opcode, sa) => {
match parse_opcode(opcode) {
ParseOpcodeResult::Command(ty) => {
if sa == 0 {
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
if rctd {
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
}
} else {
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
}
}
ParseOpcodeResult::ServiceAction(unparsed_sa) => {
if let Some(ty) = unparsed_sa.parse(sa) {
one_command_supported(data_in, ty).map_err(CmdError::DataIn)?;
if rctd {
timeout_descriptor(data_in).map_err(CmdError::DataIn)?;
}
} else {
warn!("Reporting that we don't support command {:#2x}/{:#2x}. It might be worth adding.", opcode, sa);
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
}
}
ParseOpcodeResult::Invalid => {
warn!("Reporting that we don't support command {:#2x}[/{:#2x}]. It might be worth adding.", opcode, sa);
one_command_not_supported(data_in).map_err(CmdError::DataIn)?;
}
}
}
}
Ok(CmdOutput::ok())
}
LunSpecificCommand::RequestSense(format) => {
match format {
SenseFormat::Fixed => {
data_in
.write_all(&sense::NO_ADDITIONAL_SENSE_INFORMATION.to_fixed_sense())
.map_err(CmdError::DataIn)?;
Ok(CmdOutput::ok())
}
SenseFormat::Descriptor => {
// Don't support desciptor format.
Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
}
}
}
}
}
}

View File

@ -0,0 +1,577 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
//! Data structures and parsing code for SCSI commands. A rough overview:
//! We need to deal with opcodes in two places: in parsing commands themselves,
//! and in implementing REPORT SUPPORTED OPERATION CODES. Therefore, we parse
//! commands in two steps. First, we parse the opcode (and sometimes service
//! action) into a `CommandType` (a C-style enum containing just the commands,
//! not their parameters), then using that, we parse the rest of the CDB and
//! obtain a `Cdb`, which consists of a `Command`, an enum representing a
//! command and its parameters, along with some fields shared across many or all
//! commands.
use std::convert::{TryFrom, TryInto};
use log::warn;
use num_enum::TryFromPrimitive;
use crate::scsi::emulation::mode_page::ModePage;
/// One of the modes supported by SCSI's REPORT LUNS command.
#[derive(PartialEq, Eq, TryFromPrimitive, Debug, Copy, Clone)]
#[repr(u8)]
pub(crate) enum ReportLunsSelectReport {
NoWellKnown = 0x0,
WellKnownOnly = 0x1,
All = 0x2,
Administrative = 0x10,
TopLevel = 0x11,
SameConglomerate = 0x12,
}
/// A type of "vital product data" page returned by SCSI's INQUIRY command.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub(crate) enum VpdPage {
Ascii(u8),
Ata, // *
BlockDeviceCharacteristics, // *
BlockDeviceCharacteristicsExt,
BlockLimits, // *
BlockLimitsExt,
CfaProfile,
DeviceConstituents,
DeviceIdentification, // *
ExtendedInquiry,
FormatPresets,
LogicalBlockProvisioning, // *
ManagementNetworkAddresses,
ModePagePolicy,
PowerCondition,
PowerConsumption,
PortocolSpecificLogicalUnit,
ProtocolSpecificPort,
Referrals,
ScsiFeatureSets,
ScsiPorts,
SoftwareInterfaceIdentification,
SupportedVpdPages, // *
ThirdPartyCopy,
UnitSerialNumber, // *
ZonedBlockDeviceCharacteristics, // *
}
// starred ones are ones Linux will use if available
#[derive(PartialEq, Eq, TryFromPrimitive, Debug, Copy, Clone)]
#[repr(u8)]
pub(crate) enum ModeSensePageControl {
Current = 0b00,
Changeable = 0b01,
Default = 0b10,
Saved = 0b11,
}
impl TryFrom<u8> for VpdPage {
type Error = ();
fn try_from(val: u8) -> Result<Self, ()> {
match val {
0x00 => Ok(Self::SupportedVpdPages),
0x1..=0x7f => Ok(Self::Ascii(val)),
0x80 => Ok(Self::UnitSerialNumber),
0x83 => Ok(Self::DeviceIdentification),
0x84 => Ok(Self::SoftwareInterfaceIdentification),
0x85 => Ok(Self::ManagementNetworkAddresses),
0x86 => Ok(Self::ExtendedInquiry),
0x87 => Ok(Self::ModePagePolicy),
0x88 => Ok(Self::ScsiPorts),
0x89 => Ok(Self::Ata),
0x8a => Ok(Self::PowerCondition),
0x8b => Ok(Self::DeviceConstituents),
0x8c => Ok(Self::CfaProfile),
0x8d => Ok(Self::PowerConsumption),
0x8f => Ok(Self::ThirdPartyCopy),
0x90 => Ok(Self::PortocolSpecificLogicalUnit),
0x91 => Ok(Self::ProtocolSpecificPort),
0x92 => Ok(Self::ScsiFeatureSets),
0xb0 => Ok(Self::BlockLimits),
0xb1 => Ok(Self::BlockDeviceCharacteristics),
0xb2 => Ok(Self::LogicalBlockProvisioning),
0xb3 => Ok(Self::Referrals),
0xb5 => Ok(Self::BlockDeviceCharacteristicsExt),
0xb6 => Ok(Self::ZonedBlockDeviceCharacteristics),
0xb7 => Ok(Self::BlockLimitsExt),
0xb8 => Ok(Self::FormatPresets),
_ => Err(()),
}
}
}
impl From<VpdPage> for u8 {
fn from(pc: VpdPage) -> Self {
match pc {
VpdPage::Ascii(val) => val,
VpdPage::Ata => 0x89,
VpdPage::BlockDeviceCharacteristics => 0xb1,
VpdPage::BlockDeviceCharacteristicsExt => 0xb5,
VpdPage::BlockLimits => 0xb0,
VpdPage::BlockLimitsExt => 0xb7,
VpdPage::CfaProfile => 0x8c,
VpdPage::DeviceConstituents => 0x8b,
VpdPage::DeviceIdentification => 0x83,
VpdPage::ExtendedInquiry => 0x86,
VpdPage::FormatPresets => 0xb8,
VpdPage::LogicalBlockProvisioning => 0xb2,
VpdPage::ManagementNetworkAddresses => 0x85,
VpdPage::ModePagePolicy => 0x87,
VpdPage::PowerCondition => 0x8a,
VpdPage::PowerConsumption => 0x8d,
VpdPage::PortocolSpecificLogicalUnit => 0x90,
VpdPage::ProtocolSpecificPort => 0x91,
VpdPage::Referrals => 0xb3,
VpdPage::ScsiFeatureSets => 0x92,
VpdPage::ScsiPorts => 0x88,
VpdPage::SoftwareInterfaceIdentification => 0x84,
VpdPage::SupportedVpdPages => 0x00,
VpdPage::ThirdPartyCopy => 0x8f,
VpdPage::UnitSerialNumber => 0x80,
VpdPage::ZonedBlockDeviceCharacteristics => 0xb6,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum SenseFormat {
Fixed,
Descriptor,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum ModePageSelection {
AllPageZeros,
Single(ModePage),
}
#[derive(Debug)]
pub(crate) enum LunIndependentCommand {
ReportLuns(ReportLunsSelectReport),
}
#[derive(Debug)]
pub(crate) enum LunSpecificCommand {
Inquiry(Option<VpdPage>),
ModeSense6 {
pc: ModeSensePageControl,
mode_page: ModePageSelection,
/// Disable block descriptors
dbd: bool,
},
Read10 {
/// 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 {
/// SCSI RCTD bit: whether we should include timeout descriptors.
rctd: bool,
mode: ReportSupportedOpCodesMode,
},
RequestSense(SenseFormat),
TestUnitReady,
}
#[derive(Debug)]
pub(crate) enum Command {
LunIndependentCommand(LunIndependentCommand),
LunSpecificCommand(LunSpecificCommand),
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum CommandType {
Inquiry,
ModeSense6,
Read10,
ReadCapacity10,
ReadCapacity16,
ReportLuns,
ReportSupportedOperationCodes,
RequestSense,
TestUnitReady,
}
pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
(CommandType::TestUnitReady, (0x0, None)),
(CommandType::RequestSense, (0x3, None)),
(CommandType::Inquiry, (0x12, None)),
(CommandType::ModeSense6, (0x1a, None)),
(CommandType::ReadCapacity10, (0x25, None)),
(CommandType::Read10, (0x28, None)),
(CommandType::ReadCapacity16, (0x9e, Some(0x10))),
(CommandType::ReportLuns, (0xa0, None)),
(
CommandType::ReportSupportedOperationCodes,
(0xa3, Some(0xc)),
),
];
#[derive(Debug, Clone, Copy)]
pub(crate) struct UnparsedServiceAction(u8);
impl UnparsedServiceAction {
pub fn parse(self, service_action: u16) -> Option<CommandType> {
OPCODES
.iter()
.find(|(_, opcode)| *opcode == (self.0, Some(service_action)))
.map(|&(ty, _)| ty)
}
}
/// See `parse_opcode`
#[derive(Debug, Clone, Copy)]
pub(crate) enum ParseOpcodeResult {
/// The opcode represents a single command.
Command(CommandType),
/// The opcode requires a service action.
ServiceAction(UnparsedServiceAction),
/// The opcode is invalid.
Invalid,
}
/// Determine the command that corresponds to a SCSI opcode.
///
/// This is a little weird. Most SCSI commands are just identified by the
/// opcode - the first byte of the CDB - but some opcodes require a second
/// byte, called the service action. Generally, each distinct service action
/// value is treated as a first-class command. But there's some weirdness
/// around parsing, especially with invalid commands: sometimes, we're
/// expected to behave differently for a valid opcode with an invalid
/// service action vs an invalid opcode.
///
/// To allow for this, we have a two-step parsing API. First, a caller
/// calls `parse_opcode` with the first byte of the CDB. This could return
/// three things:
/// - `Command`: the opcode corresponded to a single-byte command; we're done.
/// - `Invalid`: the opcode isn't recognized at all; we're done.
/// - `ServiceAction`: the opcode is the first byte of a service action; the
/// caller needs to call .parse() on the `UnparsedServiceAction` we returned
/// with the service action byte.
pub(crate) fn parse_opcode(opcode: u8) -> ParseOpcodeResult {
let found = OPCODES.iter().find(|(_, (x, _))| *x == opcode);
match found {
Some(&(ty, (_, None))) => ParseOpcodeResult::Command(ty),
Some((_, (_, Some(_)))) => {
// we found some service action that uses this opcode; so this is a
// service action opcode, and we need the service action
ParseOpcodeResult::ServiceAction(UnparsedServiceAction(opcode))
}
None => ParseOpcodeResult::Invalid,
}
}
impl CommandType {
fn from_cdb(cdb: &[u8]) -> Result<Self, ParseError> {
// TODO: Variable-length CDBs put the service action in a different
// place. This'll need to change if we ever support those. IIRC, Linux
// doesn't ever use them, so it may never be relevant.
match parse_opcode(cdb[0]) {
ParseOpcodeResult::Command(ty) => Ok(ty),
ParseOpcodeResult::ServiceAction(sa) => sa
.parse(u16::from(cdb[1] & 0b0001_1111))
.ok_or(ParseError::InvalidField),
ParseOpcodeResult::Invalid => Err(ParseError::InvalidCommand),
}
}
/// Return the SCSI "CDB usage data" (see SPC-6 6.34.3) for this command
/// type.
///
/// Basically, this consists of a structure the size of the CDB for the
/// command, starting with the opcode and service action (if any), then
/// proceeding to a bitmap of fields we recognize.
pub const fn cdb_template(self) -> &'static [u8] {
match self {
Self::TestUnitReady => &[
0x0,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0100,
],
Self::RequestSense => &[
0x3,
0b0000_0001,
0b0000_0000,
0b0000_0000,
0b1111_1111,
0b0000_0100,
],
Self::ReportLuns => &[
0xa0,
0b0000_0000,
0b1111_1111,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b0000_0000,
0b0000_0100,
],
Self::ReadCapacity10 => &[
0x25,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0100,
],
Self::ReadCapacity16 => &[
0x9e,
0x10,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b0000_0000,
0b0000_0100,
],
Self::ModeSense6 => &[
0x1a,
0b0000_1000,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b0000_0100,
],
Self::Read10 => &[
0x28,
0b1111_1100,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b0011_1111,
0b1111_1111,
0b1111_1111,
0b0000_0100,
],
Self::Inquiry => &[
0x12,
0b0000_0001,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b0000_0100,
],
Self::ReportSupportedOperationCodes => &[
0xa3,
0xc,
0b1000_0111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b0000_0000,
0b0000_0100,
],
}
}
}
#[derive(Debug)]
pub(crate) struct Cdb {
pub command: Command,
pub allocation_length: Option<u32>,
pub naca: bool,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum ParseError {
/// The opcode (specifically the first byte of the CDB) is unknown, i.e. we
/// should respond with INVALID COMMAND OPERATION CODE
InvalidCommand,
/// Another field of the CDB (including the service action, if any) is
/// invalid, i.e. we should respond with INVALID FIELD IN CDB.
InvalidField,
/// The CDB has fewer bytes than necessary for its opcode.
TooSmall,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum ReportSupportedOpCodesMode {
All,
OneCommand(u8),
OneServiceAction(u8, u16),
OneCommandOrServiceAction(u8, u16),
}
impl Cdb {
// TODO: do we want to ensure reserved fields are 0? SCSI allows, but
// doesn't require, us to do so.
pub(crate) fn parse(cdb: &[u8]) -> Result<Self, ParseError> {
let ct = CommandType::from_cdb(cdb)?;
if cdb.len() < ct.cdb_template().len() {
return Err(ParseError::TooSmall);
}
// Shrink the cdb down to its size, so accidentally accessing fields past the
// length panics
let cdb = &cdb[..ct.cdb_template().len()];
// unwraps below are safe: they're just calling TryFrom to convert from slices
// to fixed-size arrays; in each case, we're using constant indexes and we
// verified above that they're in bounds, so none of them can panic at runtime
match ct {
CommandType::Inquiry => {
// INQUIRY
let evpd = match cdb[1] {
0 => false,
1 => true,
// obselete or reserved bits set
_ => return Err(ParseError::InvalidField),
};
let page_code_raw = cdb[2];
let page_code = match (evpd, page_code_raw) {
(false, 0) => None,
(true, pc) => Some(pc.try_into().map_err(|_| ParseError::InvalidField)?),
(false, _) => return Err(ParseError::InvalidField),
};
Ok(Self {
command: Command::LunSpecificCommand(LunSpecificCommand::Inquiry(page_code)),
allocation_length: Some(u32::from(u16::from_be_bytes(
cdb[3..5].try_into().unwrap(),
))),
naca: (cdb[5] & 0b0000_0100) != 0,
})
}
CommandType::ModeSense6 => {
let dbd = match cdb[1] {
0b0000_1000 => true,
0b0000_0000 => false,
_ => return Err(ParseError::InvalidField),
};
let pc = (cdb[2] & 0b1100_0000) >> 6;
let page_code = cdb[2] & 0b0011_1111;
let subpage_code = cdb[3];
let mode: ModePageSelection = match (page_code, subpage_code) {
(0x8, 0x0) => ModePageSelection::Single(ModePage::Caching),
(0x3f, 0x0) => ModePageSelection::AllPageZeros,
_ => {
warn!(
"Rejecting request for unknown mode page {:#2x}/{:#2x}.",
page_code, subpage_code
);
return Err(ParseError::InvalidField);
}
};
Ok(Self {
command: Command::LunSpecificCommand(LunSpecificCommand::ModeSense6 {
pc: pc.try_into().map_err(|_| ParseError::InvalidField)?,
mode_page: mode,
dbd,
}),
allocation_length: Some(u32::from(cdb[4])),
naca: (cdb[5] & 0b0000_0100) != 0,
})
}
CommandType::Read10 => {
if cdb[1] & 0b1110_0100 != 0 {
// Features (protection and rebuild assist) 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::Read10 {
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,
naca: (cdb[9] & 0b0000_0100) != 0,
}),
CommandType::ReadCapacity16 => Ok(Self {
command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity16),
allocation_length: Some(u32::from_be_bytes(cdb[10..14].try_into().unwrap())),
naca: (cdb[15] & 0b0000_0100) != 0,
}),
CommandType::ReportLuns => Ok(Self {
command: Command::LunIndependentCommand(LunIndependentCommand::ReportLuns(
cdb[2].try_into().map_err(|_| ParseError::InvalidField)?,
)),
allocation_length: Some(u32::from_be_bytes(cdb[6..10].try_into().unwrap())),
naca: (cdb[9] & 0b0000_0100) != 0,
}),
CommandType::ReportSupportedOperationCodes => {
let rctd = cdb[2] & 0b1000_0000 != 0;
let mode = match cdb[2] & 0b0000_0111 {
0b000 => ReportSupportedOpCodesMode::All,
0b001 => ReportSupportedOpCodesMode::OneCommand(cdb[3]),
0b010 => ReportSupportedOpCodesMode::OneServiceAction(
cdb[3],
u16::from_be_bytes(cdb[4..6].try_into().unwrap()),
),
0b011 => ReportSupportedOpCodesMode::OneCommandOrServiceAction(
cdb[3],
u16::from_be_bytes(cdb[4..6].try_into().unwrap()),
),
_ => return Err(ParseError::InvalidField),
};
Ok(Self {
command: Command::LunSpecificCommand(
LunSpecificCommand::ReportSupportedOperationCodes { rctd, mode },
),
allocation_length: Some(u32::from_be_bytes(cdb[6..10].try_into().unwrap())),
naca: (cdb[11] & 0b0000_0100) != 0,
})
}
CommandType::RequestSense => {
let format = if cdb[1] & 0b0000_0001 == 1 {
SenseFormat::Descriptor
} else {
SenseFormat::Fixed
};
Ok(Self {
command: Command::LunSpecificCommand(LunSpecificCommand::RequestSense(format)),
allocation_length: Some(u32::from(cdb[4])),
naca: (cdb[5] & 0b0000_0100) != 0,
})
}
CommandType::TestUnitReady => Ok(Self {
command: Command::LunSpecificCommand(LunSpecificCommand::TestUnitReady),
allocation_length: None,
naca: (cdb[5] & 0b0000_0100) != 0,
}),
}
}
}

View File

@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::io::{Read, Write};
use super::{
command::{LunSpecificCommand, SenseFormat},
response_data::{respond_standard_inquiry_data, SilentlyTruncate},
target::{LogicalUnit, LunRequest},
};
use crate::scsi::{sense, CmdError, CmdError::DataIn, CmdOutput};
pub(crate) struct MissingLun;
impl LogicalUnit for MissingLun {
fn execute_command(
&mut self,
data_in: &mut SilentlyTruncate<&mut dyn Write>,
_data_out: &mut dyn Read,
_req: LunRequest,
cmd: LunSpecificCommand,
) -> Result<CmdOutput, CmdError> {
match cmd {
LunSpecificCommand::Inquiry(page_code) => {
// peripheral qualifier 0b011: logical unit not accessible
// device type 0x1f: unknown/no device type
data_in.write_all(&[0b0110_0000 | 0x1f]).map_err(DataIn)?;
match page_code {
Some(_) => {
// SPC-6 7.7.2: "If the PERIPHERAL QUALIFIER field is
// not set to 000b, the contents of the PAGE LENGTH
// field and the VPD parameters are outside the
// scope of this standard."
//
// Returning a 0 length and no data seems sensible enough.
data_in.write_all(&[0]).map_err(DataIn)?;
}
None => {
respond_standard_inquiry_data(data_in).map_err(DataIn)?;
}
}
Ok(CmdOutput::ok())
}
LunSpecificCommand::RequestSense(format) => {
match format {
SenseFormat::Fixed => {
data_in
.write_all(&sense::LOGICAL_UNIT_NOT_SUPPORTED.to_fixed_sense())
.map_err(DataIn)?;
Ok(CmdOutput::ok())
}
SenseFormat::Descriptor => {
// Don't support desciptor format.
Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
}
}
}
_ => Ok(CmdOutput::check_condition(
sense::LOGICAL_UNIT_NOT_SUPPORTED,
)),
}
}
}

View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
pub(crate) mod block_device;
mod command;
pub(crate) mod missing_lun;
pub(crate) mod mode_page;
mod response_data;
pub(crate) mod target;

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::io::{self, Write};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ModePage {
Caching,
}
impl ModePage {
pub(crate) const ALL_ZERO: &'static [Self] = &[Self::Caching];
pub(crate) const fn page_code(self) -> (u8, u8) {
match self {
Self::Caching => (0x8, 0),
}
}
pub(crate) const fn page_length(self) -> u8 {
match self {
Self::Caching => 0x12,
}
}
pub(crate) fn write(self, data_in: &mut impl Write) -> io::Result<()> {
assert_eq!(self.page_code().1, 0, "Subpages aren't supported yet.");
data_in.write_all(&[
self.page_code().0, // top 2 bits: no subpage, saving not supported
self.page_length(), // page length
])?;
match self {
Self::Caching => {
data_in.write_all(&[
// Writeback Cache Enable, lots of bits zero
// n.b. kernel logs will show WCE off; it always says
// that for read-only devices, which we are rn
0b0000_0100,
])?;
// various cache fine-tuning stuff we can't really control
data_in.write_all(&[0; 0x11])?;
}
}
Ok(())
}
}

View File

@ -0,0 +1,107 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
//! Some helpers for writing response data, shared between `BlockDevice` and
//! `MissingLun`
use std::{cmp::min, convert::TryFrom, io, io::Write};
/// A wrapper around a `Write` that silently truncates its input after a given
/// number of bytes. This matches the semantics of SCSI's ALLOCATION LENGTH
/// field; anything beyond the allocation length is silently omitted.
pub struct SilentlyTruncate<W: Write>(W, usize);
impl<W: Write> SilentlyTruncate<W> {
pub const fn new(writer: W, len: usize) -> Self {
Self(writer, len)
}
}
impl<W: Write> Write for SilentlyTruncate<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.1 == 0 {
// our goal is to silently fail, so once we've stopped actually
// writing, just pretend all writes work
return Ok(buf.len());
}
let len = min(buf.len(), self.1);
let buf = &buf[..len];
let written = self.0.write(buf)?;
self.1 -= written;
Ok(written)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}
fn encode_lun(lun: u16) -> [u8; 8] {
let lun = u8::try_from(lun).expect("more than 255 LUNs are currently unsupported");
[0, lun, 0, 0, 0, 0, 0, 0]
}
/// Write the response data for a REPORT LUNS command.
pub fn respond_report_luns<T>(data_in: &mut impl Write, luns: T) -> io::Result<()>
where
T: IntoIterator<Item = u16>,
T::IntoIter: ExactSizeIterator,
{
let iter = luns.into_iter();
data_in.write_all(
&(u32::try_from(iter.len() * 8))
.expect("less than 256 LUNS")
.to_be_bytes(),
)?;
data_in.write_all(&[0; 4])?; // reserved
for lun in iter {
data_in.write_all(&encode_lun(lun))?;
}
Ok(())
}
/// Write the response data for a standard (i.e. not VPD) inquiry, excluding the
/// first byte (the peripheal qualifier and device type).
pub fn respond_standard_inquiry_data(data_in: &mut impl Write) -> io::Result<()> {
// TODO: Feature bits here we might want to support:
// - NormACA
// - command queueing
data_in.write_all(&[
// various bits: not removable, not part of a
// conglomerate, no info on hotpluggability
0,
0x7, // version: SPC-6
// bits: don't support NormACA, support modern LUN format
// INQUIRY data version 2
0b0001_0000 | 0x2,
91, // additional INQURIY data length
// bunch of feature bits we don't support:
0,
0,
0,
])?;
// TODO: register this or another name with T10
data_in.write_all(b"rust-vmm")?;
data_in.write_all(b"vhost-user-scsi ")?;
data_in.write_all(b"v0 ")?;
// The Linux kernel doesn't request any more than this, so any data we return
// after this point is mostly academic.
data_in.write_all(&[0; 22])?;
let product_descs: &[u16; 8] = &[
0x00c0, // SAM-6 (no version claimed)
0x05c0, // SPC-5 (no version claimed)
0x0600, // SBC-4 (no version claimed)
0x0, 0x0, 0x0, 0x0, 0x0,
];
for desc in product_descs {
data_in.write_all(&desc.to_be_bytes())?;
}
data_in.write_all(&[0; 22])?;
Ok(())
}

View File

@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::convert::TryFrom;
use std::io::{Read, Write};
use log::error;
use super::{
command::{
Cdb, Command, LunIndependentCommand, LunSpecificCommand, ParseError, ReportLunsSelectReport,
},
missing_lun::MissingLun,
response_data::{respond_report_luns, SilentlyTruncate},
};
use crate::scsi::{sense, CmdError, CmdOutput, Request, Target, TaskAttr};
pub(crate) struct LunRequest {
pub _id: u64,
pub task_attr: TaskAttr,
pub crn: u8,
pub prio: u8,
pub _allocation_length: Option<u32>,
pub naca: bool,
}
/// A single logical unit of an emulated SCSI device.
pub(crate) trait LogicalUnit: Send + Sync {
/// Process a SCSI command sent to this logical unit.
///
/// # Return value
/// This function returns a Result, but it should return Err only in limited
/// circumstances: when something goes wrong at the transport level, such
/// as writes to `req.data_in` failing or `req.cdb` being too short.
/// Any other errors, such as invalid SCSI commands or I/O errors
/// accessing an underlying file, should result in an Ok return value
/// with a `CmdOutput` representing a SCSI-level error (i.e. CHECK
/// CONDITION status, and appropriate sense data).
fn execute_command(
&mut self,
data_in: &mut SilentlyTruncate<&mut dyn Write>,
data_out: &mut dyn Read,
parameters: LunRequest,
command: LunSpecificCommand,
) -> Result<CmdOutput, CmdError>;
}
/// A SCSI target implemented by emulating a device within vhost-user-scsi.
pub(crate) struct EmulatedTarget {
luns: Vec<Box<dyn LogicalUnit>>,
}
impl EmulatedTarget {
pub(crate) fn new() -> Self {
Self { luns: Vec::new() }
}
pub(crate) fn add_lun(&mut self, logical_unit: Box<dyn LogicalUnit>) {
self.luns.push(logical_unit);
}
pub(crate) fn luns(&self) -> impl Iterator<Item = u16> + ExactSizeIterator + '_ {
// unwrap is safe: we limit LUNs at 256
self.luns
.iter()
.enumerate()
.map(|(idx, _logical_unit)| u16::try_from(idx).unwrap())
}
}
impl Default for EmulatedTarget {
fn default() -> Self {
Self::new()
}
}
impl Target for EmulatedTarget {
fn execute_command(
&mut self,
lun: u16,
data_out: &mut dyn Read,
data_in: &mut dyn Write,
req: Request,
) -> Result<CmdOutput, CmdError> {
match Cdb::parse(req.cdb) {
Ok(cdb) => {
let mut data_in = SilentlyTruncate::new(
data_in,
cdb.allocation_length.map_or(usize::MAX, |x| x as usize),
);
match cdb.command {
Command::LunIndependentCommand(cmd) => match cmd {
LunIndependentCommand::ReportLuns(select_report) => {
match select_report {
ReportLunsSelectReport::NoWellKnown
| ReportLunsSelectReport::All => {
respond_report_luns(&mut data_in, self.luns())
.map_err(CmdError::DataIn)?;
}
ReportLunsSelectReport::WellKnownOnly
| ReportLunsSelectReport::Administrative
| ReportLunsSelectReport::TopLevel
| ReportLunsSelectReport::SameConglomerate => {
respond_report_luns(&mut data_in, vec![].into_iter())
.map_err(CmdError::DataIn)?;
}
}
Ok(CmdOutput::ok())
}
},
Command::LunSpecificCommand(cmd) => {
let req = LunRequest {
_id: req.id,
task_attr: req.task_attr,
crn: req.crn,
prio: req.prio,
_allocation_length: cdb.allocation_length,
naca: cdb.naca,
};
match self.luns.get_mut(lun as usize) {
Some(lun) => lun.execute_command(&mut data_in, data_out, req, cmd),
None => MissingLun.execute_command(&mut data_in, data_out, req, cmd),
}
}
}
}
Err(ParseError::InvalidCommand) => {
error!("Rejecting CDB for unknown command: {:?}", req.cdb);
Ok(CmdOutput::check_condition(
sense::INVALID_COMMAND_OPERATION_CODE,
))
}
// TODO: SCSI has a provision for INVALID FIELD IN CDB to include the
// index of the invalid field, but it's not clear if that's mandatory.
// In any case, QEMU omits it.
Err(ParseError::InvalidField) => {
error!("Rejecting CDB with invalid field: {:?}", req.cdb);
Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB))
}
Err(ParseError::TooSmall) => Err(CmdError::CdbTooShort),
}
}
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
pub mod emulation;
pub mod sense;
use std::io::{self, Read, Write};

View File

@ -22,6 +22,7 @@ impl SenseTriple {
const NO_SENSE: u8 = 0;
const MEDIUM_ERROR: u8 = 0x3;
const HARDWARE_ERROR: u8 = 0x4;
const ILLEGAL_REQUEST: u8 = 0x5;
pub const NO_ADDITIONAL_SENSE_INFORMATION: SenseTriple = SenseTriple(NO_SENSE, 0, 0);
@ -33,3 +34,4 @@ pub const LOGICAL_UNIT_NOT_SUPPORTED: SenseTriple = SenseTriple(ILLEGAL_REQUEST,
pub const SAVING_PARAMETERS_NOT_SUPPORTED: SenseTriple = SenseTriple(ILLEGAL_REQUEST, 0x39, 0x0);
pub const UNRECOVERED_READ_ERROR: SenseTriple = SenseTriple(MEDIUM_ERROR, 0x11, 0x0);
pub const TARGET_FAILURE: SenseTriple = SenseTriple(HARDWARE_ERROR, 0x44, 0x0);