modules/rpc: Check for existence of external libraries
[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 posix = require "posix"
31 local table = require "table"
32
33 local luci  = {}
34 luci.util   = require "luci.util"
35 luci.fs     = require "luci.fs"
36 luci.ip     = require "luci.ip"
37
38 local tonumber, ipairs, pairs = tonumber, ipairs, pairs
39
40
41 --- LuCI Linux and POSIX system utilities.
42 module "luci.sys"
43
44
45 --- Invoke the luci-flash executable to write an image to the flash memory.
46 -- @param image         Local path or URL to image file
47 -- @param kpattern      Pattern of files to keep over flash process
48 -- @return                      Return value of os.execute()
49 function flash(image, kpattern)
50         local cmd = "luci-flash "
51         if kpattern then
52                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
53         end
54         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
55
56         return os.execute(cmd)
57 end
58
59 --- Retrieve information about currently mounted file systems.
60 -- @return      Table containing mount information
61 function mounts()
62         local data = {}
63         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
64         local ps = luci.util.execi("df")
65         
66         if not ps then
67                 return
68         else
69                 ps()
70         end
71         
72         for line in ps do
73                 local row = {}
74                 
75                 local j = 1
76                 for value in line:gmatch("[^%s]+") do
77                         row[k[j]] = value
78                         j = j + 1
79                 end
80                 
81                 if row[k[1]] then
82                         table.insert(data, row)
83                 end
84         end
85         
86         return data
87 end
88
89 --- Retrieve environment variables. If no variable is given then a table
90 -- containing the whole environment is returned otherwise this function returns
91 -- the corresponding string value for the given name or nil if no such variable
92 -- exists.
93 -- @class               function
94 -- @name                getenv
95 -- @param var   Name of the environment variable to retrieve (optional)
96 -- @return              String containg the value of the specified variable
97 -- @return              Table containing all variables if no variable name is given
98 getenv = posix.getenv
99
100 --- Determine the current hostname.
101 -- @return              String containing the system hostname
102 function hostname()
103         return io.lines("/proc/sys/kernel/hostname")()
104 end
105
106 --- Returns the contents of a documented referred by an URL.
107 -- @param url    The URL to retrieve
108 -- @param stream Return a stream instead of a buffer
109 -- @param target Directly write to target file name
110 -- @return              String containing the contents of given the URL
111 function httpget(url, stream, target)
112         if not target then
113                 local source = stream and io.open or luci.util.exec
114                 return source("wget -qO- '"..url:gsub("'", "").."'")
115         else
116                 return os.execute("wget -qO '%s' '%s'" %
117                         {target:gsub("'", ""), url:gsub("'", "")})
118         end
119 end
120
121 --- Returns the system load average values.
122 -- @return      String containing the average load value 1 minute ago
123 -- @return      String containing the average load value 5 minutes ago
124 -- @return      String containing the average load value 15 minutes ago
125 -- @return      String containing the active and total number of processes
126 -- @return      String containing the last used pid
127 function loadavg()
128         local loadavg = io.lines("/proc/loadavg")()
129         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
130 end
131
132 --- Initiate a system reboot.
133 -- @return      Return value of os.execute()
134 function reboot()
135         return os.execute("reboot >/dev/null 2>&1")
136 end
137
138 --- Returns the system type, cpu name and installed physical memory.
139 -- @return      String containing the system or platform identifier
140 -- @return      String containing hardware model information
141 -- @return      String containing the total memory amount in kB
142 -- @return      String containing the memory used for caching in kB
143 -- @return      String containing the memory used for buffering in kB
144 -- @return      String containing the free memory amount in kB
145 function sysinfo()
146         local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
147         local c2 = "uname -m 2>/dev/null"
148         local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
149         local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
150         local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
151         local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
152         local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
153         local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
154
155         local system = luci.util.trim(luci.util.exec(c1))
156         local model = ""
157         local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
158         local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
159         local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
160         local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
161
162         if system == "" then
163                 system = luci.util.trim(luci.util.exec(c2))
164                 model = luci.util.trim(luci.util.exec(c3))
165         else
166                 model = luci.util.trim(luci.util.exec(c4))
167         end
168
169         return system, model, memtotal, memcached, membuffers, memfree
170 end
171
172 --- Retrieves the output of the "logread" command.
173 -- @return      String containing the current log buffer
174 function syslog()
175         return luci.util.exec("logread")
176 end
177
178 --- Generates a random id with specified length.
179 -- @param bytes Number of bytes for the unique id
180 -- @return              String containing hex encoded id
181 function uniqueid(bytes)
182         local fp    = io.open("/dev/urandom")
183         local chunk = { fp:read(bytes):byte(1, bytes) }
184         fp:close()
185
186         local hex = ""
187
188         local pattern = "%02X"
189         for i, byte in ipairs(chunk) do
190                 hex = hex .. pattern:format(byte)
191         end
192
193         return hex
194 end
195
196 --- Returns the current system uptime stats.
197 -- @return      String containing total uptime in seconds
198 -- @return      String containing idle time in seconds
199 function uptime()
200         local loadavg = io.lines("/proc/uptime")()
201         return loadavg:match("^(.-) (.-)$")
202 end
203
204 --- LuCI system utilities / POSIX user group related functions.
205 -- @class       module
206 -- @name        luci.sys.group
207 group = {}
208
209 --- Returns information about a POSIX user group.
210 -- @class function
211 -- @name                getgroup
212 -- @param group Group ID or name of a system user group
213 -- @return      Table with information about the requested group
214 group.getgroup = posix.getgroup
215
216
217 --- LuCI system utilities / network related functions.
218 -- @class       module
219 -- @name        luci.sys.net
220 net = {}
221
222 --- Returns the current arp-table entries as two-dimensional table.
223 -- @return      Table of table containing the current arp entries.
224 --                      The following fields are defined for arp entry objects:
225 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
226 function net.arptable()
227         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
228 end
229
230 --- Determine the current default route.
231 -- @return      Table with the properties of the current default route.
232 --                      The following fields are defined:
233 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
234 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
235 function net.defaultroute()
236         local routes = net.routes()
237         local route = nil
238
239         for i, r in pairs(luci.sys.net.routes()) do
240                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
241                         route = r
242                 end
243         end
244
245         return route
246 end
247
248 --- Determine the names of available network interfaces.
249 -- @return      Table containing all current interface names
250 function net.devices()
251         local devices = {}
252         for line in io.lines("/proc/net/dev") do
253                 table.insert(devices, line:match(" *(.-):"))
254         end
255         return devices
256 end
257
258
259 --- Return information about available network interfaces.
260 -- @return      Table containing all current interface names and their information
261 function net.deviceinfo()
262         local devices = {}
263         for line in io.lines("/proc/net/dev") do
264                 local name, data = line:match("^ *(.-): *(.*)$")
265                 if name and data then
266                         devices[name] = luci.util.split(data, " +", nil, true)
267                 end
268         end
269         return devices
270 end
271
272
273 -- Determine the MAC address belonging to the given IP address.
274 -- @param ip    IPv4 address
275 -- @return              String containing the MAC address or nil if it cannot be found
276 function net.ip4mac(ip)
277         local mac = nil
278
279         for i, l in ipairs(net.arptable()) do
280                 if l["IP address"] == ip then
281                         mac = l["HW address"]
282                 end
283         end
284
285         return mac
286 end
287
288 --- Returns the current kernel routing table entries.
289 -- @return      Table of tables with properties of the corresponding routes.
290 --                      The following fields are defined for route entry tables:
291 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
292 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
293 function net.routes()
294         return _parse_delimited_table(io.lines("/proc/net/route"))
295 end
296
297
298 --- Tests whether the given host responds to ping probes.
299 -- @param host  String containing a hostname or IPv4 address
300 -- @return              Number containing 0 on success and >= 1 on error
301 function net.pingtest(host)
302         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
303 end
304
305
306 --- LuCI system utilities / process related functions.
307 -- @class       module
308 -- @name        luci.sys.process
309 process = {}
310
311 --- Get the current process id.
312 -- @return      Number containing the current pid
313 process.info = posix.getpid
314
315 --- Retrieve information about currently running processes.
316 -- @return      Table containing process information
317 function process.list()
318         local data = {}
319         local k
320         local ps = luci.util.execi("top -bn1")
321         
322         if not ps then
323                 return
324         end
325         
326         while true do
327                 local line = ps()
328                 if not line then
329                         return
330                 end
331                 
332                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
333                 if k[1] == "PID" then
334                         break
335                 end
336         end
337         
338         for line in ps do
339                 local row = {}
340                 
341                 line = luci.util.trim(line)
342                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
343                         row[k[i]] = value
344                 end
345                 
346                 local pid = tonumber(row[k[1]])
347                 if pid then
348                         data[pid] = row
349                 end
350         end
351         
352         return data
353 end
354
355 --- Set the gid of a process identified by given pid.
356 -- @param pid   Number containing the process id
357 -- @param gid   Number containing the Unix group id
358 -- @return              Boolean indicating successful operation
359 -- @return              String containing the error message if failed
360 -- @return              Number containing the error code if failed
361 function process.setgroup(pid, gid)
362         return posix.setpid("g", pid, gid)
363 end
364
365 --- Set the uid of a process identified by given pid.
366 -- @param pid   Number containing the process id
367 -- @param uid   Number containing the Unix user id
368 -- @return              Boolean indicating successful operation
369 -- @return              String containing the error message if failed
370 -- @return              Number containing the error code if failed
371 function process.setuser(pid, uid)
372         return posix.setpid("u", pid, uid)
373 end
374
375 --- Send a signal to a process identified by given pid.
376 -- @param pid   Number containing the process id
377 -- @param sig   Signal to send (default: 15 [SIGTERM])
378 -- @return              Boolean indicating successful operation
379 -- @return              Number containing the error code if failed
380 process.signal = posix.kill
381
382
383 --- LuCI system utilities / user related functions.
384 -- @class       module
385 -- @name        luci.sys.user
386 user = {}
387
388 --- Retrieve user informations for given uid.
389 -- @class               function
390 -- @name                getuser
391 -- @param uid   Number containing the Unix user id
392 -- @return              Table containing the following fields:
393 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
394 user.getuser = posix.getpasswd
395
396 --- Test whether given string matches the password of a given system user.
397 -- @param username      String containing the Unix user name
398 -- @param password      String containing the password to compare
399 -- @return                      Boolean indicating wheather the passwords are equal
400 function user.checkpasswd(username, password)
401         local account = user.getuser(username)
402
403         if account then
404                 if account.passwd == "!" then
405                         return true
406                 else
407                         return (account.passwd == posix.crypt(password, account.passwd))
408                 end
409         end
410 end
411
412 --- Change the password of given user.
413 -- @param username      String containing the Unix user name
414 -- @param password      String containing the password to compare
415 -- @return                      Number containing 0 on success and >= 1 on error
416 function user.setpasswd(username, password)
417         if password then
418                 password = password:gsub("'", "")
419         end
420
421         if username then
422                 username = username:gsub("'", "")
423         end
424
425         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
426         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
427         return os.execute(cmd)
428 end
429
430
431 --- LuCI system utilities / wifi related functions.
432 -- @class       module
433 -- @name        luci.sys.wifi
434 wifi = {}
435
436 --- Get iwconfig output for all wireless devices.
437 -- @return      Table of tables containing the iwconfing output for each wifi device
438 function wifi.getiwconfig()
439         local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
440         local iwc = {}
441
442         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
443                 local k = l:match("^(.-) ")
444                 l = l:gsub("^(.-) +", "", 1)
445                 if k then
446                         iwc[k] = _parse_mixed_record(l)
447                 end
448         end
449
450         return iwc
451 end
452
453 --- Get iwlist scan output from all wireless devices.
454 -- @return      Table of tables contaiing all scan results
455 function wifi.iwscan(iface)
456         local siface = iface or ""
457         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
458         local iws = {}
459
460         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
461                 local k = l:match("^(.-) ")
462                 l = l:gsub("^[^\n]+", "", 1)
463                 l = luci.util.trim(l)
464                 if k then
465                         iws[k] = {}
466                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
467                                 c = c:gsub("^(.-)- ", "", 1)
468                                 c = luci.util.split(c, "\n", 7)
469                                 c = table.concat(c, "\n", 1)
470                                 table.insert(iws[k], _parse_mixed_record(c))
471                         end
472                 end
473         end
474
475         return iface and (iws[iface] or {}) or iws
476 end
477
478
479 -- Internal functions
480
481 function _parse_delimited_table(iter, delimiter)
482         delimiter = delimiter or "%s+"
483
484         local data  = {}
485         local trim  = luci.util.trim
486         local split = luci.util.split
487
488         local keys = split(trim(iter()), delimiter, nil, true)
489         for i, j in pairs(keys) do
490                 keys[i] = trim(keys[i])
491         end
492
493         for line in iter do
494                 local row = {}
495                 line = trim(line)
496                 if #line > 0 then
497                         for i, j in pairs(split(line, delimiter, nil, true)) do
498                                 if keys[i] then
499                                         row[keys[i]] = j
500                                 end
501                         end
502                 end
503                 table.insert(data, row)
504         end
505
506         return data
507 end
508
509 function _parse_mixed_record(cnt)
510         local data = {}
511
512         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
513         for j, f in pairs(luci.util.split(luci.util.trim(l), "  ")) do
514                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
515
516             if k then
517                                 if x == "" then
518                                         table.insert(data, k)
519                                 else
520                         data[k] = v
521                                 end
522             end
523         end
524         end
525
526     return data
527 end