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 environment variables. If no variable is given then a table
49 -- containing the whole environment is returned otherwise this function returns
50 -- the corresponding string value for the given name or nil if no such variable
54 -- @param var Name of the environment variable to retrieve (optional)
55 -- @return String containg the value of the specified variable
56 -- @return Table containing all variables if no variable name is given
59 --- Determine the current hostname.
60 -- @return String containing the system hostname
62 return io.lines("/proc/sys/kernel/hostname")()
65 --- Returns the contents of a documented referred by an URL.
66 -- @param url The URL to retrieve
67 -- @param stream Return a stream instead of a buffer
68 -- @return String containing the contents of given the URL
69 function httpget(url, stream)
70 local source = stream and io.open or luci.util.exec
71 return source("wget -qO- '"..url:gsub("'", "").."'")
74 --- Returns the system load average values.
75 -- @return String containing the average load value 1 minute ago
76 -- @return String containing the average load value 5 minutes ago
77 -- @return String containing the average load value 15 minutes ago
78 -- @return String containing the active and total number of processes
79 -- @return String containing the last used pid
81 local loadavg = io.lines("/proc/loadavg")()
82 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
85 --- Initiate a system reboot.
86 -- @return Return value of os.execute()
88 return os.execute("reboot >/dev/null 2>&1")
91 --- Returns the system type, cpu name and installed physical memory.
92 -- @return String containing the system or platform identifier
93 -- @return String containing hardware model information
94 -- @return String containing the total memory amount in kB
95 -- @return String containing the memory used for caching in kB
96 -- @return String containing the memory used for buffering in kB
97 -- @return String containing the free memory amount in kB
98 -- @return Number containing free memory in percent
99 -- @return Number containing buffer memory in percent
100 -- @return Number containing cache memory in percent
102 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
103 local c2 = "uname -m 2>/dev/null"
104 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
105 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
106 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
107 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
108 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
109 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
111 local system = luci.util.trim(luci.util.exec(c1))
113 local memtotal = luci.util.trim(luci.util.exec(c5))
114 local memcached = luci.util.trim(luci.util.exec(c6))
115 local memfree = luci.util.trim(luci.util.exec(c7))
116 local membuffers = luci.util.trim(luci.util.exec(c8))
117 local perc_memfree = math.floor((memfree/memtotal)*100)
118 local perc_membuffers = math.floor((membuffers/memtotal)*100)
119 local perc_memcached = math.floor((memcached/memtotal)*100)
122 system = luci.util.trim(luci.util.exec(c2))
123 model = luci.util.trim(luci.util.exec(c3))
125 model = luci.util.trim(luci.util.exec(c4))
128 return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
131 --- Retrieves the output of the "logread" command.
132 -- @return String containing the current log buffer
134 return luci.util.exec("logread")
137 --- Generates a random id with specified length.
138 -- @param bytes Number of bytes for the unique id
139 -- @return String containing hex encoded id
140 function uniqueid(bytes)
141 local fp = io.open("/dev/urandom")
142 local chunk = { fp:read(bytes):byte(1, bytes) }
147 local pattern = "%02X"
148 for i, byte in ipairs(chunk) do
149 hex = hex .. pattern:format(byte)
155 --- Returns the current system uptime stats.
156 -- @return String containing total uptime in seconds
157 -- @return String containing idle time in seconds
159 local loadavg = io.lines("/proc/uptime")()
160 return loadavg:match("^(.-) (.-)$")
163 --- LuCI system utilities / POSIX user group related functions.
165 -- @name luci.sys.group
168 --- Returns information about a POSIX user group.
169 -- @param group Group ID or name of a system user group
170 -- @return Table with information about the requested group
171 group.getgroup = posix.getgroup
174 --- LuCI system utilities / network related functions.
176 -- @name luci.sys.net
179 --- Returns the current arp-table entries as two-dimensional table.
180 -- @return Table of table containing the current arp entries.
181 -- The following fields are defined for arp entry objects:
182 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
183 function net.arptable()
184 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
187 --- Determine the current default route.
188 -- @return Table with the properties of the current default route.
189 -- The following fields are defined:
190 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
191 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
192 function net.defaultroute()
193 local routes = net.routes()
196 for i, r in pairs(luci.sys.net.routes()) do
197 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
205 --- Determine the names of available network interfaces.
206 -- @return Table containing all current interface names
207 function net.devices()
209 for line in io.lines("/proc/net/dev") do
210 table.insert(devices, line:match(" *(.-):"))
216 --- Return information about available network interfaces.
217 -- @return Table containing all current interface names and their information
218 function net.deviceinfo()
220 for line in io.lines("/proc/net/dev") do
221 local name, data = line:match("^ *(.-): *(.*)$")
222 if name and data then
223 devices[name] = luci.util.split(data, " +", nil, true)
230 -- Determine the MAC address belonging to the given IP address.
231 -- @param ip IPv4 address
232 -- @return String containing the MAC address or nil if it cannot be found
233 function net.ip4mac(ip)
236 for i, l in ipairs(net.arptable()) do
237 if l["IP address"] == ip then
238 mac = l["HW address"]
245 --- Returns the current kernel routing table entries.
246 -- @return Table of tables with properties of the corresponding routes.
247 -- The following fields are defined for route entry tables:
248 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
249 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
250 function net.routes()
251 return _parse_delimited_table(io.lines("/proc/net/route"))
255 --- Tests whether the given host responds to ping probes.
256 -- @param host String containing a hostname or IPv4 address
257 -- @return Number containing 0 on success and >= 1 on error
258 function net.pingtest(host)
259 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
263 --- LuCI system utilities / process related functions.
265 -- @name luci.sys.process
268 --- Get the current process id.
269 -- @return Number containing the current pid
270 process.info = posix.getpid
272 --- Set the gid of a process identified by given pid.
273 -- @param pid Number containing the process id
274 -- @param gid Number containing the Unix group id
275 -- @return Boolean indicating successful operation
276 -- @return String containing the error message if failed
277 -- @return Number containing the error code if failed
278 function process.setgroup(pid, gid)
279 return posix.setpid("g", pid, gid)
282 --- Set the uid of a process identified by given pid.
283 -- @param pid Number containing the process id
284 -- @param uid Number containing the Unix user id
285 -- @return Boolean indicating successful operation
286 -- @return String containing the error message if failed
287 -- @return Number containing the error code if failed
288 function process.setuser(pid, uid)
289 return posix.setpid("u", pid, uid)
293 --- LuCI system utilities / user related functions.
295 -- @name luci.sys.user
298 --- Retrieve user informations for given uid.
301 -- @param uid Number containing the Unix user id
302 -- @return Table containing the following fields:
303 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
304 user.getuser = posix.getpasswd
306 --- Test whether given string matches the password of a given system user.
307 -- @param username String containing the Unix user name
308 -- @param password String containing the password to compare
309 -- @return Boolean indicating wheather the passwords are equal
310 function user.checkpasswd(username, password)
311 local account = user.getuser(username)
314 if account.passwd == "!" then
317 return (account.passwd == posix.crypt(password, account.passwd))
322 --- Change the password of given user.
323 -- @param username String containing the Unix user name
324 -- @param password String containing the password to compare
325 -- @return Number containing 0 on success and >= 1 on error
326 function user.setpasswd(username, password)
328 password = password:gsub("'", "")
332 username = username:gsub("'", "")
335 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
336 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
337 return os.execute(cmd)
341 --- LuCI system utilities / wifi related functions.
343 -- @name luci.sys.wifi
346 --- Get iwconfig output for all wireless devices.
347 -- @return Table of tables containing the iwconfing output for each wifi device
348 function wifi.getiwconfig()
349 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
352 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
353 local k = l:match("^(.-) ")
354 l = l:gsub("^(.-) +", "", 1)
356 iwc[k] = _parse_mixed_record(l)
363 --- Get iwlist scan output from all wireless devices.
364 -- @return Table of tables contaiing all scan results
365 function wifi.iwscan()
366 local cnt = luci.util.exec("iwlist scan 2>/dev/null")
369 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
370 local k = l:match("^(.-) ")
371 l = l:gsub("^[^\n]+", "", 1)
372 l = luci.util.trim(l)
375 for j, c in pairs(luci.util.split(l, "\n Cell")) do
376 c = c:gsub("^(.-)- ", "", 1)
377 c = luci.util.split(c, "\n", 7)
378 c = table.concat(c, "\n", 1)
379 table.insert(iws[k], _parse_mixed_record(c))
388 -- Internal functions
390 function _parse_delimited_table(iter, delimiter)
391 delimiter = delimiter or "%s+"
394 local trim = luci.util.trim
395 local split = luci.util.split
397 local keys = split(trim(iter()), delimiter, nil, true)
398 for i, j in pairs(keys) do
399 keys[i] = trim(keys[i])
406 for i, j in pairs(split(line, delimiter, nil, true)) do
412 table.insert(data, row)
418 function _parse_mixed_record(cnt)
421 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
422 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
423 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
427 table.insert(data, k)