summaryrefslogtreecommitdiffhomepage
path: root/libs/core
diff options
context:
space:
mode:
authorJo-Philipp Wich <jow@openwrt.org>2010-10-12 05:28:49 +0000
committerJo-Philipp Wich <jow@openwrt.org>2010-10-12 05:28:49 +0000
commit5c2000a1572afd0df74339775dfa8b61e287a53e (patch)
treed6d7648afe87f9c128a497f2590a0399fa9abbb0 /libs/core
parent2472cd5613cf5dfc5760c084c8094bead7ddb507 (diff)
libs: merge libs/uci into libs/core
Diffstat (limited to 'libs/core')
-rw-r--r--libs/core/luasrc/model/network.lua895
-rw-r--r--libs/core/luasrc/model/uci.lua345
-rw-r--r--libs/core/luasrc/model/uci/bind.lua173
-rw-r--r--libs/core/root/etc/config/ucitrack48
-rwxr-xr-xlibs/core/root/sbin/luci-reload40
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"