Skip to content
Snippets Groups Projects
Select Git revision
  • 8a422ac0cb2e028be8ebc5b2248a10cbeb0ed94f
  • v2018.2.x default
  • experimental
  • master
  • v2021.1.2-ffs
  • v2021.1.1-ffs
  • nrb/gluon-master-cpe510
  • v2021.1-ffs
  • v2020.2.3-ffs
  • nrbffs/fastd-remove-delay
  • v2020.2.2-ffs
  • v2020.2.1-ffs
  • v2020.2-ffs
  • v2020.2.x
  • v2020.1.3-ffs
  • v2020.1.1-ffs
  • v2020.1-ffs
  • v2019.1.2-ffs
  • v2019.1.1-ffs
  • nrb/test-radv-filter
  • v2019.1-ffs
  • nrbffs/netgear-ex6120
  • v2021.1.2-ffs0.2
  • v2021.1.2-ffs0.1
  • v2021.1.1-ffs0.4
  • v2021.1.1-ffs0.3
  • v2021.1.1-ffs0.2
  • v2021.1.1-ffs0.1
  • v2021.1-ffs0.1
  • v2020.2.3-ffs0.3
  • v2020.2.3-ffs0.2
  • v2020.2.3-ffs0.1
  • v2020.2.2-ffs0.1
  • v2020.2.1-ffs0.1
  • v2020.2-ffs0.1
  • v2020.2
  • v2020.2.x-ffs0.1
  • v2020.1.3-ffs0.1
  • v2020.1.1-ffs0.1
  • v2020.1-ffs0.1
  • v2019.1.2-ffs0.1
  • v2019.1.1-ffs0.1
42 results

check_site.lua

