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