c291f678961ee2c98dad87d529405bb3f05ea63b
[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