Having all the workspace crates under the crates/ directory is unnecessary. Rust documentation itself recommends all crates to be in the root directory: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#creating-the-second-package-in-the-workspace I paste the text content here, in case the online page ever changes or becomes unavailable: ## Creating the Second Package in the Workspace Next, let’s create another member package in the workspace and call it add_one. Change the top-level Cargo.toml to specify the add_one path in the members list: Filename: Cargo.toml [workspace] members = [ "adder", "add_one", ] Then generate a new library crate named add_one: $ cargo new add_one --lib Created library `add_one` package Your add directory should now have these directories and files: ├── Cargo.lock ├── Cargo.toml ├── add_one │ ├── Cargo.toml │ └── src │ └── lib.rs ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
520 lines
14 KiB
Rust
520 lines
14 KiB
Rust
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
|
|
|
#![cfg(test)]
|
|
|
|
mod bad_lun;
|
|
mod generic;
|
|
mod report_supported_operation_codes;
|
|
|
|
use std::{
|
|
fs::File,
|
|
io::Write,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
use tempfile::tempfile;
|
|
|
|
use super::{
|
|
block_device::{
|
|
BlockDevice, BlockDeviceBackend, BlockOffset, BlockSize, ByteOffset, FileBackend,
|
|
},
|
|
target::EmulatedTarget,
|
|
};
|
|
use crate::scsi::{
|
|
sense::{self, SenseTriple},
|
|
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())
|
|
}
|
|
|
|
fn test_image() -> FileBackend {
|
|
let mut f = tempfile().unwrap();
|
|
// generate 16 512-byte sectors, each of which consist of a single
|
|
// repeated hex character, i.e.
|
|
// sector 00: 0000000....0000
|
|
// sector 15: fffffff....ffff
|
|
for chr in b'0'..=b'9' {
|
|
f.write_all(&[chr; 512]).unwrap();
|
|
}
|
|
for chr in b'a'..=b'f' {
|
|
f.write_all(&[chr; 512]).unwrap();
|
|
}
|
|
FileBackend::new(f)
|
|
}
|
|
|
|
fn do_command_in_lun(
|
|
target: &mut EmulatedTarget,
|
|
lun: u16,
|
|
cdb: &[u8],
|
|
data_out: &[u8],
|
|
expected_data_in: &[u8],
|
|
) {
|
|
let mut data_in = Vec::new();
|
|
|
|
let res = target.execute_command(
|
|
lun,
|
|
&mut &data_out[..],
|
|
&mut data_in,
|
|
Request {
|
|
id: 0,
|
|
cdb,
|
|
task_attr: TaskAttr::Simple,
|
|
crn: 0,
|
|
prio: 0,
|
|
},
|
|
);
|
|
|
|
assert_eq!(res.unwrap(), CmdOutput::ok());
|
|
assert_eq!(&data_in, expected_data_in);
|
|
}
|
|
|
|
fn do_command_fail_lun(
|
|
target: &mut EmulatedTarget,
|
|
lun: u16,
|
|
cdb: &[u8],
|
|
expected_error: SenseTriple,
|
|
) {
|
|
let mut data_in = Vec::new();
|
|
let mut data_out: &[u8] = &[];
|
|
|
|
let res = target.execute_command(
|
|
lun,
|
|
&mut data_out,
|
|
&mut data_in,
|
|
Request {
|
|
id: 0,
|
|
cdb,
|
|
task_attr: TaskAttr::Simple,
|
|
crn: 0,
|
|
prio: 0,
|
|
},
|
|
);
|
|
|
|
assert_eq!(res.unwrap(), CmdOutput::check_condition(expected_error));
|
|
assert_eq!(&data_in, &[]);
|
|
}
|
|
|
|
fn do_command_in(
|
|
target: &mut EmulatedTarget,
|
|
cdb: &[u8],
|
|
data_out: &[u8],
|
|
expected_data_in: &[u8],
|
|
) {
|
|
do_command_in_lun(target, 0, cdb, data_out, expected_data_in);
|
|
}
|
|
|
|
fn do_command_fail(target: &mut EmulatedTarget, cdb: &[u8], expected_error: SenseTriple) {
|
|
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();
|
|
let dev = BlockDevice::new(null_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
do_command_in(&mut target, &[0, 0, 0, 0, 0, 0], &[], &[]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_report_luns() {
|
|
let mut target = EmulatedTarget::new();
|
|
for _ in 0..5 {
|
|
let dev = BlockDevice::new(null_image());
|
|
target.add_lun(Box::new(dev));
|
|
}
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0xa0, // REPORT LUNS
|
|
0, // reserved
|
|
0, // select report
|
|
0, 0, 0, // reserved
|
|
0, 0, 1, 0, // alloc length: 256
|
|
0, 0,
|
|
],
|
|
&[],
|
|
&[
|
|
0, 0, 0, 40, // length: 5*8 = 40
|
|
0, 0, 0, 0, // reserved
|
|
0, 0, 0, 0, 0, 0, 0, 0, // LUN 0
|
|
0, 1, 0, 0, 0, 0, 0, 0, // LUN 1
|
|
0, 2, 0, 0, 0, 0, 0, 0, // LUN 2
|
|
0, 3, 0, 0, 0, 0, 0, 0, // LUN 3
|
|
0, 4, 0, 0, 0, 0, 0, 0, // LUN 4
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_10() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(test_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
// TODO: this test relies on the default logical block size of 512. We should
|
|
// make that explicit.
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0x28, // READ (10)
|
|
0, // flags
|
|
0, 0, 0, 5, // LBA: 5
|
|
0, // reserved, group #
|
|
0, 1, // transfer length: 1
|
|
0, // control
|
|
],
|
|
&[],
|
|
&[b'5'; 512],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_10_last_block() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(test_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
// TODO: this test relies on the default logical block size of 512. We should
|
|
// make that explicit.
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0x28, // READ (10)
|
|
0, // flags
|
|
0, 0, 0, 15, // LBA: 5
|
|
0, // reserved, group #
|
|
0, 1, // transfer length: 1
|
|
0, // control
|
|
],
|
|
&[],
|
|
&[b'f'; 512],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_10_out_of_range() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(test_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
// TODO: this test relies on the default logical block size of 512. We should
|
|
// make that explicit.
|
|
|
|
do_command_fail(
|
|
&mut target,
|
|
&[
|
|
0x28, // READ (10)
|
|
0, // flags
|
|
0, 0, 0, 16, // LBA: 16
|
|
0, // reserved, group #
|
|
0, 1, // transfer length: 1
|
|
0, // control
|
|
],
|
|
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_10_cross_out() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(null_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
// TODO: this test relies on the default logical block size of 512. We should
|
|
// make that explicit.
|
|
|
|
do_command_fail(
|
|
&mut target,
|
|
&[
|
|
0x28, // READ (10)
|
|
0, // flags
|
|
0, 0, 0, 15, // LBA: 15
|
|
0, // reserved, group #
|
|
0, 2, // transfer length: 2
|
|
0, // control
|
|
],
|
|
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
|
|
);
|
|
}
|
|
|
|
#[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_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();
|
|
let dev = BlockDevice::new(test_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
// TODO: this test relies on the default logical block size of 512. We should
|
|
// make that explicit.
|
|
|
|
// TODO: we should test behavior with ≥ 2 TiB images. But not sure how we
|
|
// can do that reliably without risking using 2 TiB of disk
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0x25, // READ CAPACITY (10)
|
|
0, 0, 0, 0, 0, 0, 0, 0, // flags
|
|
0, // control
|
|
],
|
|
&[],
|
|
&[
|
|
0, 0, 0, 15, // returned LBA (last valid LBA),
|
|
0, 0, 2, 0, // block size (512)
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_capacity_16() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(test_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
// TODO: this test relies on the default logical block size of 512. We should
|
|
// make that explicit.
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0x9e, 0x10, // READ CAPACITY (16)
|
|
0, 0, 0, 0, 0, 0, 0, 0, // obsolete
|
|
0, 0, 0, 32, // allocation length: 32
|
|
0, // obselete/reserved
|
|
0, // control
|
|
],
|
|
&[],
|
|
&[
|
|
0, 0, 0, 0, 0, 0, 0, 15, // returned LBA (last valid LBA),
|
|
0, 0, 2, 0, // block size (512)
|
|
0, // reserved, zoned stuff, protection stuff
|
|
0, // one PB per LB
|
|
0xc0, // thin provisioning, unmapped blocks read 0
|
|
0, // LBA 0 is aligned (top bits above)
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // reserved
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_inquiry() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(null_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0x12, // INQUIRY
|
|
0, // EVPD bit: 0
|
|
0, // page code
|
|
1, 0, // alloc length: 256
|
|
0, // control
|
|
],
|
|
&[],
|
|
// some empty comments to get rustfmt to do something vaguely sensible
|
|
&[
|
|
0, // accessible; direct acccess block device
|
|
0, // features
|
|
0x7, // version
|
|
0x12, // response data format v2, HiSup = 1
|
|
91, // addl length
|
|
0, 0, 0, // unsupported features
|
|
// vendor
|
|
b'r', b'u', b's', b't', b'-', b'v', b'm', b'm', //
|
|
// product
|
|
b'v', b'h', b'o', b's', b't', b'-', b'u', b's', b'e', b'r', b'-', b's', b'c', b's',
|
|
b'i', b' ', //
|
|
// revision
|
|
b'v', b'0', b' ', b' ', //
|
|
// reserved/obselete/vendor specific
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
// version descriptors
|
|
0x0, 0xc0, // SAM-6
|
|
0x05, 0xc0, // SPC-5 (no code assigned for 6 yet)
|
|
0x06, 0, // SBC-4
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //
|
|
// reserved
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_request_sense() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(null_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
do_command_in(
|
|
&mut target,
|
|
&[
|
|
0x3, // INQUIRY
|
|
0, // desc bit: 0
|
|
0, 0, // reserved
|
|
255, // alloc length
|
|
0, // control
|
|
],
|
|
&[],
|
|
// We'll always return this - modern SCSI has autosense, so any errors are sent with the
|
|
// response to the command that caused them (and therefore immediately cleared), and
|
|
// REQUEST SENSE returns an actual error only under some exceptional circumstances
|
|
// we don't implement.
|
|
&sense::NO_ADDITIONAL_SENSE_INFORMATION.to_fixed_sense(),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_request_sense_descriptor_format() {
|
|
let mut target = EmulatedTarget::new();
|
|
let dev = BlockDevice::new(null_image());
|
|
target.add_lun(Box::new(dev));
|
|
|
|
do_command_fail(
|
|
&mut target,
|
|
&[
|
|
0x3, // INQUIRY
|
|
1, // desc bit: 1
|
|
0, 0, // reserved
|
|
255, // alloc length
|
|
0, // control
|
|
],
|
|
// We don't support descriptor format sense data.
|
|
sense::INVALID_FIELD_IN_CDB,
|
|
);
|
|
}
|