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