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