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