217a36b0f4a1ef6db8767701982cc2708fb9fe3f
[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 wheather the current system is operating in big endian mode.
35 -- @return Boolean value indicating wheather system is big endian
36 function bigendian()
37 local fp = io.open("/bin/sh")
38 fp:seek("set", 5)
39 local be = (fp:read(1):byte() ~= 1)
40 fp:close()
41 return be
42 end
43
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")
50 pp:close()
51
52 return data
53 end
54
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)
60 local line = ""
61 local data = {}
62
63 while true do
64 line = pp:read()
65 if (line == nil) then break end
66 table.insert(data, line)
67 end
68 pp:close()
69
70 return data
71 end
72
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 "
78 if kpattern then
79 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
80 end
81 cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
82
83 return os.execute(cmd)
84 end
85
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
89 -- exists.
90 -- @class function
91 -- @name getenv
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
95 getenv = posix.getenv
96
97 --- Determine the current hostname.
98 -- @return String containing the system hostname
99 function hostname()
100 return io.lines("/proc/sys/kernel/hostname")()
101 end
102
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("'", "").."'")
108 end
109
110 --- Returns the absolute path to LuCI base directory.
111 -- @return String containing the directory path
112 function libpath()
113 return luci.fs.dirname(require("luci.debug").__file__)
114 end
115
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
122 function loadavg()
123 local loadavg = io.lines("/proc/loadavg")()
124 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
125 end
126
127 --- Initiate a system reboot.
128 -- @return Return value of os.execute()
129 function reboot()
130 return os.execute("reboot >/dev/null 2>&1")
131 end
132
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
143 function sysinfo()
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"
152
153 local system = luci.util.trim(exec(c1))
154 local model = ""
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)
162
163 if system == "" then
164 system = luci.util.trim(exec(c2))
165 model = luci.util.trim(exec(c3))
166 else
167 model = luci.util.trim(exec(c4))
168 end
169
170 return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
171 end
172
173 --- Retrieves the output of the "logread" command.
174 -- @return String containing the current log buffer
175 function syslog()
176 return exec("logread")
177 end
178
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) }
185 fp:close()
186
187 local hex = ""
188
189 local pattern = "%02X"
190 for i, byte in ipairs(chunk) do
191 hex = hex .. pattern:format(byte)
192 end
193
194 return hex
195 end
196
197 --- Returns the current system uptime stats.
198 -- @return String containing total uptime in seconds
199 -- @return String containing idle time in seconds
200 function uptime()
201 local loadavg = io.lines("/proc/uptime")()
202 return loadavg:match("^(.-) (.-)$")
203 end
204
205 --- LuCI system utilities / POSIX user group related functions.
206 -- @class module
207 -- @name luci.sys.group
208 group = {}
209
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
214
215
216 --- LuCI system utilities / network related functions.
217 -- @class module
218 -- @name luci.sys.net
219 net = {}
220
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+")
227 end
228
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))
236 end
237
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()
245 local route = nil
246
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
249 route = r
250 end
251 end
252
253 return route
254 end
255
256 --- Determine the names of available network interfaces.
257 -- @return Table containing all current interface names
258 function net.devices()
259 local devices = {}
260 for line in io.lines("/proc/net/dev") do
261 table.insert(devices, line:match(" *(.-):"))
262 end
263 return devices
264 end
265
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)
270 local mac = nil
271
272 for i, l in ipairs(net.arptable()) do
273 if l["IP address"] == ip then
274 mac = l["HW address"]
275 end
276 end
277
278 return mac
279 end
280
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)
286
287 if not bin then
288 return nil
289 end
290
291 return #luci.util.split(bin, "1")-1
292 end
293
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"))
301 end
302
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)
308 if #hex ~= 8 then
309 return nil
310 end
311
312 be = be or bigendian()
313
314 local hexdec = luci.bits.Hex2Dec
315
316 local ip = ""
317 if be then
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)))
322 else
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)))
327 end
328
329 return ip
330 end
331
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, '.')
337 if #parts ~= 4 then
338 return nil
339 end
340
341 local decbin = luci.bits.Dec2Bin
342
343 local bin = ""
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)
348
349 return bin
350 end
351
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")
357 end
358
359
360 --- LuCI system utilities / process related functions.
361 -- @class module
362 -- @name luci.sys.process
363 process = {}
364
365 --- Get the current process id.
366 -- @return Number containing the current pid
367 process.info = posix.getpid
368
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)
377 end
378
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)
387 end
388
389
390 --- LuCI system utilities / user related functions.
391 -- @class module
392 -- @name luci.sys.user
393 user = {}
394
395 --- Retrieve user informations for given uid.
396 -- @class function
397 -- @name getuser
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
402
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)
409
410 -- FIXME: detect testing environment
411 if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
412 return true
413 elseif account then
414 if account.passwd == "!" then
415 return true
416 else
417 return (account.passwd == posix.crypt(password, account.passwd))
418 end
419 end
420 end
421
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)
427 if password then
428 password = password:gsub("'", "")
429 end
430
431 if username then
432 username = username:gsub("'", "")
433 end
434
435 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
436 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
437 return os.execute(cmd)
438 end
439
440
441 --- LuCI system utilities / wifi related functions.
442 -- @class module
443 -- @name luci.sys.wifi
444 wifi = {}
445
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")
450 local iwc = {}
451
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)
455 if k then
456 iwc[k] = _parse_mixed_record(l)
457 end
458 end
459
460 return iwc
461 end
462
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")
467 local iws = {}
468
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)
473 if k then
474 iws[k] = {}
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))
480 end
481 end
482 end
483
484 return iws
485 end
486
487
488 -- Internal functions
489
490 function _parse_delimited_table(iter, delimiter)
491 delimiter = delimiter or "%s+"
492
493 local data = {}
494 local trim = luci.util.trim
495 local split = luci.util.split
496
497 local keys = split(trim(iter()), delimiter, nil, true)
498 for i, j in pairs(keys) do
499 keys[i] = trim(keys[i])
500 end
501
502 for line in iter do
503 local row = {}
504 line = trim(line)
505 if #line > 0 then
506 for i, j in pairs(split(line, delimiter, nil, true)) do
507 if keys[i] then
508 row[keys[i]] = j
509 end
510 end
511 end
512 table.insert(data, row)
513 end
514
515 return data
516 end
517
518 function _parse_mixed_record(cnt)
519 local data = {}
520
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"]*)"*')
524
525 if k then
526 if x == "" then
527 table.insert(data, k)
528 else
529 data[k] = v
530 end
531 end
532 end
533 end
534
535 return data
536 end