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