libs/core: Cleaned up luci.sys in favor of the new luci.ip module
[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 --- LuCI Linux and POSIX system utilities.
28 module("luci.sys", package.seeall)
29 require("posix")
30 require("luci.bits")
31 require("luci.util")
32 require("luci.fs")
33 require("luci.ip")
34
35 --- Invoke the luci-flash executable to write an image to the flash memory.
36 -- @param kpattern      Pattern of files to keep over flash process
37 -- @return                      Return value of os.execute()
38 function flash(image, kpattern)
39         local cmd = "luci-flash "
40         if kpattern then
41                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
42         end
43         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
44
45         return os.execute(cmd)
46 end
47
48 --- Retrieve environment variables. If no variable is given then a table
49 -- containing the whole environment is returned otherwise this function returns
50 -- the corresponding string value for the given name or nil if no such variable
51 -- exists.
52 -- @class               function
53 -- @name                getenv
54 -- @param var   Name of the environment variable to retrieve (optional)
55 -- @return              String containg the value of the specified variable
56 -- @return              Table containing all variables if no variable name is given
57 getenv = posix.getenv
58
59 --- Determine the current hostname.
60 -- @return              String containing the system hostname
61 function hostname()
62         return io.lines("/proc/sys/kernel/hostname")()
63 end
64
65 --- Returns the contents of a documented referred by an URL.
66 -- @param url    The URL to retrieve
67 -- @param stream Return a stream instead of a buffer
68 -- @return              String containing the contents of given the URL
69 function httpget(url, stream)
70         local source = stream and io.open or luci.util.exec
71         return source("wget -qO- '"..url:gsub("'", "").."'")
72 end
73
74 --- Returns the system load average values.
75 -- @return      String containing the average load value 1 minute ago
76 -- @return      String containing the average load value 5 minutes ago
77 -- @return      String containing the average load value 15 minutes ago
78 -- @return      String containing the active and total number of processes
79 -- @return      String containing the last used pid
80 function loadavg()
81         local loadavg = io.lines("/proc/loadavg")()
82         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
83 end
84
85 --- Initiate a system reboot.
86 -- @return      Return value of os.execute()
87 function reboot()
88         return os.execute("reboot >/dev/null 2>&1")
89 end
90
91 --- Returns the system type, cpu name and installed physical memory.
92 -- @return      String containing the system or platform identifier
93 -- @return      String containing hardware model information
94 -- @return      String containing the total memory amount in kB
95 -- @return      String containing the memory used for caching in kB
96 -- @return      String containing the memory used for buffering in kB
97 -- @return      String containing the free memory amount in kB
98 -- @return      Number containing free memory in percent
99 -- @return      Number containing buffer memory in percent
100 -- @return      Number containing cache memory in percent
101 function sysinfo()
102         local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
103         local c2 = "uname -m 2>/dev/null"
104         local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
105         local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
106         local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
107         local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
108         local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
109         local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
110
111         local system = luci.util.trim(luci.util.exec(c1))
112         local model = ""
113         local memtotal = luci.util.trim(luci.util.exec(c5))
114         local memcached = luci.util.trim(luci.util.exec(c6))
115         local memfree = luci.util.trim(luci.util.exec(c7))
116         local membuffers = luci.util.trim(luci.util.exec(c8))
117         local perc_memfree = math.floor((memfree/memtotal)*100)
118         local perc_membuffers = math.floor((membuffers/memtotal)*100)
119         local perc_memcached = math.floor((memcached/memtotal)*100)
120
121         if system == "" then
122                 system = luci.util.trim(luci.util.exec(c2))
123                 model = luci.util.trim(luci.util.exec(c3))
124         else
125                 model = luci.util.trim(luci.util.exec(c4))
126         end
127
128         return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
129 end
130
131 --- Retrieves the output of the "logread" command.
132 -- @return      String containing the current log buffer
133 function syslog()
134         return luci.util.exec("logread")
135 end
136
137 --- Generates a random id with specified length.
138 -- @param bytes Number of bytes for the unique id
139 -- @return              String containing hex encoded id
140 function uniqueid(bytes)
141         local fp    = io.open("/dev/urandom")
142         local chunk = { fp:read(bytes):byte(1, bytes) }
143         fp:close()
144
145         local hex = ""
146
147         local pattern = "%02X"
148         for i, byte in ipairs(chunk) do
149                 hex = hex .. pattern:format(byte)
150         end
151
152         return hex
153 end
154
155 --- Returns the current system uptime stats.
156 -- @return      String containing total uptime in seconds
157 -- @return      String containing idle time in seconds
158 function uptime()
159         local loadavg = io.lines("/proc/uptime")()
160         return loadavg:match("^(.-) (.-)$")
161 end
162
163 --- LuCI system utilities / POSIX user group related functions.
164 -- @class       module
165 -- @name        luci.sys.group
166 group = {}
167
168 --- Returns information about a POSIX user group.
169 -- @param group Group ID or name of a system user group
170 -- @return      Table with information about the requested group
171 group.getgroup = posix.getgroup
172
173
174 --- LuCI system utilities / network related functions.
175 -- @class       module
176 -- @name        luci.sys.net
177 net = {}
178
179 --- Returns the current arp-table entries as two-dimensional table.
180 -- @return      Table of table containing the current arp entries.
181 --                      The following fields are defined for arp entry objects:
182 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
183 function net.arptable()
184         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
185 end
186
187 --- Determine the current default route.
188 -- @return      Table with the properties of the current default route.
189 --                      The following fields are defined:
190 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
191 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
192 function net.defaultroute()
193         local routes = net.routes()
194         local route = nil
195
196         for i, r in pairs(luci.sys.net.routes()) do
197                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
198                         route = r
199                 end
200         end
201
202         return route
203 end
204
205 --- Determine the names of available network interfaces.
206 -- @return      Table containing all current interface names
207 function net.devices()
208         local devices = {}
209         for line in io.lines("/proc/net/dev") do
210                 table.insert(devices, line:match(" *(.-):"))
211         end
212         return devices
213 end
214
215 -- Determine the MAC address belonging to the given IP address.
216 -- @param ip    IPv4 address
217 -- @return              String containing the MAC address or nil if it cannot be found
218 function net.ip4mac(ip)
219         local mac = nil
220
221         for i, l in ipairs(net.arptable()) do
222                 if l["IP address"] == ip then
223                         mac = l["HW address"]
224                 end
225         end
226
227         return mac
228 end
229
230 --- Returns the current kernel routing table entries.
231 -- @return      Table of tables with properties of the corresponding routes.
232 --                      The following fields are defined for route entry tables:
233 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
234 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
235 function net.routes()
236         return _parse_delimited_table(io.lines("/proc/net/route"))
237 end
238
239
240 --- Tests whether the given host responds to ping probes.
241 -- @param host  String containing a hostname or IPv4 address
242 -- @return              Number containing 0 on success and >= 1 on error
243 function net.pingtest(host)
244         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
245 end
246
247
248 --- LuCI system utilities / process related functions.
249 -- @class       module
250 -- @name        luci.sys.process
251 process = {}
252
253 --- Get the current process id.
254 -- @return      Number containing the current pid
255 process.info = posix.getpid
256
257 --- Set the gid of a process identified by given pid.
258 -- @param pid   Number containing the process id
259 -- @param gid   Number containing the Unix group id
260 -- @return              Boolean indicating successful operation
261 -- @return              String containing the error message if failed
262 -- @return              Number containing the error code if failed
263 function process.setgroup(pid, gid)
264         return posix.setpid("g", pid, gid)
265 end
266
267 --- Set the uid of a process identified by given pid.
268 -- @param pid   Number containing the process id
269 -- @param uid   Number containing the Unix user id
270 -- @return              Boolean indicating successful operation
271 -- @return              String containing the error message if failed
272 -- @return              Number containing the error code if failed
273 function process.setuser(pid, uid)
274         return posix.setpid("u", pid, uid)
275 end
276
277
278 --- LuCI system utilities / user related functions.
279 -- @class       module
280 -- @name        luci.sys.user
281 user = {}
282
283 --- Retrieve user informations for given uid.
284 -- @class               function
285 -- @name                getuser
286 -- @param uid   Number containing the Unix user id
287 -- @return              Table containing the following fields:
288 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
289 user.getuser = posix.getpasswd
290
291 --- Test whether given string matches the password of a given system user.
292 -- @param username      String containing the Unix user name
293 -- @param password      String containing the password to compare
294 -- @return                      Boolean indicating wheather the passwords are equal
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 --- Change the password of given user.
311 -- @param username      String containing the Unix user name
312 -- @param password      String containing the password to compare
313 -- @return                      Number containing 0 on success and >= 1 on error
314 function user.setpasswd(username, password)
315         if password then
316                 password = password:gsub("'", "")
317         end
318
319         if username then
320                 username = username:gsub("'", "")
321         end
322
323         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
324         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
325         return os.execute(cmd)
326 end
327
328
329 --- LuCI system utilities / wifi related functions.
330 -- @class       module
331 -- @name        luci.sys.wifi
332 wifi = {}
333
334 --- Get iwconfig output for all wireless devices.
335 -- @return      Table of tables containing the iwconfing output for each wifi device
336 function wifi.getiwconfig()
337         local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
338         local iwc = {}
339
340         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
341                 local k = l:match("^(.-) ")
342                 l = l:gsub("^(.-) +", "", 1)
343                 if k then
344                         iwc[k] = _parse_mixed_record(l)
345                 end
346         end
347
348         return iwc
349 end
350
351 --- Get iwlist scan output from all wireless devices.
352 -- @return      Table of tables contaiing all scan results
353 function wifi.iwscan()
354         local cnt = luci.util.exec("iwlist scan 2>/dev/null")
355         local iws = {}
356
357         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
358                 local k = l:match("^(.-) ")
359                 l = l:gsub("^[^\n]+", "", 1)
360                 l = luci.util.trim(l)
361                 if k then
362                         iws[k] = {}
363                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
364                                 c = c:gsub("^(.-)- ", "", 1)
365                                 c = luci.util.split(c, "\n", 7)
366                                 c = table.concat(c, "\n", 1)
367                                 table.insert(iws[k], _parse_mixed_record(c))
368                         end
369                 end
370         end
371
372         return iws
373 end
374
375
376 -- Internal functions
377
378 function _parse_delimited_table(iter, delimiter)
379         delimiter = delimiter or "%s+"
380
381         local data  = {}
382         local trim  = luci.util.trim
383         local split = luci.util.split
384
385         local keys = split(trim(iter()), delimiter, nil, true)
386         for i, j in pairs(keys) do
387                 keys[i] = trim(keys[i])
388         end
389
390         for line in iter do
391                 local row = {}
392                 line = trim(line)
393                 if #line > 0 then
394                         for i, j in pairs(split(line, delimiter, nil, true)) do
395                                 if keys[i] then
396                                         row[keys[i]] = j
397                                 end
398                         end
399                 end
400                 table.insert(data, row)
401         end
402
403         return data
404 end
405
406 function _parse_mixed_record(cnt)
407         local data = {}
408
409         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
410         for j, f in pairs(luci.util.split(luci.util.trim(l), "  ")) do
411                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
412
413             if k then
414                                 if x == "" then
415                                         table.insert(data, k)
416                                 else
417                         data[k] = v
418                                 end
419             end
420         end
421         end
422
423     return data
424 end