Implement all 6 universal filesystem metadata properties inside inspect command

This commit is contained in:
venus
2026-07-17 01:20:57 -05:00
parent 1b53658a50
commit 3664208c74

View File

@@ -2,10 +2,19 @@
# Library for forensic analysis # Library for forensic analysis
import click import click
from dataclasses import dataclass from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from rich.console import Console import stat
from rich.table import Table import os
import sys
from typing import List, Dict
try:
import pwd
import grp
except ImportError:
pwd = None
grp = None
@click.group(name="forensics") @click.group(name="forensics")
def forensics_group(): pass def forensics_group(): pass
@@ -20,27 +29,117 @@ class FileMetadata:
magic: str magic: str
extension: str extension: str
# Task 1: POSIX Permissions
permissions_octal: str
permissions_symbolic: str
# Task 2: Ownership Identity
owner_uid: int
owner_username: str
owner_gid: int
owner_groupname: str
# Task 3: Allocation Metrics
allocated_size: int
# Task 4: Hard Links
hard_links: int
# Task 5: Inode & Device Identifiers
inode: int
device: int
# Task 6: Extended Attributes
extended_attributes: Dict[str, str] = field(default_factory=dict)
@forensics_group.command() @forensics_group.command()
@click.argument('path', type=click.Path(exists=True)) @click.argument('path', type=click.Path(exists=True))
def inspect(path): def inspect(path):
p = Path(path) p = Path(path)
size = p.stat().st_size stat_info = p.stat()
# Apparent size & extension
size = stat_info.st_size
extension = p.suffix extension = p.suffix
# Magic bytes
try: try:
with open(p, 'rb') as f: with open(p, 'rb') as f:
magic = f.read(4).hex().upper() magic = f.read(4).hex().upper()
except Exception: except Exception:
magic = "UNKNOWN" magic = ""
# Task 1: POSIX Permissions
mode = stat_info.st_mode
permissions_octal = oct(stat.S_IMODE(mode))
permissions_symbolic = stat.filemode(mode)
# Task 2: Ownership Identity
owner_uid = stat_info.st_uid
owner_gid = stat_info.st_gid
owner_username = str(owner_uid)
owner_groupname = str(owner_gid)
if pwd is not None:
try:
owner_username = pwd.getpwuid(owner_uid).pw_name
except KeyError:
pass
if grp is not None:
try:
owner_groupname = grp.getgrgid(owner_gid).gr_name
except KeyError:
pass
# Task 3: Allocation Metrics
if hasattr(stat_info, "st_blocks"):
allocated_size = stat_info.st_blocks * 512
else:
allocated_size = size
# Task 4: Hard Links
hard_links = stat_info.st_nlink
# Task 5: Inode & Device Identifiers
inode = stat_info.st_ino
device = stat_info.st_dev
# Task 6: Extended Attributes
extended_attributes = {}
if hasattr(os, "listxattr") and hasattr(os, "getxattr"):
try:
attrs = os.listxattr(p)
for attr in attrs:
try:
val = os.getxattr(p, attr)
extended_attributes[attr] = val.decode("utf-8", errors="ignore")
except OSError:
pass
except OSError:
pass
meta = FileMetadata( meta = FileMetadata(
filename=p.name, filename=p.name,
size=size, size=size,
magic=magic, magic=magic,
extension=extension extension=extension,
permissions_octal=permissions_octal,
permissions_symbolic=permissions_symbolic,
owner_uid=owner_uid,
owner_username=owner_username,
owner_gid=owner_gid,
owner_groupname=owner_groupname,
allocated_size=allocated_size,
hard_links=hard_links,
inode=inode,
device=device,
extended_attributes=extended_attributes
) )
# Present using Rich Table # Present using Rich Table
from rich.console import Console
from rich.table import Table
console = Console() console = Console()
table = Table(title=f"Metadata: {meta.filename}", show_header=False) table = Table(title=f"Metadata: {meta.filename}", show_header=False)
table.add_column("Key", style="bold cyan") table.add_column("Key", style="bold cyan")
@@ -50,6 +149,17 @@ def inspect(path):
table.add_row("Size", f"{meta.size} bytes") table.add_row("Size", f"{meta.size} bytes")
table.add_row("Magic Bytes (Hex)", meta.magic) table.add_row("Magic Bytes (Hex)", meta.magic)
table.add_row("Extension", meta.extension) table.add_row("Extension", meta.extension)
table.add_row("Permissions", f"{meta.permissions_symbolic} ({meta.permissions_octal})")
table.add_row("Owner", f"{meta.owner_username} (UID: {meta.owner_uid})")
table.add_row("Group", f"{meta.owner_groupname} (GID: {meta.owner_gid})")
table.add_row("Allocated Space", f"{meta.allocated_size} bytes")
table.add_row("Hard Links", str(meta.hard_links))
table.add_row("Inode", str(meta.inode))
table.add_row("Device", str(meta.device))
if meta.extended_attributes:
xattr_str = ", ".join(f"{k}={v}" for k, v in meta.extended_attributes.items())
table.add_row("Extended Attributes", xattr_str)
console.print(table) console.print(table)
return meta return meta