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 crate::raw::RawMetadata;
use std::path::Path;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PermsMetadata {
    pub user: String,
    pub group: String,
    pub perms: String,
    pub ownerp: String,
    pub groupp: String,
    pub othersp: String,
    pub isfile: bool,
    pub issyml: bool,
    pub isdir: bool,
    pub isexec: bool,
}

impl PermsMetadata {
    pub fn from_raw(raw: &RawMetadata) -> Self {
        let path = Path::new(&raw.pathname);
        
        // Get file type information
        let is_symlink = path.is_symlink();
        let (is_file, is_dir) = if is_symlink {
            // For symlinks, check the raw metadata to determine what it points to
            match raw.ftvalue.as_str() {
                "S_IFREG" => (true, false),   // symlink pointing to regular file
                "S_IFDIR" => (false, true),   // symlink pointing to directory
                _ => (false, false),          // symlink pointing to something else
            }
        } else {
            (path.is_file(), path.is_dir())
        };
        
        // Check if file is executable (Unix-specific)
        let is_executable = if is_file {
            #[cfg(unix)]
            {
                (raw.mode & 0o111) != 0
            }
            #[cfg(not(unix))]
            {
                false // Default to false for non-Unix systems
            }
        } else {
            false
        };
        
        // Format permissions in human-readable form
        let perms_str = format_permissions(raw.mode);
        let (owner_perms, group_perms, others_perms) = extract_permission_parts(raw.mode);
        
        // Get user and group names (Unix-specific)
        #[cfg(unix)]
        let (user_name, group_name) = {
            let uid = raw.uid;
            let gid = raw.gid;
            
            // Try to get user and group names, fallback to uid/gid if not found
            let user = get_user_name(uid).unwrap_or_else(|| uid.to_string());
            let group = get_group_name(gid).unwrap_or_else(|| gid.to_string());
            
            (user, group)
        };
        
        #[cfg(not(unix))]
        let (user_name, group_name) = ("unknown".to_string(), "unknown".to_string());
        
        PermsMetadata {
            user: user_name,
            group: group_name,
            perms: perms_str,
            ownerp: owner_perms,
            groupp: group_perms,
            othersp: others_perms,
            isfile: is_file,
            issyml: is_symlink,
            isdir: is_dir,
            isexec: is_executable,
        }
    }
}

// Helper function to format permissions in human-readable form (e.g., "drwxr-xr-x")
fn format_permissions(mode: u32) -> String {
    let mut result = String::with_capacity(10);
    
    // File type
    result.push(if (mode & 0o40000) != 0 {
        'd' // Directory
    } else if (mode & 0o100000) != 0 {
        '-' // Regular file
    } else if (mode & 0o120000) != 0 {
        'l' // Symbolic link
    } else {
        '?'
    });
    
    // Owner permissions
    result.push(if (mode & 0o400) != 0 { 'r' } else { '-' });
    result.push(if (mode & 0o200) != 0 { 'w' } else { '-' });
    result.push(if (mode & 0o100) != 0 { 'x' } else { '-' });
    
    // Group permissions
    result.push(if (mode & 0o40) != 0 { 'r' } else { '-' });
    result.push(if (mode & 0o20) != 0 { 'w' } else { '-' });
    result.push(if (mode & 0o10) != 0 { 'x' } else { '-' });
    
    // Others permissions
    result.push(if (mode & 0o4) != 0 { 'r' } else { '-' });
    result.push(if (mode & 0o2) != 0 { 'w' } else { '-' });
    result.push(if (mode & 0o1) != 0 { 'x' } else { '-' });
    
    result
}

// Helper function to extract permission parts
fn extract_permission_parts(mode: u32) -> (String, String, String) {
    let owner = format!("{}{}{}",
        if (mode & 0o400) != 0 { 'r' } else { '-' },
        if (mode & 0o200) != 0 { 'w' } else { '-' },
        if (mode & 0o100) != 0 { 'x' } else { '-' }
    );
    
    let group = format!("{}{}{}",
        if (mode & 0o40) != 0 { 'r' } else { '-' },
        if (mode & 0o20) != 0 { 'w' } else { '-' },
        if (mode & 0o10) != 0 { 'x' } else { '-' }
    );
    
    let others = format!("{}{}{}",
        if (mode & 0o4) != 0 { 'r' } else { '-' },
        if (mode & 0o2) != 0 { 'w' } else { '-' },
        if (mode & 0o1) != 0 { 'x' } else { '-' }
    );
    
    (owner, group, others)
}

// Helper function to get user name from UID (Unix-specific)
#[cfg(unix)]
fn get_user_name(uid: u32) -> Option<String> {
    unsafe {
        let passwd = libc::getpwuid(uid);
        if passwd.is_null() {
            None
        } else {
            let c_str = (*passwd).pw_name;
            Some(std::ffi::CStr::from_ptr(c_str).to_string_lossy().into_owned())
        }
    }
}

// Helper function to get group name from GID (Unix-specific)
#[cfg(unix)]
fn get_group_name(gid: u32) -> Option<String> {
    unsafe {
        let group = libc::getgrgid(gid);
        if group.is_null() {
            None
        } else {
            let c_str = (*group).gr_name;
            Some(std::ffi::CStr::from_ptr(c_str).to_string_lossy().into_owned())
        }
    }
}