libs/core: Fixed luci.fs.isfile
[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 group = {}
162 group.getgroup = posix.getgroup
163
164 net = {}
165 -- Returns the ARP-Table
166 function net.arptable()
167         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
168 end
169
170 -- Returns whether an IP-Adress belongs to a certain net
171 function net.belongs(ip, ipnet, prefix)
172         return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
173 end
174
175 -- Detect the default route
176 function net.defaultroute()
177         local routes = net.routes()
178         local route = nil
179         
180         for i, r in pairs(luci.sys.net.routes()) do
181                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
182                         route = r
183                 end
184         end
185         
186         return route
187 end
188
189 -- Returns all available network interfaces
190 function net.devices()
191         local devices = {}
192         for line in io.lines("/proc/net/dev") do
193                 table.insert(devices, line:match(" *(.-):"))
194         end
195         return devices
196 end
197
198 -- Returns the MAC-Address belonging to the given IP-Address
199 function net.ip4mac(ip)
200         local mac = nil
201         
202         for i, l in ipairs(net.arptable()) do
203                 if l["IP address"] == ip then
204                         mac = l["HW address"]
205                 end
206         end
207         
208         return mac
209 end
210
211 -- Returns the prefix to a given netmask
212 function net.mask4prefix(mask)
213         local bin = net.ip4bin(mask)
214         
215         if not bin then
216                 return nil
217         end
218         
219         return #luci.util.split(bin, "1")-1
220 end
221
222 -- Returns the kernel routing table
223 function net.routes()
224         return _parse_delimited_table(io.lines("/proc/net/route"))
225 end
226
227 -- Returns the numeric IP to a given hexstring
228 function net.hexip4(hex, be)
229         if #hex ~= 8 then
230                 return nil
231         end
232         
233         be = be or bigendian()
234         
235         local hexdec = luci.bits.Hex2Dec
236         
237         local ip = ""
238         if be then
239                 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
240                 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
241                 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
242                 ip = ip .. tostring(hexdec(hex:sub(7,8)))
243         else
244                 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
245                 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
246                 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
247                 ip = ip .. tostring(hexdec(hex:sub(1,2)))
248         end
249         
250         return ip
251 end
252
253 -- Returns the binary IP to a given IP
254 function net.ip4bin(ip)
255         local parts = luci.util.split(ip, '.')
256         if #parts ~= 4 then
257                 return nil
258         end
259         
260         local decbin = luci.bits.Dec2Bin
261         
262         local bin = ""
263         bin = bin .. decbin(parts[1], 8)
264         bin = bin .. decbin(parts[2], 8)
265         bin = bin .. decbin(parts[3], 8)
266         bin = bin .. decbin(parts[4], 8)
267         
268         return bin
269 end
270
271 -- Tests whether a host is pingable
272 function net.pingtest(host)
273         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
274 end
275
276
277 process = {}
278 process.info = posix.getpid 
279
280 -- Sets the gid of a process
281 function process.setgroup(pid, gid)
282         return posix.setpid("g", pid, gid)
283 end
284
285 -- Sets the uid of a process
286 function process.setuser(pid, uid)
287         return posix.setpid("u", pid, uid)
288 end
289
290 user = {}
291 -- returns user information to a given uid
292 user.getuser = posix.getpasswd
293
294 -- checks whether a string matches the password of a certain system user
295 function user.checkpasswd(username, password)
296         local account = user.getuser(username)
297         
298         -- FIXME: detect testing environment
299         if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
300                 return true
301         elseif account then
302                 if account.passwd == "!" then
303                         return true
304                 else
305                         return (account.passwd == posix.crypt(password, account.passwd))
306                 end
307         end
308 end
309         
310 -- Changes the user password of given user
311 function user.setpasswd(user, pwd)
312         if pwd then
313                 pwd = pwd:gsub("'", "")
314         end
315         
316         if user then
317                 user = user:gsub("'", "")
318         end
319         
320         local cmd = "(echo '"..pwd.."';sleep 1;echo '"..pwd.."')|"
321         cmd = cmd .. "passwd '"..user.."' >/dev/null 2>&1"
322         return os.execute(cmd)
323 end
324
325
326 wifi = {}
327
328 function wifi.getiwconfig()
329         local cnt = exec("/usr/sbin/iwconfig 2>/dev/null")
330         local iwc = {}
331         
332         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
333                 local k = l:match("^(.-) ")
334                 l = l:gsub("^(.-) +", "", 1)
335                 if k then
336                         iwc[k] = _parse_mixed_record(l)
337                 end
338         end
339         
340         return iwc      
341 end
342
343 function wifi.iwscan()
344         local cnt = exec("iwlist scan 2>/dev/null")
345         local iws = {}
346         
347         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
348                 local k = l:match("^(.-) ")
349                 l = l:gsub("^[^\n]+", "", 1)
350                 l = luci.util.trim(l)
351                 if k then
352                         iws[k] = {}
353                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
354                                 c = c:gsub("^(.-)- ", "", 1)
355                                 c = luci.util.split(c, "\n", 7)
356                                 c = table.concat(c, "\n", 1)
357                                 table.insert(iws[k], _parse_mixed_record(c))
358                         end
359                 end
360         end
361         
362         return iws      
363 end
364
365
366 -- Internal functions
367
368 function _parse_delimited_table(iter, delimiter)
369         delimiter = delimiter or "%s+"
370         
371         local data  = {}
372         local trim  = luci.util.trim
373         local split = luci.util.split
374         
375         local keys = split(trim(iter()), delimiter, nil, true)
376         for i, j in pairs(keys) do
377                 keys[i] = trim(keys[i])
378         end
379         
380         for line in iter do
381                 local row = {}
382                 line = trim(line)
383                 if #line > 0 then
384                         for i, j in pairs(split(line, delimiter, nil, true)) do
385                                 if keys[i] then
386                                         row[keys[i]] = j
387                                 end
388                         end
389                 end
390                 table.insert(data, row)
391         end
392         
393         return data
394 end
395
396 function _parse_mixed_record(cnt)
397         local data = {}
398         
399         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
400         for j, f in pairs(luci.util.split(luci.util.trim(l), "  ")) do
401                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
402
403             if k then
404                                 if x == "" then
405                                         table.insert(data, k)                           
406                                 else
407                         data[k] = v
408                                 end
409             end
410         end
411         end
412                 
413     return data
414 end