use bytes::{Buf, Bytes};
use super::compound::OpenArgs;
use super::mount::{Mount41, decode_fh};
use crate::error::{NfsError, Result};
const MAX_XATTR_SIZE: u32 = 1024 * 1024;
impl Mount41 {
async fn open_attr_dir(&self, fh: &Bytes, create: bool) -> Result<Bytes> {
let resp = self
.compound("openattr", |b| b.putfh(fh).openattr(create).getfh())
.await?;
resp.op_ok(1)?; resp.op_ok(2)?; let getfh = resp.op_ok(3)?;
let mut data = getfh.data.clone();
decode_fh(&mut data)
}
pub(crate) async fn getxattr(&self, fh: Bytes, name: &str) -> Result<Bytes> {
let resp = self
.compound("getxattr-lookup", |b| {
b.putfh(&fh).openattr(false).lookup(name).getfh()
})
.await?;
resp.op_ok(1)?; resp.op_ok(2)?; resp.op_ok(3)?; let getfh = resp.op_ok(4)?;
let mut data = getfh.data.clone();
let attr_fh = decode_fh(&mut data)?;
let stateid = [0u8; 16];
let resp = self
.compound_data("getxattr-read", MAX_XATTR_SIZE as usize, |b| {
b.putfh(&attr_fh).read(&stateid, 0, MAX_XATTR_SIZE)
})
.await?;
resp.op_ok(1)?; let read_op = resp.op_ok(2)?;
let mut rdata = read_op.data.clone();
if rdata.remaining() < 4 {
return Err(NfsError::Xdr("xattr READ result too short".to_string()));
}
let _eof = rdata.get_u32();
if rdata.remaining() < 4 {
return Err(NfsError::Xdr("xattr READ data length missing".to_string()));
}
let data_len = rdata.get_u32() as usize;
if rdata.remaining() < data_len {
return Err(NfsError::Xdr("xattr READ data truncated".to_string()));
}
Ok(rdata.slice(..data_len))
}
pub(crate) async fn setxattr(&self, fh: Bytes, name: &str, value: Bytes) -> Result<()> {
let open_args = OpenArgs {
seqid: 0,
share_access: 0x00000003, share_deny: 0,
client_id: self.session_holder.get().await.client_id(),
owner: Bytes::from_static(b"nfs-rs-xattr"),
create: true,
create_attrs_mask: vec![],
create_attrs_vals: vec![],
claim_file: name.to_string(),
};
let resp = self
.compound("setxattr-open", |b| {
b.putfh(&fh).openattr(true).open(&open_args).getfh()
})
.await?;
resp.op_ok(1)?; resp.op_ok(2)?; let open_op = resp.op_ok(3)?; let mut open_data = open_op.data.clone();
let stateid = super::mount::extract_stateid(&mut open_data)?;
let getfh = resp.op_ok(4)?;
let mut fh_data = getfh.data.clone();
let attr_fh = decode_fh(&mut fh_data)?;
let data_len = value.len() as u32;
let write_result = self
.compound_write("setxattr-write", value, |b| {
b.putfh(&attr_fh)
.write_header(&stateid, 0, 2 , data_len)
})
.await;
let _ = self
.compound("setxattr-close", |b| b.putfh(&attr_fh).close(0, &stateid))
.await;
write_result?;
Ok(())
}
pub(crate) async fn listxattr(&self, fh: Bytes) -> Result<Vec<String>> {
let attr_dir_fh = match self.open_attr_dir(&fh, false).await {
Ok(fh) => fh,
Err(NfsError::Nfs4(status))
if status as u32 == 10023 =>
{
return Ok(vec![]);
}
Err(e) => return Err(e),
};
let mut names = Vec::new();
let mut cookie = 0u64;
let cookieverf = [0u8; 8];
let bitmap: [u32; 0] = [];
loop {
let resp = self
.compound("listxattr-readdir", |b| {
b.putfh(&attr_dir_fh)
.readdir(cookie, &cookieverf, 4096, 32768, &bitmap)
})
.await?;
resp.op_ok(1)?; let readdir_op = resp.op_ok(2)?;
let mut data = readdir_op.data.clone();
if data.remaining() < 8 {
return Err(NfsError::Xdr(
"xattr READDIR cookieverf truncated".to_string(),
));
}
data.advance(8);
let mut last_cookie = cookie;
loop {
if data.remaining() < 4 {
return Err(NfsError::Xdr(
"xattr READDIR entry flag truncated".to_string(),
));
}
let has_entry = data.get_u32();
if has_entry == 0 {
break;
}
if data.remaining() < 8 {
return Err(NfsError::Xdr("xattr READDIR cookie truncated".to_string()));
}
last_cookie = data.get_u64();
let name = super::mount::decode_string_from_bytes(&mut data)?;
skip_fattr4(&mut data)?;
if name != "." && name != ".." {
names.push(name);
}
}
if data.remaining() < 4 {
return Err(NfsError::Xdr("xattr READDIR eof truncated".to_string()));
}
let eof = data.get_u32() != 0;
if eof || last_cookie == cookie {
break;
}
cookie = last_cookie;
}
Ok(names)
}
pub(crate) async fn removexattr(&self, fh: Bytes, name: &str) -> Result<()> {
let resp = self
.compound("removexattr", |b| b.putfh(&fh).openattr(false).remove(name))
.await?;
resp.op_ok(1)?; resp.op_ok(2)?; resp.op_ok(3)?; Ok(())
}
}
fn skip_fattr4(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("fattr4 bitmap length truncated".to_string()));
}
let bitmap_len = buf.get_u32() as usize;
let bitmap_bytes = bitmap_len
.checked_mul(4)
.ok_or_else(|| NfsError::Xdr("fattr4 bitmap overflow".to_string()))?;
if buf.remaining() < bitmap_bytes {
return Err(NfsError::Xdr("fattr4 bitmap truncated".to_string()));
}
buf.advance(bitmap_bytes);
if buf.remaining() < 4 {
return Err(NfsError::Xdr(
"fattr4 attr_vals length truncated".to_string(),
));
}
let vals_len = buf.get_u32() as usize;
let padded = (vals_len + 3) & !3;
if buf.remaining() < padded {
return Err(NfsError::Xdr("fattr4 attr_vals truncated".to_string()));
}
buf.advance(padded);
Ok(())
}