libs/sys: remove wifi.channels()
[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
840 --- LuCI system utilities / init related functions.
841 -- @class       module
842 -- @name        luci.sys.init
843 init = {}
844 init.dir = "/etc/init.d/"
845
846 --- Get the names of all installed init scripts
847 -- @return      Table containing the names of all inistalled init scripts
848 function init.names()
849         local names = { }
850         for name in fs.glob(init.dir.."*") do
851                 names[#names+1] = fs.basename(name)
852         end
853         return names
854 end
855
856 --- Get the index of he given init script
857 -- @param name  Name of the init script
858 -- @return              Numeric index value
859 function init.index(name)
860         if fs.access(init.dir..name) then
861                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
862                         %{ init.dir, name })
863         end
864 end
865
866 local function init_action(action, name)
867         if fs.access(init.dir..name) then
868                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
869         end
870 end
871
872 --- Test whether the given init script is enabled
873 -- @param name  Name of the init script
874 -- @return              Boolean indicating whether init is enabled
875 function init.enabled(name)
876         return (init_action("enabled", name) == 0)
877 end
878
879 --- Enable the given init script
880 -- @param name  Name of the init script
881 -- @return              Boolean indicating success
882 function init.enable(name)
883         return (init_action("enable", name) == 1)
884 end
885
886 --- Disable the given init script
887 -- @param name  Name of the init script
888 -- @return              Boolean indicating success
889 function init.disable(name)
890         return (init_action("disable", name) == 0)
891 end
892
893 --- Start the given init script
894 -- @param name  Name of the init script
895 -- @return              Boolean indicating success
896 function init.start(name)
897         return (init_action("start", name) == 0)
898 end
899
900 --- Stop the given init script
901 -- @param name  Name of the init script
902 -- @return              Boolean indicating success
903 function init.stop(name)
904         return (init_action("stop", name) == 0)
905 end
906
907
908 -- Internal functions
909
910 function _parse_delimited_table(iter, delimiter, callback)
911         delimiter = delimiter or "%s+"
912
913         local data  = {}
914         local trim  = luci.util.trim
915         local split = luci.util.split
916
917         local keys = split(trim(iter()), delimiter, nil, true)
918         for i, j in pairs(keys) do
919                 keys[i] = trim(keys[i])
920         end
921
922         for line in iter do
923                 local row = {}
924                 line = trim(line)
925                 if #line > 0 then
926                         for i, j in pairs(split(line, delimiter, nil, true)) do
927                                 if keys[i] then
928                                         row[keys[i]] = j
929                                 end
930                         end
931                 end
932
933                 if callback then
934                         callback(row)
935                 else
936                         data[#data+1] = row
937                 end
938         end
939
940         return data
941 end
942
943 function _parse_mixed_record(cnt, delimiter)
944         delimiter = delimiter or "  "
945         local data = {}
946         local flags = {}
947
948         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
949                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
950                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
951
952                         if k then
953                                 if x == "" then
954                                         table.insert(flags, k)
955                                 else
956                                         data[k] = v
957                                 end
958                         end
959                 end
960         end
961
962         return data, flags
963 end