123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757 |
- -- Copyright 2008 Steven Barth <steven@midlink.org>
- -- Licensed to the public under the Apache License 2.0.
- local io = require "io"
- local math = require "math"
- local table = require "table"
- local debug = require "debug"
- local ldebug = require "luci.debug"
- local string = require "string"
- local coroutine = require "coroutine"
- local tparser = require "luci.template.parser"
- local json = require "luci.jsonc"
- local lhttp = require "lucihttp"
- local _ubus = require "ubus"
- local _ubus_connection = nil
- local getmetatable, setmetatable = getmetatable, setmetatable
- local rawget, rawset, unpack, select = rawget, rawset, unpack, select
- local tostring, type, assert, error = tostring, type, assert, error
- local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring
- local require, pcall, xpcall = require, pcall, xpcall
- local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit
- module "luci.util"
- --
- -- Pythonic string formatting extension
- --
- getmetatable("").__mod = function(a, b)
- local ok, res
- if not b then
- return a
- elseif type(b) == "table" then
- local k, _
- for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end
- ok, res = pcall(a.format, a, unpack(b))
- if not ok then
- error(res, 2)
- end
- return res
- else
- if type(b) == "userdata" then b = tostring(b) end
- ok, res = pcall(a.format, a, b)
- if not ok then
- error(res, 2)
- end
- return res
- end
- end
- --
- -- Class helper routines
- --
- -- Instantiates a class
- local function _instantiate(class, ...)
- local inst = setmetatable({}, {__index = class})
- if inst.__init__ then
- inst:__init__(...)
- end
- return inst
- end
- -- The class object can be instantiated by calling itself.
- -- Any class functions or shared parameters can be attached to this object.
- -- Attaching a table to the class object makes this table shared between
- -- all instances of this class. For object parameters use the __init__ function.
- -- Classes can inherit member functions and values from a base class.
- -- Class can be instantiated by calling them. All parameters will be passed
- -- to the __init__ function of this class - if such a function exists.
- -- The __init__ function must be used to set any object parameters that are not shared
- -- with other objects of this class. Any return values will be ignored.
- function class(base)
- return setmetatable({}, {
- __call = _instantiate,
- __index = base
- })
- end
- function instanceof(object, class)
- local meta = getmetatable(object)
- while meta and meta.__index do
- if meta.__index == class then
- return true
- end
- meta = getmetatable(meta.__index)
- end
- return false
- end
- --
- -- Scope manipulation routines
- --
- function threadlocal(tbl)
- return tbl or {}
- end
- --
- -- Debugging routines
- --
- function perror(obj)
- return io.stderr:write(tostring(obj) .. "\n")
- end
- function dumptable(t, maxdepth, i, seen)
- i = i or 0
- seen = seen or setmetatable({}, {__mode="k"})
- for k,v in pairs(t) do
- perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v))
- if type(v) == "table" and (not maxdepth or i < maxdepth) then
- if not seen[v] then
- seen[v] = true
- dumptable(v, maxdepth, i+1, seen)
- else
- perror(string.rep("\t", i) .. "*** RECURSION ***")
- end
- end
- end
- end
- --
- -- String and data manipulation routines
- --
- -- compatibility wrapper for xml.pcdata
- function pcdata(value)
- local xml = require "luci.xml"
- perror("luci.util.pcdata() has been replaced by luci.xml.pcdata() - Please update your code.")
- return xml.pcdata(value)
- end
- function urlencode(value)
- if value ~= nil then
- local str = tostring(value)
- return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL)
- or str
- end
- return nil
- end
- function urldecode(value, decode_plus)
- if value ~= nil then
- local flag = decode_plus and lhttp.DECODE_PLUS or 0
- local str = tostring(value)
- return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED + flag)
- or str
- end
- return nil
- end
- -- compatibility wrapper for xml.striptags
- function striptags(value)
- local xml = require "luci.xml"
- perror("luci.util.striptags() has been replaced by luci.xml.striptags() - Please update your code.")
- return xml.striptags(value)
- end
- function shellquote(value)
- return string.format("'%s'", string.gsub(value or "", "'", "'\\''"))
- end
- -- for bash, ash and similar shells single-quoted strings are taken
- -- literally except for single quotes (which terminate the string)
- -- (and the exception noted below for dash (-) at the start of a
- -- command line parameter).
- function shellsqescape(value)
- local res
- res, _ = string.gsub(value, "'", "'\\''")
- return res
- end
- -- bash, ash and other similar shells interpret a dash (-) at the start
- -- of a command-line parameters as an option indicator regardless of
- -- whether it is inside a single-quoted string. It must be backlash
- -- escaped to resolve this. This requires in some funky special-case
- -- handling. It may actually be a property of the getopt function
- -- rather than the shell proper.
- function shellstartsqescape(value)
- res, _ = string.gsub(value, "^%-", "\\-")
- return shellsqescape(res)
- end
- -- containing the resulting substrings. The optional max parameter specifies
- -- the number of bytes to process, regardless of the actual length of the given
- -- string. The optional last parameter, regex, specifies whether the separator
- -- sequence is interpreted as regular expression.
- -- pattern as regular expression (optional, default is false)
- function split(str, pat, max, regex)
- pat = pat or "\n"
- max = max or #str
- local t = {}
- local c = 1
- if #str == 0 then
- return {""}
- end
- if #pat == 0 then
- return nil
- end
- if max == 0 then
- return str
- end
- repeat
- local s, e = str:find(pat, c, not regex)
- max = max - 1
- if s and max < 0 then
- t[#t+1] = str:sub(c)
- else
- t[#t+1] = str:sub(c, s and s - 1)
- end
- c = e and e + 1 or #str + 1
- until not s or max < 0
- return t
- end
- function trim(str)
- return (str:gsub("^%s*(.-)%s*$", "%1"))
- end
- function cmatch(str, pat)
- local count = 0
- for _ in str:gmatch(pat) do count = count + 1 end
- return count
- end
- -- one token per invocation, the tokens are separated by whitespace. If the
- -- input value is a table, it is transformed into a string first. A nil value
- -- will result in a valid iterator which aborts with the first invocation.
- function imatch(v)
- if type(v) == "table" then
- local k = nil
- return function()
- k = next(v, k)
- return v[k]
- end
- elseif type(v) == "number" or type(v) == "boolean" then
- local x = true
- return function()
- if x then
- x = false
- return tostring(v)
- end
- end
- elseif type(v) == "userdata" or type(v) == "string" then
- return tostring(v):gmatch("%S+")
- end
- return function() end
- end
- -- value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
- -- Recognized units are:
- -- o "y" - one year (60*60*24*366)
- -- o "m" - one month (60*60*24*31)
- -- o "w" - one week (60*60*24*7)
- -- o "d" - one day (60*60*24)
- -- o "h" - one hour (60*60)
- -- o "min" - one minute (60)
- -- o "kb" - one kilobyte (1024)
- -- o "mb" - one megabyte (1024*1024)
- -- o "gb" - one gigabyte (1024*1024*1024)
- -- o "kib" - one si kilobyte (1000)
- -- o "mib" - one si megabyte (1000*1000)
- -- o "gib" - one si gigabyte (1000*1000*1000)
- function parse_units(ustr)
- local val = 0
- -- unit map
- local map = {
- -- date stuff
- y = 60 * 60 * 24 * 366,
- m = 60 * 60 * 24 * 31,
- w = 60 * 60 * 24 * 7,
- d = 60 * 60 * 24,
- h = 60 * 60,
- min = 60,
- -- storage sizes
- kb = 1024,
- mb = 1024 * 1024,
- gb = 1024 * 1024 * 1024,
- -- storage sizes (si)
- kib = 1000,
- mib = 1000 * 1000,
- gib = 1000 * 1000 * 1000
- }
- -- parse input string
- for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
- local num = spec:gsub("[^0-9%.]+$","")
- local spn = spec:gsub("^[0-9%.]+", "")
- if map[spn] or map[spn:sub(1,1)] then
- val = val + num * ( map[spn] or map[spn:sub(1,1)] )
- else
- val = val + num
- end
- end
- return val
- end
- -- also register functions above in the central string class for convenience
- string.split = split
- string.trim = trim
- string.cmatch = cmatch
- string.parse_units = parse_units
- function append(src, ...)
- for i, a in ipairs({...}) do
- if type(a) == "table" then
- for j, v in ipairs(a) do
- src[#src+1] = v
- end
- else
- src[#src+1] = a
- end
- end
- return src
- end
- function combine(...)
- return append({}, ...)
- end
- function contains(table, value)
- for k, v in pairs(table) do
- if value == v then
- return k
- end
- end
- return false
- end
- -- Both table are - in fact - merged together.
- function update(t, updates)
- for k, v in pairs(updates) do
- t[k] = v
- end
- end
- function keys(t)
- local keys = { }
- if t then
- for k, _ in kspairs(t) do
- keys[#keys+1] = k
- end
- end
- return keys
- end
- function clone(object, deep)
- local copy = {}
- for k, v in pairs(object) do
- if deep and type(v) == "table" then
- v = clone(v, deep)
- end
- copy[k] = v
- end
- return setmetatable(copy, getmetatable(object))
- end
- -- Serialize the contents of a table value.
- function _serialize_table(t, seen)
- assert(not seen[t], "Recursion detected.")
- seen[t] = true
- local data = ""
- local idata = ""
- local ilen = 0
- for k, v in pairs(t) do
- if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
- k = serialize_data(k, seen)
- v = serialize_data(v, seen)
- data = data .. ( #data > 0 and ", " or "" ) ..
- '[' .. k .. '] = ' .. v
- elseif k > ilen then
- ilen = k
- end
- end
- for i = 1, ilen do
- local v = serialize_data(t[i], seen)
- idata = idata .. ( #idata > 0 and ", " or "" ) .. v
- end
- return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
- end
- -- with loadstring().
- function serialize_data(val, seen)
- seen = seen or setmetatable({}, {__mode="k"})
- if val == nil then
- return "nil"
- elseif type(val) == "number" then
- return val
- elseif type(val) == "string" then
- return "%q" % val
- elseif type(val) == "boolean" then
- return val and "true" or "false"
- elseif type(val) == "function" then
- return "loadstring(%q)" % get_bytecode(val)
- elseif type(val) == "table" then
- return "{ " .. _serialize_table(val, seen) .. " }"
- else
- return '"[unhandled data type:' .. type(val) .. ']"'
- end
- end
- function restore_data(str)
- return loadstring("return " .. str)()
- end
- --
- -- Byte code manipulation routines
- --
- -- will be stripped before it is returned.
- function get_bytecode(val)
- local code
- if type(val) == "function" then
- code = string.dump(val)
- else
- code = string.dump( loadstring( "return " .. serialize_data(val) ) )
- end
- return code -- and strip_bytecode(code)
- end
- -- numbers and debugging numbers will be discarded. Original version by
- -- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
- function strip_bytecode(code)
- local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
- local subint
- if endian == 1 then
- subint = function(code, i, l)
- local val = 0
- for n = l, 1, -1 do
- val = val * 256 + code:byte(i + n - 1)
- end
- return val, i + l
- end
- else
- subint = function(code, i, l)
- local val = 0
- for n = 1, l, 1 do
- val = val * 256 + code:byte(i + n - 1)
- end
- return val, i + l
- end
- end
- local function strip_function(code)
- local count, offset = subint(code, 1, size)
- local stripped = { string.rep("\0", size) }
- local dirty = offset + count
- offset = offset + count + int * 2 + 4
- offset = offset + int + subint(code, offset, int) * ins
- count, offset = subint(code, offset, int)
- for n = 1, count do
- local t
- t, offset = subint(code, offset, 1)
- if t == 1 then
- offset = offset + 1
- elseif t == 4 then
- offset = offset + size + subint(code, offset, size)
- elseif t == 3 then
- offset = offset + num
- elseif t == 254 or t == 9 then
- offset = offset + lnum
- end
- end
- count, offset = subint(code, offset, int)
- stripped[#stripped+1] = code:sub(dirty, offset - 1)
- for n = 1, count do
- local proto, off = strip_function(code:sub(offset, -1))
- stripped[#stripped+1] = proto
- offset = offset + off - 1
- end
- offset = offset + subint(code, offset, int) * int + int
- count, offset = subint(code, offset, int)
- for n = 1, count do
- offset = offset + subint(code, offset, size) + size + int * 2
- end
- count, offset = subint(code, offset, int)
- for n = 1, count do
- offset = offset + subint(code, offset, size) + size
- end
- stripped[#stripped+1] = string.rep("\0", int * 3)
- return table.concat(stripped), offset
- end
- return code:sub(1,12) .. strip_function(code:sub(13,-1))
- end
- --
- -- Sorting iterator functions
- --
- function _sortiter( t, f )
- local keys = { }
- local k, v
- for k, v in pairs(t) do
- keys[#keys+1] = k
- end
- local _pos = 0
- table.sort( keys, f )
- return function()
- _pos = _pos + 1
- if _pos <= #keys then
- return keys[_pos], t[keys[_pos]], _pos
- end
- end
- end
- -- the provided callback function.
- function spairs(t,f)
- return _sortiter( t, f )
- end
- -- The table pairs are sorted by key.
- function kspairs(t)
- return _sortiter( t )
- end
- -- The table pairs are sorted by value.
- function vspairs(t)
- return _sortiter( t, function (a,b) return t[a] < t[b] end )
- end
- --
- -- System utility functions
- --
- function bigendian()
- return string.byte(string.dump(function() end), 7) == 0
- end
- function exec(command)
- local pp = io.popen(command)
- local data = pp:read("*a")
- pp:close()
- return data
- end
- function execi(command)
- local pp = io.popen(command)
- return pp and function()
- local line = pp:read()
- if not line then
- pp:close()
- end
- return line
- end
- end
- -- Deprecated
- function execl(command)
- local pp = io.popen(command)
- local line = ""
- local data = {}
- while true do
- line = pp:read()
- if (line == nil) then break end
- data[#data+1] = line
- end
- pp:close()
- return data
- end
- local ubus_codes = {
- "INVALID_COMMAND",
- "INVALID_ARGUMENT",
- "METHOD_NOT_FOUND",
- "NOT_FOUND",
- "NO_DATA",
- "PERMISSION_DENIED",
- "TIMEOUT",
- "NOT_SUPPORTED",
- "UNKNOWN_ERROR",
- "CONNECTION_FAILED"
- }
- local function ubus_return(...)
- if select('#', ...) == 2 then
- local rv, err = select(1, ...), select(2, ...)
- if rv == nil and type(err) == "number" then
- return nil, err, ubus_codes[err]
- end
- end
- return ...
- end
- function ubus(object, method, data, path, timeout)
- if not _ubus_connection then
- _ubus_connection = _ubus.connect(path, timeout)
- assert(_ubus_connection, "Unable to establish ubus connection")
- end
- if object and method then
- if type(data) ~= "table" then
- data = { }
- end
- return ubus_return(_ubus_connection:call(object, method, data))
- elseif object then
- return _ubus_connection:signatures(object)
- else
- return _ubus_connection:objects()
- end
- end
- function serialize_json(x, cb)
- local js = json.stringify(x)
- if type(cb) == "function" then
- cb(js)
- else
- return js
- end
- end
- function libpath()
- return require "nixio.fs".dirname(ldebug.__file__)
- end
- function checklib(fullpathexe, wantedlib)
- local fs = require "nixio.fs"
- local haveldd = fs.access('/usr/bin/ldd')
- local haveexe = fs.access(fullpathexe)
- if not haveldd or not haveexe then
- return false
- end
- local libs = exec(string.format("/usr/bin/ldd %s", shellquote(fullpathexe)))
- if not libs then
- return false
- end
- for k, v in ipairs(split(libs)) do
- if v:find(wantedlib) then
- return true
- end
- end
- return false
- end
- -------------------------------------------------------------------------------
- -- Coroutine safe xpcall and pcall versions
- --
- -- Encapsulates the protected calls with a coroutine based loop, so errors can
- -- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines
- -- yielding inside the call to pcall or xpcall.
- --
- -- Authors: Roberto Ierusalimschy and Andre Carregal
- -- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas
- --
- -- Copyright 2005 - Kepler Project
- --
- -- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $
- -------------------------------------------------------------------------------
- -------------------------------------------------------------------------------
- -- Implements xpcall with coroutines
- -------------------------------------------------------------------------------
- local coromap = setmetatable({}, { __mode = "k" })
- local function handleReturnValue(err, co, status, ...)
- if not status then
- return false, err(debug.traceback(co, (...)), ...)
- end
- if coroutine.status(co) == 'suspended' then
- return performResume(err, co, coroutine.yield(...))
- else
- return true, ...
- end
- end
- function performResume(err, co, ...)
- return handleReturnValue(err, co, coroutine.resume(co, ...))
- end
- local function id(trace, ...)
- return trace
- end
- function coxpcall(f, err, ...)
- local current = coroutine.running()
- if not current then
- if err == id then
- return pcall(f, ...)
- else
- if select("#", ...) > 0 then
- local oldf, params = f, { ... }
- f = function() return oldf(unpack(params)) end
- end
- return xpcall(f, err)
- end
- else
- local res, co = pcall(coroutine.create, f)
- if not res then
- local newf = function(...) return f(...) end
- co = coroutine.create(newf)
- end
- coromap[co] = current
- return performResume(err, co, ...)
- end
- end
- function copcall(f, ...)
- return coxpcall(f, id, ...)
- end
|