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