From 9cda3244899db9a1a64c4889685634fcb0b91943 Mon Sep 17 00:00:00 2001 From: digital-freak Date: Mon, 18 May 2026 04:54:16 +0500 Subject: [PATCH] Update README --- README.md | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5edd2fa..b760a8c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,221 @@ # lua-sysmount -A lightweight, high-performance, and secure Lua binding for the native FreeBSD getfsstat(2) system call. \ No newline at end of file +A lightweight, high-performance, and secure Lua binding for the native FreeBSD `getfsstat(2)` system call. + +This module allows Lua scripts to query active storage mounts and filesystem types (such as UFS, ZFS, msdosfs) directly from the kernel memory space. By operating entirely through C bindings, it completely eliminates the need to spawn external shell processes or parse text output from utilities like `mount(8)`, ensuring maximum security and execution speed. + +## Features + +- **100% Native**: Fetches live data directly from FreeBSD kernel memory structures. +- **No Shell Spawning**: Avoids risky and slow `io.popen("/sbin/mount")` calls. +- **Extended Metadata**: Returns filesystem types (`ufs`, `zfs`, etc.) alongside device names and mount points. +- **FreeBSD Ports Framework Ready**: Fully supports `FLAVORS` and complies with modern FreeBSD packaging standards. + +--- + +## Installation + +### Method 1: Build & Install via FreeBSD Ports Framework (Recommended) + +This method automatically handles Lua `FLAVORS` and registers the module in the system package database (`pkg`). + +1. Copy the port directory to your local ports tree: + ```bash + cp -R port/sysutils/lua-sysmount /usr/ports/sysutils/ + ``` +2. Build and install for your default Lua version (e.g., Lua 5.4): + ```bash + cd /usr/ports/sysutils/lua-sysmount + sudo make install clean + ``` +3. Alternatively, target a specific Lua flavor (e.g., Lua 5.3): + ```bash + sudo make FLAVOR=lua53 install clean + ``` + +### Method 2: Manual Standalone Build + +If you don't want to use the ports tree, you can compile the `.so` module directly in any directory. It will use `pkg-config` to discover your active Lua setup. + +```bash +# Clone the repository and navigate to the source directory +git clone https://github.com +cd lua-sysmount/src + +# Compile and install globally +make +sudo make install +make clean +``` + +--- + +## Usage Examples + +### 1. Basic Module Verification + +You can test the native binding directly from your interactive Lua shell: + +```lua +$ lua54 +Lua 5.4.8 Copyright (C) 1994-2025 Lua.org, PUC-Rio +> local sysmount = require("sysmount") +> local mounts = sysmount.getfsstat() +> +> -- Print the first mount point entry +> print(mounts[1].dev, "->", mounts[1].mnt, "[" .. mounts[1].type .. "]") +/dev/ada0p2 -> / [ufs] +``` + +### 2. Full Storage Topography Script + +Below is a production-ready example combining `sysctl` (via `lua-sysctl` port) for disk architecture, your custom `sysmount` for actual mount points, and safe `smartctl` execution for drive temperatures. + +Save this as `disks.lua`: + +```lua +#!/usr/bin/env lua + +local sysctl = require("sysctl") +local sysmount = require("sysmount") + +-- 1. Fetch GEOM storage layout from the kernel +local function get_geom_xml() + return sysctl.get("kern.geom.confxml") +end + +-- 2. Fetch live mount points via our custom native C module +local function get_mounts() + local mounts = {} + local mnt_list = sysmount.getfsstat() or {} + + for _, entry in ipairs(mnt_list) do + if entry.dev and entry.mnt then + mounts[entry.dev] = { path = entry.mnt, fstype = entry.type } + + -- Cache short name (e.g., ada0p2) without /dev/ prefix + local short_dev = entry.dev:match("/dev/(.+)") + if short_dev then + mounts[short_dev] = { path = entry.mnt, fstype = entry.type } + end + end + end + return mounts +end + +-- 3. Safely query S.M.A.R.T. temperature (Requires root privileges) +local function get_disk_temp(disk_name) + if not disk_name:match("^%w+$") then return "—" end + + local f = io.popen("/usr/local/sbin/smartctl -a /dev/" .. disk_name .. " 2>/dev/null") + if not f then return "—" end + + local temp = nil + for line in f:lines() do + if line:match("^%s*194%s") or line:match("^%s*190%s") then + local val = line:match("%s+(%d+)%s*$") or line:match("%s+(%d+)%s+%(.-%)%s*$") + if val then temp = val; break end + end + end + f:close() + return temp and (temp .. " °C") or "—" +end + +-- 4. Parse GEOM XML layout +local function parse_geom_xml(xml_data) + local disks = {} + local raw_partitions = {} + local clean_xml = xml_data:gsub("%s*\n%s*", "") + + for class_attr, class_node in clean_xml:gmatch("(.-)") do + local class_name = class_node:match("(.-)") + if class_name == "DISK" or class_name == "PART" then + for geom_attr, geom_node in class_node:gmatch("(.-)") do + for prov_attr, provider_node in geom_node:gmatch("(.-)") do + local p_name = provider_node:match("(.-)") + local p_size = provider_node:match("(.-)") + + if p_name then + local size_gb = tonumber(p_size) and string.format("%.2f GB", p_size / 1024^3) or "unknown" + + if class_name == "DISK" then + local p_descr = provider_node:match("(.-)") or "Generic Drive" + disks[p_name] = { name = p_name, size = size_gb, model = p_descr, partitions = {} } + elseif class_name == "PART" then + table.insert(raw_partitions, { name = p_name, size = size_gb }) + end + end + end + end + end + end + + for _, part in ipairs(raw_partitions) do + for disk_name, disk_info in pairs(disks) do + if part.name:sub(1, #disk_name) == disk_name and part.name ~= disk_name then + table.insert(disk_info.partitions, part) + break + end + end + end + return disks +end + +-- Main Output Render +local function main() + local xml_data = get_geom_xml() + local mounts = get_mounts() + local devices = parse_geom_xml(xml_data) + + print(string.format("%-14s %-12s %-8s %-8s %-30s %s", "Device", "Size", "Temp.", "FS", "Model / Description", "Mount Point")) + print(string.rep("-", 115)) + + local sorted_disks = {} + for k in pairs(devices) do table.insert(sorted_disks, k) end + table.sort(sorted_disks) + + for _, disk_name in ipairs(sorted_disks) do + local disk = devices[disk_name] + local disk_mnt_info = mounts[disk.name] + + local disk_path = disk_mnt_info and disk_mnt_info.path or "" + local disk_fs = disk_mnt_info and disk_mnt_info.fstype or "—" + local disk_temp = get_disk_temp(disk.name) + + print(string.format("📦 %-11s %-12s %-8s %-8s %-30s %s", disk.name, disk.size, disk_temp, disk_fs, disk.model, disk_path)) + + table.sort(disk.partitions, function(a, b) return a.name < b.name end) + for _, part in ipairs(disk.partitions) do + local part_mnt_info = mounts[part.name] + if part_mnt_info then + print(string.format(" ├── 💾 %-6s %-12s %-8s %-8s %-30s %s", + part.name, part.size, "—", part_mnt_info.fstype, "—", part_mnt_info.path)) + end + end + end +end + +main() +``` + +Run the script with **root privileges** to ensure S.M.A.R.T. temperatures are displayed: +```bash +sudo chmod +x disks.lua +sudo ./disks.lua +``` + +### Expected Output Structure: +```text +Device Size Temp. FS Model / Description Mount Point +------------------------------------------------------------------------------------------------------------------- +📦 ada0 465.76 GB 32 °C — Samsung SSD 860 EVO 500GB + ├── 💾 ada0p2 63.75 GB — ufs — / + ├── 💾 ada0p4 256.00 GB — ufs — /usr +📦 ada1 1863.02 GB 38 °C — ST2000NM000A-2J2100 + ├── 💾 ada1p1 1863.00 GB — ufs — /home +📦 ada2 223.57 GB 30 °C — INTEL SSDSC2BB240G7 +``` + +## License + +This project is licensed under the BSD 2-Clause License.