luci-base: additionally return error code strings in luci.util.ubus()
[project/luci.git] / modules / luci-base / luasrc / util.lua
index 8b28b17..ce42af2 100644 (file)
@@ -9,13 +9,15 @@ 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 = rawget, rawset, unpack
-local tostring, type, assert = tostring, type, assert
+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
@@ -26,14 +28,27 @@ 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
-               return a:format(unpack(b))
+
+               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
-               return a:format(b)
+
+               ok, res = pcall(a.format, a, b)
+               if not ok then
+                       error(res, 2)
+               end
+               return res
        end
 end
 
@@ -146,10 +161,55 @@ function pcdata(value)
        return value and tparser.pcdata(tostring(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
+
 function striptags(value)
        return value and tparser.striptags(tostring(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, "^\-", "\\-")
+   res, _ = string.gsub(res, "^-", "\-")
+   return shellsqescape(value)
+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
@@ -347,16 +407,6 @@ function clone(object, deep)
 end
 
 
-function dtable()
-        return setmetatable({}, { __index =
-                function(tbl, key)
-                        return rawget(tbl, key)
-                         or rawget(rawset(tbl, key, dtable()), key)
-                end
-        })
-end
-
-
 -- Serialize the contents of a table value.
 function _serialize_table(t, seen)
        assert(not seen[t], "Recursion detected.")
@@ -581,6 +631,20 @@ function execl(command)
        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"
+}
+
 function ubus(object, method, data)
        if not _ubus_connection then
                _ubus_connection = _ubus.connect()
@@ -591,7 +655,8 @@ function ubus(object, method, data)
                if type(data) ~= "table" then
                        data = { }
                end
-               return _ubus_connection:call(object, method, data)
+               local rv, err = _ubus_connection:call(object, method, data)
+               return rv, err, ubus_codes[err]
        elseif object then
                return _ubus_connection:signatures(object)
        else
@@ -600,55 +665,11 @@ function ubus(object, method, data)
 end
 
 function serialize_json(x, cb)
-       local rv, push = nil, cb
-       if not push then
-               rv = { }
-               push = function(tok) rv[#rv+1] = tok end
-       end
-
-       if x == nil then
-               push("null")
-       elseif type(x) == "table" then
-               -- test if table is array like
-               local k, v
-               local n1, n2 = 0, 0
-               for k in pairs(x) do n1 = n1 + 1 end
-               for k in ipairs(x) do n2 = n2 + 1 end
-
-               if n1 == n2 and n1 > 0 then
-                       push("[")
-                       for k = 1, n2 do
-                               if k > 1 then
-                                       push(",")
-                               end
-                               serialize_json(x[k], push)
-                       end
-                       push("]")
-               else
-                       push("{")
-                       for k, v in pairs(x) do
-                               push("%q:" % tostring(k))
-                               serialize_json(v, push)
-                               if next(x, k) then
-                                       push(",")
-                               end
-                       end
-                       push("}")
-               end
-       elseif type(x) == "number" or type(x) == "boolean" then
-               if (x ~= x) then
-                       -- NaN is the only value that doesn't equal to itself.
-                       push("Number.NaN")
-               else
-                       push(tostring(x))
-               end
+       local js = json.stringify(x)
+       if type(cb) == "function" then
+               cb(js)
        else
-               push('"%s"' % tostring(x):gsub('["%z\1-\31]',
-                       function(c) return '\\u%04x' % c:byte(1) end))
-       end
-
-       if not cb then
-               return table.concat(rv, "")
+               return js
        end
 end
 
@@ -657,6 +678,24 @@ 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 modified for Luci