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 kpattern (ToDo: clearify this)
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 --- Get group information
206 -- @return Group (ToDo: clearify)
208 group.getgroup = posix.getgroup
211 --- LuCI system utilities / network related functions.
213 -- @name luci.sys.net
216 --- Returns the current arp-table entries as two-dimensional table.
217 -- @return Table of table containing the current arp entries.
218 -- The following fields are defined for arp entry objects:
219 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
220 function net.arptable()
221 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
224 --- Test whether an IP-Adress belongs to a certain net.
225 -- @param ip IPv4 address to test
226 -- @param ipnet IPv4 network address of the net range to compare against
227 -- @param prefix Network prefix of the net range to compare against
228 -- @return Boolean indicating wheather the ip is within the range
229 function net.belongs(ip, ipnet, prefix)
230 return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
233 --- Determine the current default route.
234 -- @return Table with the properties of the current default route.
235 -- The following fields are defined:
236 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
237 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
238 function net.defaultroute()
239 local routes = net.routes()
242 for i, r in pairs(luci.sys.net.routes()) do
243 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
251 --- Determine the names of available network interfaces.
252 -- @return Table containing all current interface names
253 function net.devices()
255 for line in io.lines("/proc/net/dev") do
256 table.insert(devices, line:match(" *(.-):"))
261 -- Determine the MAC address belonging to the given IP address.
262 -- @param ip IPv4 address
263 -- @return String containing the MAC address or nil if it cannot be found
264 function net.ip4mac(ip)
267 for i, l in ipairs(net.arptable()) do
268 if l["IP address"] == ip then
269 mac = l["HW address"]
276 --- Calculate the prefix from a given netmask.
277 -- @param mask IPv4 net mask
278 -- @return Number containing the corresponding numerical prefix
279 function net.mask4prefix(mask)
280 local bin = net.ip4bin(mask)
286 return #luci.util.split(bin, "1")-1
289 --- Returns the current kernel routing table entries.
290 -- @return Table of tables with properties of the corresponding routes.
291 -- The following fields are defined for route entry tables:
292 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
293 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
294 function net.routes()
295 return _parse_delimited_table(io.lines("/proc/net/route"))
298 --- Convert hexadecimal 32 bit value to IPv4 address.
299 -- @param hex String containing the hexadecimal value
300 -- @param be Boolean indicating wheather the given value is big endian
301 -- @return String containing the corresponding IP4 address
302 function net.hexip4(hex, be)
307 be = be or bigendian()
309 local hexdec = luci.bits.Hex2Dec
313 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
314 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
315 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
316 ip = ip .. tostring(hexdec(hex:sub(7,8)))
318 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
319 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
320 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
321 ip = ip .. tostring(hexdec(hex:sub(1,2)))
327 --- Convert given IPv4 address to binary value.
328 -- @param ip String containing a IPv4 address
329 -- @return String containing corresponding binary value
330 function net.ip4bin(ip)
331 local parts = luci.util.split(ip, '.')
336 local decbin = luci.bits.Dec2Bin
339 bin = bin .. decbin(parts[1], 8)
340 bin = bin .. decbin(parts[2], 8)
341 bin = bin .. decbin(parts[3], 8)
342 bin = bin .. decbin(parts[4], 8)
347 --- Tests whether the given host responds to ping probes.
348 -- @param host String containing a hostname or IPv4 address
349 -- @return Number containing 0 on success and >= 1 on error
350 function net.pingtest(host)
351 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
355 --- LuCI system utilities / process related functions.
357 -- @name luci.sys.process
360 --- Get the current process id.
361 -- @return Number containing the current pid
362 process.info = posix.getpid
364 --- Set the gid of a process identified by given pid.
365 -- @param pid Number containing the process id
366 -- @param gid Number containing the Unix group id
367 -- @return Boolean indicating successful operation
368 -- @return String containing the error message if failed
369 -- @return Number containing the error code if failed
370 function process.setgroup(pid, gid)
371 return posix.setpid("g", pid, gid)
374 --- Set the uid of a process identified by given pid.
375 -- @param pid Number containing the process id
376 -- @param uid Number containing the Unix user id
377 -- @return Boolean indicating successful operation
378 -- @return String containing the error message if failed
379 -- @return Number containing the error code if failed
380 function process.setuser(pid, uid)
381 return posix.setpid("u", pid, uid)
385 --- LuCI system utilities / user related functions.
387 -- @name luci.sys.user
390 --- Retrieve user informations for given uid.
393 -- @param uid Number containing the Unix user id
394 -- @return Table containing the following fields:
395 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
396 user.getuser = posix.getpasswd
398 --- Test whether given string matches the password of a given system user.
399 -- @param username String containing the Unix user name
400 -- @param password String containing the password to compare
401 -- @return Boolean indicating wheather the passwords are equal
402 function user.checkpasswd(username, password)
403 local account = user.getuser(username)
405 -- FIXME: detect testing environment
406 if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
409 if account.passwd == "!" then
412 return (account.passwd == posix.crypt(password, account.passwd))
417 --- Change the password of given user.
418 -- @param username String containing the Unix user name
419 -- @param password String containing the password to compare
420 -- @return Number containing 0 on success and >= 1 on error
421 function user.setpasswd(username, password)
423 password = password:gsub("'", "")
427 username = username:gsub("'", "")
430 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
431 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
432 return os.execute(cmd)
436 --- LuCI system utilities / wifi related functions.
438 -- @name luci.sys.wifi
441 --- Get iwconfig output for all wireless devices.
442 -- @return Table of tables containing the iwconfing output for each wifi device
443 function wifi.getiwconfig()
444 local cnt = exec("/usr/sbin/iwconfig 2>/dev/null")
447 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
448 local k = l:match("^(.-) ")
449 l = l:gsub("^(.-) +", "", 1)
451 iwc[k] = _parse_mixed_record(l)
458 --- Get iwlist scan output from all wireless devices.
459 -- @return Table of tables contaiing all scan results
460 function wifi.iwscan()
461 local cnt = exec("iwlist scan 2>/dev/null")
464 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
465 local k = l:match("^(.-) ")
466 l = l:gsub("^[^\n]+", "", 1)
467 l = luci.util.trim(l)
470 for j, c in pairs(luci.util.split(l, "\n Cell")) do
471 c = c:gsub("^(.-)- ", "", 1)
472 c = luci.util.split(c, "\n", 7)
473 c = table.concat(c, "\n", 1)
474 table.insert(iws[k], _parse_mixed_record(c))
483 -- Internal functions
485 function _parse_delimited_table(iter, delimiter)
486 delimiter = delimiter or "%s+"
489 local trim = luci.util.trim
490 local split = luci.util.split
492 local keys = split(trim(iter()), delimiter, nil, true)
493 for i, j in pairs(keys) do
494 keys[i] = trim(keys[i])
501 for i, j in pairs(split(line, delimiter, nil, true)) do
507 table.insert(data, row)
513 function _parse_mixed_record(cnt)
516 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
517 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
518 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
522 table.insert(data, k)