From 8b8d83e42dd3d10d82e29a8614a7b3e3e94b16c6 Mon Sep 17 00:00:00 2001
From: Sven Roederer <freifunk@it-solutions.geroedel.de>
Date: Mon, 1 Jul 2019 21:52:07 +0200
Subject: luci-base: move some generic classes into a separate luci-base-libs
 package

The new package luci-base-libs provides the modules that not strictly relate
to the web-interface of luci. By separating these libs they can be used by
other packages without having to install the web-components.
This change was inspired by providing a shell-only interface for 4MB-flash
devices, by keeping as much code common with a full install.

Signed-off-by: Sven Roederer <freifunk@it-solutions.geroedel.de>
---
 libs/luci-lib-base/luasrc/util.lua | 776 +++++++++++++++++++++++++++++++++++++
 1 file changed, 776 insertions(+)
 create mode 100644 libs/luci-lib-base/luasrc/util.lua

(limited to 'libs/luci-lib-base/luasrc/util.lua')

diff --git a/libs/luci-lib-base/luasrc/util.lua b/libs/luci-lib-base/luasrc/util.lua
new file mode 100644
index 0000000000..a30e8b72f3
--- /dev/null
+++ b/libs/luci-lib-base/luasrc/util.lua
@@ -0,0 +1,776 @@
+-- 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
+--
+
+coxpt = setmetatable({}, { __mode = "kv" })
+
+local tl_meta = {
+	__mode = "k",
+
+	__index = function(self, key)
+		local t = rawget(self, coxpt[coroutine.running()]
+		 or coroutine.running() or 0)
+		return t and t[key]
+	end,
+
+	__newindex = function(self, key, value)
+		local c = coxpt[coroutine.running()] or coroutine.running() or 0
+		local r = rawget(self, c)
+		if not r then
+			rawset(self, c, { [key] = value })
+		else
+			r[key] = value
+		end
+	end
+}
+
+-- the current active coroutine. A thread local store is private a table object
+-- whose values can't be accessed from outside of the running coroutine.
+function threadlocal(tbl)
+	return setmetatable(tbl or {}, tl_meta)
+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
+--
+
+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, "^%-", "\\-")
+   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.pcdata      = pcdata
+string.striptags   = striptags
+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)
+	if not _ubus_connection then
+		_ubus_connection = _ubus.connect()
+		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
+		coxpt[co] = coxpt[current] or current or 0
+		return performResume(err, co, ...)
+	end
+end
+
+function copcall(f, ...)
+	return coxpcall(f, id, ...)
+end
-- 
cgit v1.2.3