557a7ce54c7347d8d2f4e4ee8312d838122ad75f
[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 -- @param group Group ID or name of a system user group
211 -- @return Table with information about the requested group
212 group.getgroup = posix.getgroup
213
214
215 --- LuCI system utilities / network related functions.
216 -- @class module
217 -- @name luci.sys.net
218 net = {}
219
220 --- Returns the current arp-table entries as two-dimensional table.
221 -- @return Table of table containing the current arp entries.
222 -- The following fields are defined for arp entry objects:
223 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
224 function net.arptable()
225 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
226 end
227
228 --- Determine the current default route.
229 -- @return Table with the properties of the current default route.
230 -- The following fields are defined:
231 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
232 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
233 function net.defaultroute()
234 local routes = net.routes()
235 local route = nil
236
237 for i, r in pairs(luci.sys.net.routes()) do
238 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
239 route = r
240 end
241 end
242
243 return route
244 end
245
246 --- Determine the names of available network interfaces.
247 -- @return Table containing all current interface names
248 function net.devices()
249 local devices = {}
250 for line in io.lines("/proc/net/dev") do
251 table.insert(devices, line:match(" *(.-):"))
252 end
253 return devices
254 end
255
256
257 --- Return information about available network interfaces.
258 -- @return Table containing all current interface names and their information
259 function net.deviceinfo()
260 local devices = {}
261 for line in io.lines("/proc/net/dev") do
262 local name, data = line:match("^ *(.-): *(.*)$")
263 if name and data then
264 devices[name] = luci.util.split(data, " +", nil, true)
265 end
266 end
267 return devices
268 end
269
270
271 -- Determine the MAC address belonging to the given IP address.
272 -- @param ip IPv4 address
273 -- @return String containing the MAC address or nil if it cannot be found
274 function net.ip4mac(ip)
275 local mac = nil
276
277 for i, l in ipairs(net.arptable()) do
278 if l["IP address"] == ip then
279 mac = l["HW address"]
280 end
281 end
282
283 return mac
284 end
285
286 --- Returns the current kernel routing table entries.
287 -- @return Table of tables with properties of the corresponding routes.
288 -- The following fields are defined for route entry tables:
289 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
290 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
291 function net.routes()
292 return _parse_delimited_table(io.lines("/proc/net/route"))
293 end
294
295
296 --- Tests whether the given host responds to ping probes.
297 -- @param host String containing a hostname or IPv4 address
298 -- @return Number containing 0 on success and >= 1 on error
299 function net.pingtest(host)
300 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
301 end
302
303
304 --- LuCI system utilities / process related functions.
305 -- @class module
306 -- @name luci.sys.process
307 process = {}
308
309 --- Get the current process id.
310 -- @return Number containing the current pid
311 process.info = posix.getpid
312
313 --- Retrieve information about currently running processes.
314 -- @return Table containing process information
315 function process.list()
316 local data = {}
317 local k
318 local ps = luci.util.execi("top -bn1")
319
320 if not ps then
321 return
322 end
323
324 while true do
325 local line = ps()
326 if not line then
327 return
328 end
329
330 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
331 if k[1] == "PID" then
332 break
333 end
334 end
335
336 for line in ps do
337 local row = {}
338
339 line = luci.util.trim(line)
340 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
341 row[k[i]] = value
342 end
343
344 local pid = tonumber(row[k[1]])
345 if pid then
346 data[pid] = row
347 end
348 end
349
350 return data
351 end
352
353 --- Set the gid of a process identified by given pid.
354 -- @param pid Number containing the process id
355 -- @param gid Number containing the Unix group id
356 -- @return Boolean indicating successful operation
357 -- @return String containing the error message if failed
358 -- @return Number containing the error code if failed
359 function process.setgroup(pid, gid)
360 return posix.setpid("g", pid, gid)
361 end
362
363 --- Set the uid of a process identified by given pid.
364 -- @param pid Number containing the process id
365 -- @param uid Number containing the Unix user id
366 -- @return Boolean indicating successful operation
367 -- @return String containing the error message if failed
368 -- @return Number containing the error code if failed
369 function process.setuser(pid, uid)
370 return posix.setpid("u", pid, uid)
371 end
372
373 --- Send a signal to a process identified by given pid.
374 -- @param pid Number containing the process id
375 -- @param sig Signal to send (default: 15 [SIGTERM])
376 -- @return Boolean indicating successful operation
377 -- @return Number containing the error code if failed
378 process.signal = posix.kill
379
380
381 --- LuCI system utilities / user related functions.
382 -- @class module
383 -- @name luci.sys.user
384 user = {}
385
386 --- Retrieve user informations for given uid.
387 -- @class function
388 -- @name getuser
389 -- @param uid Number containing the Unix user id
390 -- @return Table containing the following fields:
391 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
392 user.getuser = posix.getpasswd
393
394 --- Test whether given string matches the password of a given system user.
395 -- @param username String containing the Unix user name
396 -- @param password String containing the password to compare
397 -- @return Boolean indicating wheather the passwords are equal
398 function user.checkpasswd(username, password)
399 local account = user.getuser(username)
400
401 if account then
402 if account.passwd == "!" then
403 return true
404 else
405 return (account.passwd == posix.crypt(password, account.passwd))
406 end
407 end
408 end
409
410 --- Change the password of given user.
411 -- @param username String containing the Unix user name
412 -- @param password String containing the password to compare
413 -- @return Number containing 0 on success and >= 1 on error
414 function user.setpasswd(username, password)
415 if password then
416 password = password:gsub("'", "")
417 end
418
419 if username then
420 username = username:gsub("'", "")
421 end
422
423 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
424 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
425 return os.execute(cmd)
426 end
427
428
429 --- LuCI system utilities / wifi related functions.
430 -- @class module
431 -- @name luci.sys.wifi
432 wifi = {}
433
434 --- Get iwconfig output for all wireless devices.
435 -- @return Table of tables containing the iwconfing output for each wifi device
436 function wifi.getiwconfig()
437 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
438 local iwc = {}
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("^(.-) +", "", 1)
443 if k then
444 iwc[k] = _parse_mixed_record(l)
445 end
446 end
447
448 return iwc
449 end
450
451 --- Get iwlist scan output from all wireless devices.
452 -- @return Table of tables contaiing all scan results
453 function wifi.iwscan(iface)
454 local siface = iface or ""
455 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
456 local iws = {}
457
458 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
459 local k = l:match("^(.-) ")
460 l = l:gsub("^[^\n]+", "", 1)
461 l = luci.util.trim(l)
462 if k then
463 iws[k] = {}
464 for j, c in pairs(luci.util.split(l, "\n Cell")) do
465 c = c:gsub("^(.-)- ", "", 1)
466 c = luci.util.split(c, "\n", 7)
467 c = table.concat(c, "\n", 1)
468 table.insert(iws[k], _parse_mixed_record(c))
469 end
470 end
471 end
472
473 return iface and (iws[iface] or {}) or iws
474 end
475
476
477 -- Internal functions
478
479 function _parse_delimited_table(iter, delimiter)
480 delimiter = delimiter or "%s+"
481
482 local data = {}
483 local trim = luci.util.trim
484 local split = luci.util.split
485
486 local keys = split(trim(iter()), delimiter, nil, true)
487 for i, j in pairs(keys) do
488 keys[i] = trim(keys[i])
489 end
490
491 for line in iter do
492 local row = {}
493 line = trim(line)
494 if #line > 0 then
495 for i, j in pairs(split(line, delimiter, nil, true)) do
496 if keys[i] then
497 row[keys[i]] = j
498 end
499 end
500 end
501 table.insert(data, row)
502 end
503
504 return data
505 end
506
507 function _parse_mixed_record(cnt)
508 local data = {}
509
510 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
511 for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do
512 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
513
514 if k then
515 if x == "" then
516 table.insert(data, k)
517 else
518 data[k] = v
519 end
520 end
521 end
522 end
523
524 return data
525 end