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 environment variables. If no variable is given then a table
48 -- containing the whole environment is returned otherwise this function returns
49 -- the corresponding string value for the given name or nil if no such variable
53 -- @param var Name of the environment variable to retrieve (optional)
54 -- @return String containg the value of the specified variable
55 -- @return Table containing all variables if no variable name is given
58 --- Determine the current hostname.
59 -- @return String containing the system hostname
61 return io.lines("/proc/sys/kernel/hostname")()
64 --- Returns the contents of a documented referred by an URL.
65 -- @param url The URL to retrieve
66 -- @param stream Return a stream instead of a buffer
67 -- @return String containing the contents of given the URL
68 function httpget(url, stream)
69 local source = stream and io.open or luci.util.exec
70 return source("wget -qO- '"..url:gsub("'", "").."'")
73 --- Returns the system load average values.
74 -- @return String containing the average load value 1 minute ago
75 -- @return String containing the average load value 5 minutes ago
76 -- @return String containing the average load value 15 minutes ago
77 -- @return String containing the active and total number of processes
78 -- @return String containing the last used pid
80 local loadavg = io.lines("/proc/loadavg")()
81 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
84 --- Initiate a system reboot.
85 -- @return Return value of os.execute()
87 return os.execute("reboot >/dev/null 2>&1")
90 --- Returns the system type, cpu name and installed physical memory.
91 -- @return String containing the system or platform identifier
92 -- @return String containing hardware model information
93 -- @return String containing the total memory amount in kB
94 -- @return String containing the memory used for caching in kB
95 -- @return String containing the memory used for buffering in kB
96 -- @return String containing the free memory amount in kB
97 -- @return Number containing free memory in percent
98 -- @return Number containing buffer memory in percent
99 -- @return Number containing cache memory in percent
101 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
102 local c2 = "uname -m 2>/dev/null"
103 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
104 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
105 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
106 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
107 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
108 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
110 local system = luci.util.trim(luci.util.exec(c1))
112 local memtotal = luci.util.trim(luci.util.exec(c5))
113 local memcached = luci.util.trim(luci.util.exec(c6))
114 local memfree = luci.util.trim(luci.util.exec(c7))
115 local membuffers = luci.util.trim(luci.util.exec(c8))
116 local perc_memfree = math.floor((memfree/memtotal)*100)
117 local perc_membuffers = math.floor((membuffers/memtotal)*100)
118 local perc_memcached = math.floor((memcached/memtotal)*100)
121 system = luci.util.trim(luci.util.exec(c2))
122 model = luci.util.trim(luci.util.exec(c3))
124 model = luci.util.trim(luci.util.exec(c4))
127 return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
130 --- Retrieves the output of the "logread" command.
131 -- @return String containing the current log buffer
133 return luci.util.exec("logread")
136 --- Generates a random id with specified length.
137 -- @param bytes Number of bytes for the unique id
138 -- @return String containing hex encoded id
139 function uniqueid(bytes)
140 local fp = io.open("/dev/urandom")
141 local chunk = { fp:read(bytes):byte(1, bytes) }
146 local pattern = "%02X"
147 for i, byte in ipairs(chunk) do
148 hex = hex .. pattern:format(byte)
154 --- Returns the current system uptime stats.
155 -- @return String containing total uptime in seconds
156 -- @return String containing idle time in seconds
158 local loadavg = io.lines("/proc/uptime")()
159 return loadavg:match("^(.-) (.-)$")
162 --- LuCI system utilities / POSIX user group related functions.
164 -- @name luci.sys.group
167 --- Returns information about a POSIX user group.
168 -- @param group Group ID or name of a system user group
169 -- @return Table with information about the requested group
170 group.getgroup = posix.getgroup
173 --- LuCI system utilities / network related functions.
175 -- @name luci.sys.net
178 --- Returns the current arp-table entries as two-dimensional table.
179 -- @return Table of table containing the current arp entries.
180 -- The following fields are defined for arp entry objects:
181 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
182 function net.arptable()
183 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
186 --- Test whether an IP-Adress belongs to a certain net.
187 -- @param ip IPv4 address to test
188 -- @param ipnet IPv4 network address of the net range to compare against
189 -- @param prefix Network prefix of the net range to compare against
190 -- @return Boolean indicating wheather the ip is within the range
191 function net.belongs(ip, ipnet, prefix)
192 return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
195 --- Determine the current default route.
196 -- @return Table with the properties of the current default route.
197 -- The following fields are defined:
198 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
199 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
200 function net.defaultroute()
201 local routes = net.routes()
204 for i, r in pairs(luci.sys.net.routes()) do
205 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
213 --- Determine the names of available network interfaces.
214 -- @return Table containing all current interface names
215 function net.devices()
217 for line in io.lines("/proc/net/dev") do
218 table.insert(devices, line:match(" *(.-):"))
223 -- Determine the MAC address belonging to the given IP address.
224 -- @param ip IPv4 address
225 -- @return String containing the MAC address or nil if it cannot be found
226 function net.ip4mac(ip)
229 for i, l in ipairs(net.arptable()) do
230 if l["IP address"] == ip then
231 mac = l["HW address"]
238 --- Calculate the prefix from a given netmask.
239 -- @param mask IPv4 net mask
240 -- @return Number containing the corresponding numerical prefix
241 function net.mask4prefix(mask)
242 local bin = net.ip4bin(mask)
248 return #luci.util.split(bin, "1")-1
251 --- Returns the current kernel routing table entries.
252 -- @return Table of tables with properties of the corresponding routes.
253 -- The following fields are defined for route entry tables:
254 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
255 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
256 function net.routes()
257 return _parse_delimited_table(io.lines("/proc/net/route"))
260 --- Convert hexadecimal 32 bit value to IPv4 address.
261 -- @param hex String containing the hexadecimal value
262 -- @param be Boolean indicating wheather the given value is big endian
263 -- @return String containing the corresponding IP4 address
264 function net.hexip4(hex, be)
269 be = be or luci.util.bigendian()
271 local hexdec = luci.bits.Hex2Dec
275 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
276 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
277 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
278 ip = ip .. tostring(hexdec(hex:sub(7,8)))
280 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
281 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
282 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
283 ip = ip .. tostring(hexdec(hex:sub(1,2)))
289 --- Convert given IPv4 address to binary value.
290 -- @param ip String containing a IPv4 address
291 -- @return String containing corresponding binary value
292 function net.ip4bin(ip)
293 local parts = luci.util.split(ip, '.')
298 local decbin = luci.bits.Dec2Bin
301 bin = bin .. decbin(parts[1], 8)
302 bin = bin .. decbin(parts[2], 8)
303 bin = bin .. decbin(parts[3], 8)
304 bin = bin .. decbin(parts[4], 8)
309 --- Tests whether the given host responds to ping probes.
310 -- @param host String containing a hostname or IPv4 address
311 -- @return Number containing 0 on success and >= 1 on error
312 function net.pingtest(host)
313 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
317 --- LuCI system utilities / process related functions.
319 -- @name luci.sys.process
322 --- Get the current process id.
323 -- @return Number containing the current pid
324 process.info = posix.getpid
326 --- Set the gid of a process identified by given pid.
327 -- @param pid Number containing the process id
328 -- @param gid Number containing the Unix group id
329 -- @return Boolean indicating successful operation
330 -- @return String containing the error message if failed
331 -- @return Number containing the error code if failed
332 function process.setgroup(pid, gid)
333 return posix.setpid("g", pid, gid)
336 --- Set the uid of a process identified by given pid.
337 -- @param pid Number containing the process id
338 -- @param uid Number containing the Unix user id
339 -- @return Boolean indicating successful operation
340 -- @return String containing the error message if failed
341 -- @return Number containing the error code if failed
342 function process.setuser(pid, uid)
343 return posix.setpid("u", pid, uid)
347 --- LuCI system utilities / user related functions.
349 -- @name luci.sys.user
352 --- Retrieve user informations for given uid.
355 -- @param uid Number containing the Unix user id
356 -- @return Table containing the following fields:
357 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
358 user.getuser = posix.getpasswd
360 --- Test whether given string matches the password of a given system user.
361 -- @param username String containing the Unix user name
362 -- @param password String containing the password to compare
363 -- @return Boolean indicating wheather the passwords are equal
364 function user.checkpasswd(username, password)
365 local account = user.getuser(username)
367 -- FIXME: detect testing environment
368 if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
371 if account.passwd == "!" then
374 return (account.passwd == posix.crypt(password, account.passwd))
379 --- Change the password of given user.
380 -- @param username String containing the Unix user name
381 -- @param password String containing the password to compare
382 -- @return Number containing 0 on success and >= 1 on error
383 function user.setpasswd(username, password)
385 password = password:gsub("'", "")
389 username = username:gsub("'", "")
392 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
393 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
394 return os.execute(cmd)
398 --- LuCI system utilities / wifi related functions.
400 -- @name luci.sys.wifi
403 --- Get iwconfig output for all wireless devices.
404 -- @return Table of tables containing the iwconfing output for each wifi device
405 function wifi.getiwconfig()
406 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
409 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
410 local k = l:match("^(.-) ")
411 l = l:gsub("^(.-) +", "", 1)
413 iwc[k] = _parse_mixed_record(l)
420 --- Get iwlist scan output from all wireless devices.
421 -- @return Table of tables contaiing all scan results
422 function wifi.iwscan()
423 local cnt = luci.util.exec("iwlist scan 2>/dev/null")
426 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
427 local k = l:match("^(.-) ")
428 l = l:gsub("^[^\n]+", "", 1)
429 l = luci.util.trim(l)
432 for j, c in pairs(luci.util.split(l, "\n Cell")) do
433 c = c:gsub("^(.-)- ", "", 1)
434 c = luci.util.split(c, "\n", 7)
435 c = table.concat(c, "\n", 1)
436 table.insert(iws[k], _parse_mixed_record(c))
445 -- Internal functions
447 function _parse_delimited_table(iter, delimiter)
448 delimiter = delimiter or "%s+"
451 local trim = luci.util.trim
452 local split = luci.util.split
454 local keys = split(trim(iter()), delimiter, nil, true)
455 for i, j in pairs(keys) do
456 keys[i] = trim(keys[i])
463 for i, j in pairs(split(line, delimiter, nil, true)) do
469 table.insert(data, row)
475 function _parse_mixed_record(cnt)
478 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
479 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
480 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
484 table.insert(data, k)