X-Git-Url: http://git.archive.openwrt.org/?a=blobdiff_plain;f=libs%2Fsys%2Fluasrc%2Fsys.lua;h=2332cebd837e47bb369206cc788605621e111773;hb=7a9c3efcec48d01aefdb2a9167554e3116910794;hp=14a2f294f3feca2d91d41f53d466cbbbf158a537;hpb=c9a29250557d1891354257433fd3c6c49ef68f84;p=project%2Fluci.git diff --git a/libs/sys/luasrc/sys.lua b/libs/sys/luasrc/sys.lua index 14a2f294f..2332cebd8 100644 --- a/libs/sys/luasrc/sys.lua +++ b/libs/sys/luasrc/sys.lua @@ -24,14 +24,41 @@ limitations under the License. ]]-- + +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.bits") -require("luci.util") -require("luci.fs") +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) @@ -44,6 +71,53 @@ function flash(image, kpattern) return os.execute(cmd) end +--- Retrieve information about currently mounted file systems. +-- @return Table containing mount information +function mounts() + 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 + --- 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 @@ -58,16 +132,22 @@ getenv = posix.getenv --- Determine the current hostname. -- @return String containing the system hostname function hostname() - return io.lines("/proc/sys/kernel/hostname")() + return posix.uname("%n") 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.popen 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. @@ -94,37 +174,25 @@ end -- @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 Number containing free memory in percent --- @return Number containing buffer memory in percent --- @return Number containing cache memory in percent 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 = luci.util.trim(luci.util.exec(c5)) - local memcached = luci.util.trim(luci.util.exec(c6)) - local memfree = luci.util.trim(luci.util.exec(c7)) - local membuffers = luci.util.trim(luci.util.exec(c8)) - local perc_memfree = math.floor((memfree/memtotal)*100) - local perc_membuffers = math.floor((membuffers/memtotal)*100) - local perc_memcached = math.floor((memcached/memtotal)*100) - - 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 = posix.uname("%m") + model = cpuinfo:match("model name.-:%s*([^\n]+)") else - model = luci.util.trim(luci.util.exec(c4)) + model = cpuinfo:match("cpu model.-:%s*([^\n]+)") end - return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached + return system, model, memtotal, memcached, membuffers, memfree end --- Retrieves the output of the "logread" command. @@ -165,6 +233,8 @@ end 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 @@ -183,13 +253,36 @@ function net.arptable() return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+") end ---- Test whether an IP-Adress belongs to a certain net. --- @param ip IPv4 address to test --- @param ipnet IPv4 network address of the net range to compare against --- @param prefix Network prefix of the net range to compare against --- @return Boolean indicating wheather the ip is within the range -function net.belongs(ip, ipnet, prefix) - return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix)) +--- 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, flags = _parse_mixed_record(line, " +") + entry.layer3 = flags[1] + entry.layer4 = flags[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, flags = _parse_mixed_record(line, " +") + entry.layer3 = "ipv4" + entry.layer4 = flags[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. @@ -220,6 +313,21 @@ function net.devices() return devices end + +--- Return information about available network interfaces. +-- @return Table containing all current interface names and their information +function net.deviceinfo() + local devices = {} + for line in io.lines("/proc/net/dev") do + local name, data = line:match("^ *(.-): *(.*)$") + if name and data then + devices[name] = luci.util.split(data, " +", nil, true) + end + end + return devices +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 @@ -235,19 +343,6 @@ function net.ip4mac(ip) return mac end ---- Calculate the prefix from a given netmask. --- @param mask IPv4 net mask --- @return Number containing the corresponding numerical prefix -function net.mask4prefix(mask) - local bin = net.ip4bin(mask) - - if not bin then - return nil - end - - return #luci.util.split(bin, "1")-1 -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: @@ -257,54 +352,6 @@ function net.routes() return _parse_delimited_table(io.lines("/proc/net/route")) end ---- Convert hexadecimal 32 bit value to IPv4 address. --- @param hex String containing the hexadecimal value --- @param be Boolean indicating wheather the given value is big endian --- @return String containing the corresponding IP4 address -function net.hexip4(hex, be) - if #hex ~= 8 then - return nil - end - - be = be or luci.util.bigendian() - - local hexdec = luci.bits.Hex2Dec - - local ip = "" - if be then - ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "." - ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." - ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." - ip = ip .. tostring(hexdec(hex:sub(7,8))) - else - ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "." - ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." - ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." - ip = ip .. tostring(hexdec(hex:sub(1,2))) - end - - return ip -end - ---- Convert given IPv4 address to binary value. --- @param ip String containing a IPv4 address --- @return String containing corresponding binary value -function net.ip4bin(ip) - local parts = luci.util.split(ip, '.') - if #parts ~= 4 then - return nil - end - - local decbin = luci.bits.Dec2Bin - - local bin = "" - bin = bin .. decbin(parts[1], 8) - bin = bin .. decbin(parts[2], 8) - bin = bin .. decbin(parts[3], 8) - bin = bin .. decbin(parts[4], 8) - - return bin -end --- Tests whether the given host responds to ping probes. -- @param host String containing a hostname or IPv4 address @@ -320,9 +367,51 @@ end process = {} --- Get the current process id. +-- @class function +-- @name process.info -- @return Number containing the current pid process.info = posix.getpid +--- Retrieve information about currently running processes. +-- @return Table containing process information +function process.list() + 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 + --- 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 @@ -343,6 +432,15 @@ function process.setuser(pid, uid) return posix.setpid("u", pid, 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 = posix.kill + --- LuCI system utilities / user related functions. -- @class module @@ -364,16 +462,35 @@ user.getuser = posix.getpasswd function user.checkpasswd(username, password) local account = user.getuser(username) - -- FIXME: detect testing environment - if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then - return true - elseif account then - if account.passwd == "!" then - return true - else - return (account.passwd == posix.crypt(password, account.passwd)) + 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 + + 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. @@ -410,7 +527,11 @@ function wifi.getiwconfig() 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 @@ -419,8 +540,9 @@ end --- 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 @@ -433,12 +555,70 @@ function wifi.iwscan() 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 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 @@ -472,16 +652,18 @@ function _parse_delimited_table(iter, delimiter) return data end -function _parse_mixed_record(cnt) +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), " ")) 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(data, k) + table.insert(flags, k) else data[k] = v end @@ -489,5 +671,5 @@ function _parse_mixed_record(cnt) end end - return data + return data, flags end