local io = require "io"
local os = require "os"
-local posix = require "posix"
+local nixio = require "nixio"
local table = require "table"
local luci = {}
luci.fs = require "luci.fs"
luci.ip = require "luci.ip"
-local tonumber, ipairs, pairs = tonumber, ipairs, pairs
+local tonumber, ipairs, pairs, pcall, type =
+ tonumber, ipairs, pairs, pcall, type
--- LuCI Linux and POSIX system utilities.
-- @param var Name of the environment variable to retrieve (optional)
-- @return String containg the value of the specified variable
-- @return Table containing all variables if no variable name is given
-getenv = posix.getenv
+getenv = nixio.getenv
---- Determine the current hostname.
+--- Get or set the current hostname.
+-- @param String containing a new hostname to set (optional)
-- @return String containing the system hostname
-function hostname()
- return io.lines("/proc/sys/kernel/hostname")()
+function hostname(newname)
+ if type(newname) == "string" and #newname > 0 then
+ luci.fs.writefile( "/proc/sys/kernel/hostname", newname .. "\n" )
+ return newname
+ else
+ return nixio.uname().nodename
+ end
end
--- Returns the contents of a documented referred by an URL.
-- @return String containing the contents of given the URL
function httpget(url, stream, target)
if not target then
- local source = stream and io.open or luci.util.exec
+ local source = stream and io.popen or luci.util.exec
return source("wget -qO- '"..url:gsub("'", "").."'")
else
return os.execute("wget -qO '%s' '%s'" %
-- @return String containing the average load value 1 minute ago
-- @return String containing the average load value 5 minutes ago
-- @return String containing the average load value 15 minutes ago
--- @return String containing the active and total number of processes
--- @return String containing the last used pid
function loadavg()
- local loadavg = io.lines("/proc/loadavg")()
- return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
+ local info = nixio.sysinfo()
+ return info.loads[1], info.loads[2], info.loads[3]
end
--- Initiate a system reboot.
-- @return String containing the memory used for buffering in kB
-- @return String containing the free memory amount in kB
function sysinfo()
- local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
- local c2 = "uname -m 2>/dev/null"
- local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
- local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
- local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
- local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
- local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
- local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
-
- local system = luci.util.trim(luci.util.exec(c1))
+ local cpuinfo = luci.fs.readfile("/proc/cpuinfo")
+ local meminfo = luci.fs.readfile("/proc/meminfo")
+
+ local system = cpuinfo:match("system typ.-:%s*([^\n]+)")
local model = ""
- local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
- local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
- local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
- local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
-
- if system == "" then
- system = luci.util.trim(luci.util.exec(c2))
- model = luci.util.trim(luci.util.exec(c3))
+ local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
+ local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
+ local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
+ local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
+
+ if not system then
+ system = nixio.uname().machine
+ model = cpuinfo:match("model name.-:%s*([^\n]+)")
+ if not model then
+ model = cpuinfo:match("Processor.-:%s*([^\n]+)")
+ end
else
- model = luci.util.trim(luci.util.exec(c4))
+ model = cpuinfo:match("cpu model.-:%s*([^\n]+)")
end
return system, model, memtotal, memcached, membuffers, memfree
return luci.util.exec("logread")
end
+--- Retrieves the output of the "dmesg" command.
+-- @return String containing the current log buffer
+function dmesg()
+ return luci.util.exec("dmesg")
+end
+
--- Generates a random id with specified length.
-- @param bytes Number of bytes for the unique id
-- @return String containing hex encoded id
return loadavg:match("^(.-) (.-)$")
end
---- LuCI system utilities / POSIX user group related functions.
--- @class module
--- @name luci.sys.group
-group = {}
-
---- Returns information about a POSIX user group.
--- @class function
--- @name getgroup
--- @param group Group ID or name of a system user group
--- @return Table with information about the requested group
-group.getgroup = posix.getgroup
-
--- LuCI system utilities / network related functions.
-- @class module
-- @return Table with the currently tracked IP connections
function net.conntrack()
local connt = {}
- if luci.fs.access("/proc/net/nf_conntrack") then
+ if luci.fs.access("/proc/net/nf_conntrack", "r") then
for line in io.lines("/proc/net/nf_conntrack") do
- local entry = _parse_mixed_record(line, " +")
- entry.layer3 = entry[1]
- entry.layer4 = entry[2]
+ line = line:match "^(.-( [^ =]+=).-)%2"
+ local entry, flags = _parse_mixed_record(line, " +")
+ entry.layer3 = flags[1]
+ entry.layer4 = flags[3]
for i=1, #entry do
entry[i] = nil
end
connt[#connt+1] = entry
end
- elseif luci.fs.access("/proc/net/ip_conntrack") then
- for line in io.lines("/proc/net/nf_conntrack") do
- local entry = _parse_mixed_record(line, " +")
+ elseif luci.fs.access("/proc/net/ip_conntrack", "r") then
+ for line in io.lines("/proc/net/ip_conntrack") do
+ line = line:match "^(.-( [^ =]+=).-)%2"
+ local entry, flags = _parse_mixed_record(line, " +")
entry.layer3 = "ipv4"
- entry.layer4 = entry[1]
+ entry.layer4 = flags[1]
for i=1, #entry do
entry[i] = nil
end
return connt
end
---- Determine the current default route.
+--- Determine the current IPv4 default route. If multiple default routes exist,
+-- return the one with the lowest metric.
-- @return Table with the properties of the current default route.
-- The following fields are defined:
--- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
--- "MTU", "Gateway", "Destination", "Metric", "Use" }
+-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
+-- "flags", "device" }
function net.defaultroute()
- local routes = net.routes()
local route = nil
-
- for i, r in pairs(luci.sys.net.routes()) do
- if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
+ for _, r in pairs(net.routes()) do
+ if r.dest:prefix() == 0 and (not route or route.metric > r.metric) then
route = r
end
end
+ return route
+end
+--- Determine the current IPv6 default route. If multiple default routes exist,
+-- return the one with the lowest metric.
+-- @return Table with the properties of the current default route.
+-- The following fields are defined:
+-- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
+-- "flags", "device" }
+function net.defaultroute6()
+ local route = nil
+ local routes6 = net.routes6()
+ if routes6 then
+ for _, r in pairs(routes6) do
+ if r.dest:prefix() == 0 and
+ (not route or route.metric > r.metric)
+ then
+ route = r
+ end
+ end
+ end
return route
end
--- Returns the current kernel routing table entries.
-- @return Table of tables with properties of the corresponding routes.
-- The following fields are defined for route entry tables:
--- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
--- "MTU", "Gateway", "Destination", "Metric", "Use" }
+-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
+-- "flags", "device" }
function net.routes()
- return _parse_delimited_table(io.lines("/proc/net/route"))
+ local routes = { }
+
+ for line in io.lines("/proc/net/route") do
+
+ local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
+ dst_mask, mtu, win, irtt = line:match(
+ "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
+ "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
+ )
+
+ if dev then
+ gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
+ dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
+ dst_ip = luci.ip.Hex(
+ dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
+ )
+
+ routes[#routes+1] = {
+ dest = dst_ip,
+ gateway = gateway,
+ metric = tonumber(metric),
+ refcount = tonumber(refcnt),
+ usecount = tonumber(usecnt),
+ mtu = tonumber(mtu),
+ window = tonumber(window),
+ irtt = tonumber(irtt),
+ flags = tonumber(flags, 16),
+ device = dev
+ }
+ end
+ end
+
+ return routes
end
+--- Returns the current ipv6 kernel routing table entries.
+-- @return Table of tables with properties of the corresponding routes.
+-- The following fields are defined for route entry tables:
+-- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
+-- "flags", "device" }
+function net.routes6()
+ if luci.fs.access("/proc/net/ipv6_route", "r") then
+ local routes = { }
+
+ for line in io.lines("/proc/net/ipv6_route") do
+
+ local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
+ metric, refcnt, usecnt, flags, dev = line:match(
+ "([a-f0-9]+) ([a-f0-9]+) " ..
+ "([a-f0-9]+) ([a-f0-9]+) " ..
+ "([a-f0-9]+) ([a-f0-9]+) " ..
+ "([a-f0-9]+) ([a-f0-9]+) " ..
+ "([a-f0-9]+) +([^%s]+)"
+ )
+
+ src_ip = luci.ip.Hex(
+ src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
+ )
+
+ dst_ip = luci.ip.Hex(
+ dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
+ )
+
+ nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
+
+ routes[#routes+1] = {
+ source = src_ip,
+ dest = dst_ip,
+ nexthop = nexthop,
+ metric = tonumber(metric, 16),
+ refcount = tonumber(refcnt, 16),
+ usecount = tonumber(usecnt, 16),
+ flags = tonumber(flags, 16),
+ device = dev
+ }
+ end
+
+ return routes
+ end
+end
--- Tests whether the given host responds to ping probes.
-- @param host String containing a hostname or IPv4 address
-- @class function
-- @name process.info
-- @return Number containing the current pid
-process.info = posix.getpid
+function process.info(key)
+ local s = {uid = nixio.getuid(), gid = nixio.getgid()}
+ return not key and s or s[key]
+end
--- Retrieve information about currently running processes.
-- @return Table containing process information
end
--- Set the gid of a process identified by given pid.
--- @param pid Number containing the process id
-- @param gid Number containing the Unix group id
-- @return Boolean indicating successful operation
-- @return String containing the error message if failed
-- @return Number containing the error code if failed
-function process.setgroup(pid, gid)
- return posix.setpid("g", pid, gid)
+function process.setgroup(gid)
+ return nixio.setgid(gid)
end
--- Set the uid of a process identified by given pid.
--- @param pid Number containing the process id
-- @param uid Number containing the Unix user id
-- @return Boolean indicating successful operation
-- @return String containing the error message if failed
-- @return Number containing the error code if failed
-function process.setuser(pid, uid)
- return posix.setpid("u", pid, uid)
+function process.setuser(uid)
+ return nixio.setuid(uid)
end
--- Send a signal to a process identified by given pid.
-- @param sig Signal to send (default: 15 [SIGTERM])
-- @return Boolean indicating successful operation
-- @return Number containing the error code if failed
-process.signal = posix.kill
+process.signal = nixio.kill
--- LuCI system utilities / user related functions.
-- @param uid Number containing the Unix user id
-- @return Table containing the following fields:
-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
-user.getuser = posix.getpasswd
+user.getuser = nixio.getpw
--- Test whether given string matches the password of a given system user.
-- @param username String containing the Unix user name
--- @param password String containing the password to compare
+-- @param pass String containing the password to compare
-- @return Boolean indicating wheather the passwords are equal
-function user.checkpasswd(username, password)
- local account = user.getuser(username)
-
- if account then
- local pwd = account.passwd
- local shadowpw
- if #pwd == 1 then
- if luci.fs.stat("/etc/shadow") then
- if not pcall(function()
- for l in io.lines("/etc/shadow") do
- shadowpw = l:match("^%s:([^:]+)" % username)
- if shadowpw then
- pwd = shadowpw
- break
- end
- end
- end) then
- return nil, "Unable to access shadow-file"
- end
- end
-
- if pwd == "!" then
- return true
- end
- end
-
- return (pwd == posix.crypt(password, pwd))
+function user.checkpasswd(username, pass)
+ local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
+ local pwh = pwe and (pwe.pwdp or pwe.passwd)
+ if not pwh or #pwh < 1 or pwh ~= "!" and nixio.crypt(pass, pwh) ~= pwh then
+ return false
+ else
+ return true
end
end
--- Get iwconfig output for all wireless devices.
-- @return Table of tables containing the iwconfing output for each wifi device
function wifi.getiwconfig()
- local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
+ local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
local iwc = {}
for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
local k = l:match("^(.-) ")
l = l:gsub("^(.-) +", "", 1)
if k then
- iwc[k] = _parse_mixed_record(l)
+ local entry, flags = _parse_mixed_record(l)
+ if entry then
+ entry.flags = flags
+ end
+ iwc[k] = entry
end
end
c = c:gsub("^(.-)- ", "", 1)
c = luci.util.split(c, "\n", 7)
c = table.concat(c, "\n", 1)
- table.insert(iws[k], _parse_mixed_record(c))
+ local entry, flags = _parse_mixed_record(c)
+ if entry then
+ entry.flags = flags
+ end
+ table.insert(iws[k], entry)
end
end
end
return iface and (iws[iface] or {}) or iws
end
+--- Get available channels from given wireless iface.
+-- @param iface Wireless interface (optional)
+-- @return Table of available channels
+function wifi.channels(iface)
+ local cmd = "iwlist " .. ( iface or "" ) .. " freq 2>/dev/null"
+ local cns = { }
+
+ local fd = io.popen(cmd)
+ if fd then
+ local ln, c, f
+ while true do
+ ln = fd:read("*l")
+ if not ln then break end
+ c, f = ln:match("Channel (%d+) : (%d+%.%d+) GHz")
+ if c and f then
+ cns[tonumber(c)] = tonumber(f)
+ end
+ end
+ fd:close()
+ end
+
+ if not ((pairs(cns))(cns)) then
+ cns = {
+ 2.412, 2.417, 2.422, 2.427, 2.432, 2.437,
+ 2.442, 2.447, 2.452, 2.457, 2.462
+ }
+ end
+
+ return cns
+end
+
--- LuCI system utilities / init related functions.
-- @class module
function _parse_mixed_record(cnt, delimiter)
delimiter = delimiter or " "
local data = {}
+ local flags = {}
for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
- local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
+ local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
- if k then
+ if k then
if x == "" then
- table.insert(data, k)
+ table.insert(flags, k)
else
- data[k] = v
+ data[k] = v
end
- end
- end
+ end
+ end
end
- return data
+ return data, flags
end