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 Return value of os.execute()
64 function flash(image, kpattern)
65 local cmd = "luci-flash "
67 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
69 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
71 return os.execute(cmd)
74 --- Retrieve information about currently mounted file systems.
75 -- @return Table containing mount information
78 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
79 local ps = luci.util.execi("df")
91 for value in line:gmatch("[^%s]+") do
97 table.insert(data, row)
104 --- Retrieve environment variables. If no variable is given then a table
105 -- containing the whole environment is returned otherwise this function returns
106 -- the corresponding string value for the given name or nil if no such variable
110 -- @param var Name of the environment variable to retrieve (optional)
111 -- @return String containg the value of the specified variable
112 -- @return Table containing all variables if no variable name is given
113 getenv = posix.getenv
115 --- Determine the current hostname.
116 -- @return String containing the system hostname
118 return io.lines("/proc/sys/kernel/hostname")()
121 --- Returns the contents of a documented referred by an URL.
122 -- @param url The URL to retrieve
123 -- @param stream Return a stream instead of a buffer
124 -- @param target Directly write to target file name
125 -- @return String containing the contents of given the URL
126 function httpget(url, stream, target)
128 local source = stream and io.open or luci.util.exec
129 return source("wget -qO- '"..url:gsub("'", "").."'")
131 return os.execute("wget -qO '%s' '%s'" %
132 {target:gsub("'", ""), url:gsub("'", "")})
136 --- Returns the system load average values.
137 -- @return String containing the average load value 1 minute ago
138 -- @return String containing the average load value 5 minutes ago
139 -- @return String containing the average load value 15 minutes ago
140 -- @return String containing the active and total number of processes
141 -- @return String containing the last used pid
143 local loadavg = io.lines("/proc/loadavg")()
144 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
147 --- Initiate a system reboot.
148 -- @return Return value of os.execute()
150 return os.execute("reboot >/dev/null 2>&1")
153 --- Returns the system type, cpu name and installed physical memory.
154 -- @return String containing the system or platform identifier
155 -- @return String containing hardware model information
156 -- @return String containing the total memory amount in kB
157 -- @return String containing the memory used for caching in kB
158 -- @return String containing the memory used for buffering in kB
159 -- @return String containing the free memory amount in kB
161 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
162 local c2 = "uname -m 2>/dev/null"
163 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
164 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
165 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
166 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
167 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
168 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
170 local system = luci.util.trim(luci.util.exec(c1))
172 local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
173 local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
174 local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
175 local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
178 system = luci.util.trim(luci.util.exec(c2))
179 model = luci.util.trim(luci.util.exec(c3))
181 model = luci.util.trim(luci.util.exec(c4))
184 return system, model, memtotal, memcached, membuffers, memfree
187 --- Retrieves the output of the "logread" command.
188 -- @return String containing the current log buffer
190 return luci.util.exec("logread")
193 --- Generates a random id with specified length.
194 -- @param bytes Number of bytes for the unique id
195 -- @return String containing hex encoded id
196 function uniqueid(bytes)
197 local fp = io.open("/dev/urandom")
198 local chunk = { fp:read(bytes):byte(1, bytes) }
203 local pattern = "%02X"
204 for i, byte in ipairs(chunk) do
205 hex = hex .. pattern:format(byte)
211 --- Returns the current system uptime stats.
212 -- @return String containing total uptime in seconds
213 -- @return String containing idle time in seconds
215 local loadavg = io.lines("/proc/uptime")()
216 return loadavg:match("^(.-) (.-)$")
219 --- LuCI system utilities / POSIX user group related functions.
221 -- @name luci.sys.group
224 --- Returns information about a POSIX user group.
227 -- @param group Group ID or name of a system user group
228 -- @return Table with information about the requested group
229 group.getgroup = posix.getgroup
232 --- LuCI system utilities / network related functions.
234 -- @name luci.sys.net
237 --- Returns the current arp-table entries as two-dimensional table.
238 -- @return Table of table containing the current arp entries.
239 -- The following fields are defined for arp entry objects:
240 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
241 function net.arptable()
242 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
245 --- Determine the current default route.
246 -- @return Table with the properties of the current default route.
247 -- The following fields are defined:
248 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
249 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
250 function net.defaultroute()
251 local routes = net.routes()
254 for i, r in pairs(luci.sys.net.routes()) do
255 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
263 --- Determine the names of available network interfaces.
264 -- @return Table containing all current interface names
265 function net.devices()
267 for line in io.lines("/proc/net/dev") do
268 table.insert(devices, line:match(" *(.-):"))
274 --- Return information about available network interfaces.
275 -- @return Table containing all current interface names and their information
276 function net.deviceinfo()
278 for line in io.lines("/proc/net/dev") do
279 local name, data = line:match("^ *(.-): *(.*)$")
280 if name and data then
281 devices[name] = luci.util.split(data, " +", nil, true)
288 -- Determine the MAC address belonging to the given IP address.
289 -- @param ip IPv4 address
290 -- @return String containing the MAC address or nil if it cannot be found
291 function net.ip4mac(ip)
294 for i, l in ipairs(net.arptable()) do
295 if l["IP address"] == ip then
296 mac = l["HW address"]
303 --- Returns the current kernel routing table entries.
304 -- @return Table of tables with properties of the corresponding routes.
305 -- The following fields are defined for route entry tables:
306 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
307 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
308 function net.routes()
309 return _parse_delimited_table(io.lines("/proc/net/route"))
313 --- Tests whether the given host responds to ping probes.
314 -- @param host String containing a hostname or IPv4 address
315 -- @return Number containing 0 on success and >= 1 on error
316 function net.pingtest(host)
317 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
321 --- LuCI system utilities / process related functions.
323 -- @name luci.sys.process
326 --- Get the current process id.
328 -- @name process.info
329 -- @return Number containing the current pid
330 process.info = posix.getpid
332 --- Retrieve information about currently running processes.
333 -- @return Table containing process information
334 function process.list()
337 local ps = luci.util.execi("top -bn1")
349 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
350 if k[1] == "PID" then
358 line = luci.util.trim(line)
359 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
363 local pid = tonumber(row[k[1]])
372 --- Set the gid of a process identified by given pid.
373 -- @param pid Number containing the process id
374 -- @param gid Number containing the Unix group id
375 -- @return Boolean indicating successful operation
376 -- @return String containing the error message if failed
377 -- @return Number containing the error code if failed
378 function process.setgroup(pid, gid)
379 return posix.setpid("g", pid, gid)
382 --- Set the uid of a process identified by given pid.
383 -- @param pid Number containing the process id
384 -- @param uid Number containing the Unix user id
385 -- @return Boolean indicating successful operation
386 -- @return String containing the error message if failed
387 -- @return Number containing the error code if failed
388 function process.setuser(pid, uid)
389 return posix.setpid("u", pid, uid)
392 --- Send a signal to a process identified by given pid.
394 -- @name process.signal
395 -- @param pid Number containing the process id
396 -- @param sig Signal to send (default: 15 [SIGTERM])
397 -- @return Boolean indicating successful operation
398 -- @return Number containing the error code if failed
399 process.signal = posix.kill
402 --- LuCI system utilities / user related functions.
404 -- @name luci.sys.user
407 --- Retrieve user informations for given uid.
410 -- @param uid Number containing the Unix user id
411 -- @return Table containing the following fields:
412 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
413 user.getuser = posix.getpasswd
415 --- Test whether given string matches the password of a given system user.
416 -- @param username String containing the Unix user name
417 -- @param password String containing the password to compare
418 -- @return Boolean indicating wheather the passwords are equal
419 function user.checkpasswd(username, password)
420 local account = user.getuser(username)
423 if account.passwd == "!" then
426 return (account.passwd == posix.crypt(password, account.passwd))
431 --- Change the password of given user.
432 -- @param username String containing the Unix user name
433 -- @param password String containing the password to compare
434 -- @return Number containing 0 on success and >= 1 on error
435 function user.setpasswd(username, password)
437 password = password:gsub("'", "")
441 username = username:gsub("'", "")
444 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
445 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
446 return os.execute(cmd)
450 --- LuCI system utilities / wifi related functions.
452 -- @name luci.sys.wifi
455 --- Get iwconfig output for all wireless devices.
456 -- @return Table of tables containing the iwconfing output for each wifi device
457 function wifi.getiwconfig()
458 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
461 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
462 local k = l:match("^(.-) ")
463 l = l:gsub("^(.-) +", "", 1)
465 iwc[k] = _parse_mixed_record(l)
472 --- Get iwlist scan output from all wireless devices.
473 -- @return Table of tables contaiing all scan results
474 function wifi.iwscan(iface)
475 local siface = iface or ""
476 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
479 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
480 local k = l:match("^(.-) ")
481 l = l:gsub("^[^\n]+", "", 1)
482 l = luci.util.trim(l)
485 for j, c in pairs(luci.util.split(l, "\n Cell")) do
486 c = c:gsub("^(.-)- ", "", 1)
487 c = luci.util.split(c, "\n", 7)
488 c = table.concat(c, "\n", 1)
489 table.insert(iws[k], _parse_mixed_record(c))
494 return iface and (iws[iface] or {}) or iws
498 -- Internal functions
500 function _parse_delimited_table(iter, delimiter)
501 delimiter = delimiter or "%s+"
504 local trim = luci.util.trim
505 local split = luci.util.split
507 local keys = split(trim(iter()), delimiter, nil, true)
508 for i, j in pairs(keys) do
509 keys[i] = trim(keys[i])
516 for i, j in pairs(split(line, delimiter, nil, true)) do
522 table.insert(data, row)
528 function _parse_mixed_record(cnt)
531 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
532 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
533 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
537 table.insert(data, k)