jfsm 0.1.5

A command-line tool to read file system metadata then return it in JSON format (output and errors).
Documentation
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use crate::logger::JsonError;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RawMetadata {
    pub pathname: String,
    pub dev: u64,
    pub inode: u64,
    pub nlink: u64,
    pub size: u64,
    pub blksize: u64,
    pub blocks: u64,
    pub rdev: u64,
    pub ftvalue: String,
    pub mode: u32,
    pub uid: u32,
    pub gid: u32,
    pub atime: i64,
    pub mtime: i64,
    pub ctime: i64,
    pub atimensec: i64,
    pub mtimensec: i64,
    pub ctimensec: i64,
}

impl RawMetadata {
    pub fn from_path(filepath: &str) -> Result<Self, JsonError> {
        let path = Path::new(filepath);
        
        if !path.exists() {
            return Err(JsonError::new(
                filepath.to_string(),
                "PATH-NOT-EXIST".to_string(),
            ));
        }
        
        // Get metadata first to check if it's a symlink
        let metadata_check = fs::symlink_metadata(path);
        let is_symlink = metadata_check.as_ref().map_or(false, |m| m.file_type().is_symlink());
        
        // For symlinks, use the original path; for others, try to canonicalize
        let pathname = if is_symlink {
            filepath.to_string()
        } else {
            match path.canonicalize() {
                Ok(canonical_path) => canonical_path.to_string_lossy().to_string(),
                Err(_) => filepath.to_string(),
            }
        };
        
        match metadata_check {
            Ok(metadata) => {
                // Determine file type based on mode bits on Unix systems
                #[cfg(unix)]
                let file_type = {
                    use std::os::unix::fs::MetadataExt;
                    let mode = metadata.mode();
                    match mode & 0o170000 {  // S_IFMT mask
                        0o010000 => "S_IFIFO".to_string(),   // FIFO
                        0o020000 => "S_IFCHR".to_string(),   // Character device
                        0o040000 => "S_IFDIR".to_string(),   // Directory
                        0o060000 => "S_IFBLK".to_string(),   // Block device
                        0o100000 => "S_IFREG".to_string(),   // Regular file
                        0o120000 => "S_IFLNK".to_string(),   // Symbolic link
                        0o140000 => "S_IFSOCK".to_string(),  // Socket
                        _ => "UNKNOWN".to_string(),
                    }
                };
                
                #[cfg(not(unix))]
                let file_type = if metadata.is_dir() {
                    "S_IFDIR".to_string()
                } else if metadata.is_file() {
                    "S_IFREG".to_string()
                } else {
                    "UNKNOWN".to_string()
                };
                
                // Get the mode from permissions (Unix-specific)
                #[cfg(unix)]
                let mode = {
                    use std::os::unix::fs::MetadataExt;
                    metadata.mode()
                };
                
                #[cfg(not(unix))]
                let mode = 0o777; // Default mode for non-Unix systems
                
                // Get Unix-specific metadata
                #[cfg(unix)]
                let (dev, inode, nlink, blksize, blocks, rdev, uid, gid) = {
                    use std::os::unix::fs::MetadataExt;
                    (
                        metadata.dev(),
                        metadata.ino(),
                        metadata.nlink(),
                        metadata.blksize(),
                        metadata.blocks(),
                        metadata.rdev(),
                        metadata.uid(),
                        metadata.gid(),
                    )
                };
                
                #[cfg(not(unix))]
                let (dev, inode, nlink, blksize, blocks, rdev, uid, gid) = (
                    0u64, 0u64, 1u64, 0u64, 0u64, 0u64, 0u32, 0u32,
                );
                
                // Convert timestamps to seconds since epoch
                #[cfg(unix)]
                let (atime, mtime, ctime, atimensec, mtimensec, ctimensec) = {
                    use std::os::unix::fs::MetadataExt;
                    (
                        metadata.atime(),
                        metadata.mtime(),
                        metadata.ctime(),
                        metadata.atime_nsec(),
                        metadata.mtime_nsec(),
                        metadata.ctime_nsec(),
                    )
                };
                
                #[cfg(not(unix))]
                let (atime, mtime, ctime, atimensec, mtimensec, ctimensec) = {
                    let atime = metadata.accessed()
                        .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs() as i64)
                        .unwrap_or(0);
                    let mtime = metadata.modified()
                        .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs() as i64)
                        .unwrap_or(0);
                    let ctime = metadata.created()
                        .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs() as i64)
                        .unwrap_or(0);
                    (atime, mtime, ctime, 0, 0, 0)
                };
                
                Ok(RawMetadata {
                    pathname,
                    dev,
                    inode,
                    nlink,
                    size: metadata.len(),
                    blksize,
                    blocks,
                    rdev,
                    ftvalue: file_type,
                    mode,
                    uid,
                    gid,
                    atime,
                    mtime,
                    ctime,
                    atimensec,
                    mtimensec,
                    ctimensec,
                })
            }
            Err(e) => {
                // Check if this is a permission error
                let error_message = if e.kind() == std::io::ErrorKind::PermissionDenied {
                    "PERMISSION-ERROR".to_string()
                } else {
                    "PARSING-ERROR".to_string()
                };
                Err(JsonError::new(
                    filepath.to_string(),
                    error_message,
                ))
            },
        }
    }
}