use std::io::{Read, Write};
use tracing::{debug, error, trace};
use crate::protocol::rpc;
use crate::protocol::xdr::{self, deserialize, nfs3, Serialize};
pub async fn nfsproc3_readdirplus(
xid: u32,
input: &mut impl Read,
output: &mut impl Write,
context: &rpc::Context,
) -> Result<(), anyhow::Error> {
let args = deserialize::<nfs3::dir::READDIRPLUS3args>(input)?;
debug!("nfsproc3_readdirplus({:?},{:?}) ", xid, args);
let dirid = context.vfs.fh_to_id(&args.dir);
if let Err(stat) = dirid {
xdr::rpc::make_success_reply(xid).serialize(output)?;
stat.serialize(output)?;
nfs3::post_op_attr::None.serialize(output)?;
return Ok(());
}
let dirid = dirid.unwrap();
let dir_attr_maybe = context.vfs.getattr(dirid).await;
let dir_attr = dir_attr_maybe.as_ref().ok().copied();
match context.vfs.check_access(dirid, &context.auth, nfs3::ACCESS3_READ).await {
Ok(granted) if granted & nfs3::ACCESS3_READ != 0 => {}
Ok(_) => {
xdr::rpc::make_success_reply(xid).serialize(output)?;
nfs3::nfsstat3::NFS3ERR_ACCES.serialize(output)?;
dir_attr.serialize(output)?;
return Ok(());
}
Err(stat) => {
xdr::rpc::make_success_reply(xid).serialize(output)?;
stat.serialize(output)?;
dir_attr.serialize(output)?;
return Ok(());
}
}
let dirversion = if let Ok(ref dir_attr) = dir_attr_maybe {
let cvf_version =
((dir_attr.mtime.seconds as u64) << 32) | (dir_attr.mtime.nseconds as u64);
cvf_version.to_be_bytes()
} else {
nfs3::cookieverf3::default()
};
debug!(" -- Dir attr {:?}", dir_attr);
debug!(" -- Dir version {:?}", dirversion);
let has_version = args.cookieverf != nfs3::cookieverf3::default();
if has_version && args.cookieverf != dirversion {
xdr::rpc::make_success_reply(xid).serialize(output)?;
nfs3::nfsstat3::NFS3ERR_BAD_COOKIE.serialize(output)?;
dir_attr.serialize(output)?;
return Ok(());
}
let start_index = match usize::try_from(args.cookie) {
Ok(idx) => idx,
Err(_) => {
xdr::rpc::make_success_reply(xid).serialize(output)?;
nfs3::nfsstat3::NFS3ERR_BAD_COOKIE.serialize(output)?;
dir_attr.serialize(output)?;
return Ok(());
}
};
let max_bytes_allowed = (args.maxcount as usize).saturating_sub(128);
let estimated_max_results = std::cmp::max(1, args.dircount / 16) as usize;
let max_dircount_bytes = args.dircount as usize;
let mut ctr = 0;
match context.vfs.readdir_index(dirid, start_index, estimated_max_results).await {
Ok(result) => {
let mut accumulated_dircount: usize = 0;
let mut all_entries_written = true;
let mut wrote_any = false;
let mut too_small = false;
let mut counting_output = crate::write_counter::WriteCounter::new(Vec::new());
xdr::rpc::make_success_reply(xid).serialize(&mut counting_output)?;
nfs3::nfsstat3::NFS3_OK.serialize(&mut counting_output)?;
dir_attr.serialize(&mut counting_output)?;
dirversion.serialize(&mut counting_output)?;
let mut current_index = start_index;
for entry in result.entries.into_iter() {
let obj_attr = entry.attr;
let handle = nfs3::post_op_fh3::Some(context.vfs.id_to_fh(entry.fileid));
let next_cookie = current_index.saturating_add(1) as nfs3::cookie3;
let entry = nfs3::dir::entryplus3 {
fileid: entry.fileid,
name: entry.name,
cookie: next_cookie,
name_attributes: nfs3::post_op_attr::Some(obj_attr),
name_handle: handle,
};
current_index = current_index.saturating_add(1);
let mut write_buf: Vec<u8> = Vec::new();
let mut write_cursor = std::io::Cursor::new(&mut write_buf);
true.serialize(&mut write_cursor)?;
entry.serialize(&mut write_cursor)?;
write_cursor.flush()?;
let added_dircount = std::mem::size_of::<nfs3::fileid3>() + std::mem::size_of::<u32>() + entry.name.len() + std::mem::size_of::<nfs3::cookie3>(); let added_output_bytes = write_buf.len();
if added_output_bytes + counting_output.bytes_written() < max_bytes_allowed
&& added_dircount + accumulated_dircount < max_dircount_bytes
{
trace!(" -- dirent {:?}", entry);
ctr += 1;
counting_output.write_all(&write_buf)?;
accumulated_dircount += added_dircount;
wrote_any = true;
trace!(
" -- lengths: {:?} / {:?} {:?} / {:?}",
accumulated_dircount,
max_dircount_bytes,
counting_output.bytes_written(),
max_bytes_allowed
);
} else {
trace!(" -- insufficient space. truncating");
all_entries_written = false;
if !wrote_any {
too_small = true;
}
break;
}
}
if too_small {
xdr::rpc::make_success_reply(xid).serialize(output)?;
nfs3::nfsstat3::NFS3ERR_TOOSMALL.serialize(output)?;
dir_attr.serialize(output)?;
return Ok(());
}
false.serialize(&mut counting_output)?;
if all_entries_written {
debug!(" -- readdir eof {:?}", result.end);
result.end.serialize(&mut counting_output)?;
} else {
debug!(" -- readdir eof {:?}", false);
false.serialize(&mut counting_output)?;
}
output.write_all(&counting_output.into_inner())?;
debug!(
"readir {}, has_version {}, start at {}, flushing {} entries, complete {}",
dirid, has_version, args.cookie, ctr, all_entries_written
);
}
Err(stat) => {
error!("readdir error {:?} --> {:?} ", xid, stat);
xdr::rpc::make_success_reply(xid).serialize(output)?;
stat.serialize(output)?;
dir_attr.serialize(output)?;
}
}
Ok(())
}