---[[
-LuCI - System library
-
-Description:
-Utilities for interaction with the Linux system
-
-FileId:
-$Id$
-
-License:
-Copyright 2008 Steven Barth <steven@midlink.org>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-]]--
-
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Licensed to the public under the Apache License 2.0.
local io = require "io"
local os = require "os"
tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
---- LuCI Linux and POSIX system utilities.
module "luci.sys"
---- Execute a given shell command and return the error code
--- @class function
--- @name call
--- @param ... Command to call
--- @return Error code of the command
function call(...)
return os.execute(...) / 256
end
---- Execute a given shell command and capture its standard output
--- @class function
--- @name exec
--- @param command Command to call
--- @return String containg the return the output of the command
exec = luci.util.exec
---- Retrieve information about currently mounted file systems.
--- @return Table containing mount information
function mounts()
local data = {}
local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
return data
end
---- Retrieve environment variables. If no variable is given then a table
-- containing the whole environment is returned otherwise this function returns
-- the corresponding string value for the given name or nil if no such variable
-- exists.
--- @class function
--- @name getenv
--- @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 = nixio.getenv
---- Get or set the current hostname.
--- @param String containing a new hostname to set (optional)
--- @return String containing the system hostname
function hostname(newname)
if type(newname) == "string" and #newname > 0 then
fs.writefile( "/proc/sys/kernel/hostname", newname )
end
end
---- Returns the contents of a documented referred by an URL.
--- @param url The URL to retrieve
--- @param stream Return a stream instead of a buffer
--- @param target Directly write to target file name
--- @return String containing the contents of given the URL
function httpget(url, stream, target)
if not target then
local source = stream and io.popen or luci.util.exec
end
end
---- Returns the system load average values.
--- @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
-function loadavg()
- local info = nixio.sysinfo()
- return info.loads[1], info.loads[2], info.loads[3]
-end
-
---- Initiate a system reboot.
--- @return Return value of os.execute()
function reboot()
return os.execute("reboot >/dev/null 2>&1")
end
---- Returns the system type, cpu name and installed physical memory.
--- @return String containing the system or platform identifier
--- @return String containing hardware model information
--- @return String containing the total memory amount in kB
--- @return String containing the memory used for caching in kB
--- @return String containing the memory used for buffering in kB
--- @return String containing the free memory amount in kB
--- @return String containing the cpu bogomips (number)
-function sysinfo()
- local cpuinfo = fs.readfile("/proc/cpuinfo")
- local meminfo = fs.readfile("/proc/meminfo")
-
- 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+)"))
- local bogomips = tonumber(cpuinfo:match("[Bb]ogo[Mm][Ii][Pp][Ss].-: ([^\n]+)")) or 0
- local swaptotal = tonumber(meminfo:match("SwapTotal:%s*(%d+)"))
- local swapcached = tonumber(meminfo:match("SwapCached:%s*(%d+)"))
- local swapfree = tonumber(meminfo:match("SwapFree:%s*(%d+)"))
-
- local system =
- cpuinfo:match("system type\t+: ([^\n]+)") or
- cpuinfo:match("Processor\t+: ([^\n]+)") or
- cpuinfo:match("model name\t+: ([^\n]+)")
-
- local model =
- fs.readfile("/proc/device-tree/model") or
- luci.util.pcdata(fs.readfile("/tmp/sysinfo/model")) or
- cpuinfo:match("machine\t+: ([^\n]+)") or
- cpuinfo:match("Hardware\t+: ([^\n]+)") or
- luci.util.pcdata(fs.readfile("/proc/diag/model")) or
- nixio.uname().machine or
- system
-
- return system, model, memtotal, memcached, membuffers, memfree, bogomips, swaptotal, swapcached, swapfree
-end
-
---- Retrieves the output of the "logread" command.
--- @return String containing the current log buffer
function syslog()
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
function uniqueid(bytes)
local rand = fs.readfile("/dev/urandom", bytes)
return rand and nixio.bin.hexlify(rand)
end
---- Returns the current system uptime stats.
--- @return String containing total uptime in seconds
function uptime()
return nixio.sysinfo().uptime
end
---- LuCI system utilities / network related functions.
--- @class module
--- @name luci.sys.net
net = {}
---- Returns the current arp-table entries as two-dimensional table.
--- @return Table of table containing the current arp entries.
-- The following fields are defined for arp entry objects:
-- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
function net.arptable(callback)
end
end
---- Returns a two-dimensional table of mac address hints.
--- @return Table of table containing known hosts from various sources.
-- Each entry contains the values in the following order:
-- [ "mac", "name" ]
function net.mac_hints(callback)
end
end
---- Returns a two-dimensional table of IPv4 address hints.
--- @return Table of table containing known hosts from various sources.
-- Each entry contains the values in the following order:
-- [ "ip", "name" ]
function net.ipv4_hints(callback)
end
end
---- Returns a two-dimensional table of IPv6 address hints.
--- @return Table of table containing known hosts from various sources.
-- Each entry contains the values in the following order:
-- [ "ip", "name" ]
function net.ipv6_hints(callback)
end
end
---- Returns conntrack information
--- @return Table with the currently tracked IP connections
function net.conntrack(callback)
- local connt = {}
- if fs.access("/proc/net/nf_conntrack", "r") then
- for line in io.lines("/proc/net/nf_conntrack") do
- line = line:match "^(.-( [^ =]+=).-)%2"
- local entry, flags = _parse_mixed_record(line, " +")
- if flags[6] ~= "TIME_WAIT" then
- entry.layer3 = flags[1]
- entry.layer4 = flags[3]
- for i=1, #entry do
- entry[i] = nil
- end
-
- if callback then
- callback(entry)
- else
- connt[#connt+1] = entry
- end
- end
- end
- elseif 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, " +")
- if flags[4] ~= "TIME_WAIT" then
- entry.layer3 = "ipv4"
- entry.layer4 = flags[1]
- for i=1, #entry do
- entry[i] = nil
- end
-
- if callback then
- callback(entry)
- else
- connt[#connt+1] = entry
- end
- end
- end
- else
+ local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
+ if not ok or not nfct then
return nil
end
- return connt
-end
---- 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:
--- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
--- "flags", "device" }
-function net.defaultroute()
- local route
-
- net.routes(function(rt)
- if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
- route = rt
- end
- end)
+ local line, connt = nil, (not callback) and { }
+ for line in nfct do
+ local fam, l3, l4, timeout, state, tuples =
+ line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +([A-Z_]+) +(.+)$")
- 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
-
- net.routes6(function(rt)
- if rt.dest:prefix() == 0 and rt.device ~= "lo" and
- (not route or route.metric > rt.metric)
+ if fam and l3 and l4 and timeout and state and tuples and
+ state ~= "TIME_WAIT"
then
- route = rt
- end
- end)
+ l4 = nixio.getprotobynumber(l4)
+
+ local entry = {
+ bytes = 0,
+ packets = 0,
+ layer3 = fam,
+ layer4 = l4 and l4.name or "unknown",
+ timeout = tonumber(timeout, 10)
+ }
- if not route then
- local global_unicast = luci.ip.IPv6("2000::/3")
- net.routes6(function(rt)
- if rt.dest:equal(global_unicast) and
- (not route or route.metric > rt.metric)
- then
- route = rt
+ local key, val
+ for key, val in tuples:gmatch("(%w+)=(%S+)") do
+ if key == "bytes" or key == "packets" then
+ entry[key] = entry[key] + tonumber(val, 10)
+ elseif key == "src" or key == "dst" or key == "sport" or key == "dport" then
+ if entry[key] == nil then
+ entry[key] = val
+ end
+ elseif val then
+ entry[key] = val
+ end
end
- end)
+
+ if callback then
+ callback(entry)
+ else
+ connt[#connt+1] = entry
+ end
+ end
end
- return route
+ return callback and true or connt
end
---- Determine the names of available network interfaces.
--- @return Table containing all current interface names
function net.devices()
local devs = {}
for k, v in ipairs(nixio.getifaddrs()) do
end
---- Return information about available network interfaces.
--- @return Table containing all current interface names and their information
function net.deviceinfo()
local devs = {}
for k, v in ipairs(nixio.getifaddrs()) do
end
--- Determine the MAC address belonging to the given IP address.
--- @param ip IPv4 address
--- @return String containing the MAC address or nil if it cannot be found
-function net.ip4mac(ip)
- local mac = nil
- net.arptable(function(e)
- if e["IP address"] == ip then
- mac = e["HW address"]
- end
- end)
- return mac
-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:
-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
-- "flags", "device" }
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" }
end
end
---- Tests whether the given host responds to ping probes.
--- @param host String containing a hostname or IPv4 address
--- @return Number containing 0 on success and >= 1 on error
function net.pingtest(host)
return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
end
---- LuCI system utilities / process related functions.
--- @class module
--- @name luci.sys.process
process = {}
---- Get the current process id.
--- @class function
--- @name process.info
--- @return Number containing the current pid
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
function process.list()
local data = {}
local k
return data
end
---- Set the gid of a process identified by given pid.
--- @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(gid)
return nixio.setgid(gid)
end
---- Set the uid of a process identified by given pid.
--- @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(uid)
return nixio.setuid(uid)
end
---- Send a signal to a process identified by given pid.
--- @class function
--- @name process.signal
--- @param pid Number containing the process id
--- @param sig Signal to send (default: 15 [SIGTERM])
--- @return Boolean indicating successful operation
--- @return Number containing the error code if failed
process.signal = nixio.kill
---- LuCI system utilities / user related functions.
--- @class module
--- @name luci.sys.user
user = {}
---- Retrieve user informations for given uid.
--- @class function
--- @name getuser
--- @param uid Number containing the Unix user id
--- @return Table containing the following fields:
-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
user.getuser = nixio.getpw
---- Retrieve the current user password hash.
--- @param username String containing the username to retrieve the password for
--- @return String containing the hash or nil if no password is set.
--- @return Password database entry
function user.getpasswd(username)
local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
local pwh = pwe and (pwe.pwdp or pwe.passwd)
end
end
---- Test whether given string matches the password of a given system user.
--- @param username String containing the Unix user name
--- @param pass String containing the password to compare
--- @return Boolean indicating wheather the passwords are equal
function user.checkpasswd(username, pass)
local pwh, pwe = user.getpasswd(username)
if pwe then
return false
end
---- Change the password of given user.
--- @param username String containing the Unix user name
--- @param password String containing the password to compare
--- @return Number containing 0 on success and >= 1 on error
function user.setpasswd(username, password)
if password then
password = password:gsub("'", [['"'"']])
end
---- LuCI system utilities / wifi related functions.
--- @class module
--- @name luci.sys.wifi
wifi = {}
---- Get wireless information for given interface.
--- @param ifname String containing the interface name
--- @return A wrapped iwinfo object instance
function wifi.getiwinfo(ifname)
local stat, iwinfo = pcall(require, "iwinfo")
if ifname then
- local c = 0
- local u = uci.cursor_state()
local d, n = ifname:match("^(%w+)%.network(%d+)")
- if d and n then
+ local wstate = luci.util.ubus("network.wireless", "status") or { }
+
+ d = d or ifname
+ n = n and tonumber(n) or 1
+
+ if type(wstate[d]) == "table" and
+ type(wstate[d].interfaces) == "table" and
+ type(wstate[d].interfaces[n]) == "table" and
+ type(wstate[d].interfaces[n].ifname) == "string"
+ then
+ ifname = wstate[d].interfaces[n].ifname
+ else
ifname = d
- n = tonumber(n)
- u:foreach("wireless", "wifi-iface",
- function(s)
- if s.device == d then
- c = c + 1
- if c == n then
- ifname = s.ifname or s.device
- return false
- end
- end
- end)
- elseif u:get("wireless", ifname) == "wifi-device" then
- u:foreach("wireless", "wifi-iface",
- function(s)
- if s.device == ifname and s.ifname then
- ifname = s.ifname
- return false
- end
- end)
end
local t = stat and iwinfo.type(ifname)
end
---- LuCI system utilities / init related functions.
--- @class module
--- @name luci.sys.init
init = {}
init.dir = "/etc/init.d/"
---- Get the names of all installed init scripts
--- @return Table containing the names of all inistalled init scripts
function init.names()
local names = { }
for name in fs.glob(init.dir.."*") do
return names
end
---- Get the index of he given init script
--- @param name Name of the init script
--- @return Numeric index value
function init.index(name)
if fs.access(init.dir..name) then
return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
end
end
---- Test whether the given init script is enabled
--- @param name Name of the init script
--- @return Boolean indicating whether init is enabled
function init.enabled(name)
return (init_action("enabled", name) == 0)
end
---- Enable the given init script
--- @param name Name of the init script
--- @return Boolean indicating success
function init.enable(name)
return (init_action("enable", name) == 1)
end
---- Disable the given init script
--- @param name Name of the init script
--- @return Boolean indicating success
function init.disable(name)
return (init_action("disable", name) == 0)
end
---- Start the given init script
--- @param name Name of the init script
--- @return Boolean indicating success
function init.start(name)
return (init_action("start", name) == 0)
end
---- Stop the given init script
--- @param name Name of the init script
--- @return Boolean indicating success
function init.stop(name)
return (init_action("stop", name) == 0)
end
-
-
--- Internal functions
-
-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"]*)"*')
-
- if k then
- if x == "" then
- table.insert(flags, k)
- else
- data[k] = v
- end
- end
- end
- end
-
- return data, flags
-end