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 system utilities.
28 module("luci.sys", package.seeall)
34 --- Test wheather the current system is operating in big endian mode.
35 -- @return Boolean value indicating wheather system is big endian
37 local fp = io.open("/bin/sh")
39 local be = (fp:read(1):byte() ~= 1)
44 --- Execute given commandline and gather stdout.
45 -- @param command String containing command to execute
46 -- @return String containing the command's stdout
47 function exec(command)
48 local pp = io.popen(command)
49 local data = pp:read("*a")
55 --- Execute given commandline and gather stdout.
56 -- @param command String containing the command to execute
57 -- @return Table containing the command's stdout splitted up in lines
58 function execl(command)
59 local pp = io.popen(command)
65 if (line == nil) then break end
66 table.insert(data, line)
73 --- Invoke the luci-flash executable to write an image to the flash memory.
74 -- @param kpattern Pattern of files to keep over flash process
75 -- @return Return value of os.execute()
76 function flash(image, kpattern)
77 local cmd = "luci-flash "
79 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
81 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
83 return os.execute(cmd)
86 --- Retrieve environment variables. If no variable is given then a table
87 -- containing the whole environment is returned otherwise this function returns
88 -- the corresponding string value for the given name or nil if no such variable
92 -- @param var Name of the environment variable to retrieve (optional)
93 -- @return String containg the value of the specified variable
94 -- @return Table containing all variables if no variable name is given
97 --- Determine the current hostname.
98 -- @return String containing the system hostname
100 return io.lines("/proc/sys/kernel/hostname")()
103 --- Returns the contents of a documented referred by an URL.
104 -- @param url The URL to retrieve
105 -- @return String containing the contents of given the URL
106 function httpget(url)
107 return exec("wget -qO- '"..url:gsub("'", "").."'")
110 --- Returns the absolute path to LuCI base directory.
111 -- @return String containing the directory path
113 return luci.fs.dirname(require("luci.debug").__file__)
116 --- Returns the system load average values.
117 -- @return String containing the average load value 1 minute ago
118 -- @return String containing the average load value 5 minutes ago
119 -- @return String containing the average load value 15 minutes ago
120 -- @return String containing the active and total number of processes
121 -- @return String containing the last used pid
123 local loadavg = io.lines("/proc/loadavg")()
124 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
127 --- Initiate a system reboot.
128 -- @return Return value of os.execute()
130 return os.execute("reboot >/dev/null 2>&1")
133 --- Returns the system type, cpu name and installed physical memory.
134 -- @return String containing the system or platform identifier
135 -- @return String containing hardware model information
136 -- @return String containing the total memory amount in kB
137 -- @return String containing the memory used for caching in kB
138 -- @return String containing the memory used for buffering in kB
139 -- @return String containing the free memory amount in kB
140 -- @return Number containing free memory in percent
141 -- @return Number containing buffer memory in percent
142 -- @return Number containing cache memory in percent
144 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
145 local c2 = "uname -m 2>/dev/null"
146 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
147 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
148 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
149 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
150 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
151 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
153 local system = luci.util.trim(exec(c1))
155 local memtotal = luci.util.trim(exec(c5))
156 local memcached = luci.util.trim(exec(c6))
157 local memfree = luci.util.trim(exec(c7))
158 local membuffers = luci.util.trim(exec(c8))
159 local perc_memfree = math.floor((memfree/memtotal)*100)
160 local perc_membuffers = math.floor((membuffers/memtotal)*100)
161 local perc_memcached = math.floor((memcached/memtotal)*100)
164 system = luci.util.trim(exec(c2))
165 model = luci.util.trim(exec(c3))
167 model = luci.util.trim(exec(c4))
170 return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
173 --- Retrieves the output of the "logread" command.
174 -- @return String containing the current log buffer
176 return exec("logread")
179 --- Generates a random id with specified length.
180 -- @param bytes Number of bytes for the unique id
181 -- @return String containing hex encoded id
182 function uniqueid(bytes)
183 local fp = io.open("/dev/urandom")
184 local chunk = { fp:read(bytes):byte(1, bytes) }
189 local pattern = "%02X"
190 for i, byte in ipairs(chunk) do
191 hex = hex .. pattern:format(byte)
197 --- Returns the current system uptime stats.
198 -- @return String containing total uptime in seconds
199 -- @return String containing idle time in seconds
201 local loadavg = io.lines("/proc/uptime")()
202 return loadavg:match("^(.-) (.-)$")
205 --- LuCI system utilities / POSIX user group related functions.
207 -- @name luci.sys.group
210 --- Returns information about a POSIX user group.
211 -- @param group Group ID or name of a system user group
212 -- @return Table with information about the requested group
213 group.getgroup = posix.getgroup
216 --- LuCI system utilities / network related functions.
218 -- @name luci.sys.net
221 --- Returns the current arp-table entries as two-dimensional table.
222 -- @return Table of table containing the current arp entries.
223 -- The following fields are defined for arp entry objects:
224 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
225 function net.arptable()
226 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
229 --- Test whether an IP-Adress belongs to a certain net.
230 -- @param ip IPv4 address to test
231 -- @param ipnet IPv4 network address of the net range to compare against
232 -- @param prefix Network prefix of the net range to compare against
233 -- @return Boolean indicating wheather the ip is within the range
234 function net.belongs(ip, ipnet, prefix)
235 return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
238 --- Determine the current default route.
239 -- @return Table with the properties of the current default route.
240 -- The following fields are defined:
241 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
242 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
243 function net.defaultroute()
244 local routes = net.routes()
247 for i, r in pairs(luci.sys.net.routes()) do
248 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
256 --- Determine the names of available network interfaces.
257 -- @return Table containing all current interface names
258 function net.devices()
260 for line in io.lines("/proc/net/dev") do
261 table.insert(devices, line:match(" *(.-):"))
266 -- Determine the MAC address belonging to the given IP address.
267 -- @param ip IPv4 address
268 -- @return String containing the MAC address or nil if it cannot be found
269 function net.ip4mac(ip)
272 for i, l in ipairs(net.arptable()) do
273 if l["IP address"] == ip then
274 mac = l["HW address"]
281 --- Calculate the prefix from a given netmask.
282 -- @param mask IPv4 net mask
283 -- @return Number containing the corresponding numerical prefix
284 function net.mask4prefix(mask)
285 local bin = net.ip4bin(mask)
291 return #luci.util.split(bin, "1")-1
294 --- Returns the current kernel routing table entries.
295 -- @return Table of tables with properties of the corresponding routes.
296 -- The following fields are defined for route entry tables:
297 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
298 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
299 function net.routes()
300 return _parse_delimited_table(io.lines("/proc/net/route"))
303 --- Convert hexadecimal 32 bit value to IPv4 address.
304 -- @param hex String containing the hexadecimal value
305 -- @param be Boolean indicating wheather the given value is big endian
306 -- @return String containing the corresponding IP4 address
307 function net.hexip4(hex, be)
312 be = be or bigendian()
314 local hexdec = luci.bits.Hex2Dec
318 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
319 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
320 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
321 ip = ip .. tostring(hexdec(hex:sub(7,8)))
323 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
324 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
325 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
326 ip = ip .. tostring(hexdec(hex:sub(1,2)))
332 --- Convert given IPv4 address to binary value.
333 -- @param ip String containing a IPv4 address
334 -- @return String containing corresponding binary value
335 function net.ip4bin(ip)
336 local parts = luci.util.split(ip, '.')
341 local decbin = luci.bits.Dec2Bin
344 bin = bin .. decbin(parts[1], 8)
345 bin = bin .. decbin(parts[2], 8)
346 bin = bin .. decbin(parts[3], 8)
347 bin = bin .. decbin(parts[4], 8)
352 --- Tests whether the given host responds to ping probes.
353 -- @param host String containing a hostname or IPv4 address
354 -- @return Number containing 0 on success and >= 1 on error
355 function net.pingtest(host)
356 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
360 --- LuCI system utilities / process related functions.
362 -- @name luci.sys.process
365 --- Get the current process id.
366 -- @return Number containing the current pid
367 process.info = posix.getpid
369 --- Set the gid of a process identified by given pid.
370 -- @param pid Number containing the process id
371 -- @param gid Number containing the Unix group id
372 -- @return Boolean indicating successful operation
373 -- @return String containing the error message if failed
374 -- @return Number containing the error code if failed
375 function process.setgroup(pid, gid)
376 return posix.setpid("g", pid, gid)
379 --- Set the uid of a process identified by given pid.
380 -- @param pid Number containing the process id
381 -- @param uid Number containing the Unix user id
382 -- @return Boolean indicating successful operation
383 -- @return String containing the error message if failed
384 -- @return Number containing the error code if failed
385 function process.setuser(pid, uid)
386 return posix.setpid("u", pid, uid)
390 --- LuCI system utilities / user related functions.
392 -- @name luci.sys.user
395 --- Retrieve user informations for given uid.
398 -- @param uid Number containing the Unix user id
399 -- @return Table containing the following fields:
400 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
401 user.getuser = posix.getpasswd
403 --- Test whether given string matches the password of a given system user.
404 -- @param username String containing the Unix user name
405 -- @param password String containing the password to compare
406 -- @return Boolean indicating wheather the passwords are equal
407 function user.checkpasswd(username, password)
408 local account = user.getuser(username)
410 -- FIXME: detect testing environment
411 if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
414 if account.passwd == "!" then
417 return (account.passwd == posix.crypt(password, account.passwd))
422 --- Change the password of given user.
423 -- @param username String containing the Unix user name
424 -- @param password String containing the password to compare
425 -- @return Number containing 0 on success and >= 1 on error
426 function user.setpasswd(username, password)
428 password = password:gsub("'", "")
432 username = username:gsub("'", "")
435 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
436 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
437 return os.execute(cmd)
441 --- LuCI system utilities / wifi related functions.
443 -- @name luci.sys.wifi
446 --- Get iwconfig output for all wireless devices.
447 -- @return Table of tables containing the iwconfing output for each wifi device
448 function wifi.getiwconfig()
449 local cnt = exec("/usr/sbin/iwconfig 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("^(.-) +", "", 1)
456 iwc[k] = _parse_mixed_record(l)
463 --- Get iwlist scan output from all wireless devices.
464 -- @return Table of tables contaiing all scan results
465 function wifi.iwscan()
466 local cnt = exec("iwlist scan 2>/dev/null")
469 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
470 local k = l:match("^(.-) ")
471 l = l:gsub("^[^\n]+", "", 1)
472 l = luci.util.trim(l)
475 for j, c in pairs(luci.util.split(l, "\n Cell")) do
476 c = c:gsub("^(.-)- ", "", 1)
477 c = luci.util.split(c, "\n", 7)
478 c = table.concat(c, "\n", 1)
479 table.insert(iws[k], _parse_mixed_record(c))
488 -- Internal functions
490 function _parse_delimited_table(iter, delimiter)
491 delimiter = delimiter or "%s+"
494 local trim = luci.util.trim
495 local split = luci.util.split
497 local keys = split(trim(iter()), delimiter, nil, true)
498 for i, j in pairs(keys) do
499 keys[i] = trim(keys[i])
506 for i, j in pairs(split(line, delimiter, nil, true)) do
512 table.insert(data, row)
518 function _parse_mixed_record(cnt)
521 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
522 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
523 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
527 table.insert(data, k)