luci-mod-base: drop luci.sys.sysinfo() and luci.sys.loadavg()
[project/luci.git] / modules / luci-base / 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 --- Initiate a system reboot.
148 -- @return      Return value of os.execute()
149 function reboot()
150         return os.execute("reboot >/dev/null 2>&1")
151 end
152
153 --- Retrieves the output of the "logread" command.
154 -- @return      String containing the current log buffer
155 function syslog()
156         return luci.util.exec("logread")
157 end
158
159 --- Retrieves the output of the "dmesg" command.
160 -- @return      String containing the current log buffer
161 function dmesg()
162         return luci.util.exec("dmesg")
163 end
164
165 --- Generates a random id with specified length.
166 -- @param bytes Number of bytes for the unique id
167 -- @return              String containing hex encoded id
168 function uniqueid(bytes)
169         local rand = fs.readfile("/dev/urandom", bytes)
170         return rand and nixio.bin.hexlify(rand)
171 end
172
173 --- Returns the current system uptime stats.
174 -- @return      String containing total uptime in seconds
175 function uptime()
176         return nixio.sysinfo().uptime
177 end
178
179
180 --- LuCI system utilities / network related functions.
181 -- @class       module
182 -- @name        luci.sys.net
183 net = {}
184
185 --- Returns the current arp-table entries as two-dimensional table.
186 -- @return      Table of table containing the current arp entries.
187 --                      The following fields are defined for arp entry objects:
188 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
189 function net.arptable(callback)
190         local arp = (not callback) and {} or nil
191         local e, r, v
192         if fs.access("/proc/net/arp") then
193                 for e in io.lines("/proc/net/arp") do
194                         local r = { }, v
195                         for v in e:gmatch("%S+") do
196                                 r[#r+1] = v
197                         end
198
199                         if r[1] ~= "IP" then
200                                 local x = {
201                                         ["IP address"] = r[1],
202                                         ["HW type"]    = r[2],
203                                         ["Flags"]      = r[3],
204                                         ["HW address"] = r[4],
205                                         ["Mask"]       = r[5],
206                                         ["Device"]     = r[6]
207                                 }
208
209                                 if callback then
210                                         callback(x)
211                                 else
212                                         arp = arp or { }
213                                         arp[#arp+1] = x
214                                 end
215                         end
216                 end
217         end
218         return arp
219 end
220
221 local function _nethints(what, callback)
222         local _, k, e, mac, ip, name
223         local cur = uci.cursor()
224         local ifn = { }
225         local hosts = { }
226
227         local function _add(i, ...)
228                 local k = select(i, ...)
229                 if k then
230                         if not hosts[k] then hosts[k] = { } end
231                         hosts[k][1] = select(1, ...) or hosts[k][1]
232                         hosts[k][2] = select(2, ...) or hosts[k][2]
233                         hosts[k][3] = select(3, ...) or hosts[k][3]
234                         hosts[k][4] = select(4, ...) or hosts[k][4]
235                 end
236         end
237
238         if fs.access("/proc/net/arp") then
239                 for e in io.lines("/proc/net/arp") do
240                         ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
241                         if ip and mac then
242                                 _add(what, mac:upper(), ip, nil, nil)
243                         end
244                 end
245         end
246
247         if fs.access("/etc/ethers") then
248                 for e in io.lines("/etc/ethers") do
249                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
250                         if mac and ip then
251                                 _add(what, mac:upper(), ip, nil, nil)
252                         end
253                 end
254         end
255
256         if fs.access("/var/dhcp.leases") then
257                 for e in io.lines("/var/dhcp.leases") do
258                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
259                         if mac and ip then
260                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
261                         end
262                 end
263         end
264
265         cur:foreach("dhcp", "host",
266                 function(s)
267                         for mac in luci.util.imatch(s.mac) do
268                                 _add(what, mac:upper(), s.ip, nil, s.name)
269                         end
270                 end)
271
272         for _, e in ipairs(nixio.getifaddrs()) do
273                 if e.name ~= "lo" then
274                         ifn[e.name] = ifn[e.name] or { }
275                         if e.family == "packet" and e.addr and #e.addr == 17 then
276                                 ifn[e.name][1] = e.addr:upper()
277                         elseif e.family == "inet" then
278                                 ifn[e.name][2] = e.addr
279                         elseif e.family == "inet6" then
280                                 ifn[e.name][3] = e.addr
281                         end
282                 end
283         end
284
285         for _, e in pairs(ifn) do
286                 if e[what] and (e[2] or e[3]) then
287                         _add(what, e[1], e[2], e[3], e[4])
288                 end
289         end
290
291         for _, e in luci.util.kspairs(hosts) do
292                 callback(e[1], e[2], e[3], e[4])
293         end
294 end
295
296 --- Returns a two-dimensional table of mac address hints.
297 -- @return  Table of table containing known hosts from various sources.
298 --          Each entry contains the values in the following order:
299 --          [ "mac", "name" ]
300 function net.mac_hints(callback)
301         if callback then
302                 _nethints(1, function(mac, v4, v6, name)
303                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
304                         if name and name ~= mac then
305                                 callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
306                         end
307                 end)
308         else
309                 local rv = { }
310                 _nethints(1, function(mac, v4, v6, name)
311                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
312                         if name and name ~= mac then
313                                 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
314                         end
315                 end)
316                 return rv
317         end
318 end
319
320 --- Returns a two-dimensional table of IPv4 address hints.
321 -- @return  Table of table containing known hosts from various sources.
322 --          Each entry contains the values in the following order:
323 --          [ "ip", "name" ]
324 function net.ipv4_hints(callback)
325         if callback then
326                 _nethints(2, function(mac, v4, v6, name)
327                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
328                         if name and name ~= v4 then
329                                 callback(v4, name)
330                         end
331                 end)
332         else
333                 local rv = { }
334                 _nethints(2, function(mac, v4, v6, name)
335                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
336                         if name and name ~= v4 then
337                                 rv[#rv+1] = { v4, name }
338                         end
339                 end)
340                 return rv
341         end
342 end
343
344 --- Returns a two-dimensional table of IPv6 address hints.
345 -- @return  Table of table containing known hosts from various sources.
346 --          Each entry contains the values in the following order:
347 --          [ "ip", "name" ]
348 function net.ipv6_hints(callback)
349         if callback then
350                 _nethints(3, function(mac, v4, v6, name)
351                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
352                         if name and name ~= v6 then
353                                 callback(v6, name)
354                         end
355                 end)
356         else
357                 local rv = { }
358                 _nethints(3, function(mac, v4, v6, name)
359                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
360                         if name and name ~= v6 then
361                                 rv[#rv+1] = { v6, name }
362                         end
363                 end)
364                 return rv
365         end
366 end
367
368 --- Returns conntrack information
369 -- @return      Table with the currently tracked IP connections
370 function net.conntrack(callback)
371         local connt = {}
372         if fs.access("/proc/net/nf_conntrack", "r") then
373                 for line in io.lines("/proc/net/nf_conntrack") do
374                         line = line:match "^(.-( [^ =]+=).-)%2"
375                         local entry, flags = _parse_mixed_record(line, " +")
376                         if flags[6] ~= "TIME_WAIT" then
377                                 entry.layer3 = flags[1]
378                                 entry.layer4 = flags[3]
379                                 for i=1, #entry do
380                                         entry[i] = nil
381                                 end
382
383                                 if callback then
384                                         callback(entry)
385                                 else
386                                         connt[#connt+1] = entry
387                                 end
388                         end
389                 end
390         elseif fs.access("/proc/net/ip_conntrack", "r") then
391                 for line in io.lines("/proc/net/ip_conntrack") do
392                         line = line:match "^(.-( [^ =]+=).-)%2"
393                         local entry, flags = _parse_mixed_record(line, " +")
394                         if flags[4] ~= "TIME_WAIT" then
395                                 entry.layer3 = "ipv4"
396                                 entry.layer4 = flags[1]
397                                 for i=1, #entry do
398                                         entry[i] = nil
399                                 end
400
401                                 if callback then
402                                         callback(entry)
403                                 else
404                                         connt[#connt+1] = entry
405                                 end
406                         end
407                 end
408         else
409                 return nil
410         end
411         return connt
412 end
413
414 --- Determine the current IPv4 default route. If multiple default routes exist,
415 -- return the one with the lowest metric.
416 -- @return      Table with the properties of the current default route.
417 --                      The following fields are defined:
418 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
419 --                        "flags", "device" }
420 function net.defaultroute()
421         local route
422
423         net.routes(function(rt)
424                 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
425                         route = rt
426                 end
427         end)
428
429         return route
430 end
431
432 --- Determine the current IPv6 default route. If multiple default routes exist,
433 -- return the one with the lowest metric.
434 -- @return      Table with the properties of the current default route.
435 --                      The following fields are defined:
436 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
437 --                        "flags", "device" }
438 function net.defaultroute6()
439         local route
440
441         net.routes6(function(rt)
442                 if rt.dest:prefix() == 0 and rt.device ~= "lo" and
443                    (not route or route.metric > rt.metric)
444                 then
445                         route = rt
446                 end
447         end)
448
449         if not route then
450                 local global_unicast = luci.ip.IPv6("2000::/3")
451                 net.routes6(function(rt)
452                         if rt.dest:equal(global_unicast) and
453                            (not route or route.metric > rt.metric)
454                         then
455                                 route = rt
456                         end
457                 end)
458         end
459
460         return route
461 end
462
463 --- Determine the names of available network interfaces.
464 -- @return      Table containing all current interface names
465 function net.devices()
466         local devs = {}
467         for k, v in ipairs(nixio.getifaddrs()) do
468                 if v.family == "packet" then
469                         devs[#devs+1] = v.name
470                 end
471         end
472         return devs
473 end
474
475
476 --- Return information about available network interfaces.
477 -- @return      Table containing all current interface names and their information
478 function net.deviceinfo()
479         local devs = {}
480         for k, v in ipairs(nixio.getifaddrs()) do
481                 if v.family == "packet" then
482                         local d = v.data
483                         d[1] = d.rx_bytes
484                         d[2] = d.rx_packets
485                         d[3] = d.rx_errors
486                         d[4] = d.rx_dropped
487                         d[5] = 0
488                         d[6] = 0
489                         d[7] = 0
490                         d[8] = d.multicast
491                         d[9] = d.tx_bytes
492                         d[10] = d.tx_packets
493                         d[11] = d.tx_errors
494                         d[12] = d.tx_dropped
495                         d[13] = 0
496                         d[14] = d.collisions
497                         d[15] = 0
498                         d[16] = 0
499                         devs[v.name] = d
500                 end
501         end
502         return devs
503 end
504
505
506 -- Determine the MAC address belonging to the given IP address.
507 -- @param ip    IPv4 address
508 -- @return              String containing the MAC address or nil if it cannot be found
509 function net.ip4mac(ip)
510         local mac = nil
511         net.arptable(function(e)
512                 if e["IP address"] == ip then
513                         mac = e["HW address"]
514                 end
515         end)
516         return mac
517 end
518
519 --- Returns the current kernel routing table entries.
520 -- @return      Table of tables with properties of the corresponding routes.
521 --                      The following fields are defined for route entry tables:
522 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
523 --                        "flags", "device" }
524 function net.routes(callback)
525         local routes = { }
526
527         for line in io.lines("/proc/net/route") do
528
529                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
530                           dst_mask, mtu, win, irtt = line:match(
531                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
532                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
533                 )
534
535                 if dev then
536                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
537                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
538                         dst_ip   = luci.ip.Hex(
539                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
540                         )
541
542                         local rt = {
543                                 dest     = dst_ip,
544                                 gateway  = gateway,
545                                 metric   = tonumber(metric),
546                                 refcount = tonumber(refcnt),
547                                 usecount = tonumber(usecnt),
548                                 mtu      = tonumber(mtu),
549                                 window   = tonumber(window),
550                                 irtt     = tonumber(irtt),
551                                 flags    = tonumber(flags, 16),
552                                 device   = dev
553                         }
554
555                         if callback then
556                                 callback(rt)
557                         else
558                                 routes[#routes+1] = rt
559                         end
560                 end
561         end
562
563         return routes
564 end
565
566 --- Returns the current ipv6 kernel routing table entries.
567 -- @return      Table of tables with properties of the corresponding routes.
568 --                      The following fields are defined for route entry tables:
569 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
570 --                        "flags", "device" }
571 function net.routes6(callback)
572         if fs.access("/proc/net/ipv6_route", "r") then
573                 local routes = { }
574
575                 for line in io.lines("/proc/net/ipv6_route") do
576
577                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
578                                   metric, refcnt, usecnt, flags, dev = line:match(
579                                 "([a-f0-9]+) ([a-f0-9]+) " ..
580                                 "([a-f0-9]+) ([a-f0-9]+) " ..
581                                 "([a-f0-9]+) ([a-f0-9]+) " ..
582                                 "([a-f0-9]+) ([a-f0-9]+) " ..
583                                 "([a-f0-9]+) +([^%s]+)"
584                         )
585
586                         if dst_ip and dst_prefix and
587                            src_ip and src_prefix and
588                            nexthop and metric and
589                            refcnt and usecnt and
590                            flags and dev
591                         then
592                                 src_ip = luci.ip.Hex(
593                                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
594                                 )
595
596                                 dst_ip = luci.ip.Hex(
597                                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
598                                 )
599
600                                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
601
602                                 local rt = {
603                                         source   = src_ip,
604                                         dest     = dst_ip,
605                                         nexthop  = nexthop,
606                                         metric   = tonumber(metric, 16),
607                                         refcount = tonumber(refcnt, 16),
608                                         usecount = tonumber(usecnt, 16),
609                                         flags    = tonumber(flags, 16),
610                                         device   = dev,
611
612                                         -- lua number is too small for storing the metric
613                                         -- add a metric_raw field with the original content
614                                         metric_raw = metric
615                                 }
616
617                                 if callback then
618                                         callback(rt)
619                                 else
620                                         routes[#routes+1] = rt
621                                 end
622                         end
623                 end
624
625                 return routes
626         end
627 end
628
629 --- Tests whether the given host responds to ping probes.
630 -- @param host  String containing a hostname or IPv4 address
631 -- @return              Number containing 0 on success and >= 1 on error
632 function net.pingtest(host)
633         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
634 end
635
636
637 --- LuCI system utilities / process related functions.
638 -- @class       module
639 -- @name        luci.sys.process
640 process = {}
641
642 --- Get the current process id.
643 -- @class function
644 -- @name  process.info
645 -- @return      Number containing the current pid
646 function process.info(key)
647         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
648         return not key and s or s[key]
649 end
650
651 --- Retrieve information about currently running processes.
652 -- @return      Table containing process information
653 function process.list()
654         local data = {}
655         local k
656         local ps = luci.util.execi("/bin/busybox top -bn1")
657
658         if not ps then
659                 return
660         end
661
662         for line in ps do
663                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
664                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
665                 )
666
667                 local idx = tonumber(pid)
668                 if idx then
669                         data[idx] = {
670                                 ['PID']     = pid,
671                                 ['PPID']    = ppid,
672                                 ['USER']    = user,
673                                 ['STAT']    = stat,
674                                 ['VSZ']     = vsz,
675                                 ['%MEM']    = mem,
676                                 ['%CPU']    = cpu,
677                                 ['COMMAND'] = cmd
678                         }
679                 end
680         end
681
682         return data
683 end
684
685 --- Set the gid of a process identified by given pid.
686 -- @param gid   Number containing the Unix group id
687 -- @return              Boolean indicating successful operation
688 -- @return              String containing the error message if failed
689 -- @return              Number containing the error code if failed
690 function process.setgroup(gid)
691         return nixio.setgid(gid)
692 end
693
694 --- Set the uid of a process identified by given pid.
695 -- @param uid   Number containing the Unix user id
696 -- @return              Boolean indicating successful operation
697 -- @return              String containing the error message if failed
698 -- @return              Number containing the error code if failed
699 function process.setuser(uid)
700         return nixio.setuid(uid)
701 end
702
703 --- Send a signal to a process identified by given pid.
704 -- @class function
705 -- @name  process.signal
706 -- @param pid   Number containing the process id
707 -- @param sig   Signal to send (default: 15 [SIGTERM])
708 -- @return              Boolean indicating successful operation
709 -- @return              Number containing the error code if failed
710 process.signal = nixio.kill
711
712
713 --- LuCI system utilities / user related functions.
714 -- @class       module
715 -- @name        luci.sys.user
716 user = {}
717
718 --- Retrieve user informations for given uid.
719 -- @class               function
720 -- @name                getuser
721 -- @param uid   Number containing the Unix user id
722 -- @return              Table containing the following fields:
723 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
724 user.getuser = nixio.getpw
725
726 --- Retrieve the current user password hash.
727 -- @param username      String containing the username to retrieve the password for
728 -- @return                      String containing the hash or nil if no password is set.
729 -- @return                      Password database entry
730 function user.getpasswd(username)
731         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
732         local pwh = pwe and (pwe.pwdp or pwe.passwd)
733         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
734                 return nil, pwe
735         else
736                 return pwh, pwe
737         end
738 end
739
740 --- Test whether given string matches the password of a given system user.
741 -- @param username      String containing the Unix user name
742 -- @param pass          String containing the password to compare
743 -- @return                      Boolean indicating wheather the passwords are equal
744 function user.checkpasswd(username, pass)
745         local pwh, pwe = user.getpasswd(username)
746         if pwe then
747                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
748         end
749         return false
750 end
751
752 --- Change the password of given user.
753 -- @param username      String containing the Unix user name
754 -- @param password      String containing the password to compare
755 -- @return                      Number containing 0 on success and >= 1 on error
756 function user.setpasswd(username, password)
757         if password then
758                 password = password:gsub("'", [['"'"']])
759         end
760
761         if username then
762                 username = username:gsub("'", [['"'"']])
763         end
764
765         return os.execute(
766                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
767                 "passwd '" .. username .. "' >/dev/null 2>&1"
768         )
769 end
770
771
772 --- LuCI system utilities / wifi related functions.
773 -- @class       module
774 -- @name        luci.sys.wifi
775 wifi = {}
776
777 --- Get wireless information for given interface.
778 -- @param ifname        String containing the interface name
779 -- @return              A wrapped iwinfo object instance
780 function wifi.getiwinfo(ifname)
781         local stat, iwinfo = pcall(require, "iwinfo")
782
783         if ifname then
784                 local c = 0
785                 local u = uci.cursor_state()
786                 local d, n = ifname:match("^(%w+)%.network(%d+)")
787                 if d and n then
788                         ifname = d
789                         n = tonumber(n)
790                         u:foreach("wireless", "wifi-iface",
791                                 function(s)
792                                         if s.device == d then
793                                                 c = c + 1
794                                                 if c == n then
795                                                         ifname = s.ifname or s.device
796                                                         return false
797                                                 end
798                                         end
799                                 end)
800                 elseif u:get("wireless", ifname) == "wifi-device" then
801                         u:foreach("wireless", "wifi-iface",
802                                 function(s)
803                                         if s.device == ifname and s.ifname then
804                                                 ifname = s.ifname
805                                                 return false
806                                         end
807                                 end)
808                 end
809
810                 local t = stat and iwinfo.type(ifname)
811                 local x = t and iwinfo[t] or { }
812                 return setmetatable({}, {
813                         __index = function(t, k)
814                                 if k == "ifname" then
815                                         return ifname
816                                 elseif x[k] then
817                                         return x[k](ifname)
818                                 end
819                         end
820                 })
821         end
822 end
823
824
825 --- LuCI system utilities / init related functions.
826 -- @class       module
827 -- @name        luci.sys.init
828 init = {}
829 init.dir = "/etc/init.d/"
830
831 --- Get the names of all installed init scripts
832 -- @return      Table containing the names of all inistalled init scripts
833 function init.names()
834         local names = { }
835         for name in fs.glob(init.dir.."*") do
836                 names[#names+1] = fs.basename(name)
837         end
838         return names
839 end
840
841 --- Get the index of he given init script
842 -- @param name  Name of the init script
843 -- @return              Numeric index value
844 function init.index(name)
845         if fs.access(init.dir..name) then
846                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
847                         %{ init.dir, name })
848         end
849 end
850
851 local function init_action(action, name)
852         if fs.access(init.dir..name) then
853                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
854         end
855 end
856
857 --- Test whether the given init script is enabled
858 -- @param name  Name of the init script
859 -- @return              Boolean indicating whether init is enabled
860 function init.enabled(name)
861         return (init_action("enabled", name) == 0)
862 end
863
864 --- Enable the given init script
865 -- @param name  Name of the init script
866 -- @return              Boolean indicating success
867 function init.enable(name)
868         return (init_action("enable", name) == 1)
869 end
870
871 --- Disable the given init script
872 -- @param name  Name of the init script
873 -- @return              Boolean indicating success
874 function init.disable(name)
875         return (init_action("disable", name) == 0)
876 end
877
878 --- Start the given init script
879 -- @param name  Name of the init script
880 -- @return              Boolean indicating success
881 function init.start(name)
882         return (init_action("start", name) == 0)
883 end
884
885 --- Stop the given init script
886 -- @param name  Name of the init script
887 -- @return              Boolean indicating success
888 function init.stop(name)
889         return (init_action("stop", name) == 0)
890 end
891
892
893 -- Internal functions
894
895 function _parse_mixed_record(cnt, delimiter)
896         delimiter = delimiter or "  "
897         local data = {}
898         local flags = {}
899
900         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
901                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
902                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
903
904                         if k then
905                                 if x == "" then
906                                         table.insert(flags, k)
907                                 else
908                                         data[k] = v
909                                 end
910                         end
911                 end
912         end
913
914         return data, flags
915 end