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