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.
45 --- Invoke the luci-flash executable to write an image to the flash memory.
46 -- @param image Local path or URL to image file
47 -- @param kpattern Pattern of files to keep over flash process
48 -- @return Return value of os.execute()
49 function flash(image, kpattern)
50 local cmd = "luci-flash "
52 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
54 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
56 return os.execute(cmd)
59 --- Retrieve information about currently mounted file systems.
60 -- @return Table containing mount information
63 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
64 local ps = luci.util.execi("df")
76 for value in line:gmatch("[^%s]+") do
82 table.insert(data, row)
89 --- Retrieve environment variables. If no variable is given then a table
90 -- containing the whole environment is returned otherwise this function returns
91 -- the corresponding string value for the given name or nil if no such variable
95 -- @param var Name of the environment variable to retrieve (optional)
96 -- @return String containg the value of the specified variable
97 -- @return Table containing all variables if no variable name is given
100 --- Determine the current hostname.
101 -- @return String containing the system hostname
103 return io.lines("/proc/sys/kernel/hostname")()
106 --- Returns the contents of a documented referred by an URL.
107 -- @param url The URL to retrieve
108 -- @param stream Return a stream instead of a buffer
109 -- @param target Directly write to target file name
110 -- @return String containing the contents of given the URL
111 function httpget(url, stream, target)
113 local source = stream and io.open or luci.util.exec
114 return source("wget -qO- '"..url:gsub("'", "").."'")
116 return os.execute("wget -qO '%s' '%s'" %
117 {target:gsub("'", ""), url:gsub("'", "")})
121 --- Returns the system load average values.
122 -- @return String containing the average load value 1 minute ago
123 -- @return String containing the average load value 5 minutes ago
124 -- @return String containing the average load value 15 minutes ago
125 -- @return String containing the active and total number of processes
126 -- @return String containing the last used pid
128 local loadavg = io.lines("/proc/loadavg")()
129 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
132 --- Initiate a system reboot.
133 -- @return Return value of os.execute()
135 return os.execute("reboot >/dev/null 2>&1")
138 --- Returns the system type, cpu name and installed physical memory.
139 -- @return String containing the system or platform identifier
140 -- @return String containing hardware model information
141 -- @return String containing the total memory amount in kB
142 -- @return String containing the memory used for caching in kB
143 -- @return String containing the memory used for buffering in kB
144 -- @return String containing the free memory amount in kB
146 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
147 local c2 = "uname -m 2>/dev/null"
148 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
149 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
150 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
151 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
152 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
153 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
155 local system = luci.util.trim(luci.util.exec(c1))
157 local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
158 local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
159 local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
160 local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
163 system = luci.util.trim(luci.util.exec(c2))
164 model = luci.util.trim(luci.util.exec(c3))
166 model = luci.util.trim(luci.util.exec(c4))
169 return system, model, memtotal, memcached, membuffers, memfree
172 --- Retrieves the output of the "logread" command.
173 -- @return String containing the current log buffer
175 return luci.util.exec("logread")
178 --- Generates a random id with specified length.
179 -- @param bytes Number of bytes for the unique id
180 -- @return String containing hex encoded id
181 function uniqueid(bytes)
182 local fp = io.open("/dev/urandom")
183 local chunk = { fp:read(bytes):byte(1, bytes) }
188 local pattern = "%02X"
189 for i, byte in ipairs(chunk) do
190 hex = hex .. pattern:format(byte)
196 --- Returns the current system uptime stats.
197 -- @return String containing total uptime in seconds
198 -- @return String containing idle time in seconds
200 local loadavg = io.lines("/proc/uptime")()
201 return loadavg:match("^(.-) (.-)$")
204 --- LuCI system utilities / POSIX user group related functions.
206 -- @name luci.sys.group
209 --- Returns information about a POSIX user group.
212 -- @param group Group ID or name of a system user group
213 -- @return Table with information about the requested group
214 group.getgroup = posix.getgroup
217 --- LuCI system utilities / network related functions.
219 -- @name luci.sys.net
222 --- Returns the current arp-table entries as two-dimensional table.
223 -- @return Table of table containing the current arp entries.
224 -- The following fields are defined for arp entry objects:
225 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
226 function net.arptable()
227 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
230 --- Determine the current default route.
231 -- @return Table with the properties of the current default route.
232 -- The following fields are defined:
233 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
234 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
235 function net.defaultroute()
236 local routes = net.routes()
239 for i, r in pairs(luci.sys.net.routes()) do
240 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
248 --- Determine the names of available network interfaces.
249 -- @return Table containing all current interface names
250 function net.devices()
252 for line in io.lines("/proc/net/dev") do
253 table.insert(devices, line:match(" *(.-):"))
259 --- Return information about available network interfaces.
260 -- @return Table containing all current interface names and their information
261 function net.deviceinfo()
263 for line in io.lines("/proc/net/dev") do
264 local name, data = line:match("^ *(.-): *(.*)$")
265 if name and data then
266 devices[name] = luci.util.split(data, " +", nil, true)
273 -- Determine the MAC address belonging to the given IP address.
274 -- @param ip IPv4 address
275 -- @return String containing the MAC address or nil if it cannot be found
276 function net.ip4mac(ip)
279 for i, l in ipairs(net.arptable()) do
280 if l["IP address"] == ip then
281 mac = l["HW address"]
288 --- Returns the current kernel routing table entries.
289 -- @return Table of tables with properties of the corresponding routes.
290 -- The following fields are defined for route entry tables:
291 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
292 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
293 function net.routes()
294 return _parse_delimited_table(io.lines("/proc/net/route"))
298 --- Tests whether the given host responds to ping probes.
299 -- @param host String containing a hostname or IPv4 address
300 -- @return Number containing 0 on success and >= 1 on error
301 function net.pingtest(host)
302 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
306 --- LuCI system utilities / process related functions.
308 -- @name luci.sys.process
311 --- Get the current process id.
312 -- @return Number containing the current pid
313 process.info = posix.getpid
315 --- Retrieve information about currently running processes.
316 -- @return Table containing process information
317 function process.list()
320 local ps = luci.util.execi("top -bn1")
332 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
333 if k[1] == "PID" then
341 line = luci.util.trim(line)
342 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
346 local pid = tonumber(row[k[1]])
355 --- Set the gid of a process identified by given pid.
356 -- @param pid Number containing the process id
357 -- @param gid Number containing the Unix group id
358 -- @return Boolean indicating successful operation
359 -- @return String containing the error message if failed
360 -- @return Number containing the error code if failed
361 function process.setgroup(pid, gid)
362 return posix.setpid("g", pid, gid)
365 --- Set the uid of a process identified by given pid.
366 -- @param pid Number containing the process id
367 -- @param uid Number containing the Unix user id
368 -- @return Boolean indicating successful operation
369 -- @return String containing the error message if failed
370 -- @return Number containing the error code if failed
371 function process.setuser(pid, uid)
372 return posix.setpid("u", pid, uid)
375 --- Send a signal to a process identified by given pid.
376 -- @param pid Number containing the process id
377 -- @param sig Signal to send (default: 15 [SIGTERM])
378 -- @return Boolean indicating successful operation
379 -- @return Number containing the error code if failed
380 process.signal = posix.kill
383 --- LuCI system utilities / user related functions.
385 -- @name luci.sys.user
388 --- Retrieve user informations for given uid.
391 -- @param uid Number containing the Unix user id
392 -- @return Table containing the following fields:
393 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
394 user.getuser = posix.getpasswd
396 --- Test whether given string matches the password of a given system user.
397 -- @param username String containing the Unix user name
398 -- @param password String containing the password to compare
399 -- @return Boolean indicating wheather the passwords are equal
400 function user.checkpasswd(username, password)
401 local account = user.getuser(username)
404 if account.passwd == "!" then
407 return (account.passwd == posix.crypt(password, account.passwd))
412 --- Change the password of given user.
413 -- @param username String containing the Unix user name
414 -- @param password String containing the password to compare
415 -- @return Number containing 0 on success and >= 1 on error
416 function user.setpasswd(username, password)
418 password = password:gsub("'", "")
422 username = username:gsub("'", "")
425 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
426 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
427 return os.execute(cmd)
431 --- LuCI system utilities / wifi related functions.
433 -- @name luci.sys.wifi
436 --- Get iwconfig output for all wireless devices.
437 -- @return Table of tables containing the iwconfing output for each wifi device
438 function wifi.getiwconfig()
439 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
442 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
443 local k = l:match("^(.-) ")
444 l = l:gsub("^(.-) +", "", 1)
446 iwc[k] = _parse_mixed_record(l)
453 --- Get iwlist scan output from all wireless devices.
454 -- @return Table of tables contaiing all scan results
455 function wifi.iwscan(iface)
456 local siface = iface or ""
457 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
460 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
461 local k = l:match("^(.-) ")
462 l = l:gsub("^[^\n]+", "", 1)
463 l = luci.util.trim(l)
466 for j, c in pairs(luci.util.split(l, "\n Cell")) do
467 c = c:gsub("^(.-)- ", "", 1)
468 c = luci.util.split(c, "\n", 7)
469 c = table.concat(c, "\n", 1)
470 table.insert(iws[k], _parse_mixed_record(c))
475 return iface and (iws[iface] or {}) or iws
479 -- Internal functions
481 function _parse_delimited_table(iter, delimiter)
482 delimiter = delimiter or "%s+"
485 local trim = luci.util.trim
486 local split = luci.util.split
488 local keys = split(trim(iter()), delimiter, nil, true)
489 for i, j in pairs(keys) do
490 keys[i] = trim(keys[i])
497 for i, j in pairs(split(line, delimiter, nil, true)) do
503 table.insert(data, row)
509 function _parse_mixed_record(cnt)
512 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
513 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
514 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
518 table.insert(data, k)