Viewing: user.rs
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
use bitmask_enum::bitmask;
use lustreapi_sys::*;
use serde::{Deserialize, Serialize};
use std::{ffi::CString, fmt, fmt::Display, os::fd::AsRawFd, path::Path};
use crate::{
Fid, Result,
error::{cvt_null_mut_m, cvt_nz_m, cvt_rc_m},
hsm::Extent,
};
#[bitmask(u32)]
#[bitmask_config(vec_debug, flags_iter)]
pub enum HsmState {
// None = HS_NONE, // a flag for 0 doesn't make sense
Exists = HS_EXISTS,
Dirty = HS_DIRTY,
Released = HS_RELEASED,
Archived = HS_ARCHIVED,
NoRelease = HS_NORELEASE,
NoArchive = HS_NOARCHIVE,
Lost = HS_LOST,
PCCRW = HS_PCCRW,
PCCRO = HS_PCCRO,
}
impl Default for HsmState {
fn default() -> Self {
HsmState::none()
}
}
impl Display for HsmState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let v: Vec<&str> = HsmState::flags()
.filter(|&(_, value)| self.contains(*value))
.map(|(name, _)| *name)
.collect();
if v.is_empty() {
write!(f, "Empty")?;
} else {
write!(f, "{}", v.join(", "))?;
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(u32)]
pub enum UserAction {
Noop = HUA_NONE,
Archive = HUA_ARCHIVE,
Restore = HUA_RESTORE,
Release = HUA_RELEASE,
Remove = HUA_REMOVE,
Cancel = HUA_CANCEL,
}
impl From<u32> for UserAction {
fn from(action: u32) -> Self {
match action {
HUA_NONE => UserAction::Noop,
HUA_ARCHIVE => UserAction::Archive,
HUA_RESTORE => UserAction::Restore,
HUA_RELEASE => UserAction::Release,
HUA_REMOVE => UserAction::Remove,
HUA_CANCEL => UserAction::Cancel,
_ => panic!("Invalid UserAction"),
}
}
}
impl Display for UserAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(u32)]
pub enum ProgressState {
Init = HPS_NONE,
Waiting = HPS_WAITING,
Running = HPS_RUNNING,
Done = HPS_DONE,
}
impl From<u32> for ProgressState {
fn from(state: u32) -> Self {
match state {
HPS_NONE => ProgressState::Init,
HPS_WAITING => ProgressState::Waiting,
HPS_RUNNING => ProgressState::Running,
HPS_DONE => ProgressState::Done,
_ => panic!("Invalid ProgressState"),
}
}
}
impl Display for ProgressState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
/// HSM request flags from LU-18940
///
/// These flags are used in the `hr_flags` field of `hsm_request` to provide
/// additional context about HSM requests, particularly to indicate when an HSM
/// request is blocking an application.
#[bitmask(u64)]
#[bitmask_config(vec_debug, flags_iter)]
#[derive(Serialize, Deserialize)]
pub enum HsmRequestFlags {
/// Flag indicating HSM request is blocking an application
/// This occurs when a process opens a released file and triggers an HSM restore
Blocking = HSM_REQ_BLOCKING as u64,
}
impl Default for HsmRequestFlags {
fn default() -> Self {
HsmRequestFlags::none()
}
}
impl Display for HsmRequestFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let v: Vec<&str> = HsmRequestFlags::flags()
.filter(|&(_, value)| self.contains(*value))
.map(|(name, _)| *name)
.collect();
if v.is_empty() {
write!(f, "Empty")?;
} else {
write!(f, "{}", v.join(", "))?;
}
Ok(())
}
}
/*
This struct contains the hus and hca fields.
*/
#[derive(Debug)]
pub struct HsmCurrent {
pub states: HsmState,
pub archive_id: u32,
pub progress_state: ProgressState,
pub action: UserAction,
pub extent: Extent,
}
impl HsmCurrent {
pub fn get(path: &Path) -> Result<Self> {
let mut hus = hsm_user_state::default();
let mut hca = hsm_current_action::default();
let cstr = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_rc_m(
llapi_hsm_state_get(cstr.as_ptr(), &mut hus as *mut hsm_user_state),
"Failed to get HSM state".to_string(),
)?;
cvt_rc_m(
llapi_hsm_current_action(cstr.as_ptr(), &mut hca as *mut hsm_current_action),
"Failed to get current action".to_string(),
)?;
Ok(HsmCurrent {
states: HsmState::from(hus.hus_states),
archive_id: hus.hus_archive_id,
progress_state: ProgressState::from(hca.hca_state),
action: UserAction::from(hca.hca_action),
extent: Extent {
offset: hca.hca_location.offset,
length: hca.hca_location.length,
},
})
}
}
pub fn set(path_buf: &Path, set: HsmState, clear: HsmState, archive_id: u32) -> Result<()> {
let cstr = CString::new(path_buf.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_rc_m(
llapi_hsm_state_set(
cstr.as_ptr(),
u64::from(set.bits()),
u64::from(clear.bits()),
archive_id,
),
"Failed to set HSM state".to_string(),
)
}
}
pub fn set_fd<Fd: AsRawFd>(
fd: &Fd,
set: HsmState,
clear: HsmState,
archive_id: u32,
) -> Result<()> {
unsafe {
cvt_rc_m(
llapi_hsm_state_set_fd(
fd.as_raw_fd(),
u64::from(set.bits()),
u64::from(clear.bits()),
archive_id,
),
"Failed to set HSM state".to_string(),
)
}
}
}
pub fn user_request_alloc<'a>(
item_count: usize,
data_len: usize,
) -> Result<&'a mut hsm_user_request> {
unsafe {
cvt_null_mut_m(
llapi_hsm_user_request_alloc(item_count as i32, data_len as i32),
"Failed to allocate HSM request".to_string(),
)
.map(|ptr| &mut (*ptr))
}
}
pub fn user_request_free(hur: *mut hsm_user_request) {
unsafe { libc::free(hur as *mut std::ffi::c_void) }
}
fn send_request(
action: UserAction,
path: &Path,
archive_id: u32,
flags: HsmRequestFlags,
fids: Vec<Fid>,
) -> Result<()> {
let hur = user_request_alloc(fids.len(), 0)?;
hur.hur_request.hr_action = action as u32;
hur.hur_request.hr_archive_id = archive_id;
hur.hur_request.hr_flags = flags.bits();
hur.hur_request.hr_itemcount = fids.len() as u32;
hur.hur_request.hr_data_len = 0;
for (i, fid) in fids.iter().enumerate() {
let hui = unsafe {
hur.hur_user_item
.as_mut_slice(fids.len())
.get_unchecked_mut(i)
};
hui.hui_fid = fid.to_lu_fid();
hui.hui_extent.offset = 0;
hui.hui_extent.length = LUSTRE_EOF as u64;
}
let result = user_request(path, hur);
user_request_free(hur);
result
}
pub fn user_request(path: &Path, hur: &hsm_user_request) -> Result<()> {
let cstr = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_nz_m(
llapi_hsm_request(cstr.as_ptr(), hur),
"Failed to send user request".to_string(),
)
}
}
pub fn archive<P: AsRef<Path>>(
path: P,
archive_id: u32,
flags: HsmRequestFlags,
fids: Vec<Fid>,
) -> Result<()> {
send_request(UserAction::Archive, path.as_ref(), archive_id, flags, fids)
}
pub fn restore<P: AsRef<Path>>(path: P, flags: HsmRequestFlags, fids: Vec<Fid>) -> Result<()> {
send_request(UserAction::Restore, path.as_ref(), 0, flags, fids)
}
pub fn release<P: AsRef<Path>>(path: &P, flags: HsmRequestFlags, fids: Vec<Fid>) -> Result<()> {
send_request(UserAction::Release, path.as_ref(), 0, flags, fids)
}
pub fn cancel<P: AsRef<Path>>(path: P, flags: HsmRequestFlags, fids: Vec<Fid>) -> Result<()> {
send_request(UserAction::Cancel, path.as_ref(), 0, flags, fids)
}
pub fn remove<P: AsRef<Path>>(path: P, flags: HsmRequestFlags, fids: Vec<Fid>) -> Result<()> {
send_request(UserAction::Remove, path.as_ref(), 0, flags, fids)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hsm_states() {
let mut states = HsmState::default();
states |= HsmState::Exists | HsmState::Dirty;
assert_eq!(states.to_string(), "Exists, Dirty");
}
}