|
|
5 months ago | |
|---|---|---|
| freebsd-port/sysutils/lua-sysmount | 5 months ago | |
| src | 5 months ago | |
| .gitignore | 5 months ago | |
| LICENSE | 5 months ago | |
| README.md | 5 months ago | |
README.md
lua-sysmount
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
FLAVORSand complies with modern FreeBSD packaging standards.
Installation
Method 1: Build & Install via FreeBSD Ports Framework
Use this method to compile the module as a native FreeBSD package (pkg) with full FLAVORS support. The port is configured to securely fetch the release archive directly from the Gitea repository attachments.
-
Clone this repository and navigate to the directory:
git clone https://git.dc365.ru/digital-freak/lua-sysmount.git cd lua-sysmount -
Symlink the port skeleton into your system's ports tree:
sudo ln -s $(pwd)/freebsd-port/sysutils/lua-sysmount /usr/ports/sysutils/lua-sysmount -
Build and generate the binary package for your desired Lua flavor (e.g., Lua 5.4):
cd /usr/ports/sysutils/lua-sysmount sudo make FLAVOR=lua54 package -
Install the newly created package:
sudo make FLAVOR=lua54 install clean
Method 2: Manual Standalone Build
If you don't want to use the ports tree infrastructure, you can compile and install the .so module directly from the source directory using pkg-config.
cd lua-sysmount/src
make
sudo make install
make clean
Usage Examples
1. Basic Module Verification
You can test the native binding directly from your interactive Lua shell:
$ lua54
Lua 5.4.8 Copyright (C) 1994-2025 Lua.org, PUC-Rio
> sysmount = require("sysmount")
> 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:
#!/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("<class(.-)>(.-)</class>") do
local class_name = class_node:match("<name>(.-)</name>")
if class_name == "DISK" or class_name == "PART" then
for geom_attr, geom_node in class_node:gmatch("<geom(.-)>(.-)</geom>") do
for prov_attr, provider_node in geom_node:gmatch("<provider(.-)>(.-)</provider>") do
local p_name = provider_node:match("<name>(.-)</name>")
local p_size = provider_node:match("<mediasize>(.-)</mediasize>")
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("<descr>(.-)</descr>") 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()
Running Without Root Privileges (Recommended Secure Setup)
By default, smartctl requires elevated access to read disk nodes in /dev/. However, you can securely run this script as a normal user by delegating device permissions via FreeBSD devfs rules.
-
Add the following ruleset to your
/etc/devfs.rules(create the file if it doesn't exist) to grant read/write access to theoperatorgroup for SATA and NVMe drives:[localrules=10] add path 'ada*' mode 0660 group operator add path 'da*' mode 0660 group operator add path 'nvd*' mode 0660 group operator -
Enable the ruleset in
/etc/rc.conf:sysrc devfs_system_ruleset="localrules" -
Restart the
devfsservice to apply changes immediately:sudo service devfs restart -
Ensure your active user belongs to the
operatorgroup:# Add user to the group if needed sudo pw groupmod operator -m your_username
Now you can run the topology script safely without sudo:
chmod +x disks.lua
./disks.lua
Expected Output Structure:
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.