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)
35 --- Invoke the luci-flash executable to write an image to the flash memory.
36 -- @param kpattern Pattern of files to keep over flash process
37 -- @return Return value of os.execute()
38 function flash(image, kpattern)
39 local cmd = "luci-flash "
41 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
43 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
45 return os.execute(cmd)
48 --- Retrieve information about currently mounted file systems.
49 -- @return Table containing mount information
52 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
53 local ps = luci.util.execi("df")
65 for value in line:gmatch("[^%s]+") do
71 table.insert(data, row)
78 --- Retrieve environment variables. If no variable is given then a table
79 -- containing the whole environment is returned otherwise this function returns
80 -- the corresponding string value for the given name or nil if no such variable
84 -- @param var Name of the environment variable to retrieve (optional)
85 -- @return String containg the value of the specified variable
86 -- @return Table containing all variables if no variable name is given
89 --- Determine the current hostname.
90 -- @return String containing the system hostname
92 return io.lines("/proc/sys/kernel/hostname")()
95 --- Returns the contents of a documented referred by an URL.
96 -- @param url The URL to retrieve
97 -- @param stream Return a stream instead of a buffer
98 -- @return String containing the contents of given the URL
99 function httpget(url, stream)
100 local source = stream and io.open or luci.util.exec
101 return source("wget -qO- '"..url:gsub("'", "").."'")
104 --- Returns the system load average values.
105 -- @return String containing the average load value 1 minute ago
106 -- @return String containing the average load value 5 minutes ago
107 -- @return String containing the average load value 15 minutes ago
108 -- @return String containing the active and total number of processes
109 -- @return String containing the last used pid
111 local loadavg = io.lines("/proc/loadavg")()
112 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
115 --- Initiate a system reboot.
116 -- @return Return value of os.execute()
118 return os.execute("reboot >/dev/null 2>&1")
121 --- Returns the system type, cpu name and installed physical memory.
122 -- @return String containing the system or platform identifier
123 -- @return String containing hardware model information
124 -- @return String containing the total memory amount in kB
125 -- @return String containing the memory used for caching in kB
126 -- @return String containing the memory used for buffering in kB
127 -- @return String containing the free memory amount in kB
128 -- @return Number containing free memory in percent
129 -- @return Number containing buffer memory in percent
130 -- @return Number containing cache memory in percent
132 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
133 local c2 = "uname -m 2>/dev/null"
134 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
135 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
136 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
137 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
138 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
139 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
141 local system = luci.util.trim(luci.util.exec(c1))
143 local memtotal = luci.util.trim(luci.util.exec(c5))
144 local memcached = luci.util.trim(luci.util.exec(c6))
145 local memfree = luci.util.trim(luci.util.exec(c7))
146 local membuffers = luci.util.trim(luci.util.exec(c8))
147 local perc_memfree = math.floor((memfree/memtotal)*100)
148 local perc_membuffers = math.floor((membuffers/memtotal)*100)
149 local perc_memcached = math.floor((memcached/memtotal)*100)
152 system = luci.util.trim(luci.util.exec(c2))
153 model = luci.util.trim(luci.util.exec(c3))
155 model = luci.util.trim(luci.util.exec(c4))
158 return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
161 --- Retrieves the output of the "logread" command.
162 -- @return String containing the current log buffer
164 return luci.util.exec("logread")
167 --- Generates a random id with specified length.
168 -- @param bytes Number of bytes for the unique id
169 -- @return String containing hex encoded id
170 function uniqueid(bytes)
171 local fp = io.open("/dev/urandom")
172 local chunk = { fp:read(bytes):byte(1, bytes) }
177 local pattern = "%02X"
178 for i, byte in ipairs(chunk) do
179 hex = hex .. pattern:format(byte)
185 --- Returns the current system uptime stats.
186 -- @return String containing total uptime in seconds
187 -- @return String containing idle time in seconds
189 local loadavg = io.lines("/proc/uptime")()
190 return loadavg:match("^(.-) (.-)$")
193 --- LuCI system utilities / POSIX user group related functions.
195 -- @name luci.sys.group
198 --- Returns information about a POSIX user group.
199 -- @param group Group ID or name of a system user group
200 -- @return Table with information about the requested group
201 group.getgroup = posix.getgroup
204 --- LuCI system utilities / network related functions.
206 -- @name luci.sys.net
209 --- Returns the current arp-table entries as two-dimensional table.
210 -- @return Table of table containing the current arp entries.
211 -- The following fields are defined for arp entry objects:
212 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
213 function net.arptable()
214 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
217 --- Determine the current default route.
218 -- @return Table with the properties of the current default route.
219 -- The following fields are defined:
220 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
221 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
222 function net.defaultroute()
223 local routes = net.routes()
226 for i, r in pairs(luci.sys.net.routes()) do
227 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
235 --- Determine the names of available network interfaces.
236 -- @return Table containing all current interface names
237 function net.devices()
239 for line in io.lines("/proc/net/dev") do
240 table.insert(devices, line:match(" *(.-):"))
246 --- Return information about available network interfaces.
247 -- @return Table containing all current interface names and their information
248 function net.deviceinfo()
250 for line in io.lines("/proc/net/dev") do
251 local name, data = line:match("^ *(.-): *(.*)$")
252 if name and data then
253 devices[name] = luci.util.split(data, " +", nil, true)
260 -- Determine the MAC address belonging to the given IP address.
261 -- @param ip IPv4 address
262 -- @return String containing the MAC address or nil if it cannot be found
263 function net.ip4mac(ip)
266 for i, l in ipairs(net.arptable()) do
267 if l["IP address"] == ip then
268 mac = l["HW address"]
275 --- Returns the current kernel routing table entries.
276 -- @return Table of tables with properties of the corresponding routes.
277 -- The following fields are defined for route entry tables:
278 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
279 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
280 function net.routes()
281 return _parse_delimited_table(io.lines("/proc/net/route"))
285 --- Tests whether the given host responds to ping probes.
286 -- @param host String containing a hostname or IPv4 address
287 -- @return Number containing 0 on success and >= 1 on error
288 function net.pingtest(host)
289 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
293 --- LuCI system utilities / process related functions.
295 -- @name luci.sys.process
298 --- Get the current process id.
299 -- @return Number containing the current pid
300 process.info = posix.getpid
302 --- Set the gid of a process identified by given pid.
303 -- @param pid Number containing the process id
304 -- @param gid Number containing the Unix group id
305 -- @return Boolean indicating successful operation
306 -- @return String containing the error message if failed
307 -- @return Number containing the error code if failed
308 function process.setgroup(pid, gid)
309 return posix.setpid("g", pid, gid)
312 --- Set the uid of a process identified by given pid.
313 -- @param pid Number containing the process id
314 -- @param uid Number containing the Unix user id
315 -- @return Boolean indicating successful operation
316 -- @return String containing the error message if failed
317 -- @return Number containing the error code if failed
318 function process.setuser(pid, uid)
319 return posix.setpid("u", pid, uid)
323 --- LuCI system utilities / user related functions.
325 -- @name luci.sys.user
328 --- Retrieve user informations for given uid.
331 -- @param uid Number containing the Unix user id
332 -- @return Table containing the following fields:
333 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
334 user.getuser = posix.getpasswd
336 --- Test whether given string matches the password of a given system user.
337 -- @param username String containing the Unix user name
338 -- @param password String containing the password to compare
339 -- @return Boolean indicating wheather the passwords are equal
340 function user.checkpasswd(username, password)
341 local account = user.getuser(username)
344 if account.passwd == "!" then
347 return (account.passwd == posix.crypt(password, account.passwd))
352 --- Change the password of given user.
353 -- @param username String containing the Unix user name
354 -- @param password String containing the password to compare
355 -- @return Number containing 0 on success and >= 1 on error
356 function user.setpasswd(username, password)
358 password = password:gsub("'", "")
362 username = username:gsub("'", "")
365 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
366 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
367 return os.execute(cmd)
371 --- LuCI system utilities / wifi related functions.
373 -- @name luci.sys.wifi
376 --- Get iwconfig output for all wireless devices.
377 -- @return Table of tables containing the iwconfing output for each wifi device
378 function wifi.getiwconfig()
379 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
382 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
383 local k = l:match("^(.-) ")
384 l = l:gsub("^(.-) +", "", 1)
386 iwc[k] = _parse_mixed_record(l)
393 --- Get iwlist scan output from all wireless devices.
394 -- @return Table of tables contaiing all scan results
395 function wifi.iwscan()
396 local cnt = luci.util.exec("iwlist scan 2>/dev/null")
399 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
400 local k = l:match("^(.-) ")
401 l = l:gsub("^[^\n]+", "", 1)
402 l = luci.util.trim(l)
405 for j, c in pairs(luci.util.split(l, "\n Cell")) do
406 c = c:gsub("^(.-)- ", "", 1)
407 c = luci.util.split(c, "\n", 7)
408 c = table.concat(c, "\n", 1)
409 table.insert(iws[k], _parse_mixed_record(c))
418 -- Internal functions
420 function _parse_delimited_table(iter, delimiter)
421 delimiter = delimiter or "%s+"
424 local trim = luci.util.trim
425 local split = luci.util.split
427 local keys = split(trim(iter()), delimiter, nil, true)
428 for i, j in pairs(keys) do
429 keys[i] = trim(keys[i])
436 for i, j in pairs(split(line, delimiter, nil, true)) do
442 table.insert(data, row)
448 function _parse_mixed_record(cnt)
451 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
452 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
453 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
457 table.insert(data, k)