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