Detect IXP4xx processors correctly
[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, pcall = tonumber, ipairs, pairs, pcall
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
98 -- this is a rather ugly workaround to cope with wrapped lines in
99 -- the df output:
100 --
101 -- /dev/scsi/host0/bus0/target0/lun0/part3
102 -- 114382024 93566472 15005244 86% /mnt/usb
103 --
104
105 if not row[k[2]] then
106 j = 2
107 line = ps()
108 for value in line:gmatch("[^%s]+") do
109 row[k[j]] = value
110 j = j + 1
111 end
112 end
113
114 table.insert(data, row)
115 end
116 end
117
118 return data
119 end
120
121 --- Retrieve environment variables. If no variable is given then a table
122 -- containing the whole environment is returned otherwise this function returns
123 -- the corresponding string value for the given name or nil if no such variable
124 -- exists.
125 -- @class function
126 -- @name getenv
127 -- @param var Name of the environment variable to retrieve (optional)
128 -- @return String containg the value of the specified variable
129 -- @return Table containing all variables if no variable name is given
130 getenv = posix.getenv
131
132 --- Determine the current hostname.
133 -- @return String containing the system hostname
134 function hostname()
135 return posix.uname("%n")
136 end
137
138 --- Returns the contents of a documented referred by an URL.
139 -- @param url The URL to retrieve
140 -- @param stream Return a stream instead of a buffer
141 -- @param target Directly write to target file name
142 -- @return String containing the contents of given the URL
143 function httpget(url, stream, target)
144 if not target then
145 local source = stream and io.popen or luci.util.exec
146 return source("wget -qO- '"..url:gsub("'", "").."'")
147 else
148 return os.execute("wget -qO '%s' '%s'" %
149 {target:gsub("'", ""), url:gsub("'", "")})
150 end
151 end
152
153 --- Returns the system load average values.
154 -- @return String containing the average load value 1 minute ago
155 -- @return String containing the average load value 5 minutes ago
156 -- @return String containing the average load value 15 minutes ago
157 -- @return String containing the active and total number of processes
158 -- @return String containing the last used pid
159 function loadavg()
160 local loadavg = io.lines("/proc/loadavg")()
161 return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
162 end
163
164 --- Initiate a system reboot.
165 -- @return Return value of os.execute()
166 function reboot()
167 return os.execute("reboot >/dev/null 2>&1")
168 end
169
170 --- Returns the system type, cpu name and installed physical memory.
171 -- @return String containing the system or platform identifier
172 -- @return String containing hardware model information
173 -- @return String containing the total memory amount in kB
174 -- @return String containing the memory used for caching in kB
175 -- @return String containing the memory used for buffering in kB
176 -- @return String containing the free memory amount in kB
177 function sysinfo()
178 local cpuinfo = luci.fs.readfile("/proc/cpuinfo")
179 local meminfo = luci.fs.readfile("/proc/meminfo")
180
181 local system = cpuinfo:match("system typ.-:%s*([^\n]+)")
182 local model = ""
183 local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
184 local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
185 local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
186 local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
187
188 if not system then
189 system = posix.uname("%m")
190 model = cpuinfo:match("model name.-:%s*([^\n]+)")
191 if not model then
192 model = cpuinfo:match("Processor.-:%s*([^\n]+)")
193 end
194 else
195 model = cpuinfo:match("cpu model.-:%s*([^\n]+)")
196 end
197
198 return system, model, memtotal, memcached, membuffers, memfree
199 end
200
201 --- Retrieves the output of the "logread" command.
202 -- @return String containing the current log buffer
203 function syslog()
204 return luci.util.exec("logread")
205 end
206
207 --- Generates a random id with specified length.
208 -- @param bytes Number of bytes for the unique id
209 -- @return String containing hex encoded id
210 function uniqueid(bytes)
211 local fp = io.open("/dev/urandom")
212 local chunk = { fp:read(bytes):byte(1, bytes) }
213 fp:close()
214
215 local hex = ""
216
217 local pattern = "%02X"
218 for i, byte in ipairs(chunk) do
219 hex = hex .. pattern:format(byte)
220 end
221
222 return hex
223 end
224
225 --- Returns the current system uptime stats.
226 -- @return String containing total uptime in seconds
227 -- @return String containing idle time in seconds
228 function uptime()
229 local loadavg = io.lines("/proc/uptime")()
230 return loadavg:match("^(.-) (.-)$")
231 end
232
233 --- LuCI system utilities / POSIX user group related functions.
234 -- @class module
235 -- @name luci.sys.group
236 group = {}
237
238 --- Returns information about a POSIX user group.
239 -- @class function
240 -- @name getgroup
241 -- @param group Group ID or name of a system user group
242 -- @return Table with information about the requested group
243 group.getgroup = posix.getgroup
244
245
246 --- LuCI system utilities / network related functions.
247 -- @class module
248 -- @name luci.sys.net
249 net = {}
250
251 --- Returns the current arp-table entries as two-dimensional table.
252 -- @return Table of table containing the current arp entries.
253 -- The following fields are defined for arp entry objects:
254 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
255 function net.arptable()
256 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
257 end
258
259 --- Returns conntrack information
260 -- @return Table with the currently tracked IP connections
261 function net.conntrack()
262 local connt = {}
263 if luci.fs.access("/proc/net/nf_conntrack") then
264 for line in io.lines("/proc/net/nf_conntrack") do
265 local entry, flags = _parse_mixed_record(line, " +")
266 entry.layer3 = flags[1]
267 entry.layer4 = flags[2]
268 for i=1, #entry do
269 entry[i] = nil
270 end
271
272 connt[#connt+1] = entry
273 end
274 elseif luci.fs.access("/proc/net/ip_conntrack") then
275 for line in io.lines("/proc/net/ip_conntrack") do
276 local entry, flags = _parse_mixed_record(line, " +")
277 entry.layer3 = "ipv4"
278 entry.layer4 = flags[1]
279 for i=1, #entry do
280 entry[i] = nil
281 end
282
283 connt[#connt+1] = entry
284 end
285 else
286 return nil
287 end
288 return connt
289 end
290
291 --- Determine the current default route.
292 -- @return Table with the properties of the current default route.
293 -- The following fields are defined:
294 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
295 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
296 function net.defaultroute()
297 local routes = net.routes()
298 local route = nil
299
300 for i, r in pairs(luci.sys.net.routes()) do
301 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
302 route = r
303 end
304 end
305
306 return route
307 end
308
309 --- Determine the names of available network interfaces.
310 -- @return Table containing all current interface names
311 function net.devices()
312 local devices = {}
313 for line in io.lines("/proc/net/dev") do
314 table.insert(devices, line:match(" *(.-):"))
315 end
316 return devices
317 end
318
319
320 --- Return information about available network interfaces.
321 -- @return Table containing all current interface names and their information
322 function net.deviceinfo()
323 local devices = {}
324 for line in io.lines("/proc/net/dev") do
325 local name, data = line:match("^ *(.-): *(.*)$")
326 if name and data then
327 devices[name] = luci.util.split(data, " +", nil, true)
328 end
329 end
330 return devices
331 end
332
333
334 -- Determine the MAC address belonging to the given IP address.
335 -- @param ip IPv4 address
336 -- @return String containing the MAC address or nil if it cannot be found
337 function net.ip4mac(ip)
338 local mac = nil
339
340 for i, l in ipairs(net.arptable()) do
341 if l["IP address"] == ip then
342 mac = l["HW address"]
343 end
344 end
345
346 return mac
347 end
348
349 --- Returns the current kernel routing table entries.
350 -- @return Table of tables with properties of the corresponding routes.
351 -- The following fields are defined for route entry tables:
352 -- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
353 -- "MTU", "Gateway", "Destination", "Metric", "Use" }
354 function net.routes()
355 return _parse_delimited_table(io.lines("/proc/net/route"))
356 end
357
358
359 --- Tests whether the given host responds to ping probes.
360 -- @param host String containing a hostname or IPv4 address
361 -- @return Number containing 0 on success and >= 1 on error
362 function net.pingtest(host)
363 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
364 end
365
366
367 --- LuCI system utilities / process related functions.
368 -- @class module
369 -- @name luci.sys.process
370 process = {}
371
372 --- Get the current process id.
373 -- @class function
374 -- @name process.info
375 -- @return Number containing the current pid
376 process.info = posix.getpid
377
378 --- Retrieve information about currently running processes.
379 -- @return Table containing process information
380 function process.list()
381 local data = {}
382 local k
383 local ps = luci.util.execi("top -bn1")
384
385 if not ps then
386 return
387 end
388
389 while true do
390 local line = ps()
391 if not line then
392 return
393 end
394
395 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
396 if k[1] == "PID" then
397 break
398 end
399 end
400
401 for line in ps do
402 local row = {}
403
404 line = luci.util.trim(line)
405 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
406 row[k[i]] = value
407 end
408
409 local pid = tonumber(row[k[1]])
410 if pid then
411 data[pid] = row
412 end
413 end
414
415 return data
416 end
417
418 --- Set the gid of a process identified by given pid.
419 -- @param pid Number containing the process id
420 -- @param gid Number containing the Unix group id
421 -- @return Boolean indicating successful operation
422 -- @return String containing the error message if failed
423 -- @return Number containing the error code if failed
424 function process.setgroup(pid, gid)
425 return posix.setpid("g", pid, gid)
426 end
427
428 --- Set the uid of a process identified by given pid.
429 -- @param pid Number containing the process id
430 -- @param uid Number containing the Unix user id
431 -- @return Boolean indicating successful operation
432 -- @return String containing the error message if failed
433 -- @return Number containing the error code if failed
434 function process.setuser(pid, uid)
435 return posix.setpid("u", pid, uid)
436 end
437
438 --- Send a signal to a process identified by given pid.
439 -- @class function
440 -- @name process.signal
441 -- @param pid Number containing the process id
442 -- @param sig Signal to send (default: 15 [SIGTERM])
443 -- @return Boolean indicating successful operation
444 -- @return Number containing the error code if failed
445 process.signal = posix.kill
446
447
448 --- LuCI system utilities / user related functions.
449 -- @class module
450 -- @name luci.sys.user
451 user = {}
452
453 --- Retrieve user informations for given uid.
454 -- @class function
455 -- @name getuser
456 -- @param uid Number containing the Unix user id
457 -- @return Table containing the following fields:
458 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
459 user.getuser = posix.getpasswd
460
461 --- Test whether given string matches the password of a given system user.
462 -- @param username String containing the Unix user name
463 -- @param password String containing the password to compare
464 -- @return Boolean indicating wheather the passwords are equal
465 function user.checkpasswd(username, password)
466 local account = user.getuser(username)
467
468 if account then
469 local pwd = account.passwd
470 local shadowpw
471 if #pwd == 1 then
472 if luci.fs.stat("/etc/shadow") then
473 if not pcall(function()
474 for l in io.lines("/etc/shadow") do
475 shadowpw = l:match("^%s:([^:]+)" % username)
476 if shadowpw then
477 pwd = shadowpw
478 break
479 end
480 end
481 end) then
482 return nil, "Unable to access shadow-file"
483 end
484 end
485
486 if pwd == "!" then
487 return true
488 end
489 end
490
491 if pwd and #pwd > 0 and password and #password > 0 then
492 return (pwd == posix.crypt(password, pwd))
493 end
494 end
495
496 return false
497 end
498
499 --- Change the password of given user.
500 -- @param username String containing the Unix user name
501 -- @param password String containing the password to compare
502 -- @return Number containing 0 on success and >= 1 on error
503 function user.setpasswd(username, password)
504 if password then
505 password = password:gsub("'", "")
506 end
507
508 if username then
509 username = username:gsub("'", "")
510 end
511
512 local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
513 cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
514 return os.execute(cmd)
515 end
516
517
518 --- LuCI system utilities / wifi related functions.
519 -- @class module
520 -- @name luci.sys.wifi
521 wifi = {}
522
523 --- Get iwconfig output for all wireless devices.
524 -- @return Table of tables containing the iwconfing output for each wifi device
525 function wifi.getiwconfig()
526 local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
527 local iwc = {}
528
529 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
530 local k = l:match("^(.-) ")
531 l = l:gsub("^(.-) +", "", 1)
532 if k then
533 local entry, flags = _parse_mixed_record(l)
534 if entry then
535 entry.flags = flags
536 end
537 iwc[k] = entry
538 end
539 end
540
541 return iwc
542 end
543
544 --- Get iwlist scan output from all wireless devices.
545 -- @return Table of tables contaiing all scan results
546 function wifi.iwscan(iface)
547 local siface = iface or ""
548 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
549 local iws = {}
550
551 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
552 local k = l:match("^(.-) ")
553 l = l:gsub("^[^\n]+", "", 1)
554 l = luci.util.trim(l)
555 if k then
556 iws[k] = {}
557 for j, c in pairs(luci.util.split(l, "\n Cell")) do
558 c = c:gsub("^(.-)- ", "", 1)
559 c = luci.util.split(c, "\n", 7)
560 c = table.concat(c, "\n", 1)
561 local entry, flags = _parse_mixed_record(c)
562 if entry then
563 entry.flags = flags
564 end
565 table.insert(iws[k], entry)
566 end
567 end
568 end
569
570 return iface and (iws[iface] or {}) or iws
571 end
572
573
574 --- LuCI system utilities / init related functions.
575 -- @class module
576 -- @name luci.sys.init
577 init = {}
578 init.dir = "/etc/init.d/"
579
580 --- Get the names of all installed init scripts
581 -- @return Table containing the names of all inistalled init scripts
582 function init.names()
583 local names = { }
584 for _, name in ipairs(luci.fs.glob(init.dir.."*")) do
585 names[#names+1] = luci.fs.basename(name)
586 end
587 return names
588 end
589
590 --- Test whether the given init script is enabled
591 -- @param name Name of the init script
592 -- @return Boolean indicating whether init is enabled
593 function init.enabled(name)
594 if luci.fs.access(init.dir..name) then
595 return ( call(init.dir..name.." enabled") == 0 )
596 end
597 return false
598 end
599
600 --- Get the index of he given init script
601 -- @param name Name of the init script
602 -- @return Numeric index value
603 function init.index(name)
604 if luci.fs.access(init.dir..name) then
605 return call("source "..init.dir..name.."; exit $START")
606 end
607 end
608
609 --- Enable the given init script
610 -- @param name Name of the init script
611 -- @return Boolean indicating success
612 function init.enable(name)
613 if luci.fs.access(init.dir..name) then
614 return ( call(init.dir..name.." enable") == 1 )
615 end
616 end
617
618 --- Disable the given init script
619 -- @param name Name of the init script
620 -- @return Boolean indicating success
621 function init.disable(name)
622 if luci.fs.access(init.dir..name) then
623 return ( call(init.dir..name.." disable") == 0 )
624 end
625 end
626
627
628 -- Internal functions
629
630 function _parse_delimited_table(iter, delimiter)
631 delimiter = delimiter or "%s+"
632
633 local data = {}
634 local trim = luci.util.trim
635 local split = luci.util.split
636
637 local keys = split(trim(iter()), delimiter, nil, true)
638 for i, j in pairs(keys) do
639 keys[i] = trim(keys[i])
640 end
641
642 for line in iter do
643 local row = {}
644 line = trim(line)
645 if #line > 0 then
646 for i, j in pairs(split(line, delimiter, nil, true)) do
647 if keys[i] then
648 row[keys[i]] = j
649 end
650 end
651 end
652 table.insert(data, row)
653 end
654
655 return data
656 end
657
658 function _parse_mixed_record(cnt, delimiter)
659 delimiter = delimiter or " "
660 local data = {}
661 local flags = {}
662
663 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
664 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
665 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
666
667 if k then
668 if x == "" then
669 table.insert(flags, k)
670 else
671 data[k] = v
672 end
673 end
674 end
675 end
676
677 return data, flags
678 end