libs/sys: introduce net.mac_hints(), net.ipv4_hints() and net.ipv6_hints() functions...
[project/luci.git] / libs / sys / luasrc / sys.lua
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, select =
40 tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
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 local function _nethints(what, callback)
237 local _, k, e, mac, ip, name
238 local ifn = { }
239 local hosts = { }
240
241 local function _add(i, ...)
242 local k = select(i, ...)
243 if k then
244 if not hosts[k] then hosts[k] = { } end
245 hosts[k][1] = select(1, ...) or hosts[k][1]
246 hosts[k][2] = select(2, ...) or hosts[k][2]
247 hosts[k][3] = select(3, ...) or hosts[k][3]
248 hosts[k][4] = select(4, ...) or hosts[k][4]
249 end
250 end
251
252 if fs.access("/proc/net/arp") then
253 for e in io.lines("/proc/net/arp") do
254 ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
255 if ip and mac then
256 _add(what, mac:upper(), ip, nil, nil)
257 end
258 end
259 end
260
261 if fs.access("/etc/ethers") then
262 for e in io.lines("/etc/ethers") do
263 mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
264 if mac and ip then
265 _add(what, mac:upper(), ip, nil, nil)
266 end
267 end
268 end
269
270 if fs.access("/var/dhcp.leases") then
271 for e in io.lines("/var/dhcp.leases") do
272 mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
273 if mac and ip then
274 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
275 end
276 end
277 end
278
279 for _, e in ipairs(nixio.getifaddrs()) do
280 if e.name ~= "lo" then
281 ifn[e.name] = ifn[e.name] or { }
282 if e.family == "packet" and e.addr and #e.addr == 17 then
283 ifn[e.name][1] = e.addr:upper()
284 elseif e.family == "inet" then
285 ifn[e.name][2] = e.addr
286 elseif e.family == "inet6" then
287 ifn[e.name][3] = e.addr
288 end
289 end
290 end
291
292 for _, e in pairs(ifn) do
293 if e[what] and (e[2] or e[3]) then
294 _add(what, e[1], e[2], e[3], e[4])
295 end
296 end
297
298 for _, e in luci.util.kspairs(hosts) do
299 callback(e[1], e[2], e[3], e[4])
300 end
301 end
302
303 --- Returns a two-dimensional table of mac address hints.
304 -- @return Table of table containing known hosts from various sources.
305 -- Each entry contains the values in the following order:
306 -- [ "mac", "name" ]
307 function net.mac_hints(callback)
308 if callback then
309 _nethints(1, function(mac, v4, v6, name)
310 name = name or nixio.getnameinfo(v4 or v6) or v4
311 if name and name ~= mac then
312 callback(mac, name or nixio.getnameinfo(v4 or v6) or v4)
313 end
314 end)
315 else
316 local rv = { }
317 _nethints(1, function(mac, v4, v6, name)
318 name = name or nixio.getnameinfo(v4 or v6) or v4
319 if name and name ~= mac then
320 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6) or v4 }
321 end
322 end)
323 return rv
324 end
325 end
326
327 --- Returns a two-dimensional table of IPv4 address hints.
328 -- @return Table of table containing known hosts from various sources.
329 -- Each entry contains the values in the following order:
330 -- [ "ip", "name" ]
331 function net.ipv4_hints(callback)
332 if callback then
333 _nethints(2, function(mac, v4, v6, name)
334 name = name or nixio.getnameinfo(v4) or mac
335 if name and name ~= v4 then
336 callback(v4, name)
337 end
338 end)
339 else
340 local rv = { }
341 _nethints(2, function(mac, v4, v6, name)
342 name = name or nixio.getnameinfo(v4) or mac
343 if name and name ~= v4 then
344 rv[#rv+1] = { v4, name }
345 end
346 end)
347 return rv
348 end
349 end
350
351 --- Returns a two-dimensional table of IPv6 address hints.
352 -- @return Table of table containing known hosts from various sources.
353 -- Each entry contains the values in the following order:
354 -- [ "ip", "name" ]
355 function net.ipv6_hints(callback)
356 if callback then
357 _nethints(3, function(mac, v4, v6, name)
358 name = name or nixio.getnameinfo(v6) or mac
359 if name and name ~= v6 then
360 callback(v6, name)
361 end
362 end)
363 else
364 local rv = { }
365 _nethints(3, function(mac, v4, v6, name)
366 name = name or nixio.getnameinfo(v6) or mac
367 if name and name ~= v6 then
368 rv[#rv+1] = { v6, name }
369 end
370 end)
371 return rv
372 end
373 end
374
375 --- Returns conntrack information
376 -- @return Table with the currently tracked IP connections
377 function net.conntrack(callback)
378 local connt = {}
379 if fs.access("/proc/net/nf_conntrack", "r") then
380 for line in io.lines("/proc/net/nf_conntrack") do
381 line = line:match "^(.-( [^ =]+=).-)%2"
382 local entry, flags = _parse_mixed_record(line, " +")
383 if flags[6] ~= "TIME_WAIT" then
384 entry.layer3 = flags[1]
385 entry.layer4 = flags[3]
386 for i=1, #entry do
387 entry[i] = nil
388 end
389
390 if callback then
391 callback(entry)
392 else
393 connt[#connt+1] = entry
394 end
395 end
396 end
397 elseif fs.access("/proc/net/ip_conntrack", "r") then
398 for line in io.lines("/proc/net/ip_conntrack") do
399 line = line:match "^(.-( [^ =]+=).-)%2"
400 local entry, flags = _parse_mixed_record(line, " +")
401 if flags[4] ~= "TIME_WAIT" then
402 entry.layer3 = "ipv4"
403 entry.layer4 = flags[1]
404 for i=1, #entry do
405 entry[i] = nil
406 end
407
408 if callback then
409 callback(entry)
410 else
411 connt[#connt+1] = entry
412 end
413 end
414 end
415 else
416 return nil
417 end
418 return connt
419 end
420
421 --- Determine the current IPv4 default route. If multiple default routes exist,
422 -- return the one with the lowest metric.
423 -- @return Table with the properties of the current default route.
424 -- The following fields are defined:
425 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
426 -- "flags", "device" }
427 function net.defaultroute()
428 local route
429
430 net.routes(function(rt)
431 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
432 route = rt
433 end
434 end)
435
436 return route
437 end
438
439 --- Determine the current IPv6 default route. If multiple default routes exist,
440 -- return the one with the lowest metric.
441 -- @return Table with the properties of the current default route.
442 -- The following fields are defined:
443 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
444 -- "flags", "device" }
445 function net.defaultroute6()
446 local route
447
448 net.routes6(function(rt)
449 if rt.dest:prefix() == 0 and rt.device ~= "lo" and
450 (not route or route.metric > rt.metric)
451 then
452 route = rt
453 end
454 end)
455
456 if not route then
457 local global_unicast = luci.ip.IPv6("2000::/3")
458 net.routes6(function(rt)
459 if rt.dest:equal(global_unicast) and
460 (not route or route.metric > rt.metric)
461 then
462 route = rt
463 end
464 end)
465 end
466
467 return route
468 end
469
470 --- Determine the names of available network interfaces.
471 -- @return Table containing all current interface names
472 function net.devices()
473 local devs = {}
474 for k, v in ipairs(nixio.getifaddrs()) do
475 if v.family == "packet" then
476 devs[#devs+1] = v.name
477 end
478 end
479 return devs
480 end
481
482
483 --- Return information about available network interfaces.
484 -- @return Table containing all current interface names and their information
485 function net.deviceinfo()
486 local devs = {}
487 for k, v in ipairs(nixio.getifaddrs()) do
488 if v.family == "packet" then
489 local d = v.data
490 d[1] = d.rx_bytes
491 d[2] = d.rx_packets
492 d[3] = d.rx_errors
493 d[4] = d.rx_dropped
494 d[5] = 0
495 d[6] = 0
496 d[7] = 0
497 d[8] = d.multicast
498 d[9] = d.tx_bytes
499 d[10] = d.tx_packets
500 d[11] = d.tx_errors
501 d[12] = d.tx_dropped
502 d[13] = 0
503 d[14] = d.collisions
504 d[15] = 0
505 d[16] = 0
506 devs[v.name] = d
507 end
508 end
509 return devs
510 end
511
512
513 -- Determine the MAC address belonging to the given IP address.
514 -- @param ip IPv4 address
515 -- @return String containing the MAC address or nil if it cannot be found
516 function net.ip4mac(ip)
517 local mac = nil
518 net.arptable(function(e)
519 if e["IP address"] == ip then
520 mac = e["HW address"]
521 end
522 end)
523 return mac
524 end
525
526 --- Returns the current kernel routing table entries.
527 -- @return Table of tables with properties of the corresponding routes.
528 -- The following fields are defined for route entry tables:
529 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
530 -- "flags", "device" }
531 function net.routes(callback)
532 local routes = { }
533
534 for line in io.lines("/proc/net/route") do
535
536 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
537 dst_mask, mtu, win, irtt = line:match(
538 "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
539 "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
540 )
541
542 if dev then
543 gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
544 dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
545 dst_ip = luci.ip.Hex(
546 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
547 )
548
549 local rt = {
550 dest = dst_ip,
551 gateway = gateway,
552 metric = tonumber(metric),
553 refcount = tonumber(refcnt),
554 usecount = tonumber(usecnt),
555 mtu = tonumber(mtu),
556 window = tonumber(window),
557 irtt = tonumber(irtt),
558 flags = tonumber(flags, 16),
559 device = dev
560 }
561
562 if callback then
563 callback(rt)
564 else
565 routes[#routes+1] = rt
566 end
567 end
568 end
569
570 return routes
571 end
572
573 --- Returns the current ipv6 kernel routing table entries.
574 -- @return Table of tables with properties of the corresponding routes.
575 -- The following fields are defined for route entry tables:
576 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
577 -- "flags", "device" }
578 function net.routes6(callback)
579 if fs.access("/proc/net/ipv6_route", "r") then
580 local routes = { }
581
582 for line in io.lines("/proc/net/ipv6_route") do
583
584 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
585 metric, refcnt, usecnt, flags, dev = line:match(
586 "([a-f0-9]+) ([a-f0-9]+) " ..
587 "([a-f0-9]+) ([a-f0-9]+) " ..
588 "([a-f0-9]+) ([a-f0-9]+) " ..
589 "([a-f0-9]+) ([a-f0-9]+) " ..
590 "([a-f0-9]+) +([^%s]+)"
591 )
592
593 if dst_ip and dst_prefix and
594 src_ip and src_prefix and
595 nexthop and metric and
596 refcnt and usecnt and
597 flags and dev
598 then
599 src_ip = luci.ip.Hex(
600 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
601 )
602
603 dst_ip = luci.ip.Hex(
604 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
605 )
606
607 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
608
609 local rt = {
610 source = src_ip,
611 dest = dst_ip,
612 nexthop = nexthop,
613 metric = tonumber(metric, 16),
614 refcount = tonumber(refcnt, 16),
615 usecount = tonumber(usecnt, 16),
616 flags = tonumber(flags, 16),
617 device = dev,
618
619 -- lua number is too small for storing the metric
620 -- add a metric_raw field with the original content
621 metric_raw = metric
622 }
623
624 if callback then
625 callback(rt)
626 else
627 routes[#routes+1] = rt
628 end
629 end
630 end
631
632 return routes
633 end
634 end
635
636 --- Tests whether the given host responds to ping probes.
637 -- @param host String containing a hostname or IPv4 address
638 -- @return Number containing 0 on success and >= 1 on error
639 function net.pingtest(host)
640 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
641 end
642
643
644 --- LuCI system utilities / process related functions.
645 -- @class module
646 -- @name luci.sys.process
647 process = {}
648
649 --- Get the current process id.
650 -- @class function
651 -- @name process.info
652 -- @return Number containing the current pid
653 function process.info(key)
654 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
655 return not key and s or s[key]
656 end
657
658 --- Retrieve information about currently running processes.
659 -- @return Table containing process information
660 function process.list()
661 local data = {}
662 local k
663 local ps = luci.util.execi("top -bn1")
664
665 if not ps then
666 return
667 end
668
669 while true do
670 local line = ps()
671 if not line then
672 return
673 end
674
675 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
676 if k[6] == "%VSZ" then
677 k[6] = "%MEM"
678 end
679 if k[1] == "PID" then
680 break
681 end
682 end
683
684 for line in ps do
685 local row = {}
686
687 line = luci.util.trim(line)
688 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
689 row[k[i]] = value
690 end
691
692 local pid = tonumber(row[k[1]])
693 if pid then
694 data[pid] = row
695 end
696 end
697
698 return data
699 end
700
701 --- Set the gid of a process identified by given pid.
702 -- @param gid Number containing the Unix group id
703 -- @return Boolean indicating successful operation
704 -- @return String containing the error message if failed
705 -- @return Number containing the error code if failed
706 function process.setgroup(gid)
707 return nixio.setgid(gid)
708 end
709
710 --- Set the uid of a process identified by given pid.
711 -- @param uid Number containing the Unix user id
712 -- @return Boolean indicating successful operation
713 -- @return String containing the error message if failed
714 -- @return Number containing the error code if failed
715 function process.setuser(uid)
716 return nixio.setuid(uid)
717 end
718
719 --- Send a signal to a process identified by given pid.
720 -- @class function
721 -- @name process.signal
722 -- @param pid Number containing the process id
723 -- @param sig Signal to send (default: 15 [SIGTERM])
724 -- @return Boolean indicating successful operation
725 -- @return Number containing the error code if failed
726 process.signal = nixio.kill
727
728
729 --- LuCI system utilities / user related functions.
730 -- @class module
731 -- @name luci.sys.user
732 user = {}
733
734 --- Retrieve user informations for given uid.
735 -- @class function
736 -- @name getuser
737 -- @param uid Number containing the Unix user id
738 -- @return Table containing the following fields:
739 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
740 user.getuser = nixio.getpw
741
742 --- Retrieve the current user password hash.
743 -- @param username String containing the username to retrieve the password for
744 -- @return String containing the hash or nil if no password is set.
745 -- @return Password database entry
746 function user.getpasswd(username)
747 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
748 local pwh = pwe and (pwe.pwdp or pwe.passwd)
749 if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
750 return nil, pwe
751 else
752 return pwh, pwe
753 end
754 end
755
756 --- Test whether given string matches the password of a given system user.
757 -- @param username String containing the Unix user name
758 -- @param pass String containing the password to compare
759 -- @return Boolean indicating wheather the passwords are equal
760 function user.checkpasswd(username, pass)
761 local pwh, pwe = user.getpasswd(username)
762 if pwe then
763 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
764 end
765 return false
766 end
767
768 --- Change the password of given user.
769 -- @param username String containing the Unix user name
770 -- @param password String containing the password to compare
771 -- @return Number containing 0 on success and >= 1 on error
772 function user.setpasswd(username, password)
773 if password then
774 password = password:gsub("'", [['"'"']])
775 end
776
777 if username then
778 username = username:gsub("'", [['"'"']])
779 end
780
781 return os.execute(
782 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
783 "passwd '" .. username .. "' >/dev/null 2>&1"
784 )
785 end
786
787
788 --- LuCI system utilities / wifi related functions.
789 -- @class module
790 -- @name luci.sys.wifi
791 wifi = {}
792
793 --- Get wireless information for given interface.
794 -- @param ifname String containing the interface name
795 -- @return A wrapped iwinfo object instance
796 function wifi.getiwinfo(ifname)
797 local stat, iwinfo = pcall(require, "iwinfo")
798
799 if ifname then
800 local c = 0
801 local u = uci.cursor_state()
802 local d, n = ifname:match("^(%w+)%.network(%d+)")
803 if d and n then
804 n = tonumber(n)
805 u:foreach("wireless", "wifi-iface",
806 function(s)
807 if s.device == d then
808 c = c + 1
809 if c == n then
810 ifname = s.ifname or s.device
811 return false
812 end
813 end
814 end)
815 elseif u:get("wireless", ifname) == "wifi-device" then
816 u:foreach("wireless", "wifi-iface",
817 function(s)
818 if s.device == ifname and s.ifname then
819 ifname = s.ifname
820 return false
821 end
822 end)
823 end
824
825 local t = stat and iwinfo.type(ifname)
826 local x = t and iwinfo[t] or { }
827 return setmetatable({}, {
828 __index = function(t, k)
829 if k == "ifname" then
830 return ifname
831 elseif x[k] then
832 return x[k](ifname)
833 end
834 end
835 })
836 end
837 end
838
839 --- Get iwconfig output for all wireless devices.
840 -- @return Table of tables containing the iwconfing output for each wifi device
841 function wifi.getiwconfig()
842 local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
843 local iwc = {}
844
845 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
846 local k = l:match("^(.-) ")
847 l = l:gsub("^(.-) +", "", 1)
848 if k then
849 local entry, flags = _parse_mixed_record(l)
850 if entry then
851 entry.flags = flags
852 end
853 iwc[k] = entry
854 end
855 end
856
857 return iwc
858 end
859
860 --- Get iwlist scan output from all wireless devices.
861 -- @return Table of tables contaiing all scan results
862 function wifi.iwscan(iface)
863 local siface = iface or ""
864 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
865 local iws = {}
866
867 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
868 local k = l:match("^(.-) ")
869 l = l:gsub("^[^\n]+", "", 1)
870 l = luci.util.trim(l)
871 if k then
872 iws[k] = {}
873 for j, c in pairs(luci.util.split(l, "\n Cell")) do
874 c = c:gsub("^(.-)- ", "", 1)
875 c = luci.util.split(c, "\n", 7)
876 c = table.concat(c, "\n", 1)
877 local entry, flags = _parse_mixed_record(c)
878 if entry then
879 entry.flags = flags
880 end
881 table.insert(iws[k], entry)
882 end
883 end
884 end
885
886 return iface and (iws[iface] or {}) or iws
887 end
888
889 --- Get available channels from given wireless iface.
890 -- @param iface Wireless interface (optional)
891 -- @return Table of available channels
892 function wifi.channels(iface)
893 local stat, iwinfo = pcall(require, "iwinfo")
894 local cns
895
896 if stat then
897 local t = iwinfo.type(iface or "")
898 if iface and t and iwinfo[t] then
899 cns = iwinfo[t].freqlist(iface)
900 end
901 end
902
903 if not cns or #cns == 0 then
904 cns = {
905 {channel = 1, mhz = 2412},
906 {channel = 2, mhz = 2417},
907 {channel = 3, mhz = 2422},
908 {channel = 4, mhz = 2427},
909 {channel = 5, mhz = 2432},
910 {channel = 6, mhz = 2437},
911 {channel = 7, mhz = 2442},
912 {channel = 8, mhz = 2447},
913 {channel = 9, mhz = 2452},
914 {channel = 10, mhz = 2457},
915 {channel = 11, mhz = 2462}
916 }
917 end
918
919 return cns
920 end
921
922
923 --- LuCI system utilities / init related functions.
924 -- @class module
925 -- @name luci.sys.init
926 init = {}
927 init.dir = "/etc/init.d/"
928
929 --- Get the names of all installed init scripts
930 -- @return Table containing the names of all inistalled init scripts
931 function init.names()
932 local names = { }
933 for name in fs.glob(init.dir.."*") do
934 names[#names+1] = fs.basename(name)
935 end
936 return names
937 end
938
939 --- Get the index of he given init script
940 -- @param name Name of the init script
941 -- @return Numeric index value
942 function init.index(name)
943 if fs.access(init.dir..name) then
944 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
945 %{ init.dir, name })
946 end
947 end
948
949 local function init_action(action, name)
950 if fs.access(init.dir..name) then
951 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
952 end
953 end
954
955 --- Test whether the given init script is enabled
956 -- @param name Name of the init script
957 -- @return Boolean indicating whether init is enabled
958 function init.enabled(name)
959 return (init_action("enabled", name) == 0)
960 end
961
962 --- Enable the given init script
963 -- @param name Name of the init script
964 -- @return Boolean indicating success
965 function init.enable(name)
966 return (init_action("enable", name) == 1)
967 end
968
969 --- Disable the given init script
970 -- @param name Name of the init script
971 -- @return Boolean indicating success
972 function init.disable(name)
973 return (init_action("disable", name) == 0)
974 end
975
976 --- Start the given init script
977 -- @param name Name of the init script
978 -- @return Boolean indicating success
979 function init.start(name)
980 return (init_action("start", name) == 0)
981 end
982
983 --- Stop the given init script
984 -- @param name Name of the init script
985 -- @return Boolean indicating success
986 function init.stop(name)
987 return (init_action("stop", name) == 0)
988 end
989
990
991 -- Internal functions
992
993 function _parse_delimited_table(iter, delimiter, callback)
994 delimiter = delimiter or "%s+"
995
996 local data = {}
997 local trim = luci.util.trim
998 local split = luci.util.split
999
1000 local keys = split(trim(iter()), delimiter, nil, true)
1001 for i, j in pairs(keys) do
1002 keys[i] = trim(keys[i])
1003 end
1004
1005 for line in iter do
1006 local row = {}
1007 line = trim(line)
1008 if #line > 0 then
1009 for i, j in pairs(split(line, delimiter, nil, true)) do
1010 if keys[i] then
1011 row[keys[i]] = j
1012 end
1013 end
1014 end
1015
1016 if callback then
1017 callback(row)
1018 else
1019 data[#data+1] = row
1020 end
1021 end
1022
1023 return data
1024 end
1025
1026 function _parse_mixed_record(cnt, delimiter)
1027 delimiter = delimiter or " "
1028 local data = {}
1029 local flags = {}
1030
1031 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
1032 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
1033 local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
1034
1035 if k then
1036 if x == "" then
1037 table.insert(flags, k)
1038 else
1039 data[k] = v
1040 end
1041 end
1042 end
1043 end
1044
1045 return data, flags
1046 end