use super::{DRAIN_TIMEOUT, MOVE_TIMEOUT, Session};
use crate::{
error::Error,
protocol::{
cdbs::Read,
data::DataType,
image::Layout,
sense::{Fault, Refusal},
},
transport::Data,
};
use std::time::Duration;
use tracing::*;
impl Session {
pub fn read_image(&mut self, layout: &Layout, buf: &mut [u8]) -> Result<usize, Error> {
self.read_image_within(layout, buf, MOVE_TIMEOUT)
}
pub fn read_image_within(
&mut self,
layout: &Layout,
buf: &mut [u8],
timeout: Duration,
) -> Result<usize, Error> {
let chunk = self.chunk_size(layout)?;
let code = DataType::Image.row().code;
let width = layout.width_code();
let len = buf.as_mut().len();
let mut done = 0;
while done < buf.len() {
let want = chunk.min(buf.len() - done);
let cmd = Read::new(code, 0, width, want as u32);
let slice = &mut buf[done..done + want];
trace!(
cdb = ?cmd.cdb(),
want,
done,
left = len - done,
"executing image READ"
);
match self.run(&cmd.cdb(), Data::In(slice), timeout) {
Ok(completion) => {
trace!(
transferred = completion.transferred,
want, "image READ completed"
);
done += completion.transferred;
if completion.transferred < want {
break;
}
}
Err(Error::Device(fault))
if matches!(*fault, Fault::Rejected(Refusal::OutOfSequence, _)) =>
{
debug!(done, "end of stream reached");
break;
}
Err(Error::Device(fault)) => match short(&fault) {
Some(missing) => {
done += want.saturating_sub(missing as usize);
debug!(missing, "the unit had less than we asked for");
break;
}
None => return Err(Error::Device(fault)),
},
Err(e) => {
debug!(error = ?e, "image READ failed");
return Err(e);
}
}
}
trace!(bytes = done, "read image");
Ok(done)
}
pub fn image_chunks<'a>(&'a mut self, layout: &Layout) -> Result<Chunks<'a>, Error> {
let chunk = self.chunk_size(layout)?;
Ok(Chunks {
session: self,
layout: layout.clone(),
chunk,
remaining: layout.total_bytes(),
spent: false,
closed: false,
surplus: 0,
})
}
fn chunk_size(&self, layout: &Layout) -> Result<usize, Error> {
let mut chunk = self.transport.max_transfer();
if let Some(limit) = self.caps.address.scsi_buffer {
chunk = chunk.min(usize::from(limit));
}
let granule = layout.granule;
if granule > chunk {
return Err(Error::Unsupported {
op: "image read",
reason: format!(
"this unit reads in units of {granule} bytes, and no more than {chunk} can be transferred at once"
),
});
}
Ok(chunk / granule * granule)
}
}
fn short(fault: &Fault) -> Option<u32> {
let (Fault::Reported(_, Some(sense)) | Fault::Rejected(_, Some(sense))) = fault else {
return None;
};
sense.ili.then_some(sense.information).flatten()
}
pub struct Chunks<'a> {
session: &'a mut Session,
layout: Layout,
chunk: usize,
remaining: u64,
spent: bool,
closed: bool,
surplus: u64,
}
impl Chunks<'_> {
pub fn fill(&mut self, buf: &mut Vec<u8>) -> Option<Result<usize, Error>> {
if self.spent {
return None;
}
if self.remaining == 0 {
if self.layout.multiline_registered {
self.drain();
} else {
self.spent = true;
self.closed = true;
}
return None;
}
let want = self.chunk.min(self.remaining as usize);
buf.resize(want, 0);
let layout = &self.layout;
trace!(
remaining = self.remaining,
buf_len = buf.len(),
"issuing image READ from fill"
);
match self.session.read_image(layout, &mut buf[..want]) {
Err(e) => {
self.spent = true;
Some(Err(e))
}
Ok(0) => {
self.spent = true;
None
}
Ok(got) => {
let rem = self.remaining as i64 - got as i64;
if rem < 0 {
self.remaining = 0;
} else {
self.remaining = rem as u64;
}
if got < want {
self.spent = true;
}
buf.truncate(got);
Some(Ok(got))
}
}
}
fn drain(&mut self) {
self.spent = true;
self.closed = true;
let limit = self.layout.total_bytes().max(self.chunk as u64);
let mut buf = vec![0u8; self.chunk];
loop {
let want = match self.remaining {
0 if self.layout.multiline_registered => self.chunk,
0 => break,
left => self.chunk.min(left as usize),
};
match self.session.read_image_within(
&self.layout.clone(),
&mut buf[..want],
DRAIN_TIMEOUT,
) {
Ok(0) => break,
Ok(got) => {
let owed = (got as u64).min(self.remaining);
self.remaining -= owed;
self.surplus += got as u64 - owed;
debug!(
got,
remaining = self.remaining,
surplus = self.surplus,
"read off part of the remainder"
);
if got < want {
break;
}
if self.surplus >= limit {
warn!(
bytes = self.surplus,
limit,
"the unit is still handing data back, giving up on closing the scan"
);
break;
}
}
Err(e) => {
warn!(
%e,
remaining = self.remaining,
"the unit stopped giving data, so the scan is left open - it will not \
take another command until it is power cycled"
);
break;
}
}
}
if self.surplus > 0 {
warn!(
bytes = self.surplus,
"the unit held more than the layout promised, so the pass is not what it was read as"
);
}
}
pub fn capacity(&self) -> usize {
self.chunk
}
}
impl Drop for Chunks<'_> {
fn drop(&mut self) {
if self.closed {
return;
}
self.spent = true;
self.closed = true;
info!(remaining = self.remaining, "stopping the scan");
match self.session.abort() {
Ok(true) => {}
Ok(false) => {
debug!("no ABORT on this unit, so reading the remainder off instead");
self.closed = false;
self.drain();
}
Err(e) => warn!(
%e,
remaining = self.remaining,
"could not stop the scan, so it is left open - the next command \
will be refused out of sequence"
),
}
}
}