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