Moved luci.sys.exec, luci.sys.execl and luci.sys.bigendian to luci.util
[project/luci.git] / libs / core / luasrc / sys.lua
1 --[[
2 LuCI - System library
3
4 Description:
5 Utilities for interaction with the Linux system
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
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
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
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.
24
25 ]]--
26
27 --- LuCI system utilities.
28 module("luci.sys", package.seeall)
29 require("posix")
30 require("luci.bits")
31 require("luci.util")
32 require("luci.fs")
33
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 "
39 if kpattern then
40 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
41 end
42 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
43
44 return os.execute(cmd)
45 end
46
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
50 -- exists.
51 -- @class function
52 -- @name getenv
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
56 getenv = posix.getenv
57
58 --- Determine the current hostname.
59 -- @return String containing the system hostname
60 function hostname()
61 return io.lines("/proc/sys/kernel/hostname")()
62 end
63
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("'", "").."'")
71 end
72
73 --- Returns the absolute path to LuCI base directory.
74 -- @return String containing the directory path
75 function libpath()
76 return luci.fs.dirname(require("luci.debug").__file__)
77 end
78
79 --- Returns the system load average values.
80 -- @return String containing the average load value 1 minute ago
81 -- @return String containing the average load value 5 minutes ago
82 -- @return String containing the average load value 15 minutes ago
83 -- @return String containing the active and total number of processes
84 -- @return String containing the last used pid
85 function loadavg()
86 local loadavg = io.lines("/proc/loadavg")()
87 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
88 end
89
90 --- Initiate a system reboot.
91 -- @return Return value of os.execute()
92 function reboot()
93 return os.execute("reboot >/dev/null 2>&1")
94 end
95
96 --- Returns the system type, cpu name and installed physical memory.
97 -- @return String containing the system or platform identifier
98 -- @return String containing hardware model information
99 -- @return String containing the total memory amount in kB
100 -- @return String containing the memory used for caching in kB
101 -- @return String containing the memory used for buffering in kB
102 -- @return String containing the free memory amount in kB
103 -- @return Number containing free memory in percent
104 -- @return Number containing buffer memory in percent
105 -- @return Number containing cache memory in percent
106 function sysinfo()
107 local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
108 local c2 = "uname -m 2>/dev/null"
109 local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
110 local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
111 local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
112 local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
113 local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
114 local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
115
116 local system = luci.util.trim(luci.util.exec(c1))
117 local model = ""
118 local memtotal = luci.util.trim(luci.util.exec(c5))
119 local memcached = luci.util.trim(luci.util.exec(c6))
120 local memfree = luci.util.trim(luci.util.exec(c7))
121 local membuffers = luci.util.trim(luci.util.exec(c8))
122 local perc_memfree = math.floor((memfree/memtotal)*100)
123 local perc_membuffers = math.floor((membuffers/memtotal)*100)
124 local perc_memcached = math.floor((memcached/memtotal)*100)
125
126 if system == "" then
127 system = luci.util.trim(luci.util.exec(c2))
128 model = luci.util.trim(luci.util.exec(c3))
129 else
130 model = luci.util.trim(luci.util.exec(c4))
131 end
132
133 return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
134 end
135
136 --- Retrieves the output of the "logread" command.
137 -- @return String containing the current log buffer
138 function syslog()
139 return luci.util.exec("logread")
140 end
141
142 --- Generates a random id with specified length.
143 -- @param bytes Number of bytes for the unique id
144 -- @return String containing hex encoded id
145 function uniqueid(bytes)
146 local fp = io.open("/dev/urandom")
147 local chunk = { fp:read(bytes):byte(1, bytes) }
148 fp:close()
149
150 local hex = ""
151
152 local pattern = "%02X"
153 for i, byte in ipairs(chunk) do
154 hex = hex .. pattern:format(byte)
155 end
156
157 return hex
158 end
159
160 --- Returns the current system uptime stats.
161 -- @return String containing total uptime in seconds
162 -- @return String containing idle time in seconds
163 function uptime()
164 local loadavg = io.lines("/proc/uptime")()
165 return loadavg:match("^(.-) (.-)$")
166 end
167
168 --- LuCI system utilities / POSIX user group related functions.
169 -- @class module
170 -- @name luci.sys.group
171 group = {}
172
173 --- Returns information about a POSIX user group.
174 -- @param group Group ID or name of a system user group
175 -- @return Table with information about the requested group
176 group.getgroup = posix.getgroup
177
178
179 --- LuCI system utilities / network related functions.
180 -- @class module
181 -- @name luci.sys.net
182 net = {}
183
184 --- Returns the current arp-table entries as two-dimensional table.
185 -- @return Table of table containing the current arp entries.
186 -- The following fields are defined for arp entry objects:
187 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
188 function net.arptable()
189 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
190 end
191
192 --- Test whether an IP-Adress belongs to a certain net.
193 -- @param ip IPv4 address to test
194 -- @param ipnet IPv4 network address of the net range to compare against
195 -- @param prefix Network prefix of the net range to compare against
196 -- @return Boolean indicating wheather the ip is within the range
197 function net.belongs(ip, ipnet, prefix)
198 return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
199 end
200
201 --- Determine the current default route.
202 -- @return Table with the properties of the current default route.
203 -- The following fields are defined:
204 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
205 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
206 function net.defaultroute()
207 local routes = net.routes()
208 local route = nil
209
210 for i, r in pairs(luci.sys.net.routes()) do
211 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
212 route = r
213 end
214 end
215
216 return route
217 end
218
219 --- Determine the names of available network interfaces.
220 -- @return Table containing all current interface names
221 function net.devices()
222 local devices = {}
223 for line in io.lines("/proc/net/dev") do
224 table.insert(devices, line:match(" *(.-):"))
225 end
226 return devices
227 end
228
229 -- Determine the MAC address belonging to the given IP address.
230 -- @param ip IPv4 address
231 -- @return String containing the MAC address or nil if it cannot be found
232 function net.ip4mac(ip)
233 local mac = nil
234
235 for i, l in ipairs(net.arptable()) do
236 if l["IP address"] == ip then
237 mac = l["HW address"]
238 end
239 end
240
241 return mac
242 end
243
244 --- Calculate the prefix from a given netmask.
245 -- @param mask IPv4 net mask
246 -- @return Number containing the corresponding numerical prefix
247 function net.mask4prefix(mask)
248 local bin = net.ip4bin(mask)
249
250 if not bin then
251 return nil
252 end
253
254 return #luci.util.split(bin, "1")-1
255 end
256
257 --- Returns the current kernel routing table entries.
258 -- @return Table of tables with properties of the corresponding routes.
259 -- The following fields are defined for route entry tables:
260 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
261 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
262 function net.routes()
263 return _parse_delimited_table(io.lines("/proc/net/route"))
264 end
265
266 --- Convert hexadecimal 32 bit value to IPv4 address.
267 -- @param hex String containing the hexadecimal value
268 -- @param be Boolean indicating wheather the given value is big endian
269 -- @return String containing the corresponding IP4 address
270 function net.hexip4(hex, be)
271 if #hex ~= 8 then
272 return nil
273 end
274
275 be = be or luci.util.bigendian()
276
277 local hexdec = luci.bits.Hex2Dec
278
279 local ip = ""
280 if be then
281 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
282 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
283 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
284 ip = ip .. tostring(hexdec(hex:sub(7,8)))
285 else
286 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
287 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
288 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
289 ip = ip .. tostring(hexdec(hex:sub(1,2)))
290 end
291
292 return ip
293 end
294
295 --- Convert given IPv4 address to binary value.
296 -- @param ip String containing a IPv4 address
297 -- @return String containing corresponding binary value
298 function net.ip4bin(ip)
299 local parts = luci.util.split(ip, '.')
300 if #parts ~= 4 then
301 return nil
302 end
303
304 local decbin = luci.bits.Dec2Bin
305
306 local bin = ""
307 bin = bin .. decbin(parts[1], 8)
308 bin = bin .. decbin(parts[2], 8)
309 bin = bin .. decbin(parts[3], 8)
310 bin = bin .. decbin(parts[4], 8)
311
312 return bin
313 end
314
315 --- Tests whether the given host responds to ping probes.
316 -- @param host String containing a hostname or IPv4 address
317 -- @return Number containing 0 on success and >= 1 on error
318 function net.pingtest(host)
319 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
320 end
321
322
323 --- LuCI system utilities / process related functions.
324 -- @class module
325 -- @name luci.sys.process
326 process = {}
327
328 --- Get the current process id.
329 -- @return Number containing the current pid
330 process.info = posix.getpid
331
332 --- Set the gid of a process identified by given pid.
333 -- @param pid Number containing the process id
334 -- @param gid Number containing the Unix group id
335 -- @return Boolean indicating successful operation
336 -- @return String containing the error message if failed
337 -- @return Number containing the error code if failed
338 function process.setgroup(pid, gid)
339 return posix.setpid("g", pid, gid)
340 end
341
342 --- Set the uid of a process identified by given pid.
343 -- @param pid Number containing the process id
344 -- @param uid Number containing the Unix user id
345 -- @return Boolean indicating successful operation
346 -- @return String containing the error message if failed
347 -- @return Number containing the error code if failed
348 function process.setuser(pid, uid)
349 return posix.setpid("u", pid, uid)
350 end
351
352
353 --- LuCI system utilities / user related functions.
354 -- @class module
355 -- @name luci.sys.user
356 user = {}
357
358 --- Retrieve user informations for given uid.
359 -- @class function
360 -- @name getuser
361 -- @param uid Number containing the Unix user id
362 -- @return Table containing the following fields:
363 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
364 user.getuser = posix.getpasswd
365
366 --- Test whether given string matches the password of a given system user.
367 -- @param username String containing the Unix user name
368 -- @param password String containing the password to compare
369 -- @return Boolean indicating wheather the passwords are equal
370 function user.checkpasswd(username, password)
371 local account = user.getuser(username)
372
373 -- FIXME: detect testing environment
374 if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
375 return true
376 elseif account then
377 if account.passwd == "!" then
378 return true
379 else
380 return (account.passwd == posix.crypt(password, account.passwd))
381 end
382 end
383 end
384
385 --- Change the password of given user.
386 -- @param username String containing the Unix user name
387 -- @param password String containing the password to compare
388 -- @return Number containing 0 on success and >= 1 on error
389 function user.setpasswd(username, password)
390 if password then
391 password = password:gsub("'", "")
392 end
393
394 if username then
395 username = username:gsub("'", "")
396 end
397
398 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
399 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
400 return os.execute(cmd)
401 end
402
403
404 --- LuCI system utilities / wifi related functions.
405 -- @class module
406 -- @name luci.sys.wifi
407 wifi = {}
408
409 --- Get iwconfig output for all wireless devices.
410 -- @return Table of tables containing the iwconfing output for each wifi device
411 function wifi.getiwconfig()
412 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
413 local iwc = {}
414
415 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
416 local k = l:match("^(.-) ")
417 l = l:gsub("^(.-) +", "", 1)
418 if k then
419 iwc[k] = _parse_mixed_record(l)
420 end
421 end
422
423 return iwc
424 end
425
426 --- Get iwlist scan output from all wireless devices.
427 -- @return Table of tables contaiing all scan results
428 function wifi.iwscan()
429 local cnt = luci.util.exec("iwlist scan 2>/dev/null")
430 local iws = {}
431
432 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
433 local k = l:match("^(.-) ")
434 l = l:gsub("^[^\n]+", "", 1)
435 l = luci.util.trim(l)
436 if k then
437 iws[k] = {}
438 for j, c in pairs(luci.util.split(l, "\n Cell")) do
439 c = c:gsub("^(.-)- ", "", 1)
440 c = luci.util.split(c, "\n", 7)
441 c = table.concat(c, "\n", 1)
442 table.insert(iws[k], _parse_mixed_record(c))
443 end
444 end
445 end
446
447 return iws
448 end
449
450
451 -- Internal functions
452
453 function _parse_delimited_table(iter, delimiter)
454 delimiter = delimiter or "%s+"
455
456 local data = {}
457 local trim = luci.util.trim
458 local split = luci.util.split
459
460 local keys = split(trim(iter()), delimiter, nil, true)
461 for i, j in pairs(keys) do
462 keys[i] = trim(keys[i])
463 end
464
465 for line in iter do
466 local row = {}
467 line = trim(line)
468 if #line > 0 then
469 for i, j in pairs(split(line, delimiter, nil, true)) do
470 if keys[i] then
471 row[keys[i]] = j
472 end
473 end
474 end
475 table.insert(data, row)
476 end
477
478 return data
479 end
480
481 function _parse_mixed_record(cnt)
482 local data = {}
483
484 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
485 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
486 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
487
488 if k then
489 if x == "" then
490 table.insert(data, k)
491 else
492 data[k] = v
493 end
494 end
495 end
496 end
497
498 return data
499 end