diff options
Diffstat (limited to 'libs/core')
-rw-r--r-- | libs/core/luasrc/model/network.lua | 895 | ||||
-rw-r--r-- | libs/core/luasrc/model/uci.lua | 345 | ||||
-rw-r--r-- | libs/core/luasrc/model/uci/bind.lua | 173 | ||||
-rw-r--r-- | libs/core/root/etc/config/ucitrack | 48 | ||||
-rwxr-xr-x | libs/core/root/sbin/luci-reload | 40 |
5 files changed, 1501 insertions, 0 deletions
diff --git a/libs/core/luasrc/model/network.lua b/libs/core/luasrc/model/network.lua index 8459463f1..53649dde5 100644 --- a/libs/core/luasrc/model/network.lua +++ b/libs/core/luasrc/model/network.lua @@ -483,3 +483,898 @@ function interface.get_network(self) end end +--[==[ +#!/usr/bin/lua + +local uci = require "luci.model.uci".cursor_state() +local utl = require "luci.util" +local sys = require "luci.sys" +local lip = require "luci.ip" +local nxo = require "nixio" +local nfs = require "nixio.fs" + +-- patch uci +local x = getmetatable(uci) + +function x:list(...) + local val = self:get(...) + local lst = { } + + if type(val) == "list" then + local _, v + for _, v in ipairs(val) do + local i + for i in v:gmatch("%S+") do + lst[#lst+1] = i + end + end + elseif type(val) == "string" then + local i + for i in val:gmatch("%S+") do + lst[#lst+1] = i + end + end + + return lst +end + + +system = utl.class() + +system._switches = { } +system._vlans = { } + +function system:__init__() + self._networks = { } + + uci:foreach("network2", "interface", + function(s) + self._networks[#self._networks+1] = system.network(s, self) + end) +end + +function system:networks() + local index = 0 + return function() + if index <= #self._networks then + index = index + 1 + return self._networks[index] + else + return nil + end + end +end + +function system:find_network(name) + local v + for _, v in ipairs(self._networks) do + if v:name() == name then + return v + end + end +end + +function system:find_interface(name) + local v + for _, v in ipairs(self._networks) do + local i + for i in v:interfaces() do + if i:is_bridge() then + local p + for p in i:interfaces() do + if p:name() == name then + return p + end + end + end + + if i:name() == name then + return i + end + end + end +end + +function system:delete_network(name) + local i + for i = 1, #self._networks do + if self._networks[i]:name() == name then + local x + + for x in self._networks[i]:aliases() do + uci:delete("network2", x:name()) + end + + for x in self._networks[i]:routes() do + uci:delete("network2", x:name()) + end + + uci:delete("network2", self._networks[i]) + table.remove(self._networks, i) + + return true + end + end + return false +end + +function system:print() + local v + for v in self:networks() do + print(v:name()) + v:print() + print("--") + end +end + +function system.ignore_iface(ifn) + return (nil ~= ( + ifn:match("^wlan%d") or + ifn:match("^ath%d") or + ifn:match("^wl%d") or + ifn:match("^imq%d") or + ifn:match("^br%-") or + ifn:match("^/dev/") + )) +end + +function system.find_wifi_networks(net) + local lst = { } + local cnt = 0 + + uci:foreach("wireless", "wifi-iface", + function(s) + if s.device and s.network == net then + lst[#lst+1] = { s.device, s['.name'], cnt } + end + cnt = cnt + 1 + end) + + return lst +end + +function system.find_iface_names(net) + local lst = { } + + local val = uci:list("network2", net, "device") + if #val == 0 or val[1]:match("^/dev/") then + val = uci:list("network2", net, "ifname") + end + + local ifn + for _, ifn in ipairs(val) do + if not system.ignore_iface(ifn) then + lst[#lst+1] = ifn + end + end + + return lst +end + +function system.find_switch(name) + local swname, swdev, swvlan + + -- find switch + uci:foreach("network2", "switch", + function(s) + swname = s.name or s['.name'] + + -- special: rtl8366s is eth0 (wan is eth1) + if swname == "rtl8366s" then + swdev = "eth0" + + -- special: rtl8366rb is eth0 (wan + lan) + elseif swname == "rtl8366rb" then + swdev = "eth0" + + -- treat swname as swdev + else + swdev = swname + end + + return false + end) + + -- find first vlan + if swdev then + uci:foreach("network2", "switch_vlan", + function(s) + if s.device == swname then + local vlan = tonumber(s.vlan) + if vlan and (not swvlan or vlan < swvlan) then + swvlan = vlan + end + end + end) + end + + + local veth, vlan = name:match("^(%S+)%.(%d+)$") + + -- have vlan id and matching switch + if vlan and veth == swdev then + return swname, swdev, vlan + + -- have no vlan id but matching switch, assume first switch vlan + elseif not vlan and name == swdev then + return swname, swdev, swvlan + + -- have vlan and no matching switch, assume software vlan + elseif vlan then + return nil, veth, vlan + end +end + + +system.network = utl.class() + +function system.network:__init__(s, sys) + self._name = s['.name'] + self._sys = sys + self._routes = { } + self._aliases = { } + + if s.type == "bridge" then + self._interfaces = { system.network.bridge(s['.name'], self) } + else + self._interfaces = { } + + local ifn + + -- find wired ifaces + for _, ifn in ipairs(system.find_iface_names(self._name)) do + self._interfaces[#self._interfaces+1] = system.network.iface(ifn, self) + end + + -- find wifi networks + for _, ifn in ipairs(system.find_wifi_networks(self._name)) do + self._interfaces[#self._interfaces+1] = system.network.iface(ifn, self) + end + end + + -- find ipv4 routes + uci:foreach("network2", "route", + function(s) + if s.interface == self._name and s.target then + self._routes[#self._routes+1] = system.network.route(s, self) + end + end) + + -- find ipv6 routes + uci:foreach("network2", "route6", + function(s) + if s.interface == self._name and s.target then + self._routes[#self._routes+1] = system.network.route(s, self) + end + end) + + -- find aliases + uci:foreach("network2", "alias", + function(s) + if s.interface == self._name and s.proto then + self._aliases[#self._aliases+1] = system.network.alias(s, self) + end + end) +end + +function system.network:name() + return self._name +end + +function system.network:system() + return self._sys +end + +function system.network:interfaces() + local index = 0 + return function() + if index <= #self._interfaces then + index = index + 1 + return self._interfaces[index] + else + return nil + end + end +end + +function system.network:interface() + return self._interfaces[1] +end + +function system.network:num_routes() + return #self._routes +end + +function system.network:routes() + local index = 0 + return function() + if index <= #self._routes then + index = index + 1 + return self._routes[index] + else + return nil + end + end +end + +function system.network:num_aliases() + return #self._aliases +end + +function system.network:aliases() + local index = 0 + return function() + if index <= #self._aliases then + index = index + 1 + return self._aliases[index] + else + return nil + end + end +end + +function system.network:delete_route(rt) + local i + for i = 1, #self._routes do + if self._routes[i]:name() == rt:name() then + uci:delete("network2", rt:name()) + table.remove(self._routes, i) + return true + end + end + return false +end + +function system.network:delete_alias(al) + local i + for i = 1, #self._aliases do + if self._aliases[i]:name() == al:name() then + uci:delete("network2", al:name()) + table.remove(self._aliases, i) + return true + end + end + return false +end + +function system.network:print() + self:interface():print() +end + + +system.network.iface = utl.class() + +function system.network.iface:__init__(ifn, net, parent) + self._net = net + self._parent = parent + + -- is a wifi iface + if type(ifn) == "table" then + local wifidev, network, index = unpack(ifn) + + self._name = "%s.%d" %{ wifidev, index } + self._wifidev = wifidev + self._wifinet = index + self._ifname = uci:get("wireless", network, "ifname") or self._name + + -- is a wired iface + else + self._name = ifn + self._ifname = ifn + + local switch, swdev, vlan = system.find_switch(self._ifname) + + if switch then + self._switch = system.switch(switch, swdev, self) + end + + if vlan then + self._vlan = system.vlan(vlan, self._switch, self) + end + end +end + +function system.network.iface:name() + return self._name +end + +function system.network.iface:parent() + return self._parent +end + +function system.network.iface:network() + return self._net +end + +function system.network.iface:is_managed() + return (self._net ~= nil) +end + +function system.network.iface:is_vlan() + return (self._vlan ~= nil) +end + +function system.network.iface:is_software_vlan() + return (not self._switch and self._vlan ~= nil) +end + +function system.network.iface:is_hardware_vlan() + return (self._switch ~= nil and self._vlan ~= nil) +end + +function system.network.iface:_sysfs(path, default) + path = "/sys/class/net/%s/%s" %{ self._ifname, path } + + local data = nfs.readfile(path) + + if type(default) == "number" then + return tonumber(data) or default + elseif data and #data > 0 then + return data and data:gsub("%s+$", "") or default + end + + return default +end + +function system.network.iface:rx_bytes() + return self:_sysfs("statistics/rx_bytes", 0) +end + +function system.network.iface:tx_bytes() + return self:_sysfs("statistics/tx_bytes", 0) +end + +function system.network.iface:rx_packets() + return self:_sysfs("statistics/rx_packets", 0) +end + +function system.network.iface:tx_packets() + return self:_sysfs("statistics/tx_packets", 0) +end + +function system.network.iface:macaddr() + return self:_sysfs("address") +end + +function system.network.iface:mtu() + return self:_sysfs("mtu", 1500) +end + +function system.network.iface:is_bridge() + return (self:_sysfs("bridge/max_age", 0) > 0) +end + +function system.network.iface:is_bridge_port() + return (self:_sysfs("brport/port_no", 0) > 0) +end + +function system.network.iface:delete() + if self._wifidev then + local cnt = 0 + uci:foreach("wireless", "wifi-iface", + function(s) + cnt = cnt + 1 + if s.device == self._wifidev and cnt == self._wifinet then + uci:delete("wireless", s['.name']) + return false + end + end) + end +end + +function system.network.iface:print() + if self._wifidev then + print(" wifi: ", self._wifidev, "net: ", self._wifinet) + else + print(" iface: ", self._name) + end + + print(" rx: ", self:rx_bytes(), self:rx_packets()) + print(" tx: ", self:tx_bytes(), self:tx_packets()) + print(" mtu: ", self:mtu()) + print(" mac: ", self:macaddr()) + print(" bridge? ", self:is_bridge()) + print(" port? ", self:is_bridge_port()) + print(" swvlan? ", self:is_software_vlan()) + print(" hwvlan? ", self:is_hardware_vlan()) + + if self._switch then + self._switch:print() + end + + if self._vlan then + self._vlan:print() + end +end + + +system.network.bridge = utl.class(system.network.iface) + +function system.network.bridge:__init__(brn, net) + self._net = net + self._name = "br-" .. brn + self._ifname = self._name + self._interfaces = { } + + local ifn + + -- find wired ifaces + for _, ifn in ipairs(system.find_iface_names(brn)) do + self._interfaces[#self._interfaces+1] = system.network.iface(ifn, net, self) + end + + -- find wifi networks + for _, ifn in ipairs(system.find_wifi_networks(brn)) do + self._interfaces[#self._interfaces+1] = system.network.iface(ifn, net, self) + end +end + +function system.network.bridge:interfaces() + local index = 0 + return function() + if index <= #self._interfaces then + index = index + 1 + return self._interfaces[index] + else + return nil + end + end +end + +function system.network.bridge:print() + local v + for v in self:interfaces() do + io.write(" port: ") + v:print() + end + print(" rx: ", self:rx_bytes(), self:rx_packets()) + print(" tx: ", self:tx_bytes(), self:tx_packets()) + print(" mtu: ", self:mtu()) + print(" mac: ", self:macaddr()) + print(" bridge? ", self:is_bridge()) + print(" port? ", self:is_bridge_port()) +end + + +system.network.route = utl.class() + +function system.network.route:__init__(rt, net) + self._net = net + self._name = rt['.name'] + self._ipv6 = (rt['.type'] == "route6") + self._mtu = tonumber(rt.mtu) or (net and net:interface():mtu() or 1500) + self._metric = tonumber(rt.metric) or 0 + + if self._ipv6 then + self._gateway = lip.IPv6(rt.gateway or "::") + self._target = lip.IPv6(rt.target or "::") + else + self._gateway = lip.IPv4(rt.gateway or "0.0.0.0") + self._target = lip.IPv4(rt.target or "0.0.0.0", rt.netmask or "0.0.0.0") + end +end + +function system.network.route:name() + return self._name +end + +function system.network.route:network() + return self._net +end + +function system.network.route:mtu() + return self._mtu +end + +function system.network.route:metric() + return self._metric +end + +function system.network.route:is_ipv4() + return not self._ipv6 +end + +function system.network.route:is_ipv6() + return self._ipv6 +end + +function system.network.route:target() + return self._target +end + +function system.network.route:gateway() + return self._gateway +end + + +system.network.alias = utl.class() + +function system.network.alias:__init__(a, net) + self._net = net + self._name = a['.name'] +end + + +system.switch = utl.class() + +function system.switch:__init__(switch, swdev, net) + self._name = switch + self._ifname = swdev + self._net = net + + if not system._switches[switch] then + local x = io.popen("swconfig dev %q help 2>/dev/null" % switch) + if x then + local desc = x:read("*l") + + if desc then + local name, num_ports, num_cpu, num_vlans = + desc:match("Switch %d: %S+%((.-)%), ports: (%d+) %(cpu @ (%d+)%), vlans: (%d+)") + + self._model = name + self._ports = tonumber(num_ports) + self._cpuport = tonumber(num_cpu) + self._vlans = tonumber(num_vlans) + end + + x:close() + + elseif nfs.access("/proc/switch/%s" % switch) then + self._model = self:_proc("driver", switch) + self._ports = self:_proc_count("port", 6) + self._vlans = self:_proc_count("vlan", 16) + end + + -- defaults + self._model = self._model or switch + self._ports = self._ports or 6 + self._vlans = self._vlans or 16 + self._cpuport = self._cpuport or 5 + + system._switches[switch] = self + else + self._model = system._switches[switch]._model + self._ports = system._switches[switch]._ports + self._vlans = system._switches[switch]._vlans + self._cpuport = system._switches[switch]._cpuport + end +end + +function system.switch:_proc(path, default) + local data = nfs.readfile("/proc/switch/%s/%s" %{ self._name, path }) + if data then + return data:gsub("%s+$", "") + end + return default +end + +function system.switch:_proc_count(path, default) + local cnt = 0 + for _ in nfs.dir("/proc/switch/%s/%s" %{ self._name, path }) do + cnt = cnt + 1 + end + return cnt > 0 and cnt or default +end + +function system.switch:name() + return self._name +end + +function system.switch:model() + return self._model +end + +function system.switch:num_possible_vlans() + return self._vlans +end + +function system.switch:num_active_vlans() + local cnt = 0 + uci:foreach("network2", "switch_vlan", + function(s) + if s.device == self._name then cnt = cnt + 1 end + end) + return cnt +end + +function system.switch:vlans() + local index = 0 + local vlans = { } + + uci:foreach("network2", "switch_vlan", + function(s) + if s.device == self._name and tonumber(s.vlan) then + vlans[#vlans+1] = tonumber(s.vlan) + end + end) + + return function() + if index <= #vlans then + index = index + 1 + return system.vlan(vlans[index], self) + else + return nil + end + end +end + +function system.switch:num_ports() + return self._ports +end + +function system.switch:delete_vlan(vlan) + local rv = false + + uci:foreach("network2", "switch_vlan", + function(s) + if s.device == self._name and tonumber(s.vlan) == vlan then + rv = true + uci:delete("network2", s['.name']) + + if system._vlans[s.device] and system._vlans[s.device][vlan] then + table.remove(system._vlans[s.device], vlan) + end + + return false + end + end) + + return rv +end + +function system.switch:print() + print("Switch:", self._model) + print(" Ports:", self._ports, "Cpu:", self._cpuport) + print(" Vlans:", self._vlans) +end + + +system.vlan = utl.class() + +function system.vlan:__init__(vlan, switch, iface) + self._vlan = vlan + self._switch = switch + self._iface = iface + + local swid = (switch and switch:name()) or (iface and iface:name()) or "" + + if not system._vlans[swid] or not system._vlans[swid][vlan] then + self._ports = { } + + if switch then + uci:foreach("network2", "switch_vlan", + function(s) + if s.device == switch:name() and tonumber(s.vlan) == vlan then + local p + for _, p in ipairs(uci:list("network2", s['.name'], "ports")) do + self._ports[#self._ports+1] = system.vlan.port(p, self) + end + self._name = s['.name'] + end + end) + else + self._ports[#self._ports+1] = system.vlan.port("0t", self) + end + + system._vlans[swid] = system._vlans[swid] or { } + system._vlans[swid][vlan] = self + else + self._ports = system._vlans[swid][vlan]._ports + end +end + +function system.vlan:name() + return self._name +end + +function system.vlan:number() + return self._vlan +end + +function system.vlan:switch() + return self._switch +end + +function system.vlan:interface() + return self._iface +end + +function system.vlan:is_software() + return (self._switch == nil) +end + +function system.vlan:is_hardware() + return not self:is_software() +end + +function system.vlan:num_ports() + return #self._ports +end + +function system.vlan:ports() + local index = 0 + return function() + if index <= #self._ports then + index = index + 1 + return self._ports[index] + else + return nil + end + end +end + +function system.vlan:_update() + local i + local ports = { } + + for i = 1, #self._ports do + ports[#ports+1] = self._ports[i]:string() + end + + uci:set("network2", self._name, "ports", table.concat(ports, " ")) +end + +function system.vlan:delete_port(port) + if self._switch then + local i + for i = 1, #self._ports do + if self._ports[i]:number() == port then + table.remove(self._ports, i) + self:_update() + return true + end + end + end + return false +end + +function system.vlan:print() + print(" Vlan:", self._vlan, "Software?", self:is_software()) + local p + for p in self:ports() do + p:print() + end +end + + +system.vlan.port = utl.class() + +function system.vlan.port:__init__(port, vlan) + local num, tag = port:match("^(%d+)([tu]?)") + + self._vlan = vlan + self._port = tonumber(num) + self._tagged = (tag == "t") +end + +function system.vlan.port:number() + return self._port +end + +function system.vlan.port:vlan() + return self._vlan +end + +function system.vlan.port:string() + return "%i%s" %{ self._port, self._tagged ? "t" : "" } +end + +function system.vlan.port:is_tagged() + return self._tagged +end + +function system.vlan.port:print() + print(" Port:", self._port, "Tagged:", self._tagged) +end + + +-- ------------------------------ + +local s = system() + +s:print() + +s:find_network("wan"):print() +s:find_interface("eth0"):parent():print() + +]==] diff --git a/libs/core/luasrc/model/uci.lua b/libs/core/luasrc/model/uci.lua new file mode 100644 index 000000000..66bd0a026 --- /dev/null +++ b/libs/core/luasrc/model/uci.lua @@ -0,0 +1,345 @@ +--[[ +LuCI - UCI model + +Description: +Generalized UCI model + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- +local os = require "os" +local uci = require "uci" +local util = require "luci.util" +local table = require "table" + + +local setmetatable, rawget, rawset = setmetatable, rawget, rawset +local error, pairs, ipairs, tostring = error, pairs, ipairs, tostring +local require, getmetatable, type = require, getmetatable, type + +--- LuCI UCI model library. +-- The typical workflow for UCI is: Get a cursor instance from the +-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), +-- save the changes to the staging area via Cursor.save and finally +-- Cursor.commit the data to the actual config files. +-- LuCI then needs to Cursor.apply the changes so deamons etc. are +-- reloaded. +-- @cstyle instance +module "luci.model.uci" + +--- Create a new UCI-Cursor. +-- @class function +-- @name cursor +-- @return UCI-Cursor +cursor = uci.cursor + +APIVERSION = uci.APIVERSION + +--- Create a new Cursor initialized to the state directory. +-- @return UCI cursor +function cursor_state() + return cursor(nil, "/var/state") +end + + +inst = cursor() +inst_state = cursor_state() + +local Cursor = getmetatable(inst) + +--- Applies UCI configuration changes +-- @param configlist List of UCI configurations +-- @param command Don't apply only return the command +function Cursor.apply(self, configlist, command) + configlist = self:_affected(configlist) + local reloadcmd = "/sbin/luci-reload " .. table.concat(configlist, " ") + + return command and reloadcmd or os.execute(reloadcmd .. " >/dev/null 2>&1") +end + + +--- Delete all sections of a given type that match certain criteria. +-- @param config UCI config +-- @param type UCI section type +-- @param comparator Function that will be called for each section and +-- returns a boolean whether to delete the current section (optional) +function Cursor.delete_all(self, config, stype, comparator) + local del = {} + + if type(comparator) == "table" then + local tbl = comparator + comparator = function(section) + for k, v in pairs(tbl) do + if section[k] ~= v then + return false + end + end + return true + end + end + + local function helper (section) + + if not comparator or comparator(section) then + del[#del+1] = section[".name"] + end + end + + self:foreach(config, stype, helper) + + for i, j in ipairs(del) do + self:delete(config, j) + end +end + +--- Create a new section and initialize it with data. +-- @param config UCI config +-- @param type UCI section type +-- @param name UCI section name (optional) +-- @param values Table of key - value pairs to initialize the section with +-- @return Name of created section +function Cursor.section(self, config, type, name, values) + local stat = true + if name then + stat = self:set(config, name, type) + else + name = self:add(config, type) + stat = name and true + end + + if stat and values then + stat = self:tset(config, name, values) + end + + return stat and name +end + +--- Updated the data of a section using data from a table. +-- @param config UCI config +-- @param section UCI section name (optional) +-- @param values Table of key - value pairs to update the section with +function Cursor.tset(self, config, section, values) + local stat = true + for k, v in pairs(values) do + if k:sub(1, 1) ~= "." then + stat = stat and self:set(config, section, k, v) + end + end + return stat +end + +--- Get a boolean option and return it's value as true or false. +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option +-- @return Boolean +function Cursor.get_bool(self, ...) + local val = self:get(...) + return ( val == "1" or val == "true" or val == "yes" or val == "on" ) +end + +--- Get an option or list and return values as table. +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option +-- @return UCI value +function Cursor.get_list(self, config, section, option) + if config and section and option then + local val = self:get(config, section, option) + return ( type(val) == "table" and val or { val } ) + end + return nil +end + +--- Set given values as list. +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option +-- @param value UCI value +-- @return Boolean whether operation succeeded +function Cursor.set_list(self, config, section, option, value) + if config and section and option then + return self:set( + config, section, option, + ( type(value) == "table" and value or { value } ) + ) + end + return false +end + +-- Return a list of initscripts affected by configuration changes. +function Cursor._affected(self, configlist) + configlist = type(configlist) == "table" and configlist or {configlist} + + local c = cursor() + c:load("ucitrack") + + -- Resolve dependencies + local reloadlist = {} + + local function _resolve_deps(name) + local reload = {name} + local deps = {} + + c:foreach("ucitrack", name, + function(section) + if section.affects then + for i, aff in ipairs(section.affects) do + deps[#deps+1] = aff + end + end + end) + + for i, dep in ipairs(deps) do + for j, add in ipairs(_resolve_deps(dep)) do + reload[#reload+1] = add + end + end + + return reload + end + + -- Collect initscripts + for j, config in ipairs(configlist) do + for i, e in ipairs(_resolve_deps(config)) do + if not util.contains(reloadlist, e) then + reloadlist[#reloadlist+1] = e + end + end + end + + return reloadlist +end + + +--- Add an anonymous section. +-- @class function +-- @name Cursor.add +-- @param config UCI config +-- @param type UCI section type +-- @return Name of created section + +--- Get a table of saved but uncommitted changes. +-- @class function +-- @name Cursor.changes +-- @param config UCI config +-- @return Table of changes +-- @see Cursor.save + +--- Commit saved changes. +-- @class function +-- @name Cursor.commit +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.revert +-- @see Cursor.save + +--- Deletes a section or an option. +-- @class function +-- @name Cursor.delete +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option (optional) +-- @return Boolean whether operation succeeded + +--- Call a function for every section of a certain type. +-- @class function +-- @name Cursor.foreach +-- @param config UCI config +-- @param type UCI section type +-- @param callback Function to be called +-- @return Boolean whether operation succeeded + +--- Get a section type or an option +-- @class function +-- @name Cursor.get +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option (optional) +-- @return UCI value + +--- Get all sections of a config or all values of a section. +-- @class function +-- @name Cursor.get_all +-- @param config UCI config +-- @param section UCI section name (optional) +-- @return Table of UCI sections or table of UCI values + +--- Manually load a config. +-- @class function +-- @name Cursor.load +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.save +-- @see Cursor.unload + +--- Revert saved but uncommitted changes. +-- @class function +-- @name Cursor.revert +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.commit +-- @see Cursor.save + +--- Saves changes made to a config to make them committable. +-- @class function +-- @name Cursor.save +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.load +-- @see Cursor.unload + +--- Set a value or create a named section. +-- @class function +-- @name Cursor.set +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option or UCI section type +-- @param value UCI value or nil if you want to create a section +-- @return Boolean whether operation succeeded + +--- Get the configuration directory. +-- @class function +-- @name Cursor.get_confdir +-- @return Configuration directory + +--- Get the directory for uncomitted changes. +-- @class function +-- @name Cursor.get_savedir +-- @return Save directory + +--- Set the configuration directory. +-- @class function +-- @name Cursor.set_confdir +-- @param directory UCI configuration directory +-- @return Boolean whether operation succeeded + +--- Set the directory for uncommited changes. +-- @class function +-- @name Cursor.set_savedir +-- @param directory UCI changes directory +-- @return Boolean whether operation succeeded + +--- Discard changes made to a config. +-- @class function +-- @name Cursor.unload +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.load +-- @see Cursor.save diff --git a/libs/core/luasrc/model/uci/bind.lua b/libs/core/luasrc/model/uci/bind.lua new file mode 100644 index 000000000..9472dabeb --- /dev/null +++ b/libs/core/luasrc/model/uci/bind.lua @@ -0,0 +1,173 @@ +--[[ +LuCI - UCI utilities for model classes + +Copyright 2009 Jo-Philipp Wich <xm@subsignal.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local assert, pairs, type = assert, pairs, type +local utl = require "luci.util" + +module "luci.model.uci.bind" + +bind = utl.class() + +function bind.__init__(self, config, cursor) + assert(config, "call to bind() without config file") + self.cfg = config + self.uci = cursor +end + +function bind.init(self, cursor) + assert(cursor, "call to init() without uci cursor") + self.uci = cursor +end + +function bind.section(self, stype) + local x = utl.class(bsection) + x.__init__ = function(inst, sid) + assert(self.uci:get(self.cfg, sid) == stype, + "attempt to instantiate bsection(%q) of wrong type, expected %q" + % { sid, stype }) + + inst.bind = self + inst.stype = stype + inst.sid = sid + + if inst._init then + inst:_init(sid) + end + end + return x +end + +function bind.usection(self, stype) + local x = utl.class(bsection) + x.__init__ = function(inst) + inst.bind = self + inst.stype = stype + inst.sid = true + end + return x() +end + +function bind.list(self, list, add, rem) + local lookup = { } + + if type(list) == "string" then + local item + for item in list:gmatch("%S+") do + lookup[item] = true + end + + elseif type(list) == "table" then + local item + for _, item in pairs(list) do + lookup[item] = true + end + end + + if add then lookup[add] = true end + if rem then lookup[rem] = nil end + + return utl.keys(lookup) +end + +function bind.bool(self, v) + return ( v == "1" or v == "true" or v == "yes" or v == "on" ) +end + + +bsection = utl.class() + +function bsection.uciop(self, op, ...) + assert(self.bind and self.bind.uci, + "attempt to use unitialized binding") + + if op then + return self.bind.uci[op](self.bind.uci, self.bind.cfg, ...) + else + return self.bind.uci + end +end + +function bsection.get(self, k, c) + local v + if type(c) == "string" then + v = self:uciop("get", c, k) + else + self:uciop("foreach", self.stype, + function(s) + if type(c) == "table" then + local ck, cv + for ck, cv in pairs(c) do + if s[ck] ~= cv then return true end + end + end + if k ~= nil then + v = s[k] + else + v = s + end + return false + end) + end + return v +end + +function bsection.set(self, k, v, c) + local stat + if type(c) == "string" then + if type(v) == "table" and #v == 0 then + stat = self:uciop("delete", c, k) + else + stat = self:uciop("set", c, k, v) + end + else + self:uciop("foreach", self.stype, + function(s) + if type(c) == "table" then + local ck, cv + for ck, cv in pairs(c) do + if s[ck] ~= cv then return true end + end + end + stat = self:uciop("set", c, k, v) + return false + end) + end + return stat or false +end + +function bsection.property(self, k, n) + self[n or k] = function(c, val) + if val == nil then + return c:get(k, c.sid) + else + return c:set(k, val, c.sid) + end + end +end + +function bsection.property_bool(self, k, n) + self[n or k] = function(c, val) + if val == nil then + return bind:bool(c:get(k, c.sid)) + else + return c:set(k, bind:bool(val) and "1" or "0", c.sid) + end + end +end + diff --git a/libs/core/root/etc/config/ucitrack b/libs/core/root/etc/config/ucitrack new file mode 100644 index 000000000..7b47cc8d5 --- /dev/null +++ b/libs/core/root/etc/config/ucitrack @@ -0,0 +1,48 @@ +config network + option init network + list affects dhcp + +config wireless + list affects network + +config firewall + option init firewall + list affects luci-splash + list affects qos + +config olsr + option init olsrd + +config dhcp + option init dnsmasq + +config dropbear + option init dropbear + +config httpd + option init httpd + +config fstab + option init fstab + +config qos + option init qos + +config system + option init led + list affects luci_statistics + +config luci_splash + option init luci_splash + +config upnpd + option init miniupnpd + +config ntpclient + option init ntpclient + +config samba + option init samba + +config tinyproxy + option init tinyproxy diff --git a/libs/core/root/sbin/luci-reload b/libs/core/root/sbin/luci-reload new file mode 100755 index 000000000..24cf76064 --- /dev/null +++ b/libs/core/root/sbin/luci-reload @@ -0,0 +1,40 @@ +#!/bin/sh +. /etc/functions.sh + +apply_config() { + config_get init "$1" init + config_get exec "$1" exec + config_get test "$1" test + + echo "$2" > "/var/run/luci-reload-status" + + [ -n "$init" ] && reload_init "$2" "$init" "$test" + [ -n "$exec" ] && reload_exec "$2" "$exec" "$test" +} + +reload_exec() { + [ -x $2 ] && { + echo "Reloading $1... " + $2 >/dev/null 2>&1 + [ -n "$3" -a "$?" != "$3" ] && echo '!!! Failed to reload' $1 '!!!' + } +} + +reload_init() { + [ -x /etc/init.d/$2 ] && /etc/init.d/$2 enabled && { + echo "Reloading $1... " + /etc/init.d/$2 reload >/dev/null 2>&1 + [ -n "$3" -a "$?" != "$3" ] && echo '!!! Failed to reload' $1 '!!!' + } +} + +lock "/var/run/luci-reload" + +config_load ucitrack + +for i in $*; do + config_foreach apply_config $i $i +done + +rm -f "/var/run/luci-reload-status" +lock -u "/var/run/luci-reload" |