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