d0dc5acbd879a9ffda17e55763b361388555fbd8
[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