| 1 | --[[ |
|---|
| 2 | LuCI - System library |
|---|
| 3 | |
|---|
| 4 | Description: |
|---|
| 5 | Utilities for interaction with the Linux system |
|---|
| 6 | |
|---|
| 7 | FileId: |
|---|
| 8 | $Id$ |
|---|
| 9 | |
|---|
| 10 | License: |
|---|
| 11 | Copyright 2008 Steven Barth <steven@midlink.org> |
|---|
| 12 | |
|---|
| 13 | Licensed under the Apache License, Version 2.0 (the "License"); |
|---|
| 14 | you may not use this file except in compliance with the License. |
|---|
| 15 | You may obtain a copy of the License at |
|---|
| 16 | |
|---|
| 17 | http://www.apache.org/licenses/LICENSE-2.0 |
|---|
| 18 | |
|---|
| 19 | Unless required by applicable law or agreed to in writing, software |
|---|
| 20 | distributed under the License is distributed on an "AS IS" BASIS, |
|---|
| 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|---|
| 22 | See the License for the specific language governing permissions and |
|---|
| 23 | limitations under the License. |
|---|
| 24 | |
|---|
| 25 | ]]-- |
|---|
| 26 | |
|---|
| 27 | |
|---|
| 28 | local io = require "io" |
|---|
| 29 | local os = require "os" |
|---|
| 30 | local table = require "table" |
|---|
| 31 | local nixio = require "nixio" |
|---|
| 32 | local fs = require "nixio.fs" |
|---|
| 33 | local uci = require "luci.model.uci" |
|---|
| 34 | |
|---|
| 35 | local luci = {} |
|---|
| 36 | luci.util = require "luci.util" |
|---|
| 37 | luci.ip = require "luci.ip" |
|---|
| 38 | |
|---|
| 39 | local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require = |
|---|
| 40 | tonumber, ipairs, pairs, pcall, type, next, setmetatable, require |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | --- LuCI Linux and POSIX system utilities. |
|---|
| 44 | module "luci.sys" |
|---|
| 45 | |
|---|
| 46 | --- Execute a given shell command and return the error code |
|---|
| 47 | -- @class function |
|---|
| 48 | -- @name call |
|---|
| 49 | -- @param ... Command to call |
|---|
| 50 | -- @return Error code of the command |
|---|
| 51 | function call(...) |
|---|
| 52 | return os.execute(...) / 256 |
|---|
| 53 | end |
|---|
| 54 | |
|---|
| 55 | --- Execute a given shell command and capture its standard output |
|---|
| 56 | -- @class function |
|---|
| 57 | -- @name exec |
|---|
| 58 | -- @param command Command to call |
|---|
| 59 | -- @return String containg the return the output of the command |
|---|
| 60 | exec = luci.util.exec |
|---|
| 61 | |
|---|
| 62 | --- Retrieve information about currently mounted file systems. |
|---|
| 63 | -- @return Table containing mount information |
|---|
| 64 | function mounts() |
|---|
| 65 | local data = {} |
|---|
| 66 | local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"} |
|---|
| 67 | local ps = luci.util.execi("df") |
|---|
| 68 | |
|---|
| 69 | if not ps then |
|---|
| 70 | return |
|---|
| 71 | else |
|---|
| 72 | ps() |
|---|
| 73 | end |
|---|
| 74 | |
|---|
| 75 | for line in ps do |
|---|
| 76 | local row = {} |
|---|
| 77 | |
|---|
| 78 | local j = 1 |
|---|
| 79 | for value in line:gmatch("[^%s]+") do |
|---|
| 80 | row[k[j]] = value |
|---|
| 81 | j = j + 1 |
|---|
| 82 | end |
|---|
| 83 | |
|---|
| 84 | if row[k[1]] then |
|---|
| 85 | |
|---|
| 86 | -- this is a rather ugly workaround to cope with wrapped lines in |
|---|
| 87 | -- the df output: |
|---|
| 88 | -- |
|---|
| 89 | -- /dev/scsi/host0/bus0/target0/lun0/part3 |
|---|
| 90 | -- 114382024 93566472 15005244 86% /mnt/usb |
|---|
| 91 | -- |
|---|
| 92 | |
|---|
| 93 | if not row[k[2]] then |
|---|
| 94 | j = 2 |
|---|
| 95 | line = ps() |
|---|
| 96 | for value in line:gmatch("[^%s]+") do |
|---|
| 97 | row[k[j]] = value |
|---|
| 98 | j = j + 1 |
|---|
| 99 | end |
|---|
| 100 | end |
|---|
| 101 | |
|---|
| 102 | table.insert(data, row) |
|---|
| 103 | end |
|---|
| 104 | end |
|---|
| 105 | |
|---|
| 106 | return data |
|---|
| 107 | end |
|---|
| 108 | |
|---|
| 109 | --- Retrieve environment variables. If no variable is given then a table |
|---|
| 110 | -- containing the whole environment is returned otherwise this function returns |
|---|
| 111 | -- the corresponding string value for the given name or nil if no such variable |
|---|
| 112 | -- exists. |
|---|
| 113 | -- @class function |
|---|
| 114 | -- @name getenv |
|---|
| 115 | -- @param var Name of the environment variable to retrieve (optional) |
|---|
| 116 | -- @return String containg the value of the specified variable |
|---|
| 117 | -- @return Table containing all variables if no variable name is given |
|---|
| 118 | getenv = nixio.getenv |
|---|
| 119 | |
|---|
| 120 | --- Get or set the current hostname. |
|---|
| 121 | -- @param String containing a new hostname to set (optional) |
|---|
| 122 | -- @return String containing the system hostname |
|---|
| 123 | function hostname(newname) |
|---|
| 124 | if type(newname) == "string" and #newname > 0 then |
|---|
| 125 | fs.writefile( "/proc/sys/kernel/hostname", newname ) |
|---|
| 126 | return newname |
|---|
| 127 | else |
|---|
| 128 | return nixio.uname().nodename |
|---|
| 129 | end |
|---|
| 130 | end |
|---|
| 131 | |
|---|
| 132 | --- Returns the contents of a documented referred by an URL. |
|---|
| 133 | -- @param url The URL to retrieve |
|---|
| 134 | -- @param stream Return a stream instead of a buffer |
|---|
| 135 | -- @param target Directly write to target file name |
|---|
| 136 | -- @return String containing the contents of given the URL |
|---|
| 137 | function httpget(url, stream, target) |
|---|
| 138 | if not target then |
|---|
| 139 | local source = stream and io.popen or luci.util.exec |
|---|
| 140 | return source("wget -qO- '"..url:gsub("'", "").."'") |
|---|
| 141 | else |
|---|
| 142 | return os.execute("wget -qO '%s' '%s'" % |
|---|
| 143 | {target:gsub("'", ""), url:gsub("'", "")}) |
|---|
| 144 | end |
|---|
| 145 | end |
|---|
| 146 | |
|---|
| 147 | --- Returns the system load average values. |
|---|
| 148 | -- @return String containing the average load value 1 minute ago |
|---|
| 149 | -- @return String containing the average load value 5 minutes ago |
|---|
| 150 | -- @return String containing the average load value 15 minutes ago |
|---|
| 151 | function loadavg() |
|---|
| 152 | local info = nixio.sysinfo() |
|---|
| 153 | return info.loads[1], info.loads[2], info.loads[3] |
|---|
| 154 | end |
|---|
| 155 | |
|---|
| 156 | --- Initiate a system reboot. |
|---|
| 157 | -- @return Return value of os.execute() |
|---|
| 158 | function reboot() |
|---|
| 159 | return os.execute("reboot >/dev/null 2>&1") |
|---|
| 160 | end |
|---|
| 161 | |
|---|
| 162 | --- Returns the system type, cpu name and installed physical memory. |
|---|
| 163 | -- @return String containing the system or platform identifier |
|---|
| 164 | -- @return String containing hardware model information |
|---|
| 165 | -- @return String containing the total memory amount in kB |
|---|
| 166 | -- @return String containing the memory used for caching in kB |
|---|
| 167 | -- @return String containing the memory used for buffering in kB |
|---|
| 168 | -- @return String containing the free memory amount in kB |
|---|
| 169 | -- @return String containing the cpu bogomips (number) |
|---|
| 170 | function sysinfo() |
|---|
| 171 | local cpuinfo = fs.readfile("/proc/cpuinfo") |
|---|
| 172 | local meminfo = fs.readfile("/proc/meminfo") |
|---|
| 173 | |
|---|
| 174 | local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)")) |
|---|
| 175 | local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)")) |
|---|
| 176 | local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)")) |
|---|
| 177 | local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)")) |
|---|
| 178 | local bogomips = tonumber(cpuinfo:match("[Bb]ogo[Mm][Ii][Pp][Ss].-: ([^\n]+)")) or 0 |
|---|
| 179 | |
|---|
| 180 | local system = |
|---|
| 181 | cpuinfo:match("system type\t+: ([^\n]+)") or |
|---|
| 182 | cpuinfo:match("Processor\t+: ([^\n]+)") or |
|---|
| 183 | cpuinfo:match("model name\t+: ([^\n]+)") |
|---|
| 184 | |
|---|
| 185 | local model = |
|---|
| 186 | luci.util.pcdata(fs.readfile("/tmp/sysinfo/model")) or |
|---|
| 187 | cpuinfo:match("machine\t+: ([^\n]+)") or |
|---|
| 188 | cpuinfo:match("Hardware\t+: ([^\n]+)") or |
|---|
| 189 | luci.util.pcdata(fs.readfile("/proc/diag/model")) or |
|---|
| 190 | nixio.uname().machine or |
|---|
| 191 | system |
|---|
| 192 | |
|---|
| 193 | return system, model, memtotal, memcached, membuffers, memfree, bogomips |
|---|
| 194 | end |
|---|
| 195 | |
|---|
| 196 | --- Retrieves the output of the "logread" command. |
|---|
| 197 | -- @return String containing the current log buffer |
|---|
| 198 | function syslog() |
|---|
| 199 | return luci.util.exec("logread") |
|---|
| 200 | end |
|---|
| 201 | |
|---|
| 202 | --- Retrieves the output of the "dmesg" command. |
|---|
| 203 | -- @return String containing the current log buffer |
|---|
| 204 | function dmesg() |
|---|
| 205 | return luci.util.exec("dmesg") |
|---|
| 206 | end |
|---|
| 207 | |
|---|
| 208 | --- Generates a random id with specified length. |
|---|
| 209 | -- @param bytes Number of bytes for the unique id |
|---|
| 210 | -- @return String containing hex encoded id |
|---|
| 211 | function uniqueid(bytes) |
|---|
| 212 | local rand = fs.readfile("/dev/urandom", bytes) |
|---|
| 213 | return rand and nixio.bin.hexlify(rand) |
|---|
| 214 | end |
|---|
| 215 | |
|---|
| 216 | --- Returns the current system uptime stats. |
|---|
| 217 | -- @return String containing total uptime in seconds |
|---|
| 218 | function uptime() |
|---|
| 219 | return nixio.sysinfo().uptime |
|---|
| 220 | end |
|---|
| 221 | |
|---|
| 222 | |
|---|
| 223 | --- LuCI system utilities / network related functions. |
|---|
| 224 | -- @class module |
|---|
| 225 | -- @name luci.sys.net |
|---|
| 226 | net = {} |
|---|
| 227 | |
|---|
| 228 | --- Returns the current arp-table entries as two-dimensional table. |
|---|
| 229 | -- @return Table of table containing the current arp entries. |
|---|
| 230 | -- The following fields are defined for arp entry objects: |
|---|
| 231 | -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" } |
|---|
| 232 | function net.arptable(callback) |
|---|
| 233 | return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback) |
|---|
| 234 | end |
|---|
| 235 | |
|---|
| 236 | --- Returns conntrack information |
|---|
| 237 | -- @return Table with the currently tracked IP connections |
|---|
| 238 | function net.conntrack(callback) |
|---|
| 239 | local connt = {} |
|---|
| 240 | if fs.access("/proc/net/nf_conntrack", "r") then |
|---|
| 241 | for line in io.lines("/proc/net/nf_conntrack") do |
|---|
| 242 | line = line:match "^(.-( [^ =]+=).-)%2" |
|---|
| 243 | local entry, flags = _parse_mixed_record(line, " +") |
|---|
| 244 | if flags[6] ~= "TIME_WAIT" then |
|---|
| 245 | entry.layer3 = flags[1] |
|---|
| 246 | entry.layer4 = flags[3] |
|---|
| 247 | for i=1, #entry do |
|---|
| 248 | entry[i] = nil |
|---|
| 249 | end |
|---|
| 250 | |
|---|
| 251 | if callback then |
|---|
| 252 | callback(entry) |
|---|
| 253 | else |
|---|
| 254 | connt[#connt+1] = entry |
|---|
| 255 | end |
|---|
| 256 | end |
|---|
| 257 | end |
|---|
| 258 | elseif fs.access("/proc/net/ip_conntrack", "r") then |
|---|
| 259 | for line in io.lines("/proc/net/ip_conntrack") do |
|---|
| 260 | line = line:match "^(.-( [^ =]+=).-)%2" |
|---|
| 261 | local entry, flags = _parse_mixed_record(line, " +") |
|---|
| 262 | if flags[4] ~= "TIME_WAIT" then |
|---|
| 263 | entry.layer3 = "ipv4" |
|---|
| 264 | entry.layer4 = flags[1] |
|---|
| 265 | for i=1, #entry do |
|---|
| 266 | entry[i] = nil |
|---|
| 267 | end |
|---|
| 268 | |
|---|
| 269 | if callback then |
|---|
| 270 | callback(entry) |
|---|
| 271 | else |
|---|
| 272 | connt[#connt+1] = entry |
|---|
| 273 | end |
|---|
| 274 | end |
|---|
| 275 | end |
|---|
| 276 | else |
|---|
| 277 | return nil |
|---|
| 278 | end |
|---|
| 279 | return connt |
|---|
| 280 | end |
|---|
| 281 | |
|---|
| 282 | --- Determine the current IPv4 default route. If multiple default routes exist, |
|---|
| 283 | -- return the one with the lowest metric. |
|---|
| 284 | -- @return Table with the properties of the current default route. |
|---|
| 285 | -- The following fields are defined: |
|---|
| 286 | -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt", |
|---|
| 287 | -- "flags", "device" } |
|---|
| 288 | function net.defaultroute() |
|---|
| 289 | local route |
|---|
| 290 | |
|---|
| 291 | net.routes(function(rt) |
|---|
| 292 | if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then |
|---|
| 293 | route = rt |
|---|
| 294 | end |
|---|
| 295 | end) |
|---|
| 296 | |
|---|
| 297 | return route |
|---|
| 298 | end |
|---|
| 299 | |
|---|
| 300 | --- Determine the current IPv6 default route. If multiple default routes exist, |
|---|
| 301 | -- return the one with the lowest metric. |
|---|
| 302 | -- @return Table with the properties of the current default route. |
|---|
| 303 | -- The following fields are defined: |
|---|
| 304 | -- { "source", "dest", "nexthop", "metric", "refcount", "usecount", |
|---|
| 305 | -- "flags", "device" } |
|---|
| 306 | function net.defaultroute6() |
|---|
| 307 | local route |
|---|
| 308 | |
|---|
| 309 | net.routes6(function(rt) |
|---|
| 310 | if rt.dest:prefix() == 0 and rt.device ~= "lo" and |
|---|
| 311 | (not route or route.metric > rt.metric) |
|---|
| 312 | then |
|---|
| 313 | route = rt |
|---|
| 314 | end |
|---|
| 315 | end) |
|---|
| 316 | |
|---|
| 317 | if not route then |
|---|
| 318 | local global_unicast = luci.ip.IPv6("2000::/3") |
|---|
| 319 | net.routes6(function(rt) |
|---|
| 320 | if rt.dest:equal(global_unicast) and |
|---|
| 321 | (not route or route.metric > rt.metric) |
|---|
| 322 | then |
|---|
| 323 | route = rt |
|---|
| 324 | end |
|---|
| 325 | end) |
|---|
| 326 | end |
|---|
| 327 | |
|---|
| 328 | return route |
|---|
| 329 | end |
|---|
| 330 | |
|---|
| 331 | --- Determine the names of available network interfaces. |
|---|
| 332 | -- @return Table containing all current interface names |
|---|
| 333 | function net.devices() |
|---|
| 334 | local devs = {} |
|---|
| 335 | for k, v in ipairs(nixio.getifaddrs()) do |
|---|
| 336 | if v.family == "packet" then |
|---|
| 337 | devs[#devs+1] = v.name |
|---|
| 338 | end |
|---|
| 339 | end |
|---|
| 340 | return devs |
|---|
| 341 | end |
|---|
| 342 | |
|---|
| 343 | |
|---|
| 344 | --- Return information about available network interfaces. |
|---|
| 345 | -- @return Table containing all current interface names and their information |
|---|
| 346 | function net.deviceinfo() |
|---|
| 347 | local devs = {} |
|---|
| 348 | for k, v in ipairs(nixio.getifaddrs()) do |
|---|
| 349 | if v.family == "packet" then |
|---|
| 350 | local d = v.data |
|---|
| 351 | d[1] = d.rx_bytes |
|---|
| 352 | d[2] = d.rx_packets |
|---|
| 353 | d[3] = d.rx_errors |
|---|
| 354 | d[4] = d.rx_dropped |
|---|
| 355 | d[5] = 0 |
|---|
| 356 | d[6] = 0 |
|---|
| 357 | d[7] = 0 |
|---|
| 358 | d[8] = d.multicast |
|---|
| 359 | d[9] = d.tx_bytes |
|---|
| 360 | d[10] = d.tx_packets |
|---|
| 361 | d[11] = d.tx_errors |
|---|
| 362 | d[12] = d.tx_dropped |
|---|
| 363 | d[13] = 0 |
|---|
| 364 | d[14] = d.collisions |
|---|
| 365 | d[15] = 0 |
|---|
| 366 | d[16] = 0 |
|---|
| 367 | devs[v.name] = d |
|---|
| 368 | end |
|---|
| 369 | end |
|---|
| 370 | return devs |
|---|
| 371 | end |
|---|
| 372 | |
|---|
| 373 | |
|---|
| 374 | -- Determine the MAC address belonging to the given IP address. |
|---|
| 375 | -- @param ip IPv4 address |
|---|
| 376 | -- @return String containing the MAC address or nil if it cannot be found |
|---|
| 377 | function net.ip4mac(ip) |
|---|
| 378 | local mac = nil |
|---|
| 379 | net.arptable(function(e) |
|---|
| 380 | if e["IP address"] == ip then |
|---|
| 381 | mac = e["HW address"] |
|---|
| 382 | end |
|---|
| 383 | end) |
|---|
| 384 | return mac |
|---|
| 385 | end |
|---|
| 386 | |
|---|
| 387 | --- Returns the current kernel routing table entries. |
|---|
| 388 | -- @return Table of tables with properties of the corresponding routes. |
|---|
| 389 | -- The following fields are defined for route entry tables: |
|---|
| 390 | -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt", |
|---|
| 391 | -- "flags", "device" } |
|---|
| 392 | function net.routes(callback) |
|---|
| 393 | local routes = { } |
|---|
| 394 | |
|---|
| 395 | for line in io.lines("/proc/net/route") do |
|---|
| 396 | |
|---|
| 397 | local dev, dst_ip, gateway, flags, refcnt, usecnt, metric, |
|---|
| 398 | dst_mask, mtu, win, irtt = line:match( |
|---|
| 399 | "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" .. |
|---|
| 400 | "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)" |
|---|
| 401 | ) |
|---|
| 402 | |
|---|
| 403 | if dev then |
|---|
| 404 | gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 ) |
|---|
| 405 | dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 ) |
|---|
| 406 | dst_ip = luci.ip.Hex( |
|---|
| 407 | dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4 |
|---|
| 408 | ) |
|---|
| 409 | |
|---|
| 410 | local rt = { |
|---|
| 411 | dest = dst_ip, |
|---|
| 412 | gateway = gateway, |
|---|
| 413 | metric = tonumber(metric), |
|---|
| 414 | refcount = tonumber(refcnt), |
|---|
| 415 | usecount = tonumber(usecnt), |
|---|
| 416 | mtu = tonumber(mtu), |
|---|
| 417 | window = tonumber(window), |
|---|
| 418 | irtt = tonumber(irtt), |
|---|
| 419 | flags = tonumber(flags, 16), |
|---|
| 420 | device = dev |
|---|
| 421 | } |
|---|
| 422 | |
|---|
| 423 | if callback then |
|---|
| 424 | callback(rt) |
|---|
| 425 | else |
|---|
| 426 | routes[#routes+1] = rt |
|---|
| 427 | end |
|---|
| 428 | end |
|---|
| 429 | end |
|---|
| 430 | |
|---|
| 431 | return routes |
|---|
| 432 | end |
|---|
| 433 | |
|---|
| 434 | --- Returns the current ipv6 kernel routing table entries. |
|---|
| 435 | -- @return Table of tables with properties of the corresponding routes. |
|---|
| 436 | -- The following fields are defined for route entry tables: |
|---|
| 437 | -- { "source", "dest", "nexthop", "metric", "refcount", "usecount", |
|---|
| 438 | -- "flags", "device" } |
|---|
| 439 | function net.routes6(callback) |
|---|
| 440 | if fs.access("/proc/net/ipv6_route", "r") then |
|---|
| 441 | local routes = { } |
|---|
| 442 | |
|---|
| 443 | for line in io.lines("/proc/net/ipv6_route") do |
|---|
| 444 | |
|---|
| 445 | local dst_ip, dst_prefix, src_ip, src_prefix, nexthop, |
|---|
| 446 | metric, refcnt, usecnt, flags, dev = line:match( |
|---|
| 447 | "([a-f0-9]+) ([a-f0-9]+) " .. |
|---|
| 448 | "([a-f0-9]+) ([a-f0-9]+) " .. |
|---|
| 449 | "([a-f0-9]+) ([a-f0-9]+) " .. |
|---|
| 450 | "([a-f0-9]+) ([a-f0-9]+) " .. |
|---|
| 451 | "([a-f0-9]+) +([^%s]+)" |
|---|
| 452 | ) |
|---|
| 453 | |
|---|
| 454 | src_ip = luci.ip.Hex( |
|---|
| 455 | src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false |
|---|
| 456 | ) |
|---|
| 457 | |
|---|
| 458 | dst_ip = luci.ip.Hex( |
|---|
| 459 | dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false |
|---|
| 460 | ) |
|---|
| 461 | |
|---|
| 462 | nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false ) |
|---|
| 463 | |
|---|
| 464 | local rt = { |
|---|
| 465 | source = src_ip, |
|---|
| 466 | dest = dst_ip, |
|---|
| 467 | nexthop = nexthop, |
|---|
| 468 | metric = tonumber(metric, 16), |
|---|
| 469 | refcount = tonumber(refcnt, 16), |
|---|
| 470 | usecount = tonumber(usecnt, 16), |
|---|
| 471 | flags = tonumber(flags, 16), |
|---|
| 472 | device = dev, |
|---|
| 473 | |
|---|
| 474 | -- lua number is too small for storing the metric |
|---|
| 475 | -- add a metric_raw field with the original content |
|---|
| 476 | metric_raw = metric |
|---|
| 477 | } |
|---|
| 478 | |
|---|
| 479 | if callback then |
|---|
| 480 | callback(rt) |
|---|
| 481 | else |
|---|
| 482 | routes[#routes+1] = rt |
|---|
| 483 | end |
|---|
| 484 | end |
|---|
| 485 | |
|---|
| 486 | return routes |
|---|
| 487 | end |
|---|
| 488 | end |
|---|
| 489 | |
|---|
| 490 | --- Tests whether the given host responds to ping probes. |
|---|
| 491 | -- @param host String containing a hostname or IPv4 address |
|---|
| 492 | -- @return Number containing 0 on success and >= 1 on error |
|---|
| 493 | function net.pingtest(host) |
|---|
| 494 | return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1") |
|---|
| 495 | end |
|---|
| 496 | |
|---|
| 497 | |
|---|
| 498 | --- LuCI system utilities / process related functions. |
|---|
| 499 | -- @class module |
|---|
| 500 | -- @name luci.sys.process |
|---|
| 501 | process = {} |
|---|
| 502 | |
|---|
| 503 | --- Get the current process id. |
|---|
| 504 | -- @class function |
|---|
| 505 | -- @name process.info |
|---|
| 506 | -- @return Number containing the current pid |
|---|
| 507 | function process.info(key) |
|---|
| 508 | local s = {uid = nixio.getuid(), gid = nixio.getgid()} |
|---|
| 509 | return not key and s or s[key] |
|---|
| 510 | end |
|---|
| 511 | |
|---|
| 512 | --- Retrieve information about currently running processes. |
|---|
| 513 | -- @return Table containing process information |
|---|
| 514 | function process.list() |
|---|
| 515 | local data = {} |
|---|
| 516 | local k |
|---|
| 517 | local ps = luci.util.execi("top -bn1") |
|---|
| 518 | |
|---|
| 519 | if not ps then |
|---|
| 520 | return |
|---|
| 521 | end |
|---|
| 522 | |
|---|
| 523 | while true do |
|---|
| 524 | local line = ps() |
|---|
| 525 | if not line then |
|---|
| 526 | return |
|---|
| 527 | end |
|---|
| 528 | |
|---|
| 529 | k = luci.util.split(luci.util.trim(line), "%s+", nil, true) |
|---|
| 530 | if k[6] == "%VSZ" then |
|---|
| 531 | k[6] = "%MEM" |
|---|
| 532 | end |
|---|
| 533 | if k[1] == "PID" then |
|---|
| 534 | break |
|---|
| 535 | end |
|---|
| 536 | end |
|---|
| 537 | |
|---|
| 538 | for line in ps do |
|---|
| 539 | local row = {} |
|---|
| 540 | |
|---|
| 541 | line = luci.util.trim(line) |
|---|
| 542 | for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do |
|---|
| 543 | row[k[i]] = value |
|---|
| 544 | end |
|---|
| 545 | |
|---|
| 546 | local pid = tonumber(row[k[1]]) |
|---|
| 547 | if pid then |
|---|
| 548 | data[pid] = row |
|---|
| 549 | end |
|---|
| 550 | end |
|---|
| 551 | |
|---|
| 552 | return data |
|---|
| 553 | end |
|---|
| 554 | |
|---|
| 555 | --- Set the gid of a process identified by given pid. |
|---|
| 556 | -- @param gid Number containing the Unix group id |
|---|
| 557 | -- @return Boolean indicating successful operation |
|---|
| 558 | -- @return String containing the error message if failed |
|---|
| 559 | -- @return Number containing the error code if failed |
|---|
| 560 | function process.setgroup(gid) |
|---|
| 561 | return nixio.setgid(gid) |
|---|
| 562 | end |
|---|
| 563 | |
|---|
| 564 | --- Set the uid of a process identified by given pid. |
|---|
| 565 | -- @param uid Number containing the Unix user id |
|---|
| 566 | -- @return Boolean indicating successful operation |
|---|
| 567 | -- @return String containing the error message if failed |
|---|
| 568 | -- @return Number containing the error code if failed |
|---|
| 569 | function process.setuser(uid) |
|---|
| 570 | return nixio.setuid(uid) |
|---|
| 571 | end |
|---|
| 572 | |
|---|
| 573 | --- Send a signal to a process identified by given pid. |
|---|
| 574 | -- @class function |
|---|
| 575 | -- @name process.signal |
|---|
| 576 | -- @param pid Number containing the process id |
|---|
| 577 | -- @param sig Signal to send (default: 15 [SIGTERM]) |
|---|
| 578 | -- @return Boolean indicating successful operation |
|---|
| 579 | -- @return Number containing the error code if failed |
|---|
| 580 | process.signal = nixio.kill |
|---|
| 581 | |
|---|
| 582 | |
|---|
| 583 | --- LuCI system utilities / user related functions. |
|---|
| 584 | -- @class module |
|---|
| 585 | -- @name luci.sys.user |
|---|
| 586 | user = {} |
|---|
| 587 | |
|---|
| 588 | --- Retrieve user informations for given uid. |
|---|
| 589 | -- @class function |
|---|
| 590 | -- @name getuser |
|---|
| 591 | -- @param uid Number containing the Unix user id |
|---|
| 592 | -- @return Table containing the following fields: |
|---|
| 593 | -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } |
|---|
| 594 | user.getuser = nixio.getpw |
|---|
| 595 | |
|---|
| 596 | --- Retrieve the current user password hash. |
|---|
| 597 | -- @param username String containing the username to retrieve the password for |
|---|
| 598 | -- @return String containing the hash or nil if no password is set. |
|---|
| 599 | function user.getpasswd(username) |
|---|
| 600 | local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username) |
|---|
| 601 | local pwh = pwe and (pwe.pwdp or pwe.passwd) |
|---|
| 602 | if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then |
|---|
| 603 | return nil |
|---|
| 604 | else |
|---|
| 605 | return pwh |
|---|
| 606 | end |
|---|
| 607 | end |
|---|
| 608 | |
|---|
| 609 | --- Test whether given string matches the password of a given system user. |
|---|
| 610 | -- @param username String containing the Unix user name |
|---|
| 611 | -- @param pass String containing the password to compare |
|---|
| 612 | -- @return Boolean indicating wheather the passwords are equal |
|---|
| 613 | function user.checkpasswd(username, pass) |
|---|
| 614 | local pwh = user.getpasswd(username) |
|---|
| 615 | if pwh and nixio.crypt(pass, pwh) ~= pwh then |
|---|
| 616 | return false |
|---|
| 617 | else |
|---|
| 618 | return true |
|---|
| 619 | end |
|---|
| 620 | end |
|---|
| 621 | |
|---|
| 622 | --- Change the password of given user. |
|---|
| 623 | -- @param username String containing the Unix user name |
|---|
| 624 | -- @param password String containing the password to compare |
|---|
| 625 | -- @return Number containing 0 on success and >= 1 on error |
|---|
| 626 | function user.setpasswd(username, password) |
|---|
| 627 | if password then |
|---|
| 628 | password = password:gsub("'", [['"'"']]) |
|---|
| 629 | end |
|---|
| 630 | |
|---|
| 631 | if username then |
|---|
| 632 | username = username:gsub("'", [['"'"']]) |
|---|
| 633 | end |
|---|
| 634 | |
|---|
| 635 | return os.execute( |
|---|
| 636 | "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " .. |
|---|
| 637 | "passwd '" .. username .. "' >/dev/null 2>&1" |
|---|
| 638 | ) |
|---|
| 639 | end |
|---|
| 640 | |
|---|
| 641 | |
|---|
| 642 | --- LuCI system utilities / wifi related functions. |
|---|
| 643 | -- @class module |
|---|
| 644 | -- @name luci.sys.wifi |
|---|
| 645 | wifi = {} |
|---|
| 646 | |
|---|
| 647 | --- Get wireless information for given interface. |
|---|
| 648 | -- @param ifname String containing the interface name |
|---|
| 649 | -- @return A wrapped iwinfo object instance |
|---|
| 650 | function wifi.getiwinfo(ifname) |
|---|
| 651 | local stat, iwinfo = pcall(require, "iwinfo") |
|---|
| 652 | |
|---|
| 653 | if ifname then |
|---|
| 654 | local c = 0 |
|---|
| 655 | local u = uci.cursor_state() |
|---|
| 656 | local d, n = ifname:match("^(%w+)%.network(%d+)") |
|---|
| 657 | if d and n then |
|---|
| 658 | n = tonumber(n) |
|---|
| 659 | u:foreach("wireless", "wifi-iface", |
|---|
| 660 | function(s) |
|---|
| 661 | if s.device == d then |
|---|
| 662 | c = c + 1 |
|---|
| 663 | if c == n then |
|---|
| 664 | ifname = s.ifname or s.device |
|---|
| 665 | return false |
|---|
| 666 | end |
|---|
| 667 | end |
|---|
| 668 | end) |
|---|
| 669 | elseif u:get("wireless", ifname) == "wifi-device" then |
|---|
| 670 | u:foreach("wireless", "wifi-iface", |
|---|
| 671 | function(s) |
|---|
| 672 | if s.device == ifname and s.ifname then |
|---|
| 673 | ifname = s.ifname |
|---|
| 674 | return false |
|---|
| 675 | end |
|---|
| 676 | end) |
|---|
| 677 | end |
|---|
| 678 | |
|---|
| 679 | local t = stat and iwinfo.type(ifname) |
|---|
| 680 | local x = t and iwinfo[t] or { } |
|---|
| 681 | return setmetatable({}, { |
|---|
| 682 | __index = function(t, k) |
|---|
| 683 | if k == "ifname" then |
|---|
| 684 | return ifname |
|---|
| 685 | elseif x[k] then |
|---|
| 686 | return x[k](ifname) |
|---|
| 687 | end |
|---|
| 688 | end |
|---|
| 689 | }) |
|---|
| 690 | end |
|---|
| 691 | end |
|---|
| 692 | |
|---|
| 693 | --- Get iwconfig output for all wireless devices. |
|---|
| 694 | -- @return Table of tables containing the iwconfing output for each wifi device |
|---|
| 695 | function wifi.getiwconfig() |
|---|
| 696 | local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null") |
|---|
| 697 | local iwc = {} |
|---|
| 698 | |
|---|
| 699 | for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do |
|---|
| 700 | local k = l:match("^(.-) ") |
|---|
| 701 | l = l:gsub("^(.-) +", "", 1) |
|---|
| 702 | if k then |
|---|
| 703 | local entry, flags = _parse_mixed_record(l) |
|---|
| 704 | if entry then |
|---|
| 705 | entry.flags = flags |
|---|
| 706 | end |
|---|
| 707 | iwc[k] = entry |
|---|
| 708 | end |
|---|
| 709 | end |
|---|
| 710 | |
|---|
| 711 | return iwc |
|---|
| 712 | end |
|---|
| 713 | |
|---|
| 714 | --- Get iwlist scan output from all wireless devices. |
|---|
| 715 | -- @return Table of tables contaiing all scan results |
|---|
| 716 | function wifi.iwscan(iface) |
|---|
| 717 | local siface = iface or "" |
|---|
| 718 | local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null") |
|---|
| 719 | local iws = {} |
|---|
| 720 | |
|---|
| 721 | for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do |
|---|
| 722 | local k = l:match("^(.-) ") |
|---|
| 723 | l = l:gsub("^[^\n]+", "", 1) |
|---|
| 724 | l = luci.util.trim(l) |
|---|
| 725 | if k then |
|---|
| 726 | iws[k] = {} |
|---|
| 727 | for j, c in pairs(luci.util.split(l, "\n Cell")) do |
|---|
| 728 | c = c:gsub("^(.-)- ", "", 1) |
|---|
| 729 | c = luci.util.split(c, "\n", 7) |
|---|
| 730 | c = table.concat(c, "\n", 1) |
|---|
| 731 | local entry, flags = _parse_mixed_record(c) |
|---|
| 732 | if entry then |
|---|
| 733 | entry.flags = flags |
|---|
| 734 | end |
|---|
| 735 | table.insert(iws[k], entry) |
|---|
| 736 | end |
|---|
| 737 | end |
|---|
| 738 | end |
|---|
| 739 | |
|---|
| 740 | return iface and (iws[iface] or {}) or iws |
|---|
| 741 | end |
|---|
| 742 | |
|---|
| 743 | --- Get available channels from given wireless iface. |
|---|
| 744 | -- @param iface Wireless interface (optional) |
|---|
| 745 | -- @return Table of available channels |
|---|
| 746 | function wifi.channels(iface) |
|---|
| 747 | local stat, iwinfo = pcall(require, "iwinfo") |
|---|
| 748 | local cns |
|---|
| 749 | |
|---|
| 750 | if stat then |
|---|
| 751 | local t = iwinfo.type(iface or "") |
|---|
| 752 | if iface and t and iwinfo[t] then |
|---|
| 753 | cns = iwinfo[t].freqlist(iface) |
|---|
| 754 | end |
|---|
| 755 | end |
|---|
| 756 | |
|---|
| 757 | if not cns or #cns == 0 then |
|---|
| 758 | cns = { |
|---|
| 759 | {channel = 1, mhz = 2412}, |
|---|
| 760 | {channel = 2, mhz = 2417}, |
|---|
| 761 | {channel = 3, mhz = 2422}, |
|---|
| 762 | {channel = 4, mhz = 2427}, |
|---|
| 763 | {channel = 5, mhz = 2432}, |
|---|
| 764 | {channel = 6, mhz = 2437}, |
|---|
| 765 | {channel = 7, mhz = 2442}, |
|---|
| 766 | {channel = 8, mhz = 2447}, |
|---|
| 767 | {channel = 9, mhz = 2452}, |
|---|
| 768 | {channel = 10, mhz = 2457}, |
|---|
| 769 | {channel = 11, mhz = 2462} |
|---|
| 770 | } |
|---|
| 771 | end |
|---|
| 772 | |
|---|
| 773 | return cns |
|---|
| 774 | end |
|---|
| 775 | |
|---|
| 776 | |
|---|
| 777 | --- LuCI system utilities / init related functions. |
|---|
| 778 | -- @class module |
|---|
| 779 | -- @name luci.sys.init |
|---|
| 780 | init = {} |
|---|
| 781 | init.dir = "/etc/init.d/" |
|---|
| 782 | |
|---|
| 783 | --- Get the names of all installed init scripts |
|---|
| 784 | -- @return Table containing the names of all inistalled init scripts |
|---|
| 785 | function init.names() |
|---|
| 786 | local names = { } |
|---|
| 787 | for name in fs.glob(init.dir.."*") do |
|---|
| 788 | names[#names+1] = fs.basename(name) |
|---|
| 789 | end |
|---|
| 790 | return names |
|---|
| 791 | end |
|---|
| 792 | |
|---|
| 793 | --- Get the index of he given init script |
|---|
| 794 | -- @param name Name of the init script |
|---|
| 795 | -- @return Numeric index value |
|---|
| 796 | function init.index(name) |
|---|
| 797 | if fs.access(init.dir..name) then |
|---|
| 798 | return call("env -i sh -c 'source %s%s; exit $START' >/dev/null" |
|---|
| 799 | %{ init.dir, name }) |
|---|
| 800 | end |
|---|
| 801 | end |
|---|
| 802 | |
|---|
| 803 | local function init_action(action, name) |
|---|
| 804 | if fs.access(init.dir..name) then |
|---|
| 805 | return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action }) |
|---|
| 806 | end |
|---|
| 807 | end |
|---|
| 808 | |
|---|
| 809 | --- Test whether the given init script is enabled |
|---|
| 810 | -- @param name Name of the init script |
|---|
| 811 | -- @return Boolean indicating whether init is enabled |
|---|
| 812 | function init.enabled(name) |
|---|
| 813 | return (init_action("enabled", name) == 0) |
|---|
| 814 | end |
|---|
| 815 | |
|---|
| 816 | --- Enable the given init script |
|---|
| 817 | -- @param name Name of the init script |
|---|
| 818 | -- @return Boolean indicating success |
|---|
| 819 | function init.enable(name) |
|---|
| 820 | return (init_action("enable", name) == 1) |
|---|
| 821 | end |
|---|
| 822 | |
|---|
| 823 | --- Disable the given init script |
|---|
| 824 | -- @param name Name of the init script |
|---|
| 825 | -- @return Boolean indicating success |
|---|
| 826 | function init.disable(name) |
|---|
| 827 | return (init_action("disable", name) == 0) |
|---|
| 828 | end |
|---|
| 829 | |
|---|
| 830 | --- Start the given init script |
|---|
| 831 | -- @param name Name of the init script |
|---|
| 832 | -- @return Boolean indicating success |
|---|
| 833 | function init.start(name) |
|---|
| 834 | return (init_action("start", name) == 0) |
|---|
| 835 | end |
|---|
| 836 | |
|---|
| 837 | --- Stop the given init script |
|---|
| 838 | -- @param name Name of the init script |
|---|
| 839 | -- @return Boolean indicating success |
|---|
| 840 | function init.stop(name) |
|---|
| 841 | return (init_action("stop", name) == 0) |
|---|
| 842 | end |
|---|
| 843 | |
|---|
| 844 | |
|---|
| 845 | -- Internal functions |
|---|
| 846 | |
|---|
| 847 | function _parse_delimited_table(iter, delimiter, callback) |
|---|
| 848 | delimiter = delimiter or "%s+" |
|---|
| 849 | |
|---|
| 850 | local data = {} |
|---|
| 851 | local trim = luci.util.trim |
|---|
| 852 | local split = luci.util.split |
|---|
| 853 | |
|---|
| 854 | local keys = split(trim(iter()), delimiter, nil, true) |
|---|
| 855 | for i, j in pairs(keys) do |
|---|
| 856 | keys[i] = trim(keys[i]) |
|---|
| 857 | end |
|---|
| 858 | |
|---|
| 859 | for line in iter do |
|---|
| 860 | local row = {} |
|---|
| 861 | line = trim(line) |
|---|
| 862 | if #line > 0 then |
|---|
| 863 | for i, j in pairs(split(line, delimiter, nil, true)) do |
|---|
| 864 | if keys[i] then |
|---|
| 865 | row[keys[i]] = j |
|---|
| 866 | end |
|---|
| 867 | end |
|---|
| 868 | end |
|---|
| 869 | |
|---|
| 870 | if callback then |
|---|
| 871 | callback(row) |
|---|
| 872 | else |
|---|
| 873 | data[#data+1] = row |
|---|
| 874 | end |
|---|
| 875 | end |
|---|
| 876 | |
|---|
| 877 | return data |
|---|
| 878 | end |
|---|
| 879 | |
|---|
| 880 | function _parse_mixed_record(cnt, delimiter) |
|---|
| 881 | delimiter = delimiter or " " |
|---|
| 882 | local data = {} |
|---|
| 883 | local flags = {} |
|---|
| 884 | |
|---|
| 885 | for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do |
|---|
| 886 | for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do |
|---|
| 887 | local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*') |
|---|
| 888 | |
|---|
| 889 | if k then |
|---|
| 890 | if x == "" then |
|---|
| 891 | table.insert(flags, k) |
|---|
| 892 | else |
|---|
| 893 | data[k] = v |
|---|
| 894 | end |
|---|
| 895 | end |
|---|
| 896 | end |
|---|
| 897 | end |
|---|
| 898 | |
|---|
| 899 | return data, flags |
|---|
| 900 | end |
|---|