Viewing: copytool.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 crate::{
Fid, LustrePath,
error::{Result, cvt_lz_m, cvt_nz_m, cvt_rc_m},
hsm::user::HsmRequestFlags,
};
use lustreapi_sys::*;
use serde::{Deserialize, Serialize};
use std::{
alloc,
alloc::{Layout, alloc},
ffi::CString,
fmt,
fmt::Display,
fs::File,
os::fd::{AsRawFd, FromRawFd},
ptr::NonNull,
};
/// A safe wrapper around a raw file descriptor with automatic cleanup.
///
/// `RawDescriptor` encapsulates a file descriptor obtained from Lustre HSM operations
/// and is specifically designed for use with `tokio::AsyncFd` for asynchronous event polling.
/// It implements `AsRawFd` for API compatibility and automatically closes the descriptor
/// when dropped, preventing resource leaks.
///
/// # Safety
///
/// This struct takes ownership of the provided file descriptor. The descriptor should not
/// be used directly after being wrapped or after the `RawDescriptor` is dropped.
pub struct RawDescriptor(i32);
impl RawDescriptor {
/// Creates a new `RawDescriptor` by taking ownership of the provided file descriptor.
pub fn new(fd: i32) -> Self {
Self(fd)
}
}
impl AsRawFd for RawDescriptor {
/// Returns the underlying raw file descriptor.
///
/// Note that the returned descriptor is still owned by this `RawDescriptor`
/// and will be closed when dropped.
fn as_raw_fd(&self) -> i32 {
self.0
}
}
impl Drop for RawDescriptor {
/// Closes the file descriptor automatically when the `RawDescriptor` is dropped.
fn drop(&mut self) {
unsafe {
libc::close(self.0);
}
}
}
pub trait MoverRef {
fn mover_ref(&self) -> *const hsm_copytool_private;
}
#[derive(Default)]
pub struct CopytoolBuilder {
archives: Vec<i32>,
non_blocking: bool,
}
/// Builder for configuring and registering a Lustre HSM Copytool connection.
///
/// `CopytoolBuilder` allows you to customize how the `Copytool` connects to
/// the HSM coordinator, including which archives it will service and whether
/// operations should be non-blocking.
///
/// # Usage
/// See the `Copytool` struct for more details.
///
/// # Methods
///
/// - `archives()`: Specify which archive IDs this copytool will service
/// - `non_blocking()`: Configure whether operations should be non-blocking
/// - `register()`: Connect to the HSM coordinator and create the `Copytool`
///
/// When no archives are specified, the copytool will service all archives.
impl CopytoolBuilder {
/// Specifies which archive IDs this copytool will service.
///
/// # Arguments
///
/// * `archives` - Vector of archive IDs to service. Each ID typically corresponds to a
/// different storage tier in the HSM system.
///
/// # Default
///
/// If not called, the copytool will service all available archives.
///
/// # Returns
///
/// The builder instance for method chaining.
pub fn archives(mut self, archives: Vec<i32>) -> Self {
self.archives = archives;
self
}
/// Controls whether the copytool should use non-blocking I/O.
///
/// # Arguments
///
/// * `non_blocking` - When `true`, attempts to read when no data is available
/// will return immediately with `EWOULDBLOCK` or `EAGAIN` errors, suitable for
/// event-driven applications. When `false` (default), read operations will
/// block until data is available.
///
/// # Returns
///
/// The builder instance for method chaining.
pub fn non_blocking(mut self, non_blocking: bool) -> Self {
self.non_blocking = non_blocking;
self
}
/// Registers the copytool with the Lustre HSM coordinator.
///
/// This finalizes the copytool configuration and establishes a connection
/// with the HSM coordinator on the specified Lustre filesystem.
///
/// # Arguments
///
/// * `mount_dir` - Path to a Lustre filesystem mount point
///
/// # Returns
///
/// * `Result<Copytool>` - A registered copytool instance on success, or an error
/// if registration fails (e.g., if the coordinator is unavailable or the path
/// is not a valid Lustre mount point)
pub fn register(self, mount_dir: &LustrePath) -> Result<Copytool> {
let flags = if self.non_blocking {
libc::O_NONBLOCK
} else {
0
};
let cstr = CString::new(mount_dir.as_ref().as_os_str().as_encoded_bytes())?;
let mut inner: *mut hsm_copytool_private = std::ptr::null_mut();
unsafe {
cvt_rc_m(
llapi_hsm_copytool_register(
&mut inner as *mut *mut hsm_copytool_private,
cstr.as_ptr(),
self.archives.len() as i32,
self.archives.as_ptr() as *mut i32,
flags,
),
"Failed to register copytool".to_string(),
)
.map(|_| Copytool { inner })
}
}
}
/// Interface for receiving and processing Lustre HSM actions from the coordinator.
///
/// `Copytool` provides a connection to the Lustre HSM coordinator, allowing applications
/// to receive action requests (archive, restore, remove, etc.) from the filesystem. It acts
/// as the communication channel between the HSM system and actual data movement operations.
///
/// # Usage
///
/// Create a `Copytool` using the builder pattern:
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::hsm::Copytool;
/// use rustreapi::LustrePath;
///
/// let mount_path = LustrePath::parse("/mnt/lustre")?;
///
/// // Create a copytool watching archive IDs 1 and 2
/// let copytool = Copytool::builder()
/// .archives(vec![1, 2])
/// .register(&mount_path)
/// .expect("Copytool registration");
///
/// // Receive action requests
/// let action_list = copytool.receive()?;
/// # Ok(())
/// # }
/// ```
///
/// # Event-driven Processing
///
/// For event-driven applications, use `raw_fd()` to get a file descriptor
/// that can be monitored with `poll()`, `select()`, or integration with event
/// loops like Tokio:
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// # use rustreapi::hsm::Copytool;
/// # use rustreapi::LustrePath;
/// # let mount_path = LustrePath::parse("/mnt/lustre")?;
/// // Create a copytool watching all archives
/// let copytool = Copytool::builder()
/// .non_blocking(true)
/// .register(&mount_path)
/// .expect("Copytool registration");
///
/// let fd = copytool.raw_fd()?;
/// // Use fd with event notification systems
/// # Ok(())
/// # }
/// ```
///
/// # Cleanup
///
/// The `Copytool` automatically unregisters from the HSM coordinator when dropped.
///
/// # Related Types
///
/// - `CopytoolBuilder`: Configures and creates `Copytool` instances
/// - `ActionList`: Collection of HSM action items received from coordinator
/// - `ActionItem`: Individual HSM operation to be performed
/// - `Mover`: Interface for data movement operations
#[derive(Debug)]
pub struct Copytool {
inner: *mut hsm_copytool_private,
}
/// Constructor
impl Copytool {
pub fn builder() -> CopytoolBuilder {
CopytoolBuilder::default()
}
}
impl Drop for Copytool {
/// Clean up the `Copytool` instance by
/// calling `unregister()` if pointer is
/// still valid.
fn drop(&mut self) {
if !self.inner.is_null() {
self.unregister()
.expect("Copytool should be valid before calling unregister.");
}
}
}
/// `Copytool` provides a connection to the `coordinator` and
/// returns lists of `ActionItems`. It can also be used to create a
/// `Mover` to process actions.
impl Copytool {
/// Unregister the `Copytool` instance from the HSM coordinator.
///
/// This method is called automatically when the `Copytool` is dropped,
/// so manual calls are typically unnecessary. It releases resources
/// held by the HSM coordinator for this copytool.
///
/// # Returns
///
/// A `Result` indicating success or failure of the unregistration.
fn unregister(&mut self) -> Result<()> {
unsafe {
let result = cvt_rc_m(
llapi_hsm_copytool_unregister(&mut self.inner as *mut *mut hsm_copytool_private),
"Failed to unregister copytool".to_string(),
);
assert!(self.inner.is_null());
result
}
}
/// Receive HSM action requests from the coordinator.
///
/// This method blocks until action items are available, unless the
/// copytool was configured as non-blocking. In non-blocking mode,
/// it will return immediately with an `EWOULDBLOCK` error if no
/// actions are available.
///
/// # Returns
///
/// * `Ok(ActionList)` - A list of HSM actions to be processed
/// * `Err` - If there was an error receiving actions from the coordinator
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// # use rustreapi::hsm::Copytool;
/// # use rustreapi::LustrePath;
/// # let mount_path = LustrePath::parse("/mnt/lustre")?;
/// # let copytool = Copytool::builder().register(&mount_path)?;
/// // Process actions in a loop
/// loop {
/// match copytool.receive() {
/// Ok(action_list) => {
/// // Process the action list
/// for action in action_list.iter() {
/// // Handle each action...
/// }
/// },
/// Err(e) => {
/// // Handle errors (like EWOULDBLOCK in non-blocking mode)
/// break;
/// }
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub fn receive(&self) -> Result<ActionList> {
let mut hal: *mut hsm_action_list = std::ptr::null_mut();
let mut msg_len = 0;
unsafe {
cvt_rc_m(
llapi_hsm_copytool_recv(self.inner, &mut hal, &mut msg_len),
"Failed to receive copytool message".to_string(),
)?;
Ok(ActionList::new(hal, msg_len as usize))
}
}
/// Returns a file descriptor that can be used with event notification systems.
///
/// This method is particularly useful for integrating the copytool with
/// event-driven frameworks like `epoll`, `poll`, `select`, or async runtimes
/// like Tokio. When events are available on this file descriptor,
/// `receive()` can be called to retrieve the available action items.
///
/// # Returns
///
/// * `Ok(RawDescriptor)` - A wrapper around the file descriptor that will
/// automatically close it when dropped
/// * `Err` - If there was an error obtaining the file descriptor
///
/// # Example with Tokio
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// # use rustreapi::hsm::Copytool;
/// # use rustreapi::LustrePath;
/// # let mount_path = LustrePath::parse("/mnt/lustre")?;
/// let copytool = Copytool::builder()
/// .non_blocking(true)
/// .register(&mount_path)?;
///
/// let fd = copytool.raw_fd()?;
///
/// // The file descriptor can now be used with tokio::io::unix::AsyncFd
/// // or similar event notification mechanisms
/// # Ok(())
/// # }
/// ```
pub fn raw_fd(&self) -> Result<RawDescriptor> {
unsafe {
cvt_lz_m(
llapi_hsm_copytool_get_fd(self.inner),
"Failed to get copytool fd".to_string(),
)
.map(RawDescriptor::new)
}
}
}
impl MoverRef for &Copytool {
fn mover_ref(&self) -> *const hsm_copytool_private {
self.inner
}
}
/// Builder for creating a connection to the Lustre HSM mover interface.
///
/// `MoverBuilder` creates a `Mover` instance which provides the capability
/// to interact with the Lustre HSM data movement interface.
///
/// # Usage
///
/// Se
///
/// Unlike `CopytoolBuilder`, `MoverBuilder` doesn't have configuration options
/// since the mover interface is simpler.
pub struct MoverBuilder {}
impl MoverBuilder {
/// Registers a new mover with the Lustre HSM coordinator.
///
/// This method establishes a connection with the HSM system on the
/// specified Lustre filesystem, allowing the application to perform
/// data movement operations.
///
/// # Arguments
///
/// * `mount_dir` - Path to a Lustre filesystem mount point
///
/// # Returns
///
/// * `Result<Mover>` - A registered mover instance on success, or an error
/// if registration fails (e.g., if the coordinator is unavailable or the path
/// is not a valid Lustre mount point)
///
pub fn register(self, mount_dir: &LustrePath) -> Result<Mover> {
let cstr = CString::new(mount_dir.as_ref().as_os_str().as_encoded_bytes())?;
let mut inner: *mut hsm_mover_private = std::ptr::null_mut();
unsafe {
cvt_rc_m(
llapi_hsm_mover_register(&mut inner as *mut *mut hsm_mover_private, cstr.as_ptr()),
"Failed to register copytool".to_string(),
)?;
}
Ok(Mover { inner })
}
}
/// Represents a connection to the Lustre HSM mover interface for processing data transfers.
///
/// A `Mover` provides the capability to move data between storage tiers in a Lustre
/// Hierarchical Storage Management (HSM) system. It allows processing action items through
/// the HSM data movement interface.
///
/// # Usage
///
/// Create a `Mover` using the builder pattern:
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::hsm::Mover;
/// use rustreapi::LustrePath;
///
/// let mount_path = LustrePath::parse("/mnt/lustre")?;
/// let mover = Mover::builder().register(&mount_path)?;
/// # Ok(())
/// # }
/// ```
///
/// # Cleanup
///
/// The `Mover` will automatically unregister itself when dropped. If you need to
/// unregister earlier, you can call the `unregister()` method directly.
///
/// # Related Types
///
/// - `MoverBuilder`: Used to configure and create a `Mover` instance
/// - `ActionProgress`: Used with a `Mover` to track and update progress during operations
/// - `Copytool`: Interface for receiving HSM actions from the Lustre coordinator
#[derive(Debug)]
#[repr(transparent)]
pub struct Mover {
inner: *mut hsm_mover_private,
}
unsafe impl Send for Mover {}
unsafe impl Sync for Mover {}
impl Drop for Mover {
fn drop(&mut self) {
if !self.inner.is_null() {
self.unregister()
.expect("Copytool should be valid before calling unregister.");
}
}
}
impl Mover {
pub fn builder() -> MoverBuilder {
MoverBuilder {}
}
/// Unregister the `Mover` instance from the HSM coordinator.
///
/// This method is called automatically when the `Mover` is dropped,
/// so manual calls are typically unnecessary. It releases resources
/// held by the HSM coordinator for this mover.
///
/// # Returns
///
/// A `Result` indicating success or failure of the unregistration.
fn unregister(&mut self) -> Result<()> {
unsafe {
let result = cvt_rc_m(
llapi_hsm_mover_unregister(&mut self.inner as *mut *mut hsm_mover_private),
"Failed to unregister copytool".to_string(),
);
assert!(self.inner.is_null());
result
}
}
}
impl MoverRef for &Mover {
fn mover_ref(&self) -> *const hsm_copytool_private {
unsafe {
std::mem::transmute::<*const hsm_mover_private, *const hsm_copytool_private>(self.inner)
}
}
}
pub struct ProgressBuilder {}
impl ProgressBuilder {
/// This is used to start processing an action.
/// The returned `CopyAction` is used to update the progress
/// and complete the action.
pub fn action_begin(
ct: impl MoverRef,
action: &ActionItem,
open_flags: i32,
) -> Result<ActionProgress> {
Self::begin(ct, action, -1, open_flags, false)
}
/// If an action can't be processed or is cancelled, then use this
/// instead of `action_begin()` and `action_end()`.
pub fn action_error(
ct: impl MoverRef,
action: &ActionItem,
retry: bool,
err: i32,
) -> Result<()> {
let mut ca = Self::begin(ct, action, -1, 0, true)?;
let flag: i32 = if retry { HP_FLAG_RETRY as i32 } else { 0 };
ca.end(action.extent.clone(), flag, err)?;
Ok(())
}
fn begin(
copytool: impl MoverRef,
action: &ActionItem,
mdt_idx: i32,
open_flags: i32,
is_error: bool,
) -> Result<ActionProgress> {
let mut hcp: *mut hsm_copyaction_private = std::ptr::null_mut();
let hai = action.to_native();
unsafe {
cvt_rc_m(
llapi_hsm_action_begin(
&mut hcp as *mut *mut hsm_copyaction_private,
copytool.mover_ref(),
hai.as_ref(),
mdt_idx,
open_flags,
is_error,
),
"Failed to begin copy action".to_string(),
)?;
Ok(ActionProgress { inner: hcp })
}
}
}
pub struct ActionProgress {
inner: *mut hsm_copyaction_private,
}
unsafe impl Send for ActionProgress {}
unsafe impl Sync for ActionProgress {} // TODO: is this safe?
impl ActionProgress {
pub fn dfid(&self) -> Result<Fid> {
let mut dfid = lu_fid::default();
unsafe {
cvt_nz_m(
llapi_hsm_action_get_dfid(self.inner, &mut dfid as *mut lu_fid),
"Failed to get dfid".to_string(),
)?;
}
Ok(Fid::from(dfid))
}
pub fn data_file(&self) -> Result<File> {
unsafe {
cvt_lz_m(
llapi_hsm_action_get_fd(self.inner),
"Failed to get fd".to_string(),
)
.map(|fd| File::from_raw_fd(fd))
}
}
pub fn progress(&mut self, extent: Extent, total: u64, hp_flags: i32) -> Result<()> {
let extent: hsm_extent = extent.into();
unsafe {
cvt_rc_m(
llapi_hsm_action_progress(self.inner, &extent, total, hp_flags),
"Failed to update copy action progress".to_string(),
)
}
}
///
/// Complete the copy action. This will free and null the inner pointer.
/// This `CopyAction` must not be used after this.
pub fn end(&mut self, extent: Extent, flag: i32, err: i32) -> Result<()> {
let flag: i32 = if err == 0 && flag == 0 {
HP_FLAG_COMPLETED as i32
} else {
0
};
let extent: hsm_extent = extent.into();
unsafe {
cvt_rc_m(
llapi_hsm_action_end(&mut self.inner, &extent, flag, err),
"Failed to end copy action".to_string(),
)
}
}
}
/// Currently this wraps the native struct which contains
/// a list of variably sized action items.
#[derive(Debug)]
pub struct ActionList {
// Pointer to memory allocated with libc and will
// be freed when this struct is dropped.
pub hal: NonNull<hsm_action_list>,
alloc_size: usize,
}
impl ActionList {
pub fn new(hal: *const hsm_action_list, size: usize) -> Self {
// TODO: is this worth doing or should we just convert to directly to Vec<ActionItem>?
let layout = Layout::from_size_align(size, align_of::<hsm_action_list>())
.expect("Arguments should follow constraints of from_size_align.");
let ptr = unsafe { alloc(layout) };
let new_hal = match NonNull::new(ptr as *mut hsm_action_list) {
Some(p) => p,
None => alloc::handle_alloc_error(layout),
};
unsafe { std::ptr::copy(hal as *const u8, new_hal.as_ptr() as *mut u8, size) };
Self {
hal: new_hal,
alloc_size: size,
}
}
}
impl Drop for ActionList {
fn drop(&mut self) {
unsafe {
alloc::dealloc(
self.hal.as_ptr() as *mut u8,
Layout::from_size_align(self.alloc_size, align_of::<hsm_action_list>())
.expect("Arguments should follow constraints of from_size_align."),
);
}
}
}
impl ActionList {
pub fn len(&self) -> usize {
unsafe { self.hal.as_ref().hal_count as usize }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> ActionIterator<'_> {
ActionIterator {
list: self,
next: 0,
prev: None,
}
}
}
// Step through a list of hsm_action_items
// Because hsm_action_item is a variable length struct,
// we use the hai_first and hai_next to step through the
// list based on the previous item.
/// This iterator converts the list of `hsm_action_items` in
/// into `Vec<ActionItem>` where `ActionItem`
/// is a native rust version of `hsm_action_item`.
///
pub struct ActionIterator<'a> {
list: &'a ActionList,
next: usize,
prev: Option<&'a hsm_action_item>,
}
impl Iterator for ActionIterator<'_> {
type Item = ActionItem;
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.list.len() {
return None;
}
let item = if let Some(prev) = self.prev {
hai_next(prev)
} else {
hai_first(self.list.hal.as_ptr())
};
self.next += 1;
self.prev = Some(item);
Some(ActionItem::from(item))
}
}
fn hai_first<'a>(hal: *const hsm_action_list) -> &'a hsm_action_item {
unsafe { &(*hai_first__extern(hal)) }
}
fn hai_next<'a>(hai: &hsm_action_item) -> &'a hsm_action_item {
unsafe { &(*hai_next__extern(hai)) }
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ActionHeader {
pub archive_id: u32,
pub flags: HsmRequestFlags,
pub actions: Vec<ActionItem>,
}
impl From<ActionList> for ActionHeader {
fn from(al: ActionList) -> Self {
let items: Vec<ActionItem> = al.iter().collect();
let hal = unsafe { al.hal.as_ref() };
Self {
archive_id: hal.hal_archive_id,
flags: HsmRequestFlags::from(hal.hal_flags),
actions: items,
}
}
}
impl Display for ActionHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Archive ID: {}, Actions: {}",
self.archive_id,
self.actions.len()
)?;
for item in self.actions.iter() {
write!(f, "\n{item}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cookie(u64);
impl Display for Cookie {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:X}", self.0)
}
}
impl From<Cookie> for u64 {
fn from(cookie: Cookie) -> u64 {
cookie.0
}
}
impl From<u64> for Cookie {
fn from(cookie: u64) -> Self {
Cookie(cookie)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionItem {
pub action: CopytoolAction,
pub fid: Fid,
pub dfid: Fid,
pub extent: Extent,
pub cookie: Cookie,
pub gid: u64,
pub data: Vec<u8>,
}
unsafe impl Send for ActionItem {}
impl From<&hsm_action_item> for ActionItem {
fn from(hai: &hsm_action_item) -> Self {
let len: usize = hai.hai_len as usize - size_of::<hsm_action_item>();
let data = unsafe { hai.hai_data.as_slice(len) };
Self {
action: CopytoolAction::from(hai.hai_action),
fid: Fid::from(hai.hai_fid),
dfid: Fid::from(hai.hai_dfid),
extent: Extent {
offset: hai.hai_extent.offset,
length: hai.hai_extent.length,
},
cookie: hai.hai_cookie.into(),
gid: hai.hai_gid,
#[allow(clippy::unnecessary_cast)] // only an issue on arm64
data: data.iter().map(|c| *c as u8).collect(),
}
}
}
impl ActionItem {
pub fn to_native(&self) -> Box<hsm_action_item> {
let mut hai = Box::new(hsm_action_item::default());
hai.hai_len = size_of::<hsm_action_item>() as u32; // + (self.data.len() as u32);
hai.hai_action = self.action.clone() as u32;
hai.hai_fid = self.fid.to_lu_fid();
hai.hai_dfid = self.dfid.to_lu_fid();
hai.hai_extent.offset = self.extent.offset;
hai.hai_extent.length = self.extent.length;
hai.hai_cookie = self.cookie.0;
hai.hai_gid = self.gid;
// Not clear where to free this memory, so disabled for now.
// Also don't think the data field needs to be sent back to the
// coordinator since it appears to be for the copytool's use.
//
// if !self.data.is_empty() {
// let layout =
// Layout::from_size_align(self.data.len(), mem::align_of::<c_char>()).unwrap();
// let ptr = unsafe { alloc(layout) };
// unsafe {
// std::ptr::copy(self.data.as_ptr(), ptr, self.data.len());
// }
// hai.hai_data = ptr as *mut c_char;
// }
hai
}
}
impl Display for ActionItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"action: {:?} {}, extent: {}, cookie: {}, gid: {}",
self.action, self.fid, self.extent, self.cookie, self.gid
)?;
if self.fid != self.dfid {
write!(f, ", Dfid: {}", self.dfid)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[repr(u32)]
pub enum CopytoolAction {
None = HSMA_NONE,
Archive = HSMA_ARCHIVE,
Restore = HSMA_RESTORE,
Cancel = HSMA_CANCEL,
Remove = HSMA_REMOVE,
}
impl From<u32> for CopytoolAction {
fn from(value: u32) -> Self {
match value {
HSMA_NONE => CopytoolAction::None,
HSMA_ARCHIVE => CopytoolAction::Archive,
HSMA_RESTORE => CopytoolAction::Restore,
HSMA_CANCEL => CopytoolAction::Cancel,
HSMA_REMOVE => CopytoolAction::Remove,
_ => panic!("Invalid CopytoolAction"),
}
}
}
impl Display for CopytoolAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extent {
pub offset: u64,
pub length: u64,
}
impl Default for Extent {
fn default() -> Self {
Self {
offset: 0,
length: LUSTRE_EOF as u64,
}
}
}
impl From<Extent> for hsm_extent {
fn from(extent: Extent) -> Self {
Self {
offset: extent.offset,
length: extent.length,
}
}
}
impl From<hsm_extent> for Extent {
fn from(extent: hsm_extent) -> Self {
Self {
offset: extent.offset,
length: extent.length,
}
}
}
impl Display for Extent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.offset, self.length as i64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_action_list_new() {
let hal = hsm_action_list::default();
let layout = Layout::array::<hsm_action_list>(1).expect("Layout should be valid.");
assert!(layout.size() > 0);
let al = ActionList::new(&hal, layout.size());
drop(al);
}
}