Viewing: path.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::{
    Error,
    Error::{MsgErrno, NotLustreFileSystem},
    MountStats,
    error::cvt_lz_m,
};
use cstrbuf::CStrBuf;
use lustreapi_sys::llapi_get_fsname;
use nix::errno;
use serde::{Deserialize, Serialize};
use std::{
    ffi::CString,
    fmt::{Display, Formatter},
    fs::File,
    path::{Path, PathBuf},
};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct LustrePath {
    path: PathBuf,
}

/// Represents a validated path to a Lustre filesystem.
///
/// `LustrePath` encapsulates an absolute path to a Lustre filesystem mount point or a location within
/// a Lustre filesystem. It validates that the path actually points to a Lustre filesystem
/// during creation, providing early failure for operations that require Lustre-specific
/// functionality.
///
/// This type serves as a prerequisite for many Lustre operations and provides
/// safety by ensuring operations are only performed on valid Lustre paths.
///
/// # Creating a `LustrePath`
///
/// The primary way to create a `LustrePath` is through the [`parse`](#method.parse) method,
/// which validates that the provided path points to a Lustre filesystem.
///
/// # Usage with Lustre APIs
///
/// Once created, a `LustrePath` can be used with various Lustre operations like:
/// - Getting filesystem information (OST count, MDT count)
/// - Opening files with specific Lustre parameters
/// - Working with Lustre file identifiers (FIDs)
///
/// # Trait Implementations
///
/// `LustrePath` implements:
/// - `Display` for string representation
/// - `AsRef<Path>` for easy use with standard path operations
/// - `Serialize` and `Deserialize` for serialization support
///
/// # Examples
///
/// ```no_run
/// # use rustreapi::{LustrePath, Result};
/// # fn example() -> Result<()> {
/// // Create a validated Lustre path
/// let lustre_path = LustrePath::parse("/mnt/lustre")?;
///
/// // Use it to open a file handle to the Lustre mount point
/// let file = lustre_path.open()?;
/// # Ok(())
/// # }
/// ```
impl LustrePath {
    /// Parses a path string into a validated Lustre filesystem path.
    ///
    /// This method validates that the provided path points to a Lustre filesystem
    /// by attempting to retrieve the Lustre filesystem name using `llapi_get_fsname`.
    /// The path is canonicalized before validation.
    ///
    /// # Arguments
    ///
    /// * `path` - A string representing a path to a Lustre filesystem
    ///
    /// # Returns
    ///
    /// * `Ok(LustrePath)` - A validated Lustre filesystem path
    /// * `Err` - If the path is not a Lustre filesystem or other errors occur
    /// ```
    pub fn parse(path: &str) -> crate::Result<LustrePath> {
        let path = Path::new(path).canonicalize()?;
        let cstr = CString::new(path.as_os_str().as_encoded_bytes())?;
        let mut buf = CStrBuf::new(256);
        let _result = unsafe {
            cvt_lz_m(
                llapi_get_fsname(cstr.as_ptr(), buf.as_mut_ptr(), buf.buffer_len()),
                "llapi_get_fsname".to_string(),
            )
            .map_err(|e| match e {
                MsgErrno(_, errno::Errno::ENOTTY) => {
                    NotLustreFileSystem(path.to_string_lossy().to_string())
                }
                e => e,
            })?
        };

        Ok(LustrePath { path })
    }
    ///
    /// Opens this Lustre path as a file.
    ///
    /// This is a convenience method that attempts to open the path represented by
    /// this `LustrePath` and provides Lustre-specific error handling.
    ///
    /// # Returns
    ///
    /// * `Ok(File)` - A file handle for the opened Lustre path
    /// * `Err` - If opening the path fails, wrapped in a `LustreRootOpenError` with path details
    /// ```
    pub fn open(&self) -> crate::Result<File> {
        let file = match File::open(self) {
            Ok(file) => file,
            Err(e) => return Err(Error::LustreRootOpenError(self.clone(), e)),
        };

        Ok(file)
    }

    /// Finds a Lustre mount point by filesystem name.
    ///
    /// This function searches through all Lustre mounts on the system to find
    /// a mount point for the specified filesystem name. It looks for client mounts
    /// with `fs_label` matching the pattern `"Lustre-{fs_name}"`.
    ///
    /// # Arguments
    ///
    /// * `fs_name` - The name of the Lustre filesystem to search for
    ///
    /// # Returns
    ///
    /// * `Ok(LustrePath)` - A validated Lustre path to the filesystem mount point
    /// * `Err` - If the filesystem is not found or other errors occur
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use rustreapi::{LustrePath, Result};
    /// # fn example() -> Result<()> {
    /// // Find mount point for filesystem named "lustre01"
    /// let mount_path = LustrePath::find_mount_by_fsname("lustre01")?;
    /// println!("Found mount at: {}", mount_path);
    /// # Ok(())
    /// # }
    /// ```
    pub fn find_mount_by_fsname(fs_name: &str) -> crate::Result<LustrePath> {
        let mounts = MountStats::discover_mounts()?;

        let expected_label = format!("Lustre-{}", fs_name);

        for mount in mounts {
            if let Some(ref fs_label) = mount.fs_label
                && fs_label == &expected_label
            {
                return Ok(LustrePath {
                    path: mount.info.mount_point,
                });
            }
        }

        Err(Error::FilesystemNotFound {
            filesystem: fs_name.to_string(),
        })
    }
}

impl Display for LustrePath {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.path.display())
    }
}

impl AsRef<Path> for LustrePath {
    fn as_ref(&self) -> &Path {
        &self.path
    }
}