use super::{receive_stream::ReceiveStream, tlv::*};
use libc::{dev_t, gid_t, mode_t, off_t, uid_t};
use std::{
io::{self, Read},
sync::Arc,
sync::atomic::{AtomicU64, Ordering},
};
pub struct SendStream<'a, R>
{
pub version: u32,
pub reader: R,
pub data_buf: Vec<u8>,
pub cmd_attrs: [SendAttr<'a>; SendAttr::LEN],
pub stream_pos: u64,
pub atomic_pos: Option<Arc<AtomicU64>>,
#[cfg(feature = "use-crc-fast")]
pub crc_params: crc_fast::CrcParams,
}
impl<'a, R: Read> SendStream<'a, R>
{
pub fn read_cmd(&mut self) -> io::Result<SendCmd::T>
{
const BLOCK_MASK: u64 = !0x3F_FFFF;
const HDR_SZ: usize = size_of::<CmdHeader>();
unsafe {
self.cmd_attrs.as_mut_ptr().write_bytes(0, 1);
}
self.reader.read_exact(&mut self.data_buf[..HDR_SZ])?;
let CmdHeader { len, cmd, crc } = CmdHeader::from(&self.data_buf[..]);
unsafe {
(*self.data_buf.as_mut_ptr().cast::<CmdHeader>()).crc = 0;
}
let data_len = HDR_SZ + len as usize;
if data_len > self.data_buf.len() {
self.data_buf.resize_with(data_len, Default::default);
}
let data = &mut self.data_buf[..data_len];
self.reader.read_exact(&mut data[HDR_SZ..])?;
let checksum = {
#[cfg(feature = "use-crc-fast")]
{
self.crc_params.init = 0;
crc_fast::checksum_with_params(self.crc_params, data) as u32
}
#[cfg(not(feature = "use-crc-fast"))]
!crc32c::crc32c_append(u32::MAX, data)
};
if crc != checksum {
return receive_error!("crc mismatch in command");
} else {
let old_pos = self.stream_pos;
self.stream_pos += data_len as u64;
if let Some(ref atomic) = self.atomic_pos {
if (old_pos & BLOCK_MASK) != (self.stream_pos & BLOCK_MASK) {
atomic.store(self.stream_pos, Ordering::Relaxed)
}
}
}
let mut pos = HDR_SZ;
while pos < data_len {
if data_len - pos < size_of::<u16>() {
return receive_error!("Stream is truncated");
}
let tlv_type = u16::from_le_bytes([data[pos], data[pos + 1]]);
pos += size_of::<u16>();
if !SendAttr::is_valid(tlv_type as usize) {
return receive_error!("Invalid tlv in cmd");
}
let attr = &mut self.cmd_attrs[tlv_type as usize];
if self.version >= 2 && tlv_type == SendAttr::DATA as u16 {
attr.len = (data_len - pos) as u32;
} else {
if data_len - pos < size_of::<u16>() {
return receive_error!("Stream is truncated (1)");
}
attr.len = u16::from_le_bytes([data[pos], data[pos + 1]]) as u32;
pos += size_of::<u16>();
}
if data_len - pos < attr.len as usize {
return receive_error!("Stream is truncated (2)");
}
attr.data = data[pos..].as_ptr();
pos += attr.len as usize;
}
Ok(cmd)
}
}
#[allow(unused)]
fn process_cmd<R: Read>(
stream: &mut SendStream<R>,
receive: &mut ReceiveStream,
) -> io::Result<Option<()>>
{
match stream.read_cmd()? {
SendCmd::SUBVOL => {
let path = stream.tlv_get_path(SendAttr::PATH)?;
let uuid = stream.tlv_get_uuid(SendAttr::UUID)?;
let ctransid = stream.tlv_get_u64(SendAttr::CTRANSID)?;
receive.subvol(path, uuid, ctransid)
}
SendCmd::SNAPSHOT => {
let path = stream.tlv_get_path(SendAttr::PATH)?;
let uuid = stream.tlv_get_uuid(SendAttr::UUID)?;
let ctransid = stream.tlv_get_uuid(SendAttr::CTRANSID)?;
let clone_uuid = stream.tlv_get_uuid(SendAttr::CLONE_UUID)?;
let clone_ctransid = stream.tlv_get_uuid(SendAttr::CLONE_CTRANSID)?;
unimplemented!("SNAPSHOT")
}
SendCmd::MKFILE => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let ino = stream.tlv_get_u64(SendAttr::INO)?;
receive.mkfile(path, path_len)
}
SendCmd::MKDIR => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let ino = stream.tlv_get_u64(SendAttr::INO)?;
receive.mkdir(path, path_len)
}
SendCmd::MKNOD => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let ino = stream.tlv_get_u64(SendAttr::INO)?;
let mode = stream.tlv_get_u64(SendAttr::MODE)?;
let dev = stream.tlv_get_u64(SendAttr::RDEV)?;
receive.mknod(path, path_len, mode as mode_t, dev as dev_t)
}
SendCmd::MKSOCK => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let ino = stream.tlv_get_u64(SendAttr::INO)?;
receive.mksock(path, path_len)
}
SendCmd::SYMLINK => {
let (target, target_len) = stream.tlv_get_data(SendAttr::PATH)?;
let ino = stream.tlv_get_u64(SendAttr::INO)?;
let (lpath, lpath_len) = stream.tlv_get_data(SendAttr::PATH_LINK)?;
receive.symlink(target, target_len, lpath, lpath_len)
}
SendCmd::RENAME => {
let (from, from_len) = stream.tlv_get_data(SendAttr::PATH)?;
let (to, to_len) = stream.tlv_get_data(SendAttr::PATH_TO)?;
receive.rename(from, from_len, to, to_len)
}
SendCmd::LINK => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let (link, link_len) = stream.tlv_get_data(SendAttr::PATH_LINK)?;
receive.link(path, path_len, link, link_len)
}
SendCmd::UNLINK => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
receive.unlink(path, path_len)
}
SendCmd::RMDIR => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
receive.rmdir(path, path_len)
}
SendCmd::WRITE => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let offset = stream.tlv_get_u64(SendAttr::FILE_OFFSET)?;
let (data, data_len) = stream.tlv_get_data(SendAttr::DATA)?;
receive.write(path, path_len, data, data_len, offset as off_t)
}
SendCmd::ENCODED_WRITE => {
let path = stream.tlv_get_path(SendAttr::PATH)?;
let offset = stream.tlv_get_u64(SendAttr::FILE_OFFSET)?;
let unencoded_file_len = stream.tlv_get_u64(SendAttr::UNENCODED_FILE_LEN)?;
let unencoded_len = stream.tlv_get_u64(SendAttr::UNENCODED_LEN)?;
let unencoded_offset = stream.tlv_get_u64(SendAttr::UNENCODED_OFFSET)?;
let compression = if stream.cmd_attrs[SendAttr::COMPRESSION].data.is_null() {
None
} else {
Some(stream.tlv_get_u32(SendAttr::COMPRESSION)?)
};
let encryption = if stream.cmd_attrs[SendAttr::ENCRYPTION].data.is_null() {
None
} else {
Some(stream.tlv_get_u32(SendAttr::ENCRYPTION)?)
};
let (data, len) = stream.tlv_get(SendAttr::DATA)?;
unimplemented!("ENCODED_WRITE")
}
SendCmd::CLONE => {
unimplemented!("CLONE")
}
SendCmd::SET_XATTR => {
unimplemented!("SET_XATTR")
}
SendCmd::REMOVE_XATTR => {
unimplemented!("REMOVE_XATTR")
}
SendCmd::TRUNCATE => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let length = stream.tlv_get_u64(SendAttr::SIZE)?;
receive.truncate(path, path_len, length as off_t)
}
SendCmd::CHMOD => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let mode = stream.tlv_get_u64(SendAttr::MODE)?;
receive.chmod(path, path_len, mode as mode_t)
}
SendCmd::CHOWN => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let uid = stream.tlv_get_u64(SendAttr::UID)?;
let gid = stream.tlv_get_u64(SendAttr::GID)?;
receive.chown(path, path_len, uid as uid_t, gid as gid_t)
}
SendCmd::UTIMES => {
let (path, path_len) = stream.tlv_get_data(SendAttr::PATH)?;
let atime = stream.tlv_get_timespec(SendAttr::ATIME)?;
let mtime = stream.tlv_get_timespec(SendAttr::MTIME)?;
let ctime = stream.tlv_get_timespec(SendAttr::CTIME)?;
receive.utimes(path, path_len, atime, mtime)
}
SendCmd::UPDATE_EXTENT => {
let path = stream.tlv_get_path(SendAttr::PATH)?;
let offset = stream.tlv_get_u64(SendAttr::FILE_OFFSET)?;
let tmp = stream.tlv_get_u64(SendAttr::SIZE)?;
receive.update_extent(path, offset, tmp)
}
SendCmd::END => {
receive.finish_send()
}
SendCmd::FALLOCATE => {
unimplemented!("FALLOCATE")
}
SendCmd::FILEATTR => {
unimplemented!("FILEATTR")
}
_ => unimplemented!(),
}
}
pub fn process_send_stream<R: Read>(
receive: &mut ReceiveStream,
stream: &mut SendStream<R>,
) -> io::Result<()>
{
stream
.reader
.read_exact(&mut stream.data_buf[..size_of::<StreamHeader>()])?;
let StreamHeader { magic, version } = StreamHeader::from(&stream.data_buf[..]);
if &magic != BTRFS_SEND_STREAM_MAGIC {
return receive_error!("Unexpected header");
} else if version > BTRFS_SEND_STREAM_VERSION {
return receive_error!("Version not supported");
}
stream.version = version;
while process_cmd(stream, receive)?.is_some() {
continue;
}
if let Some(ref atomic) = stream.atomic_pos {
atomic.store(stream.stream_pos, Ordering::Relaxed);
}
Ok(())
}