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