45e44b80d1d3e8828c49c9dcbd12d53ae1c84a79
[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      kpattern (ToDo: clearify this)
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 --- Get group information
206 -- @return      Group (ToDo: clearify)
207 group = {}
208 group.getgroup = posix.getgroup
209
210
211 --- LuCI system utilities / network related functions.
212 -- @class       module
213 -- @name        luci.sys.net
214 net = {}
215
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+")
222 end
223
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))
231 end
232
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()
240         local route = nil
241
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
244                         route = r
245                 end
246         end
247
248         return route
249 end
250
251 --- Determine the names of available network interfaces.
252 -- @return      Table containing all current interface names
253 function net.devices()
254         local devices = {}
255         for line in io.lines("/proc/net/dev") do
256                 table.insert(devices, line:match(" *(.-):"))
257         end
258         return devices
259 end
260
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)
265         local mac = nil
266
267         for i, l in ipairs(net.arptable()) do
268                 if l["IP address"] == ip then
269                         mac = l["HW address"]
270                 end
271         end
272
273         return mac
274 end
275
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)
281
282         if not bin then
283                 return nil
284         end
285
286         return #luci.util.split(bin, "1")-1
287 end
288
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"))
296 end
297
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)
303         if #hex ~= 8 then
304                 return nil
305         end
306
307         be = be or bigendian()
308
309         local hexdec = luci.bits.Hex2Dec
310
311         local ip = ""
312         if be then
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)))
317         else
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)))
322         end
323
324         return ip
325 end
326
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, '.')
332         if #parts ~= 4 then
333                 return nil
334         end
335
336         local decbin = luci.bits.Dec2Bin
337
338         local bin = ""
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)
343
344         return bin
345 end
346
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")
352 end
353
354
355 --- LuCI system utilities / process related functions.
356 -- @class       module
357 -- @name        luci.sys.process
358 process = {}
359
360 --- Get the current process id.
361 -- @return      Number containing the current pid
362 process.info = posix.getpid
363
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)
372 end
373
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)
382 end
383
384
385 --- LuCI system utilities / user related functions.
386 -- @class       module
387 -- @name        luci.sys.user
388 user = {}
389
390 --- Retrieve user informations for given uid.
391 -- @class               function
392 -- @name                getuser
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
397
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)
404
405         -- FIXME: detect testing environment
406         if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
407                 return true
408         elseif account then
409                 if account.passwd == "!" then
410                         return true
411                 else
412                         return (account.passwd == posix.crypt(password, account.passwd))
413                 end
414         end
415 end
416
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)
422         if password then
423                 password = password:gsub("'", "")
424         end
425
426         if username then
427                 username = username:gsub("'", "")
428         end
429
430         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
431         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
432         return os.execute(cmd)
433 end
434
435
436 --- LuCI system utilities / wifi related functions.
437 -- @class       module
438 -- @name        luci.sys.wifi
439 wifi = {}
440
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")
445         local iwc = {}
446
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)
450                 if k then
451                         iwc[k] = _parse_mixed_record(l)
452                 end
453         end
454
455         return iwc
456 end
457
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")
462         local iws = {}
463
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)
468                 if k then
469                         iws[k] = {}
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))
475                         end
476                 end
477         end
478
479         return iws
480 end
481
482
483 -- Internal functions
484
485 function _parse_delimited_table(iter, delimiter)
486         delimiter = delimiter or "%s+"
487
488         local data  = {}
489         local trim  = luci.util.trim
490         local split = luci.util.split
491
492         local keys = split(trim(iter()), delimiter, nil, true)
493         for i, j in pairs(keys) do
494                 keys[i] = trim(keys[i])
495         end
496
497         for line in iter do
498                 local row = {}
499                 line = trim(line)
500                 if #line > 0 then
501                         for i, j in pairs(split(line, delimiter, nil, true)) do
502                                 if keys[i] then
503                                         row[keys[i]] = j
504                                 end
505                         end
506                 end
507                 table.insert(data, row)
508         end
509
510         return data
511 end
512
513 function _parse_mixed_record(cnt)
514         local data = {}
515
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"]*)"*')
519
520             if k then
521                                 if x == "" then
522                                         table.insert(data, k)
523                                 else
524                         data[k] = v
525                                 end
526             end
527         end
528         end
529
530     return data
531 end