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