Blame
  • Forked from firmware / FFS Gluon
    Source project has a limited visibility.
    check_site.lua 8.21 KiB
    local json = require 'jsonc'
    
    local function config_error(src, ...)
    	error(src .. ' error: ' .. string.format(...), 0)
    end
    
    local has_domains = (os.execute('ls -d "$IPKG_INSTROOT"/lib/gluon/domains/ >/dev/null 2>&1') == 0)
    
    
    local function get_domains()
    	local domains = {}
    	local dirs = io.popen("find \"$IPKG_INSTROOT\"/lib/gluon/domains/ -name '*.json'")
    	for filename in dirs:lines() do
    		local name = string.match(filename, '([^/]+).json$')
    		domains[name] = assert(json.load(filename))
    	end
    	dirs:close()
    
    	if not next(domains) then
    		config_error('site', 'no domain configurations found')
    	end
    
    	return domains
    end
    
    local site, domain_code, domain, conf
    
    
    local M = setmetatable({}, { __index = _G })
    
    
    local function merge(a, b)
    	local function is_array(t)
    		local n = 0
    		for _ in pairs(t) do
    			n = n + 1
    		end
    		return n == #t
    	end
    
    	if not b then return a end
    	if type(a) ~= type(b) then return b end
    	if type(b) ~= 'table' then return b end
    	if not next(b) then return a end
    	if is_array(a) ~= is_array(b) then return b end
    
    	local m = {}
    	for k, v in pairs(a) do
    		m[k] = v
    	end
    	for k, v in pairs(b) do
    		m[k] = merge(m[k], v)
    	end
    
    	return m
    end
    
    
    local function path_to_string(path)
    	return table.concat(path, '.')
    end
    
    local function format(val)
    	if type(val) == 'string' then
    		return string.format('%q', val)
    	else
    		return tostring(val)
    	end
    end
    
    local function array_to_string(array)
    	local strings = {}
    	for i, v in ipairs(array) do
    		strings[i] = format(v)
    	end
    	return '[' .. table.concat(strings, ', ') .. ']'
    end
    
    function M.table_keys(tbl)
    	local keys = {}
    	for k in pairs(tbl) do
    		keys[#keys + 1] = k
    	end
    	return keys
    end
    
    
    local loadpath
    
    local function site_src()
    	return 'site.conf'
    end
    
    local function domain_src()
    	return 'domains/' .. domain_code .. '.conf'
    end
    
    local function conf_src(path)
    	local src
    
    	if has_domains then
    		if loadpath(nil, domain, unpack(path)) ~= nil then
    			src = domain_src()
    		elseif loadpath(nil, site, unpack(path)) ~= nil then
    			src = site_src()
    		else
    			src = site_src() .. ' / ' .. domain_src()
    		end
    	else
    		src = site_src()
    	end
    
    	return src
    end
    
    local function var_error(path, val, msg)
    	local found = 'unset'
    	if val ~= nil then
    		found = string.format('%s (a %s value)', format(val), type(val))
    	end
    
    	config_error(conf_src(path), 'expected %s to %s, but it is %s', path_to_string(path), msg, found)
    end
    
    function M.in_site(path)
    	if has_domains and loadpath(nil, domain, unpack(path)) ~= nil then
    		config_error(domain_src(), '%s is allowed in site configuration only', path_to_string(path))
    	end
    
    	return path
    end
    
    function M.in_domain(path)
    	if has_domains and loadpath(nil, site, unpack(path)) ~= nil then
    		config_error(site_src(), '%s is allowed in domain configuration only', path_to_string(path))
    	end
    
    	return path
    end
    
    function M.this_domain()
    	return domain_code
    end
    
    
    function M.extend(path, c)
    	if not path then return nil end
    
    	local p = {unpack(path)}
    
    	for _, e in ipairs(c) do
    		p[#p+1] = e
    	end
    	return p
    end
    
    function loadpath(path, base, c, ...)
    	if not c or base == nil then
    		return base
    	end
    
    	if type(base) ~= 'table' then
    		if path then
    			var_error(path, base, 'be a table')
    		else
    			return nil
    		end
    	end
    
    	return loadpath(M.extend(path, {c}), base[c], ...)
    end
    
    local function loadvar(path)
    	return loadpath({}, conf, unpack(path))
    end
    
    local function check_type(t)
    	return function(val)
    		return type(val) == t
    	end
    end
    
    local function check_one_of(array)
    	return function(val)
    		for _, v in ipairs(array) do
    			if v == val then
    				return true
    			end
    		end
    		return false
    	end
    end
    
    
    function M.alternatives(...)
    	local errs = {'All of the following alternatives have failed:'}
    	for i, f in ipairs({...}) do
    		local ok, err = pcall(f)
    		if ok then
    			return
    		end
    		errs[#errs+1] = string.format('%i) %s', i, err)
    	end
    
    	error(table.concat(errs, '\n        '), 0)
    end
    
    
    local function check_chanlist(channels)
    	local is_valid_channel = check_one_of(channels)
    	return function(chanlist)
    		for group in chanlist:gmatch("%S+") do
    			if group:match("^%d+$") then
    				local channel = tonumber(group)
    				if not is_valid_channel(channel) then
    					return false
    				end
    			elseif group:match("^%d+-%d+$") then
    				local from, to = group:match("^(%d+)-(%d+)$")
    				from = tonumber(from)
    				to = tonumber(to)
    				if from >= to then
    					return false
    				end
    				if not is_valid_channel(from) or not is_valid_channel(to) then
    					return false
    				end
    			else
    				return false
    			end
    		end
    		return true
    	end
    end
    
    function M.need(path, check, required, msg)
    	local val = loadvar(path)
    	if required == false and val == nil then
    		return nil
    	end
    
    	if not check(val) then
    		var_error(path, val, msg)
    	end
    
    	return val
    end
    
    local function need_type(path, type, required, msg)
    	return M.need(path, check_type(type), required, msg)
    end
    
    
    function M.need_alphanumeric_key(path)
    	local val = path[#path]
    	-- We don't use character classes like %w here to be independent of the locale
    	if type(val) ~= 'string' or not val:match('^[0-9a-zA-Z_]+$') then
    		var_error(path, val, 'have a string key using only alphanumeric characters and underscores')
    	end
    end
    
    
    function M.need_string(path, required)
    	return need_type(path, 'string', required, 'be a string')
    end
    
    function M.need_string_match(path, pat, required)
    	local val = M.need_string(path, required)
    	if not val then
    		return nil
    	end
    
    	if not val:match(pat) then
    		var_error(path, val, "match pattern '" .. pat .. "'")
    	end
    
    	return val
    end
    
    function M.need_number(path, required)
    	return need_type(path, 'number', required, 'be a number')
    end
    
    function M.need_number_range(path, min, max, required)
    	local val = need_type(path, 'number', required)
    	if not val then
    		return nil
    	end
    
    	if val < min or val > max then
    		var_error(path, val, "be in range [" .. min .. ", " .. max .. "]")
    	end
    
    	return val
    end
    
    function M.need_boolean(path, required)
    	return need_type(path, 'boolean', required, 'be a boolean')
    end
    
    function M.need_array(path, subcheck, required)
    	local val = need_type(path, 'table', required, 'be an array')
    	if not val then
    		return nil
    	end
    
    	if subcheck then
    		for i = 1, #val do
    			subcheck(M.extend(path, {i}))
    		end
    	end
    
    	return val
    end
    
    function M.need_table(path, subcheck, required)
    	local val = need_type(path, 'table', required, 'be a table')
    	if not val then
    		return nil
    	end
    
    	if subcheck then
    		for k, _ in pairs(val) do
    			subcheck(M.extend(path, {k}))
    		end
    	end
    
    	return val
    end
    
    function M.need_value(path, value, required)
    	return M.need(path, function(v)
    		return v == value
    	end, required, 'be ' .. tostring(value))
    end
    
    function M.need_one_of(path, array, required)
    	return M.need(path, check_one_of(array), required, 'be one of the given array ' .. array_to_string(array))
    end
    
    function M.need_string_array(path, required)
    	return M.need_array(path, M.need_string, required)
    end
    
    function M.need_string_array_match(path, pat, required)
    	return M.need_array(path, function(e) M.need_string_match(e, pat) end, required)
    end
    
    function M.need_array_of(path, array, required)
    	return M.need_array(path, function(e) M.need_one_of(e, array) end, required)
    end
    
    function M.need_chanlist(path, channels, required)
    	local valid_chanlist = check_chanlist(channels)
    	return M.need(path, valid_chanlist, required,
    		'be a space-separated list of WiFi channels or channel-ranges (separated by a hyphen). '
    		.. 'Valid channels are: ' .. array_to_string(channels))
    end
    
    function M.need_domain_name(path)
    	M.need_string(path)
    	M.need(path, function(domain_name)
    		local f = io.open(os.getenv('IPKG_INSTROOT') .. '/lib/gluon/domains/' .. domain_name .. '.json')
    		if not f then return false end
    		f:close()
    		return true
    	end, nil, 'be a valid domain name')
    end
    
    function M.obsolete(path, msg)
    	local val = loadvar(path)
    	if val == nil then
    		return nil
    	end
    
    	if not msg then
    		msg = 'Check the release notes and documentation for details.'
    
    	end
    
    	config_error(conf_src(path), '%s is obsolete. %s', path_to_string(path), msg)
    end
    
    local check = setfenv(assert(loadfile()), M)
    
    site = assert(json.load(os.getenv('IPKG_INSTROOT') .. '/lib/gluon/site.json'))
    
    local ok, err = pcall(function()
    	if has_domains then
    		for k, v in pairs(get_domains()) do
    			domain_code = k
    			domain = v
    			conf = merge(site, domain)
    			check()
    		end
    	else
    		conf = site
    		check()
    	end
    end)
    
    if not ok then
    	io.stderr:write('*** ', err, '\n')
    	os.exit(1)
    end