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