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