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 -- @return String containing the contents of given the URL
110 function httpget(url, stream)
111 local source = stream and io.open or luci.util.exec
112 return source("wget -qO- '"..url:gsub("'", "").."'")
115 --- Returns the system load average values.
116 -- @return String containing the average load value 1 minute ago
117 -- @return String containing the average load value 5 minutes ago
118 -- @return String containing the average load value 15 minutes ago
119 -- @return String containing the active and total number of processes
120 -- @return String containing the last used pid
122 local loadavg = io.lines("/proc/loadavg")()
123 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
126 --- Initiate a system reboot.
127 -- @return Return value of os.execute()
129 return os.execute("reboot >/dev/null 2>&1")
132 --- Returns the system type, cpu name and installed physical memory.
133 -- @return String containing the system or platform identifier
134 -- @return String containing hardware model information
135 -- @return String containing the total memory amount in kB
136 -- @return String containing the memory used for caching in kB
137 -- @return String containing the memory used for buffering in kB
138 -- @return String containing the free memory amount in kB
140 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
141 local c2 = "uname -m 2>/dev/null"
142 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
143 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
144 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
145 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
146 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
147 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
149 local system = luci.util.trim(luci.util.exec(c1))
151 local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
152 local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
153 local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
154 local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
157 system = luci.util.trim(luci.util.exec(c2))
158 model = luci.util.trim(luci.util.exec(c3))
160 model = luci.util.trim(luci.util.exec(c4))
163 return system, model, memtotal, memcached, membuffers, memfree
166 --- Retrieves the output of the "logread" command.
167 -- @return String containing the current log buffer
169 return luci.util.exec("logread")
172 --- Generates a random id with specified length.
173 -- @param bytes Number of bytes for the unique id
174 -- @return String containing hex encoded id
175 function uniqueid(bytes)
176 local fp = io.open("/dev/urandom")
177 local chunk = { fp:read(bytes):byte(1, bytes) }
182 local pattern = "%02X"
183 for i, byte in ipairs(chunk) do
184 hex = hex .. pattern:format(byte)
190 --- Returns the current system uptime stats.
191 -- @return String containing total uptime in seconds
192 -- @return String containing idle time in seconds
194 local loadavg = io.lines("/proc/uptime")()
195 return loadavg:match("^(.-) (.-)$")
198 --- LuCI system utilities / POSIX user group related functions.
200 -- @name luci.sys.group
203 --- Returns information about a POSIX user group.
204 -- @param group Group ID or name of a system user group
205 -- @return Table with information about the requested group
206 group.getgroup = posix.getgroup
209 --- LuCI system utilities / network related functions.
211 -- @name luci.sys.net
214 --- Returns the current arp-table entries as two-dimensional table.
215 -- @return Table of table containing the current arp entries.
216 -- The following fields are defined for arp entry objects:
217 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
218 function net.arptable()
219 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
222 --- Determine the current default route.
223 -- @return Table with the properties of the current default route.
224 -- The following fields are defined:
225 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
226 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
227 function net.defaultroute()
228 local routes = net.routes()
231 for i, r in pairs(luci.sys.net.routes()) do
232 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
240 --- Determine the names of available network interfaces.
241 -- @return Table containing all current interface names
242 function net.devices()
244 for line in io.lines("/proc/net/dev") do
245 table.insert(devices, line:match(" *(.-):"))
251 --- Return information about available network interfaces.
252 -- @return Table containing all current interface names and their information
253 function net.deviceinfo()
255 for line in io.lines("/proc/net/dev") do
256 local name, data = line:match("^ *(.-): *(.*)$")
257 if name and data then
258 devices[name] = luci.util.split(data, " +", nil, true)
265 -- Determine the MAC address belonging to the given IP address.
266 -- @param ip IPv4 address
267 -- @return String containing the MAC address or nil if it cannot be found
268 function net.ip4mac(ip)
271 for i, l in ipairs(net.arptable()) do
272 if l["IP address"] == ip then
273 mac = l["HW address"]
280 --- Returns the current kernel routing table entries.
281 -- @return Table of tables with properties of the corresponding routes.
282 -- The following fields are defined for route entry tables:
283 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
284 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
285 function net.routes()
286 return _parse_delimited_table(io.lines("/proc/net/route"))
290 --- Tests whether the given host responds to ping probes.
291 -- @param host String containing a hostname or IPv4 address
292 -- @return Number containing 0 on success and >= 1 on error
293 function net.pingtest(host)
294 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
298 --- LuCI system utilities / process related functions.
300 -- @name luci.sys.process
303 --- Get the current process id.
304 -- @return Number containing the current pid
305 process.info = posix.getpid
307 --- Retrieve information about currently running processes.
308 -- @return Table containing process information
309 function process.list()
312 local ps = luci.util.execi("top -bn1")
324 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
325 if k[1] == "PID" then
333 line = luci.util.trim(line)
334 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
338 local pid = tonumber(row[k[1]])
347 --- Set the gid of a process identified by given pid.
348 -- @param pid Number containing the process id
349 -- @param gid Number containing the Unix group id
350 -- @return Boolean indicating successful operation
351 -- @return String containing the error message if failed
352 -- @return Number containing the error code if failed
353 function process.setgroup(pid, gid)
354 return posix.setpid("g", pid, gid)
357 --- Set the uid of a process identified by given pid.
358 -- @param pid Number containing the process id
359 -- @param uid Number containing the Unix user id
360 -- @return Boolean indicating successful operation
361 -- @return String containing the error message if failed
362 -- @return Number containing the error code if failed
363 function process.setuser(pid, uid)
364 return posix.setpid("u", pid, uid)
367 --- Send a signal to a process identified by given pid.
368 -- @param pid Number containing the process id
369 -- @param sig Signal to send (default: 15 [SIGTERM])
370 -- @return Boolean indicating successful operation
371 -- @return Number containing the error code if failed
372 process.signal = posix.kill
375 --- LuCI system utilities / user related functions.
377 -- @name luci.sys.user
380 --- Retrieve user informations for given uid.
383 -- @param uid Number containing the Unix user id
384 -- @return Table containing the following fields:
385 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
386 user.getuser = posix.getpasswd
388 --- Test whether given string matches the password of a given system user.
389 -- @param username String containing the Unix user name
390 -- @param password String containing the password to compare
391 -- @return Boolean indicating wheather the passwords are equal
392 function user.checkpasswd(username, password)
393 local account = user.getuser(username)
396 if account.passwd == "!" then
399 return (account.passwd == posix.crypt(password, account.passwd))
404 --- Change the password of given user.
405 -- @param username String containing the Unix user name
406 -- @param password String containing the password to compare
407 -- @return Number containing 0 on success and >= 1 on error
408 function user.setpasswd(username, password)
410 password = password:gsub("'", "")
414 username = username:gsub("'", "")
417 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
418 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
419 return os.execute(cmd)
423 --- LuCI system utilities / wifi related functions.
425 -- @name luci.sys.wifi
428 --- Get iwconfig output for all wireless devices.
429 -- @return Table of tables containing the iwconfing output for each wifi device
430 function wifi.getiwconfig()
431 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
434 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
435 local k = l:match("^(.-) ")
436 l = l:gsub("^(.-) +", "", 1)
438 iwc[k] = _parse_mixed_record(l)
445 --- Get iwlist scan output from all wireless devices.
446 -- @return Table of tables contaiing all scan results
447 function wifi.iwscan(iface)
448 local siface = iface or ""
449 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
452 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
453 local k = l:match("^(.-) ")
454 l = l:gsub("^[^\n]+", "", 1)
455 l = luci.util.trim(l)
458 for j, c in pairs(luci.util.split(l, "\n Cell")) do
459 c = c:gsub("^(.-)- ", "", 1)
460 c = luci.util.split(c, "\n", 7)
461 c = table.concat(c, "\n", 1)
462 table.insert(iws[k], _parse_mixed_record(c))
467 return iface and (iws[iface] or {}) or iws
471 -- Internal functions
473 function _parse_delimited_table(iter, delimiter)
474 delimiter = delimiter or "%s+"
477 local trim = luci.util.trim
478 local split = luci.util.split
480 local keys = split(trim(iter()), delimiter, nil, true)
481 for i, j in pairs(keys) do
482 keys[i] = trim(keys[i])
489 for i, j in pairs(split(line, delimiter, nil, true)) do
495 table.insert(data, row)
501 function _parse_mixed_record(cnt)
504 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
505 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
506 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
510 table.insert(data, k)