modules/admin-mini: Several tweaks, initial status page
[project/luci.git] / libs / core / 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 module("luci.sys", package.seeall)
28 require("posix")
29 require("luci.bits")
30 require("luci.util")
31 require("luci.fs")
32
33 -- Returns whether a system is bigendian
34 function bigendian()
35         local fp = io.open("/bin/sh")
36         fp:seek("set", 5)
37         local be = (fp:read(1):byte() ~= 1)
38         fp:close()
39         return be
40 end
41
42 -- Runs "command" and returns its output
43 function exec(command)
44         local pp   = io.popen(command)
45         local data = pp:read("*a")
46         pp:close()
47         
48         return data
49 end
50
51 -- Runs "command" and returns its output as a array of lines
52 function execl(command)
53         local pp   = io.popen(command)  
54         local line = ""
55         local data = {}
56         
57         while true do
58                 line = pp:read()
59                 if (line == nil) then break end
60                 table.insert(data, line)
61         end 
62         pp:close()      
63         
64         return data
65 end
66
67 -- Uses "luci-flash" to flash a new image file to the system
68 function flash(image, kpattern)
69         local cmd = "luci-flash "
70         if kpattern then
71                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
72         end
73         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
74         
75         return os.execute(cmd)
76 end
77
78 -- Returns the enivornment
79 getenv = posix.getenv
80
81 -- Returns the hostname
82 function hostname()
83         return io.lines("/proc/sys/kernel/hostname")()
84 end
85
86 -- Returns the contents of a documented referred by an URL
87 function httpget(url)
88         return exec("wget -qO- '"..url:gsub("'", "").."'")
89 end
90
91 -- Returns the FFLuci-Basedir
92 function libpath()
93         return luci.fs.dirname(require("luci.debug").__file__)
94 end
95
96 -- Returns the load average
97 function loadavg()
98         local loadavg = io.lines("/proc/loadavg")()
99         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
100 end
101
102 -- Reboots the system
103 function reboot()
104         return os.execute("reboot >/dev/null 2>&1")
105 end
106
107 -- Returns the system type, cpu name, and installed physical memory
108 function sysinfo()
109         local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
110         local c2 = "uname -m 2>/dev/null"
111         local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
112         local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
113         local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
114         local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
115         local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
116         local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
117         
118         local system = luci.util.trim(exec(c1))
119         local model = ""              
120         local memtotal = luci.util.trim(exec(c5))
121         local memcached = luci.util.trim(exec(c6))
122         local memfree = luci.util.trim(exec(c7))
123         local membuffers = luci.util.trim(exec(c8))
124         local perc_memfree = math.floor((memfree/memtotal)*100)
125         local perc_membuffers = math.floor((membuffers/memtotal)*100)
126         local perc_memcached = math.floor((memcached/memtotal)*100)
127
128         if system == "" then
129                 system = luci.util.trim(exec(c2))
130                 model = luci.util.trim(exec(c3))
131         else
132                 model = luci.util.trim(exec(c4))
133         end
134
135         return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
136 end
137
138 -- Reads the syslog
139 function syslog()
140         return exec("logread")
141 end
142
143
144 -- Generates a random key of length BYTES
145 function uniqueid(bytes)
146         local fp    = io.open("/dev/urandom")
147         local chunk = { fp:read(bytes):byte(1, bytes) }
148         fp:close()
149         
150         local hex = ""
151         
152         local pattern = "%02X" 
153         for i, byte in ipairs(chunk) do
154                 hex = hex .. pattern:format(byte)
155         end
156         
157         return hex
158 end
159
160
161 -- Returns uptime stats
162 function uptime()
163         local loadavg = io.lines("/proc/uptime")()
164         return loadavg:match("^(.-) (.-)$")
165 end
166
167
168 group = {}
169 group.getgroup = posix.getgroup
170
171 net = {}
172 -- Returns the ARP-Table
173 function net.arptable()
174         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
175 end
176
177 -- Returns whether an IP-Adress belongs to a certain net
178 function net.belongs(ip, ipnet, prefix)
179         return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
180 end
181
182 -- Detect the default route
183 function net.defaultroute()
184         local routes = net.routes()
185         local route = nil
186         
187         for i, r in pairs(luci.sys.net.routes()) do
188                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
189                         route = r
190                 end
191         end
192         
193         return route
194 end
195
196 -- Returns all available network interfaces
197 function net.devices()
198         local devices = {}
199         for line in io.lines("/proc/net/dev") do
200                 table.insert(devices, line:match(" *(.-):"))
201         end
202         return devices
203 end
204
205 -- Returns the MAC-Address belonging to the given IP-Address
206 function net.ip4mac(ip)
207         local mac = nil
208         
209         for i, l in ipairs(net.arptable()) do
210                 if l["IP address"] == ip then
211                         mac = l["HW address"]
212                 end
213         end
214         
215         return mac
216 end
217
218 -- Returns the prefix to a given netmask
219 function net.mask4prefix(mask)
220         local bin = net.ip4bin(mask)
221         
222         if not bin then
223                 return nil
224         end
225         
226         return #luci.util.split(bin, "1")-1
227 end
228
229 -- Returns the kernel routing table
230 function net.routes()
231         return _parse_delimited_table(io.lines("/proc/net/route"))
232 end
233
234 -- Returns the numeric IP to a given hexstring
235 function net.hexip4(hex, be)
236         if #hex ~= 8 then
237                 return nil
238         end
239         
240         be = be or bigendian()
241         
242         local hexdec = luci.bits.Hex2Dec
243         
244         local ip = ""
245         if be then
246                 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
247                 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
248                 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
249                 ip = ip .. tostring(hexdec(hex:sub(7,8)))
250         else
251                 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
252                 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
253                 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
254                 ip = ip .. tostring(hexdec(hex:sub(1,2)))
255         end
256         
257         return ip
258 end
259
260 -- Returns the binary IP to a given IP
261 function net.ip4bin(ip)
262         local parts = luci.util.split(ip, '.')
263         if #parts ~= 4 then
264                 return nil
265         end
266         
267         local decbin = luci.bits.Dec2Bin
268         
269         local bin = ""
270         bin = bin .. decbin(parts[1], 8)
271         bin = bin .. decbin(parts[2], 8)
272         bin = bin .. decbin(parts[3], 8)
273         bin = bin .. decbin(parts[4], 8)
274         
275         return bin
276 end
277
278 -- Tests whether a host is pingable
279 function net.pingtest(host)
280         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
281 end
282
283
284 process = {}
285 process.info = posix.getpid 
286
287 -- Sets the gid of a process
288 function process.setgroup(pid, gid)
289         return posix.setpid("g", pid, gid)
290 end
291
292 -- Sets the uid of a process
293 function process.setuser(pid, uid)
294         return posix.setpid("u", pid, uid)
295 end
296
297 user = {}
298 -- returns user information to a given uid
299 user.getuser = posix.getpasswd
300
301 -- checks whether a string matches the password of a certain system user
302 function user.checkpasswd(username, password)
303         local account = user.getuser(username)
304         
305         -- FIXME: detect testing environment
306         if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
307                 return true
308         elseif account then
309                 if account.passwd == "!" then
310                         return true
311                 else
312                         return (account.passwd == posix.crypt(password, account.passwd))
313                 end
314         end
315 end
316         
317 -- Changes the user password of given user
318 function user.setpasswd(user, pwd)
319         if pwd then
320                 pwd = pwd:gsub("'", "")
321         end
322         
323         if user then
324                 user = user:gsub("'", "")
325         end
326         
327         local cmd = "(echo '"..pwd.."';sleep 1;echo '"..pwd.."')|"
328         cmd = cmd .. "passwd '"..user.."' >/dev/null 2>&1"
329         return os.execute(cmd)
330 end
331
332
333 wifi = {}
334
335 function wifi.getiwconfig()
336         local cnt = exec("/usr/sbin/iwconfig 2>/dev/null")
337         local iwc = {}
338         
339         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
340                 local k = l:match("^(.-) ")
341                 l = l:gsub("^(.-) +", "", 1)
342                 if k then
343                         iwc[k] = _parse_mixed_record(l)
344                 end
345         end
346         
347         return iwc      
348 end
349
350 function wifi.iwscan()
351         local cnt = exec("iwlist scan 2>/dev/null")
352         local iws = {}
353         
354         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
355                 local k = l:match("^(.-) ")
356                 l = l:gsub("^[^\n]+", "", 1)
357                 l = luci.util.trim(l)
358                 if k then
359                         iws[k] = {}
360                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
361                                 c = c:gsub("^(.-)- ", "", 1)
362                                 c = luci.util.split(c, "\n", 7)
363                                 c = table.concat(c, "\n", 1)
364                                 table.insert(iws[k], _parse_mixed_record(c))
365                         end
366                 end
367         end
368         
369         return iws      
370 end
371
372
373 -- Internal functions
374
375 function _parse_delimited_table(iter, delimiter)
376         delimiter = delimiter or "%s+"
377         
378         local data  = {}
379         local trim  = luci.util.trim
380         local split = luci.util.split
381         
382         local keys = split(trim(iter()), delimiter, nil, true)
383         for i, j in pairs(keys) do
384                 keys[i] = trim(keys[i])
385         end
386         
387         for line in iter do
388                 local row = {}
389                 line = trim(line)
390                 if #line > 0 then
391                         for i, j in pairs(split(line, delimiter, nil, true)) do
392                                 if keys[i] then
393                                         row[keys[i]] = j
394                                 end
395                         end
396                 end
397                 table.insert(data, row)
398         end
399         
400         return data
401 end
402
403 function _parse_mixed_record(cnt)
404         local data = {}
405         
406         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
407         for j, f in pairs(luci.util.split(luci.util.trim(l), "  ")) do
408                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
409
410             if k then
411                                 if x == "" then
412                                         table.insert(data, k)                           
413                                 else
414                         data[k] = v
415                                 end
416             end
417         end
418         end
419                 
420     return data
421 end