Viewing: layout.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, Fid,
error::{Result, cvt_lz_m, cvt_nz, cvt_nz_m},
};
use cstrbuf::CStrBuf;
use lustreapi_sys::*;
use nix::errno;
use std::{
ffi::CString,
fmt,
fmt::Display,
fs::File,
os::fd::{AsRawFd, FromRawFd},
path::Path,
};
use bitmask_enum::bitmask;
#[cfg(not(feature = "LUSTRE_2_14"))]
#[repr(u32)]
#[derive(Clone)]
pub enum LayoutGetFlags {
NONE = 0,
EXPECTED = LLAPI_LAYOUT_GET_EXPECTED,
COPY = LLAPI_LAYOUT_GET_COPY,
CHECK = LLAPI_LAYOUT_GET_CHECK,
}
#[cfg(feature = "LUSTRE_2_14")]
#[repr(u32)]
#[derive(Clone)]
pub enum LayoutGetFlags {
NONE = 0,
EXPECTED = 0x1,
}
#[repr(u32)]
#[derive(Clone)]
pub enum CompUse {
First = LLAPI_LAYOUT_COMP_USE_FIRST,
Last = LLAPI_LAYOUT_COMP_USE_LAST,
Next = LLAPI_LAYOUT_COMP_USE_NEXT,
Prev = LLAPI_LAYOUT_COMP_USE_PREV,
}
#[cfg(not(feature = "LUSTRE_2_14"))]
#[bitmask(u32)]
#[bitmask_config(vec_debug, flags_iter)]
pub enum CompEntryFlags {
Stale = LCME_FL_STALE,
PrefRd = LCME_FL_PREF_RD,
PrefWr = LCME_FL_PREF_WR,
PrefRW = LCME_FL_PREF_RW,
Offline = LCME_FL_OFFLINE,
Init = LCME_FL_INIT,
NoSync = LCME_FL_NOSYNC,
Extension = LCME_FL_EXTENSION,
Parity = LCME_FL_PARITY,
Compress = LCME_FL_COMPRESS,
Partial = LCME_FL_PARTIAL,
NoCompr = LCME_FL_NOCOMPR,
Neg = LCME_FL_NEG,
}
#[cfg(feature = "LUSTRE_2_14")]
#[bitmask(u32)]
#[bitmask_config(vec_debug, flags_iter)]
pub enum CompEntryFlags {
Stale = LCME_FL_STALE,
PrefRd = LCME_FL_PREF_RD,
PrefWr = LCME_FL_PREF_WR,
PrefRW = LCME_FL_PREF_RW,
Offline = LCME_FL_OFFLINE,
Init = LCME_FL_INIT,
NoSync = LCME_FL_NOSYNC,
Extension = LCME_FL_EXTENSION,
Compress = LCME_FL_COMPRESS,
Partial = LCME_FL_PARTIAL,
NoCompr = LCME_FL_NOCOMPR,
Neg = LCME_FL_NEG,
}
impl Default for CompEntryFlags {
fn default() -> Self {
CompEntryFlags::none()
}
}
impl Display for CompEntryFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let v: Vec<&str> = CompEntryFlags::flags()
.filter(|&(_, value)| self.contains(*value))
.map(|(name, _)| *name)
.collect();
if v.is_empty() {
write!(f, "Uninit")?;
} else {
write!(f, "{}", v.join(", "))?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Layout {
layout: *mut llapi_layout,
}
impl Drop for Layout {
fn drop(&mut self) {
unsafe { llapi_layout_free(self.layout) }
}
}
impl Layout {
pub fn new() -> Self {
let layout = unsafe { llapi_layout_alloc() };
// Rust considers allocation failures unrecoverable
assert!(!layout.is_null());
Self { layout }
}
pub fn as_lu_layout(&self) -> *mut llapi_layout {
self.layout
}
pub fn with_path(name: &Path, flags: LayoutGetFlags) -> Result<Layout> {
let name = name.as_os_str();
let cstr = CString::new(name.as_encoded_bytes())?;
let ll = unsafe { llapi_layout_get_by_path(cstr.as_ptr(), flags as u32) };
if ll.is_null() {
return Err(Error::MsgErrno(
name.to_string_lossy().to_string(),
errno::Errno::last(),
));
}
Ok(Layout { layout: ll })
}
pub fn with_fd(file: &File, flags: LayoutGetFlags) -> Result<Layout> {
let fd = file.as_raw_fd();
unsafe {
let ll = llapi_layout_get_by_fd(fd, flags as u32);
if ll.is_null() {
Err(Error::MsgErrno(
"llapi_layout_get_by_fd".to_string(),
errno::Errno::last(),
))
} else {
Ok(Layout { layout: ll })
}
}
}
pub fn with_fid(path: &Path, fid: Fid, flags: LayoutGetFlags) -> Result<Layout> {
let cstr = CString::new(path.as_os_str().as_encoded_bytes())?;
let ll = unsafe { llapi_layout_get_by_fid(cstr.as_ptr(), &fid.to_lu_fid(), flags as u32) };
if ll.is_null() {
return Err(Error::MsgErrno(
"llapi_layout_get_by_fid".to_string(),
errno::Errno::last(),
));
}
Ok(Layout { layout: ll })
}
}
impl Default for Layout {
fn default() -> Self {
Self::new()
}
}
// Builder and getter methods for Layout and Components
// TODO: Change getters to return Option<T> instead of Result<T>.
impl Layout {
pub fn get_stripe_count(&self) -> Result<u64> {
let mut num = std::os::raw::c_ulong::default();
unsafe { cvt_nz(llapi_layout_stripe_count_get(self.layout, &mut num))? };
Ok(num)
}
pub fn get_stripe_size(&self) -> Result<u64> {
let mut num = std::os::raw::c_ulong::default();
unsafe { cvt_nz(llapi_layout_stripe_size_get(self.layout, &mut num))? };
Ok(num)
}
pub fn get_stripe_pattern(&self) -> Result<u64> {
let mut num = std::os::raw::c_ulong::default();
unsafe { cvt_nz(llapi_layout_pattern_get(self.layout, &mut num))? };
Ok(num)
}
pub fn get_ost_index(&self, stripe: u64) -> Result<u64> {
let mut num = std::os::raw::c_ulong::default();
unsafe { cvt_nz(llapi_layout_ost_index_get(self.layout, stripe, &mut num))? };
Ok(num)
}
pub fn get_pool_name(&self) -> Result<String> {
let mut cbuf = CStrBuf::new(256);
unsafe {
cvt_nz(llapi_layout_pool_name_get(
self.layout,
cbuf.as_mut_ptr(),
cbuf.buffer_len() - 1,
))?
};
cbuf.into_string().map_err(Error::StringConversionError)
}
pub fn get_flags(&self) -> Result<u32> {
let mut flags = std::os::raw::c_uint::default();
unsafe { cvt_nz(llapi_layout_flags_get(self.layout, &mut flags))? };
Ok(flags)
}
pub fn get_comp_flags(&self) -> Result<CompEntryFlags> {
let mut flags = std::os::raw::c_uint::default();
unsafe { cvt_nz(llapi_layout_comp_flags_get(self.layout, &mut flags))? };
Ok(flags.into())
}
pub fn get_mirror_count(&self) -> Result<u16> {
let mut count = std::os::raw::c_ushort::default();
unsafe { cvt_nz(llapi_layout_mirror_count_get(self.layout, &mut count))? };
Ok(count)
}
pub fn get_comp_extent(&self) -> Result<(u64, u64)> {
let mut start = std::os::raw::c_ulong::default();
let mut end = std::os::raw::c_ulong::default();
unsafe {
cvt_nz(llapi_layout_comp_extent_get(
self.layout,
&mut start,
&mut end,
))?
};
Ok((start, end))
}
pub fn get_comp_id(&self) -> Result<u32> {
let mut id = std::os::raw::c_uint::default();
unsafe { cvt_nz(llapi_layout_comp_id_get(self.layout, &mut id))? };
Ok(id)
}
pub fn stripe_count(&self, stripe_count: u64) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_stripe_count_set(self.layout, stripe_count))? };
Ok(self)
}
pub fn stripe_size(&self, stripe_size: u64) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_stripe_size_set(self.layout, stripe_size))? };
Ok(self)
}
pub fn stripe_pattern(&self, stripe_pattern: u64) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_pattern_set(self.layout, stripe_pattern))? };
Ok(self)
}
pub fn ost_index(&self, index: i32, ost: u64) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_ost_index_set(self.layout, index, ost))? };
Ok(self)
}
pub fn pool_name(&self, pool_name: &str) -> Result<&Self> {
let cstr = CString::new(pool_name)?;
unsafe { cvt_nz(llapi_layout_pool_name_set(self.layout, cstr.as_ptr()))? };
Ok(self)
}
pub fn flags(&self, flags: u32) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_flags_set(self.layout, flags))? };
Ok(self)
}
pub fn extension_size(&self, size: u64) -> Result<&Self> {
unsafe {
cvt_nz_m(
llapi_layout_extension_size_set(self.layout, size),
"extension_size".to_string(),
)?
};
Ok(self)
}
pub fn comp_flags(&self, flags: CompEntryFlags) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_comp_flags_set(self.layout, u32::from(flags)))? };
Ok(self)
}
pub fn mirror_count(&self, mirror_count: u16) -> Result<&Self> {
unsafe { cvt_nz(llapi_layout_mirror_count_set(self.layout, mirror_count))? };
Ok(self)
}
pub fn comp_extent(&self, start: u64, end: u64) -> Result<&Self> {
unsafe {
cvt_nz_m(
llapi_layout_comp_extent_set(self.layout, start, end),
"comp_extent".to_string(),
)?
};
Ok(self)
}
pub fn comp_add(&self) -> Result<&Self> {
unsafe { cvt_nz_m(llapi_layout_comp_add(self.layout), "comp_add".to_string())? };
Ok(self)
}
pub fn comp_del(&self) -> Result<()> {
unsafe { cvt_nz_m(llapi_layout_comp_del(self.layout), "comp_del".to_string())? };
Ok(())
}
pub fn file_comp_add(&self, path: &Path) -> Result<()> {
let cpath = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_nz_m(
llapi_layout_file_comp_add(cpath.as_ptr(), self.layout),
"comp_add_path".to_string(),
)?
};
Ok(())
}
/// Delete component from the file based on the component ID.
///
pub fn file_comp_del(path: &Path, id: u32) -> Result<()> {
let flags = 0;
let cpath = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_nz_m(
llapi_layout_file_comp_del(cpath.as_ptr(), id, flags),
"comp_del_path".to_string(),
)?
};
Ok(())
}
/// Delete one or more components from the file based on the component ID.
pub fn file_comp_del_flags(path: &Path, flags: CompEntryFlags) -> Result<()> {
let id = LCME_ID_INVAL;
let cpath = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_nz_m(
llapi_layout_file_comp_del(cpath.as_ptr(), id, flags.bits()),
"comp_del_path".to_string(),
)?
};
Ok(())
}
pub fn comp_use(&self, use_: CompUse) -> Result<&Self> {
unsafe {
cvt_nz_m(
llapi_layout_comp_use(self.layout, use_ as u32),
"comp_use".to_string(),
)?
};
Ok(self)
}
pub fn create(&self, path: &Path) -> Result<File> {
let cpath = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_lz_m(
llapi_layout_file_create(cpath.as_ptr(), 0, 0o660, self.layout),
cpath.to_string_lossy().to_string(),
)
.map(|fd| File::from_raw_fd(fd))
}
}
pub fn create_with_mode(&self, path: &Path, mode_in: i32) -> Result<File> {
let cpath = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_lz_m(
llapi_layout_file_create(cpath.as_ptr(), 0, mode_in, self.layout),
cpath.to_string_lossy().to_string(),
)
.map(|fd| File::from_raw_fd(fd))
}
}
pub fn open(&self, path: &Path) -> Result<File> {
let cpath = CString::new(path.as_os_str().as_encoded_bytes())?;
unsafe {
cvt_lz_m(
llapi_layout_file_open(cpath.as_ptr(), 0, 0o660, self.layout),
cpath.to_string_lossy().to_string(),
)
.map(|fd| File::from_raw_fd(fd))
}
}
pub fn sanity(&self, incomplete: bool, flr: bool) -> Result<()> {
unsafe {
cvt_nz_m(
llapi_layout_sanity(self.layout, incomplete, flr),
"sanity".to_string(),
)?
};
Ok(())
}
#[cfg(not(feature = "LUSTRE_2_14"))]
pub fn sanity_v2(&self, incomplete: bool, flr: bool, fsname: String) -> Result<()> {
use std::os::raw::c_char;
let fsname = CString::new(fsname)?;
unsafe {
cvt_nz_m(
llapi_layout_v2_sanity(
self.layout,
incomplete,
flr,
fsname.as_ptr() as *mut c_char,
),
"sanity_v2".to_string(),
)?
};
Ok(())
}
}
impl Display for Layout {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for l in self.iter() {
writeln!(f, "id: {}", l.get_comp_id()?)?;
writeln!(f, "comp_extent: {:?}", l.get_comp_extent()?)?;
writeln!(f, "mirror_count: {}", l.get_mirror_count()?)?;
writeln!(f, "stripe_count: {}", l.get_stripe_count()?)?;
writeln!(f, "stripe_size: {}", l.get_stripe_size()?)?;
writeln!(f, "pattern: {}", l.get_stripe_pattern()?)?;
writeln!(f, "pool_name: {}", l.get_pool_name()?)?;
writeln!(f, "flags: 0x{:x}", l.get_flags()?)?;
let comp_flags = l.get_comp_flags()?;
writeln!(f, "comp_flags: {comp_flags}")?;
let mut osts = Vec::new();
for i in 0..l.get_stripe_count()? {
match l.get_ost_index(i) {
Ok(ost) => osts.push(ost),
Err(_) => break,
}
}
writeln!(f, "ost_index: {osts:?}")?;
writeln!(f)?;
}
Ok(())
}
}
/// Iterator for Layout.
/// (experimental)
pub struct LayoutIter<'a> {
layout: &'a Layout,
first: bool,
}
impl<'a> Iterator for LayoutIter<'a> {
type Item = &'a Layout;
fn next(&mut self) -> Option<Self::Item> {
if self.first {
self.first = false;
return Some(self.layout);
}
match self.layout.comp_use(CompUse::Next) {
Ok(_) => Some(self.layout),
Err(_) => None,
}
}
}
pub struct LayoutIterReverse<'a> {
layout: &'a Layout,
first: bool,
}
impl<'a> Iterator for LayoutIterReverse<'a> {
type Item = &'a Layout;
fn next(&mut self) -> Option<Self::Item> {
if self.first {
self.first = false;
return Some(self.layout);
}
match self.layout.comp_use(CompUse::Prev) {
Ok(_) => Some(self.layout),
Err(_) => None,
}
}
}
impl Layout {
/// Returns an iterator over the components of the layout.
/// This starts with the first component and iterates to the last.
/// A layout always has at least 1 component, so
/// this will always return at least one item.
///
/// ```
/// use rustreapi::Layout;
///
/// let layout = Layout::new();
/// for l in layout.iter() {
/// println!("{}", l.get_stripe_count().unwrap());
/// }
/// ```
pub fn iter(&self) -> LayoutIter<'_> {
self.comp_use(CompUse::First)
.expect("Layout should have at least one component."); // First shouldn't fail if it's a valid layout
LayoutIter {
layout: self,
first: true,
}
}
/// Returns an iterator over the components of the layout.
/// This starts with the last component and iterates to the first.
/// A layout always has at least 1 component, so
/// this will always return at least one item.
pub fn iter_reverse(&self) -> LayoutIterReverse<'_> {
self.comp_use(CompUse::Last)
.expect("Layout should have at least one component."); // Last shouldn't fail if it's a valid layout
LayoutIterReverse {
layout: self,
first: true,
}
}
}
/// `CompLayout` is a rust version of Lustre's
/// internal `llapi_layout` structure. The goal
/// is to provide a more rust friendly interface
/// for retrieving layout information. However,
/// perhaps the iterator on Layout is good enough
/// and this isn't needed.
///
/// TODO: still in progress
type CompLayout = Vec<SingleLayout>;
#[derive(Default, Debug)]
pub struct SingleLayout {
pub pattern: u64,
pub stripe_size: u64,
pub stripe_count: u64,
pub osts: Vec<u64>,
pub mirror_count: u16,
pub comp_flags: CompEntryFlags,
pub pool_name: Option<String>,
pub start: u64,
pub end: u64,
comp_id: u32,
lcmd_flags: u32,
}
impl Display for SingleLayout {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"comp_id: {}\npattern: {}\nstripe_size: {}\nstripe_count: {}\nosts: {:?}\npool_name: {:?}\nstart: {}\nend: {}\nlcmd_flags: {}",
self.comp_id,
self.pattern,
self.stripe_size,
self.stripe_count,
self.osts,
self.pool_name,
self.start,
self.end as i64,
self.lcmd_flags
)
}
}
// TODO replace these with a CompLayout::from() or Layout::to_comp_layout()
pub fn get_layout(name: &Path, flags: LayoutGetFlags) -> Result<Vec<SingleLayout>> {
let ll = Layout::with_path(name, flags)?;
ll_to_comp_layout(ll)
}
pub fn get_layout_fd(file: &File, flags: LayoutGetFlags) -> Result<Vec<SingleLayout>> {
let ll = Layout::with_fd(file, flags)?;
ll_to_comp_layout(ll)
}
pub fn get_layout_fid(path: &Path, fid: Fid, flags: LayoutGetFlags) -> Result<CompLayout> {
let ll = Layout::with_fid(path, fid, flags)?;
ll_to_comp_layout(ll)
}
fn ll_to_comp_layout(ll: Layout) -> Result<CompLayout> {
ll.iter()
.map(|l| {
let (start, end) = l.get_comp_extent()?;
let mut layout = SingleLayout {
stripe_count: l.get_stripe_count()?,
stripe_size: l.get_stripe_size()?,
start,
end,
pattern: l.get_stripe_pattern()?,
comp_id: l.get_comp_id()?,
lcmd_flags: l.get_flags()?,
comp_flags: l.get_comp_flags()?,
mirror_count: l.get_mirror_count()?,
..Default::default()
};
for i in 0..layout.stripe_count {
match l.get_ost_index(i) {
Ok(ost) => layout.osts.push(ost),
Err(_) => break,
}
}
layout.pool_name = match l.get_pool_name() {
Ok(name) => {
if name.is_empty() {
None
} else {
Some(name)
}
}
Err(err) => {
println!("pool name conversion failed: {err:?}");
None
}
};
Ok(layout)
})
.collect()
}
#[cfg(test)]
mod test {
use super::*;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[test]
fn test_new_layout_iter() {
let l = Layout::new();
let mut iter = l.iter();
// Always at least one component
assert!(iter.next().is_some());
assert!(iter.next().is_none());
}
#[test]
fn test_default_values() -> Result<()> {
let l = Layout::new();
assert_eq!(l.get_stripe_count()?, LLAPI_LAYOUT_DEFAULT);
assert_eq!(l.get_stripe_size()?, LLAPI_LAYOUT_DEFAULT);
assert_eq!(l.get_stripe_pattern()?, LLAPI_LAYOUT_DEFAULT);
assert_eq!(l.get_pool_name()?, "");
assert_eq!(l.get_comp_extent()?, (0, LUSTRE_EOF as u64));
assert_eq!(l.get_comp_id()?, 0);
assert_eq!(l.get_flags()?, 0);
Ok(())
}
#[test]
fn test_flags() -> Result<()> {
let l = Layout::new();
l.flags(LCME_FL_EXTENSION)?;
assert_eq!(l.get_flags()?, LCME_FL_EXTENSION);
Ok(())
}
}