5 Utilities for interaction with the Linux system
11 Copyright 2008 Steven Barth <steven@midlink.org>
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
17 http://www.apache.org/licenses/LICENSE-2.0
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.
28 local io = require "io"
29 local os = require "os"
30 local posix = require "posix"
31 local table = require "table"
34 luci.util = require "luci.util"
35 luci.fs = require "luci.fs"
36 luci.ip = require "luci.ip"
38 local tonumber, ipairs, pairs = tonumber, ipairs, pairs
41 --- LuCI Linux and POSIX system utilities.
44 --- Execute a given shell command and return the error code
47 -- @param ... Command to call
48 -- @return Error code of the command
50 return os.execute(...) / 256
53 --- Execute a given shell command and capture its standard output
56 -- @param command Command to call
57 -- @return String containg the return the output of the command
60 --- Invoke the luci-flash executable to write an image to the flash memory.
61 -- @param image Local path or URL to image file
62 -- @param kpattern Pattern of files to keep over flash process
63 -- @return boolean indicating status
64 -- @return error message if any
65 function flash(image, kpattern)
66 local cmd = "luci-flash "
68 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
70 cmd = cmd .. "'" .. image:gsub("'", "") .. "' 2>/dev/null"
72 local fp = io.popen(cmd)
75 local line = fp:read()
77 if line == "Invalid image type" then
82 if line == "Performing system upgrade..." then
87 if line == "Performing system upgrade..." then
96 --- Retrieve information about currently mounted file systems.
97 -- @return Table containing mount information
100 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
101 local ps = luci.util.execi("df")
113 for value in line:gmatch("[^%s]+") do
119 table.insert(data, row)
126 --- Retrieve environment variables. If no variable is given then a table
127 -- containing the whole environment is returned otherwise this function returns
128 -- the corresponding string value for the given name or nil if no such variable
132 -- @param var Name of the environment variable to retrieve (optional)
133 -- @return String containg the value of the specified variable
134 -- @return Table containing all variables if no variable name is given
135 getenv = posix.getenv
137 --- Determine the current hostname.
138 -- @return String containing the system hostname
140 return io.lines("/proc/sys/kernel/hostname")()
143 --- Returns the contents of a documented referred by an URL.
144 -- @param url The URL to retrieve
145 -- @param stream Return a stream instead of a buffer
146 -- @param target Directly write to target file name
147 -- @return String containing the contents of given the URL
148 function httpget(url, stream, target)
150 local source = stream and io.open or luci.util.exec
151 return source("wget -qO- '"..url:gsub("'", "").."'")
153 return os.execute("wget -qO '%s' '%s'" %
154 {target:gsub("'", ""), url:gsub("'", "")})
158 --- Returns the system load average values.
159 -- @return String containing the average load value 1 minute ago
160 -- @return String containing the average load value 5 minutes ago
161 -- @return String containing the average load value 15 minutes ago
162 -- @return String containing the active and total number of processes
163 -- @return String containing the last used pid
165 local loadavg = io.lines("/proc/loadavg")()
166 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
169 --- Initiate a system reboot.
170 -- @return Return value of os.execute()
172 return os.execute("reboot >/dev/null 2>&1")
175 --- Returns the system type, cpu name and installed physical memory.
176 -- @return String containing the system or platform identifier
177 -- @return String containing hardware model information
178 -- @return String containing the total memory amount in kB
179 -- @return String containing the memory used for caching in kB
180 -- @return String containing the memory used for buffering in kB
181 -- @return String containing the free memory amount in kB
183 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
184 local c2 = "uname -m 2>/dev/null"
185 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
186 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
187 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
188 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
189 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
190 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
192 local system = luci.util.trim(luci.util.exec(c1))
194 local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
195 local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
196 local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
197 local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
200 system = luci.util.trim(luci.util.exec(c2))
201 model = luci.util.trim(luci.util.exec(c3))
203 model = luci.util.trim(luci.util.exec(c4))
206 return system, model, memtotal, memcached, membuffers, memfree
209 --- Retrieves the output of the "logread" command.
210 -- @return String containing the current log buffer
212 return luci.util.exec("logread")
215 --- Generates a random id with specified length.
216 -- @param bytes Number of bytes for the unique id
217 -- @return String containing hex encoded id
218 function uniqueid(bytes)
219 local fp = io.open("/dev/urandom")
220 local chunk = { fp:read(bytes):byte(1, bytes) }
225 local pattern = "%02X"
226 for i, byte in ipairs(chunk) do
227 hex = hex .. pattern:format(byte)
233 --- Returns the current system uptime stats.
234 -- @return String containing total uptime in seconds
235 -- @return String containing idle time in seconds
237 local loadavg = io.lines("/proc/uptime")()
238 return loadavg:match("^(.-) (.-)$")
241 --- LuCI system utilities / POSIX user group related functions.
243 -- @name luci.sys.group
246 --- Returns information about a POSIX user group.
249 -- @param group Group ID or name of a system user group
250 -- @return Table with information about the requested group
251 group.getgroup = posix.getgroup
254 --- LuCI system utilities / network related functions.
256 -- @name luci.sys.net
259 --- Returns the current arp-table entries as two-dimensional table.
260 -- @return Table of table containing the current arp entries.
261 -- The following fields are defined for arp entry objects:
262 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
263 function net.arptable()
264 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
267 --- Determine the current default route.
268 -- @return Table with the properties of the current default route.
269 -- The following fields are defined:
270 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
271 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
272 function net.defaultroute()
273 local routes = net.routes()
276 for i, r in pairs(luci.sys.net.routes()) do
277 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
285 --- Determine the names of available network interfaces.
286 -- @return Table containing all current interface names
287 function net.devices()
289 for line in io.lines("/proc/net/dev") do
290 table.insert(devices, line:match(" *(.-):"))
296 --- Return information about available network interfaces.
297 -- @return Table containing all current interface names and their information
298 function net.deviceinfo()
300 for line in io.lines("/proc/net/dev") do
301 local name, data = line:match("^ *(.-): *(.*)$")
302 if name and data then
303 devices[name] = luci.util.split(data, " +", nil, true)
310 -- Determine the MAC address belonging to the given IP address.
311 -- @param ip IPv4 address
312 -- @return String containing the MAC address or nil if it cannot be found
313 function net.ip4mac(ip)
316 for i, l in ipairs(net.arptable()) do
317 if l["IP address"] == ip then
318 mac = l["HW address"]
325 --- Returns the current kernel routing table entries.
326 -- @return Table of tables with properties of the corresponding routes.
327 -- The following fields are defined for route entry tables:
328 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
329 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
330 function net.routes()
331 return _parse_delimited_table(io.lines("/proc/net/route"))
335 --- Tests whether the given host responds to ping probes.
336 -- @param host String containing a hostname or IPv4 address
337 -- @return Number containing 0 on success and >= 1 on error
338 function net.pingtest(host)
339 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
343 --- LuCI system utilities / process related functions.
345 -- @name luci.sys.process
348 --- Get the current process id.
350 -- @name process.info
351 -- @return Number containing the current pid
352 process.info = posix.getpid
354 --- Retrieve information about currently running processes.
355 -- @return Table containing process information
356 function process.list()
359 local ps = luci.util.execi("top -bn1")
371 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
372 if k[1] == "PID" then
380 line = luci.util.trim(line)
381 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
385 local pid = tonumber(row[k[1]])
394 --- Set the gid of a process identified by given pid.
395 -- @param pid Number containing the process id
396 -- @param gid Number containing the Unix group id
397 -- @return Boolean indicating successful operation
398 -- @return String containing the error message if failed
399 -- @return Number containing the error code if failed
400 function process.setgroup(pid, gid)
401 return posix.setpid("g", pid, gid)
404 --- Set the uid of a process identified by given pid.
405 -- @param pid Number containing the process id
406 -- @param uid Number containing the Unix user id
407 -- @return Boolean indicating successful operation
408 -- @return String containing the error message if failed
409 -- @return Number containing the error code if failed
410 function process.setuser(pid, uid)
411 return posix.setpid("u", pid, uid)
414 --- Send a signal to a process identified by given pid.
416 -- @name process.signal
417 -- @param pid Number containing the process id
418 -- @param sig Signal to send (default: 15 [SIGTERM])
419 -- @return Boolean indicating successful operation
420 -- @return Number containing the error code if failed
421 process.signal = posix.kill
424 --- LuCI system utilities / user related functions.
426 -- @name luci.sys.user
429 --- Retrieve user informations for given uid.
432 -- @param uid Number containing the Unix user id
433 -- @return Table containing the following fields:
434 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
435 user.getuser = posix.getpasswd
437 --- Test whether given string matches the password of a given system user.
438 -- @param username String containing the Unix user name
439 -- @param password String containing the password to compare
440 -- @return Boolean indicating wheather the passwords are equal
441 function user.checkpasswd(username, password)
442 local account = user.getuser(username)
445 if account.passwd == "!" then
448 return (account.passwd == posix.crypt(password, account.passwd))
453 --- Change the password of given user.
454 -- @param username String containing the Unix user name
455 -- @param password String containing the password to compare
456 -- @return Number containing 0 on success and >= 1 on error
457 function user.setpasswd(username, password)
459 password = password:gsub("'", "")
463 username = username:gsub("'", "")
466 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
467 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
468 return os.execute(cmd)
472 --- LuCI system utilities / wifi related functions.
474 -- @name luci.sys.wifi
477 --- Get iwconfig output for all wireless devices.
478 -- @return Table of tables containing the iwconfing output for each wifi device
479 function wifi.getiwconfig()
480 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
483 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
484 local k = l:match("^(.-) ")
485 l = l:gsub("^(.-) +", "", 1)
487 iwc[k] = _parse_mixed_record(l)
494 --- Get iwlist scan output from all wireless devices.
495 -- @return Table of tables contaiing all scan results
496 function wifi.iwscan(iface)
497 local siface = iface or ""
498 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
501 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
502 local k = l:match("^(.-) ")
503 l = l:gsub("^[^\n]+", "", 1)
504 l = luci.util.trim(l)
507 for j, c in pairs(luci.util.split(l, "\n Cell")) do
508 c = c:gsub("^(.-)- ", "", 1)
509 c = luci.util.split(c, "\n", 7)
510 c = table.concat(c, "\n", 1)
511 table.insert(iws[k], _parse_mixed_record(c))
516 return iface and (iws[iface] or {}) or iws
520 -- Internal functions
522 function _parse_delimited_table(iter, delimiter)
523 delimiter = delimiter or "%s+"
526 local trim = luci.util.trim
527 local split = luci.util.split
529 local keys = split(trim(iter()), delimiter, nil, true)
530 for i, j in pairs(keys) do
531 keys[i] = trim(keys[i])
538 for i, j in pairs(split(line, delimiter, nil, true)) do
544 table.insert(data, row)
550 function _parse_mixed_record(cnt)
553 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
554 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
555 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
559 table.insert(data, k)