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