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.
27 --- LuCI Linux and POSIX system utilities.
28 module("luci.sys", package.seeall)
34 --- Invoke the luci-flash executable to write an image to the flash memory.
35 -- @param kpattern Pattern of files to keep over flash process
36 -- @return Return value of os.execute()
37 function flash(image, kpattern)
38 local cmd = "luci-flash "
40 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
42 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
44 return os.execute(cmd)
47 --- Retrieve information about currently mounted file systems.
48 -- @return Table containing mount information
51 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
52 local ps = luci.util.execi("df")
64 for value in line:gmatch("[^%s]+") do
70 table.insert(data, row)
77 --- Retrieve environment variables. If no variable is given then a table
78 -- containing the whole environment is returned otherwise this function returns
79 -- the corresponding string value for the given name or nil if no such variable
83 -- @param var Name of the environment variable to retrieve (optional)
84 -- @return String containg the value of the specified variable
85 -- @return Table containing all variables if no variable name is given
88 --- Determine the current hostname.
89 -- @return String containing the system hostname
91 return io.lines("/proc/sys/kernel/hostname")()
94 --- Returns the contents of a documented referred by an URL.
95 -- @param url The URL to retrieve
96 -- @param stream Return a stream instead of a buffer
97 -- @return String containing the contents of given the URL
98 function httpget(url, stream)
99 local source = stream and io.open or luci.util.exec
100 return source("wget -qO- '"..url:gsub("'", "").."'")
103 --- Returns the system load average values.
104 -- @return String containing the average load value 1 minute ago
105 -- @return String containing the average load value 5 minutes ago
106 -- @return String containing the average load value 15 minutes ago
107 -- @return String containing the active and total number of processes
108 -- @return String containing the last used pid
110 local loadavg = io.lines("/proc/loadavg")()
111 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
114 --- Initiate a system reboot.
115 -- @return Return value of os.execute()
117 return os.execute("reboot >/dev/null 2>&1")
120 --- Returns the system type, cpu name and installed physical memory.
121 -- @return String containing the system or platform identifier
122 -- @return String containing hardware model information
123 -- @return String containing the total memory amount in kB
124 -- @return String containing the memory used for caching in kB
125 -- @return String containing the memory used for buffering in kB
126 -- @return String containing the free memory amount in kB
128 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
129 local c2 = "uname -m 2>/dev/null"
130 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
131 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
132 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
133 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
134 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
135 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
137 local system = luci.util.trim(luci.util.exec(c1))
139 local memtotal = tonumber(luci.util.trim(luci.util.exec(c5)))
140 local memcached = tonumber(luci.util.trim(luci.util.exec(c6)))
141 local memfree = tonumber(luci.util.trim(luci.util.exec(c7)))
142 local membuffers = tonumber(luci.util.trim(luci.util.exec(c8)))
145 system = luci.util.trim(luci.util.exec(c2))
146 model = luci.util.trim(luci.util.exec(c3))
148 model = luci.util.trim(luci.util.exec(c4))
151 return system, model, memtotal, memcached, membuffers, memfree
154 --- Retrieves the output of the "logread" command.
155 -- @return String containing the current log buffer
157 return luci.util.exec("logread")
160 --- Generates a random id with specified length.
161 -- @param bytes Number of bytes for the unique id
162 -- @return String containing hex encoded id
163 function uniqueid(bytes)
164 local fp = io.open("/dev/urandom")
165 local chunk = { fp:read(bytes):byte(1, bytes) }
170 local pattern = "%02X"
171 for i, byte in ipairs(chunk) do
172 hex = hex .. pattern:format(byte)
178 --- Returns the current system uptime stats.
179 -- @return String containing total uptime in seconds
180 -- @return String containing idle time in seconds
182 local loadavg = io.lines("/proc/uptime")()
183 return loadavg:match("^(.-) (.-)$")
186 --- LuCI system utilities / POSIX user group related functions.
188 -- @name luci.sys.group
191 --- Returns information about a POSIX user group.
192 -- @param group Group ID or name of a system user group
193 -- @return Table with information about the requested group
194 group.getgroup = posix.getgroup
197 --- LuCI system utilities / network related functions.
199 -- @name luci.sys.net
202 --- Returns the current arp-table entries as two-dimensional table.
203 -- @return Table of table containing the current arp entries.
204 -- The following fields are defined for arp entry objects:
205 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
206 function net.arptable()
207 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
210 --- Determine the current default route.
211 -- @return Table with the properties of the current default route.
212 -- The following fields are defined:
213 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
214 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
215 function net.defaultroute()
216 local routes = net.routes()
219 for i, r in pairs(luci.sys.net.routes()) do
220 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
228 --- Determine the names of available network interfaces.
229 -- @return Table containing all current interface names
230 function net.devices()
232 for line in io.lines("/proc/net/dev") do
233 table.insert(devices, line:match(" *(.-):"))
239 --- Return information about available network interfaces.
240 -- @return Table containing all current interface names and their information
241 function net.deviceinfo()
243 for line in io.lines("/proc/net/dev") do
244 local name, data = line:match("^ *(.-): *(.*)$")
245 if name and data then
246 devices[name] = luci.util.split(data, " +", nil, true)
253 -- Determine the MAC address belonging to the given IP address.
254 -- @param ip IPv4 address
255 -- @return String containing the MAC address or nil if it cannot be found
256 function net.ip4mac(ip)
259 for i, l in ipairs(net.arptable()) do
260 if l["IP address"] == ip then
261 mac = l["HW address"]
268 --- Returns the current kernel routing table entries.
269 -- @return Table of tables with properties of the corresponding routes.
270 -- The following fields are defined for route entry tables:
271 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
272 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
273 function net.routes()
274 return _parse_delimited_table(io.lines("/proc/net/route"))
278 --- Tests whether the given host responds to ping probes.
279 -- @param host String containing a hostname or IPv4 address
280 -- @return Number containing 0 on success and >= 1 on error
281 function net.pingtest(host)
282 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
286 --- LuCI system utilities / process related functions.
288 -- @name luci.sys.process
291 --- Get the current process id.
292 -- @return Number containing the current pid
293 process.info = posix.getpid
295 --- Retrieve information about currently running processes.
296 -- @return Table containing process information
297 function process.list()
300 local ps = luci.util.execi("top -bn1")
312 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
313 if k[1] == "PID" then
321 line = luci.util.trim(line)
322 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
326 local pid = tonumber(row[k[1]])
335 --- Set the gid of a process identified by given pid.
336 -- @param pid Number containing the process id
337 -- @param gid Number containing the Unix group id
338 -- @return Boolean indicating successful operation
339 -- @return String containing the error message if failed
340 -- @return Number containing the error code if failed
341 function process.setgroup(pid, gid)
342 return posix.setpid("g", pid, gid)
345 --- Set the uid of a process identified by given pid.
346 -- @param pid Number containing the process id
347 -- @param uid Number containing the Unix user id
348 -- @return Boolean indicating successful operation
349 -- @return String containing the error message if failed
350 -- @return Number containing the error code if failed
351 function process.setuser(pid, uid)
352 return posix.setpid("u", pid, uid)
355 --- Send a signal to a process identified by given pid.
356 -- @param pid Number containing the process id
357 -- @param sig Signal to send (default: 15 [SIGTERM])
358 -- @return Boolean indicating successful operation
359 -- @return Number containing the error code if failed
360 process.signal = posix.kill
363 --- LuCI system utilities / user related functions.
365 -- @name luci.sys.user
368 --- Retrieve user informations for given uid.
371 -- @param uid Number containing the Unix user id
372 -- @return Table containing the following fields:
373 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
374 user.getuser = posix.getpasswd
376 --- Test whether given string matches the password of a given system user.
377 -- @param username String containing the Unix user name
378 -- @param password String containing the password to compare
379 -- @return Boolean indicating wheather the passwords are equal
380 function user.checkpasswd(username, password)
381 local account = user.getuser(username)
384 if account.passwd == "!" then
387 return (account.passwd == posix.crypt(password, account.passwd))
392 --- Change the password of given user.
393 -- @param username String containing the Unix user name
394 -- @param password String containing the password to compare
395 -- @return Number containing 0 on success and >= 1 on error
396 function user.setpasswd(username, password)
398 password = password:gsub("'", "")
402 username = username:gsub("'", "")
405 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
406 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
407 return os.execute(cmd)
411 --- LuCI system utilities / wifi related functions.
413 -- @name luci.sys.wifi
416 --- Get iwconfig output for all wireless devices.
417 -- @return Table of tables containing the iwconfing output for each wifi device
418 function wifi.getiwconfig()
419 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
422 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
423 local k = l:match("^(.-) ")
424 l = l:gsub("^(.-) +", "", 1)
426 iwc[k] = _parse_mixed_record(l)
433 --- Get iwlist scan output from all wireless devices.
434 -- @return Table of tables contaiing all scan results
435 function wifi.iwscan()
436 local cnt = luci.util.exec("iwlist scan 2>/dev/null")
439 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
440 local k = l:match("^(.-) ")
441 l = l:gsub("^[^\n]+", "", 1)
442 l = luci.util.trim(l)
445 for j, c in pairs(luci.util.split(l, "\n Cell")) do
446 c = c:gsub("^(.-)- ", "", 1)
447 c = luci.util.split(c, "\n", 7)
448 c = table.concat(c, "\n", 1)
449 table.insert(iws[k], _parse_mixed_record(c))
458 -- Internal functions
460 function _parse_delimited_table(iter, delimiter)
461 delimiter = delimiter or "%s+"
464 local trim = luci.util.trim
465 local split = luci.util.split
467 local keys = split(trim(iter()), delimiter, nil, true)
468 for i, j in pairs(keys) do
469 keys[i] = trim(keys[i])
476 for i, j in pairs(split(line, delimiter, nil, true)) do
482 table.insert(data, row)
488 function _parse_mixed_record(cnt)
491 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
492 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
493 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
497 table.insert(data, k)