use std::ffi::OsStr;
use std::fs::OpenOptions;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub(super) struct Extent {
pub logical: u64,
pub physical: u64,
pub length: u64,
}
pub(super) fn fiemap_path(path: &OsStr) -> Result<Vec<Extent>> {
let file = OpenOptions::new()
.read(true)
.open(path)
.with_context(|| format!("opening {path:?}"))?;
let fd = file.as_raw_fd();
fiemap(fd).with_context(|| format!("mapping {path:?}"))
}
fn fiemap(fd: RawFd) -> Result<Vec<Extent>> {
let mut m = ffi::fiemap::new();
let mut extents: Vec<Extent> = Vec::new();
loop {
m.fm_start = match extents.iter().last() {
Some(extent) => extent.logical + extent.length,
None => 0,
};
unsafe { ffi::ioctl::fs_ioc_fiemap(fd, &mut m).context("ioctl(FS_IOC_FIEMAP)")? };
if m.fm_mapped_extents == 0 {
break;
}
let mut found_last = false;
for extent in m.fm_extents.iter().take(m.fm_mapped_extents as usize) {
if extent.fe_flags & ffi::FIEMAP_EXTENT_NOT_ALIGNED > 0 {
bail!("extent not aligned");
} else if extent.fe_flags & ffi::FIEMAP_EXTENT_MERGED > 0 {
bail!("file does not support extents");
} else if extent.fe_flags & ffi::FIEMAP_EXTENT_ENCODED > 0 {
bail!("extent encoded");
} else if extent.fe_flags & ffi::FIEMAP_EXTENT_DELALLOC > 0 {
bail!("extent not allocated yet");
} else if extent.fe_flags & ffi::FIEMAP_EXTENT_UNWRITTEN > 0 {
bail!("extent preallocated");
} else if extent.fe_flags & ffi::FIEMAP_EXTENT_UNKNOWN > 0 {
bail!("extent inaccessible");
}
extents.push(Extent {
logical: extent.fe_logical,
physical: extent.fe_physical,
length: extent.fe_length,
});
if extent.fe_flags & ffi::FIEMAP_EXTENT_LAST > 0 {
found_last = true;
}
}
if found_last {
break;
}
}
Ok(extents)
}
mod ffi {
use std::mem::{size_of, zeroed};
const EXTENT_COUNT: usize = 32;
const FIEMAP_SIZE: u32 =
(size_of::<fiemap>() as u32) - (size_of::<[fiemap_extent; EXTENT_COUNT]>() as u32);
#[allow(clippy::missing_safety_doc)]
pub mod ioctl {
use nix::{ioctl_readwrite_bad, request_code_readwrite};
ioctl_readwrite_bad!(
fs_ioc_fiemap,
request_code_readwrite!(b'f', 11, super::FIEMAP_SIZE),
super::fiemap
);
}
#[allow(dead_code)]
#[allow(clippy::unreadable_literal)]
pub mod fiemap_extent_flags {
pub const FIEMAP_EXTENT_LAST: u32 = 0x00000001; pub const FIEMAP_EXTENT_UNKNOWN: u32 = 0x00000002; pub const FIEMAP_EXTENT_DELALLOC: u32 = 0x00000004; pub const FIEMAP_EXTENT_ENCODED: u32 = 0x00000008; pub const FIEMAP_EXTENT_DATA_ENCRYPTED: u32 = 0x00000080; pub const FIEMAP_EXTENT_NOT_ALIGNED: u32 = 0x00000100; pub const FIEMAP_EXTENT_DATA_INLINE: u32 = 0x00000200; pub const FIEMAP_EXTENT_DATA_TAIL: u32 = 0x00000400; pub const FIEMAP_EXTENT_UNWRITTEN: u32 = 0x00000800; pub const FIEMAP_EXTENT_MERGED: u32 = 0x00001000; pub const FIEMAP_EXTENT_SHARED: u32 = 0x00002000; }
pub use fiemap_extent_flags::*;
#[repr(C)]
#[derive(Debug)]
pub struct fiemap {
pub fm_start: u64, pub fm_length: u64, pub fm_flags: u32, pub fm_mapped_extents: u32, pub fm_extent_count: u32, pub fm_reserved: u32,
pub fm_extents: [fiemap_extent; EXTENT_COUNT], }
#[repr(C)]
#[derive(Debug)]
pub struct fiemap_extent {
pub fe_logical: u64, pub fe_physical: u64, pub fe_length: u64, pub fe_reserved64: [u64; 2],
pub fe_flags: u32, pub fe_reserved: [u32; 3],
}
impl fiemap {
pub fn new() -> Self {
let mut r: Self = unsafe { zeroed() };
r.fm_extent_count = EXTENT_COUNT as u32;
r.fm_length = u64::MAX;
r
}
}
}