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