]]--
+
+local io = require "io"
+local os = require "os"
+local posix = require "posix"
+local table = require "table"
+
+local luci = {}
+luci.util = require "luci.util"
+luci.fs = require "luci.fs"
+luci.ip = require "luci.ip"
+
+local tonumber, ipairs, pairs, pcall = tonumber, ipairs, pairs, pcall
+
+
--- LuCI Linux and POSIX system utilities.
-module("luci.sys", package.seeall)
-require("posix")
-require("luci.util")
-require("luci.fs")
-require("luci.ip")
+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
--- Invoke the luci-flash executable to write an image to the flash memory.
+-- @param image Local path or URL to image file
-- @param kpattern Pattern of files to keep over flash process
-- @return Return value of os.execute()
function flash(image, kpattern)
local data = {}
local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
local ps = luci.util.execi("df")
-
+
if not ps then
return
else
ps()
end
-
+
for line in ps do
local row = {}
-
+
local j = 1
for value in line:gmatch("[^%s]+") do
row[k[j]] = value
j = j + 1
end
-
+
if row[k[1]] then
+
+ -- this is a rather ugly workaround to cope with wrapped lines in
+ -- the df output:
+ --
+ -- /dev/scsi/host0/bus0/target0/lun0/part3
+ -- 114382024 93566472 15005244 86% /mnt/usb
+ --
+
+ if not row[k[2]] then
+ j = 2
+ line = ps()
+ for value in line:gmatch("[^%s]+") do
+ row[k[j]] = value
+ j = j + 1
+ end
+ end
+
table.insert(data, row)
end
end
-
+
return data
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)
- local source = stream and io.open or luci.util.exec
- return source("wget -qO- '"..url:gsub("'", "").."'")
+function httpget(url, stream, target)
+ if not target then
+ local source = stream and io.open or luci.util.exec
+ return source("wget -qO- '"..url:gsub("'", "").."'")
+ else
+ return os.execute("wget -qO '%s' '%s'" %
+ {target:gsub("'", ""), url:gsub("'", "")})
+ end
end
--- Returns the system load average values.
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
return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
end
+--- Returns conntrack information
+-- @return Table with the currently tracked IP connections
+function net.conntrack()
+ local connt = {}
+ if luci.fs.access("/proc/net/nf_conntrack") 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]
+ 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/ip_conntrack") do
+ local entry = _parse_mixed_record(line, " +")
+ entry.layer3 = "ipv4"
+ entry.layer4 = entry[1]
+ for i=1, #entry do
+ entry[i] = nil
+ end
+
+ connt[#connt+1] = entry
+ end
+ else
+ return nil
+ end
+ return connt
+end
+
--- Determine the current default route.
-- @return Table with the properties of the current default route.
-- The following fields are defined:
process = {}
--- Get the current process id.
+-- @class function
+-- @name process.info
-- @return Number containing the current pid
process.info = posix.getpid
local data = {}
local k
local ps = luci.util.execi("top -bn1")
-
+
if not ps then
return
end
-
+
while true do
local line = ps()
if not line then
return
end
-
+
k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
if k[1] == "PID" then
break
end
end
-
+
for line in ps do
local row = {}
-
+
line = luci.util.trim(line)
for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
row[k[i]] = value
end
-
+
local pid = tonumber(row[k[1]])
if pid then
data[pid] = row
end
end
-
+
return data
end
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
local account = user.getuser(username)
if account then
- if account.passwd == "!" then
- return true
- else
- return (account.passwd == posix.crypt(password, account.passwd))
+ 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
+
+ if pwd and #pwd > 0 and password and #password > 0 then
+ return (pwd == posix.crypt(password, pwd))
end
end
+
+ return false
end
--- Change the password of given user.
--- Get iwlist scan output from all wireless devices.
-- @return Table of tables contaiing all scan results
-function wifi.iwscan()
- local cnt = luci.util.exec("iwlist scan 2>/dev/null")
+function wifi.iwscan(iface)
+ local siface = iface or ""
+ local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
local iws = {}
for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
end
end
- return iws
+ return iface and (iws[iface] or {}) or iws
+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 ipairs(luci.fs.glob(init.dir.."*")) do
+ names[#names+1] = luci.fs.basename(name)
+ end
+ return names
+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)
+ if luci.fs.access(init.dir..name) then
+ return ( call(init.dir..name.." enabled") == 0 )
+ end
+ return false
+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 luci.fs.access(init.dir..name) then
+ return call("source "..init.dir..name.."; exit $START")
+ end
+end
+
+--- Enable the given init script
+-- @param name Name of the init script
+-- @return Boolean indicating success
+function init.enable(name)
+ if luci.fs.access(init.dir..name) then
+ return ( call(init.dir..name.." enable") == 1 )
+ end
+end
+
+--- Disable the given init script
+-- @param name Name of the init script
+-- @return Boolean indicating success
+function init.disable(name)
+ if luci.fs.access(init.dir..name) then
+ return ( call(init.dir..name.." disable") == 0 )
+ end
end
return data
end
-function _parse_mixed_record(cnt)
+function _parse_mixed_record(cnt, delimiter)
+ delimiter = delimiter or " "
local data = {}
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), " ")) 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