#!/usr/bin/env lua local json = require "luci.jsonc" local fs = require "nixio.fs" local function readfile(path) local s = fs.readfile(path) return s and (s:gsub("^%s+", ""):gsub("%s+$", "")) end local methods = { initList = { args = { name = "name" }, call = function(args) local sys = require "luci.sys" local _, name, scripts = nil, nil, {} for _, name in ipairs(args.name and { args.name } or sys.init.names()) do local index = sys.init.index(name) if index then scripts[name] = { index = index, enabled = sys.init.enabled(name) } else return { error = "No such init script" } end end return { result = scripts } end }, initCall = { args = { name = "name", action = "action" }, call = function(args) local sys = require "luci.sys" if type(sys.init[args.action]) ~= "function" then return { error = "Invalid action" } end return { result = sys.init[args.action](args.name) } end }, getLocaltime = { call = function(args) return { localtime = os.time() } end }, setLocaltime = { args = { localtime = 0 }, call = function(args) local sys = require "luci.sys" local date = os.date("*t", args.localtime) if date then sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec }) sys.call("/etc/init.d/sysfixtime restart >/dev/null") end return { localtime = args.localtime } end }, timezone = { call = function(args) local util = require "luci.util" local zones = require "luci.sys.zoneinfo" local tz = readfile("/etc/TZ") local res = util.ubus("uci", "get", { config = "system", section = "@system[0]", option = "zonename" }) local result = {} local _, zone for _, zone in ipairs(zones.TZ) do result[zone[1]] = { tzstring = zone[2], active = (res and res.value == zone[1]) and true or nil } end return { result = result } end }, leds = { call = function() local iter = fs.dir("/sys/class/leds") local result = { } if iter then local led for led in iter do local m, s result[led] = { triggers = {} } s = readfile("/sys/class/leds/"..led.."/trigger") for s in (s or ""):gmatch("%S+") do m = s:match("^%[(.+)%]$") result[led].triggers[#result[led].triggers+1] = m or s result[led].active_trigger = m or result[led].active_trigger end s = readfile("/sys/class/leds/"..led.."/brightness") if s then result[led].brightness = tonumber(s) end s = readfile("/sys/class/leds/"..led.."/max_brightness") if s then result[led].max_brightness = tonumber(s) end end end return result end }, usb = { call = function() local fs = require "nixio.fs" local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer") local result = { } if iter then result.devices = {} local p for p in iter do local id = p:match("%d+-%d+") result.devices[#result.devices+1] = { id = id, vid = readfile("/sys/bus/usb/devices/"..id.."/idVendor"), pid = readfile("/sys/bus/usb/devices/"..id.."/idProduct"), vendor = readfile("/sys/bus/usb/devices/"..id.."/manufacturer"), product = readfile("/sys/bus/usb/devices/"..id.."/product"), speed = tonumber((readfile("/sys/bus/usb/devices/"..id.."/product"))) } end end iter = fs.glob("/sys/bus/usb/devices/*/usb[0-9]*-port[0-9]*") if iter then result.ports = {} local p for p in iter do local bus, port = p:match("usb(%d+)-port(%d+)") result.ports[#result.ports+1] = { hub = tonumber(bus), port = tonumber(port) } end end return result end }, ifaddrs = { call = function() return { result = nixio.getifaddrs() } end }, host_hints = { call = function() local sys = require "luci.sys" return sys.net.host_hints() end }, duid_hints = { call = function() local fp = io.open('/var/hosts/odhcpd') local result = { } if fp then for line in fp:lines() do local dev, duid, name = string.match(line, '# (%S+)%s+(%S+)%s+%d+%s+(%S+)') if dev and duid and name then result[duid] = { name = (name ~= "-") and name or nil, device = dev } end end fp:close() end return result end }, leases = { args = { family = 0 }, call = function(args) local s = require "luci.tools.status" if args.family == 4 then return { dhcp_leases = s.dhcp_leases() } elseif args.family == 6 then return { dhcp6_leases = s.dhcp6_leases() } else return { dhcp_leases = s.dhcp_leases(), dhcp6_leases = s.dhcp6_leases() } end end }, netdevs = { call = function(args) local dir = fs.dir("/sys/class/net") local result = { } if dir then local dev for dev in dir do if not result[dev] then result[dev] = { name = dev } end if fs.access("/sys/class/net/"..dev.."/master") then local brname = fs.basename(fs.readlink("/sys/class/net/"..dev.."/master")) if not result[brname] then result[brname] = { name = brname } end if not result[brname].ports then result[brname].ports = { } end result[brname].ports[#result[brname].ports+1] = dev elseif fs.access("/sys/class/net/"..dev.."/bridge") then if not result[dev].ports then result[dev].ports = { } end result[dev].id = readfile("/sys/class/net/"..dev.."/bridge/bridge_id") result[dev].stp = (readfile("/sys/class/net/"..dev.."/bridge/stp_state") ~= "0") result[dev].bridge = true end local opr = readfile("/sys/class/net/"..dev.."/operstate") result[dev].up = (opr == "up" or opr == "unknown") result[dev].type = tonumber(readfile("/sys/class/net/"..dev.."/type")) result[dev].name = dev local mtu = tonumber(readfile("/sys/class/net/"..dev.."/mtu")) if mtu and mtu > 0 then result[dev].mtu = mtu end local qlen = tonumber(readfile("/sys/class/net/"..dev.."/tx_queue_len")) if qlen and qlen > 0 then result[dev].qlen = qlen end local master = fs.readlink("/sys/class/net/"..dev.."/master") if master then result[dev].master = fs.basename(master) end local mac = readfile("/sys/class/net/"..dev.."/address") if mac and #mac == 17 then result[dev].mac = mac end end end return result end }, boardjson = { call = function(args) local jsc = require "luci.jsonc" return jsc.parse(fs.readfile("/etc/board.json") or "") end }, offload_support = { call = function() local fs = require "nixio.fs" return { offload_support = not not fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") } end }, conntrack_helpers = { call = function() local fd = io.open("/usr/share/fw3/helpers.conf", "r") local rv = {} local line, entry while true do line = fd:read("*l") if not line then break end if line:match("^%s*config%s") then if entry then rv[#rv+1] = entry end entry = {} else local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$") if opt and val then opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1") val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1") entry[opt] = val end end end if entry then rv[#rv+1] = entry end return { helpers = rv } end } } local function parseInput() local parse = json.new() local done, err while true do local chunk = io.read(4096) if not chunk then break elseif not done and not err then done, err = parse:parse(chunk) end end if not done then print(json.stringify({ error = err or "Incomplete input" })) os.exit(1) end return parse:get() end local function validateArgs(func, uargs) local method = methods[func] if not method then print(json.stringify({ error = "Method not found" })) os.exit(1) end if type(uargs) ~= "table" then print(json.stringify({ error = "Invalid arguments" })) os.exit(1) end uargs.ubus_rpc_session = nil local k, v local margs = method.args or {} for k, v in pairs(uargs) do if margs[k] == nil or (v ~= nil and type(v) ~= type(margs[k])) then print(json.stringify({ error = "Invalid arguments" })) os.exit(1) end end return method end if arg[1] == "list" then local _, method, rv = nil, nil, {} for _, method in pairs(methods) do rv[_] = method.args or {} end print((json.stringify(rv):gsub(":%[%]", ":{}"))) elseif arg[1] == "call" then local args = parseInput() local method = validateArgs(arg[2], args) local result, code = method.call(args) print((json.stringify(result):gsub("^%[%]$", "{}"))) os.exit(code or 0) end