use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use thiserror::Error;
pub const MAX_COMMAND_CUSTODY_RECORD_BYTES: u64 = 32 * 1024;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(try_from = "CustodyFields")]
pub struct CommandCustodyRecord {
version: u16,
journal: PathBuf,
operation_sequence: u64,
device: u64,
inode: u64,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct CustodyFields {
version: u16,
journal: PathBuf,
operation_sequence: u64,
device: u64,
inode: u64,
}
impl TryFrom<CustodyFields> for CommandCustodyRecord {
type Error = CommandCustodyRecordError;
fn try_from(fields: CustodyFields) -> Result<Self, Self::Error> {
if fields.version != 1 {
return Err(CommandCustodyRecordError::UnsupportedVersion(
fields.version,
));
}
Self::new(
fields.journal,
fields.operation_sequence,
fields.device,
fields.inode,
)
}
}
impl CommandCustodyRecord {
pub(crate) fn new(
journal: PathBuf,
operation_sequence: u64,
device: u64,
inode: u64,
) -> Result<Self, CommandCustodyRecordError> {
if !super::journal_path::is_canonical(&journal, 4096) {
return Err(CommandCustodyRecordError::InvalidJournal { journal });
}
if inode == 0 {
return Err(CommandCustodyRecordError::UnknownFileIdentity);
}
Ok(Self {
version: 1,
journal,
operation_sequence,
device,
inode,
})
}
#[must_use]
pub fn journal(&self) -> &Path {
&self.journal
}
#[must_use]
pub const fn operation_sequence(&self) -> u64 {
self.operation_sequence
}
#[must_use]
pub const fn device(&self) -> u64 {
self.device
}
#[must_use]
pub const fn inode(&self) -> u64 {
self.inode
}
pub(crate) const fn matches_file(&self, device: u64, inode: u64) -> bool {
self.device == device && self.inode == inode
}
}
#[derive(Debug, Error)]
pub enum CommandCustodyRecordError {
#[error("unsupported command custody version {0}")]
UnsupportedVersion(u16),
#[error("invalid command journal location: {journal:?}")]
InvalidJournal {
journal: PathBuf,
},
#[error("command custody file identity is unknown")]
UnknownFileIdentity,
}
#[cfg(test)]
mod tests;