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
86 --- Retrieve information about currently mounted file systems.
87 -- @return Table containing mount information
90 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
91 local ps = luci.util.execi("df")
103 for value in line:gmatch("[^%s]+") do
109 table.insert(data, row)
116 --- Retrieve environment variables. If no variable is given then a table
117 -- containing the whole environment is returned otherwise this function returns
118 -- the corresponding string value for the given name or nil if no such variable
122 -- @param var Name of the environment variable to retrieve (optional)
123 -- @return String containg the value of the specified variable
124 -- @return Table containing all variables if no variable name is given
125 getenv = posix.getenv
127 --- Determine the current hostname.
128 -- @return String containing the system hostname
130 return io.lines("/proc/sys/kernel/hostname")()
133 --- Returns the contents of a documented referred by an URL.
134 -- @param url The URL to retrieve
135 -- @param stream Return a stream instead of a buffer
136 -- @param target Directly write to target file name
137 -- @return String containing the contents of given the URL
138 function httpget(url, stream, target)
140 local source = stream and io.open or luci.util.exec
141 return source("wget -qO- '"..url:gsub("'", "").."'")
143 return os.execute("wget -qO '%s' '%s'" %
144 {target:gsub("'", ""), url:gsub("'", "")})
148 --- Returns the system load average values.
149 -- @return String containing the average load value 1 minute ago
150 -- @return String containing the average load value 5 minutes ago
151 -- @return String containing the average load value 15 minutes ago
152 -- @return String containing the active and total number of processes
153 -- @return String containing the last used pid
155 local loadavg = io.lines("/proc/loadavg")()
156 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
159 --- Initiate a system reboot.
160 -- @return Return value of os.execute()
162 return os.execute("reboot >/dev/null 2>&1")
165 --- Returns the system type, cpu name and installed physical memory.
166 -- @return String containing the system or platform identifier
167 -- @return String containing hardware model information
168 -- @return String containing the total memory amount in kB
169 -- @return String containing the memory used for caching in kB
170 -- @return String containing the memory used for buffering in kB
171 -- @return String containing the free memory amount in kB
173 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
174 local c2 = "uname -m 2>/dev/null"
175 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
176 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
177 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
178 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
179 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
180 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
182 local system = luci.util.trim(luci.util.exec(c1))
184 local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
185 local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
186 local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
187 local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
190 system = luci.util.trim(luci.util.exec(c2))
191 model = luci.util.trim(luci.util.exec(c3))
193 model = luci.util.trim(luci.util.exec(c4))
196 return system, model, memtotal, memcached, membuffers, memfree
199 --- Retrieves the output of the "logread" command.
200 -- @return String containing the current log buffer
202 return luci.util.exec("logread")
205 --- Generates a random id with specified length.
206 -- @param bytes Number of bytes for the unique id
207 -- @return String containing hex encoded id
208 function uniqueid(bytes)
209 local fp = io.open("/dev/urandom")
210 local chunk = { fp:read(bytes):byte(1, bytes) }
215 local pattern = "%02X"
216 for i, byte in ipairs(chunk) do
217 hex = hex .. pattern:format(byte)
223 --- Returns the current system uptime stats.
224 -- @return String containing total uptime in seconds
225 -- @return String containing idle time in seconds
227 local loadavg = io.lines("/proc/uptime")()
228 return loadavg:match("^(.-) (.-)$")
231 --- LuCI system utilities / POSIX user group related functions.
233 -- @name luci.sys.group
236 --- Returns information about a POSIX user group.
239 -- @param group Group ID or name of a system user group
240 -- @return Table with information about the requested group
241 group.getgroup = posix.getgroup
244 --- LuCI system utilities / network related functions.
246 -- @name luci.sys.net
249 --- Returns the current arp-table entries as two-dimensional table.
250 -- @return Table of table containing the current arp entries.
251 -- The following fields are defined for arp entry objects:
252 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
253 function net.arptable()
254 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
257 --- Determine the current default route.
258 -- @return Table with the properties of the current default route.
259 -- The following fields are defined:
260 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
261 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
262 function net.defaultroute()
263 local routes = net.routes()
266 for i, r in pairs(luci.sys.net.routes()) do
267 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
275 --- Determine the names of available network interfaces.
276 -- @return Table containing all current interface names
277 function net.devices()
279 for line in io.lines("/proc/net/dev") do
280 table.insert(devices, line:match(" *(.-):"))
286 --- Return information about available network interfaces.
287 -- @return Table containing all current interface names and their information
288 function net.deviceinfo()
290 for line in io.lines("/proc/net/dev") do
291 local name, data = line:match("^ *(.-): *(.*)$")
292 if name and data then
293 devices[name] = luci.util.split(data, " +", nil, true)
300 -- Determine the MAC address belonging to the given IP address.
301 -- @param ip IPv4 address
302 -- @return String containing the MAC address or nil if it cannot be found
303 function net.ip4mac(ip)
306 for i, l in ipairs(net.arptable()) do
307 if l["IP address"] == ip then
308 mac = l["HW address"]
315 --- Returns the current kernel routing table entries.
316 -- @return Table of tables with properties of the corresponding routes.
317 -- The following fields are defined for route entry tables:
318 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
319 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
320 function net.routes()
321 return _parse_delimited_table(io.lines("/proc/net/route"))
325 --- Tests whether the given host responds to ping probes.
326 -- @param host String containing a hostname or IPv4 address
327 -- @return Number containing 0 on success and >= 1 on error
328 function net.pingtest(host)
329 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
333 --- LuCI system utilities / process related functions.
335 -- @name luci.sys.process
338 --- Get the current process id.
340 -- @name process.info
341 -- @return Number containing the current pid
342 process.info = posix.getpid
344 --- Retrieve information about currently running processes.
345 -- @return Table containing process information
346 function process.list()
349 local ps = luci.util.execi("top -bn1")
361 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
362 if k[1] == "PID" then
370 line = luci.util.trim(line)
371 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
375 local pid = tonumber(row[k[1]])
384 --- Set the gid of a process identified by given pid.
385 -- @param pid Number containing the process id
386 -- @param gid Number containing the Unix group id
387 -- @return Boolean indicating successful operation
388 -- @return String containing the error message if failed
389 -- @return Number containing the error code if failed
390 function process.setgroup(pid, gid)
391 return posix.setpid("g", pid, gid)
394 --- Set the uid of a process identified by given pid.
395 -- @param pid Number containing the process id
396 -- @param uid Number containing the Unix user 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.setuser(pid, uid)
401 return posix.setpid("u", pid, uid)
404 --- Send a signal to a process identified by given pid.
406 -- @name process.signal
407 -- @param pid Number containing the process id
408 -- @param sig Signal to send (default: 15 [SIGTERM])
409 -- @return Boolean indicating successful operation
410 -- @return Number containing the error code if failed
411 process.signal = posix.kill
414 --- LuCI system utilities / user related functions.
416 -- @name luci.sys.user
419 --- Retrieve user informations for given uid.
422 -- @param uid Number containing the Unix user id
423 -- @return Table containing the following fields:
424 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
425 user.getuser = posix.getpasswd
427 --- Test whether given string matches the password of a given system user.
428 -- @param username String containing the Unix user name
429 -- @param password String containing the password to compare
430 -- @return Boolean indicating wheather the passwords are equal
431 function user.checkpasswd(username, password)
432 local account = user.getuser(username)
435 if account.passwd == "!" then
438 return (account.passwd == posix.crypt(password, account.passwd))
443 --- Change the password of given user.
444 -- @param username String containing the Unix user name
445 -- @param password String containing the password to compare
446 -- @return Number containing 0 on success and >= 1 on error
447 function user.setpasswd(username, password)
449 password = password:gsub("'", "")
453 username = username:gsub("'", "")
456 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
457 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
458 return os.execute(cmd)
462 --- LuCI system utilities / wifi related functions.
464 -- @name luci.sys.wifi
467 --- Get iwconfig output for all wireless devices.
468 -- @return Table of tables containing the iwconfing output for each wifi device
469 function wifi.getiwconfig()
470 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
473 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
474 local k = l:match("^(.-) ")
475 l = l:gsub("^(.-) +", "", 1)
477 iwc[k] = _parse_mixed_record(l)
484 --- Get iwlist scan output from all wireless devices.
485 -- @return Table of tables contaiing all scan results
486 function wifi.iwscan(iface)
487 local siface = iface or ""
488 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
491 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
492 local k = l:match("^(.-) ")
493 l = l:gsub("^[^\n]+", "", 1)
494 l = luci.util.trim(l)
497 for j, c in pairs(luci.util.split(l, "\n Cell")) do
498 c = c:gsub("^(.-)- ", "", 1)
499 c = luci.util.split(c, "\n", 7)
500 c = table.concat(c, "\n", 1)
501 table.insert(iws[k], _parse_mixed_record(c))
506 return iface and (iws[iface] or {}) or iws
510 -- Internal functions
512 function _parse_delimited_table(iter, delimiter)
513 delimiter = delimiter or "%s+"
516 local trim = luci.util.trim
517 local split = luci.util.split
519 local keys = split(trim(iter()), delimiter, nil, true)
520 for i, j in pairs(keys) do
521 keys[i] = trim(keys[i])
528 for i, j in pairs(split(line, delimiter, nil, true)) do
534 table.insert(data, row)
540 function _parse_mixed_record(cnt)
543 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
544 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
545 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
549 table.insert(data, k)