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