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