sys.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. local io = require "io"
  4. local os = require "os"
  5. local table = require "table"
  6. local nixio = require "nixio"
  7. local fs = require "nixio.fs"
  8. local uci = require "luci.model.uci"
  9. local luci = {}
  10. luci.util = require "luci.util"
  11. luci.ip = require "luci.ip"
  12. local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select =
  13. tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
  14. module "luci.sys"
  15. function call(...)
  16. return os.execute(...) / 256
  17. end
  18. exec = luci.util.exec
  19. function mounts()
  20. local data = {}
  21. local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
  22. local ps = luci.util.execi("df")
  23. if not ps then
  24. return
  25. else
  26. ps()
  27. end
  28. for line in ps do
  29. local row = {}
  30. local j = 1
  31. for value in line:gmatch("[^%s]+") do
  32. row[k[j]] = value
  33. j = j + 1
  34. end
  35. if row[k[1]] then
  36. -- this is a rather ugly workaround to cope with wrapped lines in
  37. -- the df output:
  38. --
  39. -- /dev/scsi/host0/bus0/target0/lun0/part3
  40. -- 114382024 93566472 15005244 86% /mnt/usb
  41. --
  42. if not row[k[2]] then
  43. j = 2
  44. line = ps()
  45. for value in line:gmatch("[^%s]+") do
  46. row[k[j]] = value
  47. j = j + 1
  48. end
  49. end
  50. table.insert(data, row)
  51. end
  52. end
  53. return data
  54. end
  55. -- containing the whole environment is returned otherwise this function returns
  56. -- the corresponding string value for the given name or nil if no such variable
  57. -- exists.
  58. getenv = nixio.getenv
  59. function hostname(newname)
  60. if type(newname) == "string" and #newname > 0 then
  61. fs.writefile( "/proc/sys/kernel/hostname", newname )
  62. return newname
  63. else
  64. return nixio.uname().nodename
  65. end
  66. end
  67. function httpget(url, stream, target)
  68. if not target then
  69. local source = stream and io.popen or luci.util.exec
  70. return source("wget -qO- '"..url:gsub("'", "").."'")
  71. else
  72. return os.execute("wget -qO '%s' '%s'" %
  73. {target:gsub("'", ""), url:gsub("'", "")})
  74. end
  75. end
  76. function reboot()
  77. return os.execute("reboot >/dev/null 2>&1")
  78. end
  79. function syslog()
  80. return luci.util.exec("logread")
  81. end
  82. function dmesg()
  83. return luci.util.exec("dmesg")
  84. end
  85. function uniqueid(bytes)
  86. local rand = fs.readfile("/dev/urandom", bytes)
  87. return rand and nixio.bin.hexlify(rand)
  88. end
  89. function uptime()
  90. return nixio.sysinfo().uptime
  91. end
  92. net = {}
  93. -- The following fields are defined for arp entry objects:
  94. -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
  95. function net.arptable(callback)
  96. local arp = (not callback) and {} or nil
  97. local e, r, v
  98. if fs.access("/proc/net/arp") then
  99. for e in io.lines("/proc/net/arp") do
  100. local r = { }, v
  101. for v in e:gmatch("%S+") do
  102. r[#r+1] = v
  103. end
  104. if r[1] ~= "IP" then
  105. local x = {
  106. ["IP address"] = r[1],
  107. ["HW type"] = r[2],
  108. ["Flags"] = r[3],
  109. ["HW address"] = r[4],
  110. ["Mask"] = r[5],
  111. ["Device"] = r[6]
  112. }
  113. if callback then
  114. callback(x)
  115. else
  116. arp = arp or { }
  117. arp[#arp+1] = x
  118. end
  119. end
  120. end
  121. end
  122. return arp
  123. end
  124. local function _nethints(what, callback)
  125. local _, k, e, mac, ip, name
  126. local cur = uci.cursor()
  127. local ifn = { }
  128. local hosts = { }
  129. local function _add(i, ...)
  130. local k = select(i, ...)
  131. if k then
  132. if not hosts[k] then hosts[k] = { } end
  133. hosts[k][1] = select(1, ...) or hosts[k][1]
  134. hosts[k][2] = select(2, ...) or hosts[k][2]
  135. hosts[k][3] = select(3, ...) or hosts[k][3]
  136. hosts[k][4] = select(4, ...) or hosts[k][4]
  137. end
  138. end
  139. if fs.access("/proc/net/arp") then
  140. for e in io.lines("/proc/net/arp") do
  141. ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
  142. if ip and mac then
  143. _add(what, mac:upper(), ip, nil, nil)
  144. end
  145. end
  146. end
  147. if fs.access("/etc/ethers") then
  148. for e in io.lines("/etc/ethers") do
  149. mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
  150. if mac and ip then
  151. _add(what, mac:upper(), ip, nil, nil)
  152. end
  153. end
  154. end
  155. if fs.access("/var/dhcp.leases") then
  156. for e in io.lines("/var/dhcp.leases") do
  157. mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
  158. if mac and ip then
  159. _add(what, mac:upper(), ip, nil, name ~= "*" and name)
  160. end
  161. end
  162. end
  163. cur:foreach("dhcp", "host",
  164. function(s)
  165. for mac in luci.util.imatch(s.mac) do
  166. _add(what, mac:upper(), s.ip, nil, s.name)
  167. end
  168. end)
  169. for _, e in ipairs(nixio.getifaddrs()) do
  170. if e.name ~= "lo" then
  171. ifn[e.name] = ifn[e.name] or { }
  172. if e.family == "packet" and e.addr and #e.addr == 17 then
  173. ifn[e.name][1] = e.addr:upper()
  174. elseif e.family == "inet" then
  175. ifn[e.name][2] = e.addr
  176. elseif e.family == "inet6" then
  177. ifn[e.name][3] = e.addr
  178. end
  179. end
  180. end
  181. for _, e in pairs(ifn) do
  182. if e[what] and (e[2] or e[3]) then
  183. _add(what, e[1], e[2], e[3], e[4])
  184. end
  185. end
  186. for _, e in luci.util.kspairs(hosts) do
  187. callback(e[1], e[2], e[3], e[4])
  188. end
  189. end
  190. -- Each entry contains the values in the following order:
  191. -- [ "mac", "name" ]
  192. function net.mac_hints(callback)
  193. if callback then
  194. _nethints(1, function(mac, v4, v6, name)
  195. name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
  196. if name and name ~= mac then
  197. callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
  198. end
  199. end)
  200. else
  201. local rv = { }
  202. _nethints(1, function(mac, v4, v6, name)
  203. name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
  204. if name and name ~= mac then
  205. rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
  206. end
  207. end)
  208. return rv
  209. end
  210. end
  211. -- Each entry contains the values in the following order:
  212. -- [ "ip", "name" ]
  213. function net.ipv4_hints(callback)
  214. if callback then
  215. _nethints(2, function(mac, v4, v6, name)
  216. name = name or nixio.getnameinfo(v4, nil, 100) or mac
  217. if name and name ~= v4 then
  218. callback(v4, name)
  219. end
  220. end)
  221. else
  222. local rv = { }
  223. _nethints(2, function(mac, v4, v6, name)
  224. name = name or nixio.getnameinfo(v4, nil, 100) or mac
  225. if name and name ~= v4 then
  226. rv[#rv+1] = { v4, name }
  227. end
  228. end)
  229. return rv
  230. end
  231. end
  232. -- Each entry contains the values in the following order:
  233. -- [ "ip", "name" ]
  234. function net.ipv6_hints(callback)
  235. if callback then
  236. _nethints(3, function(mac, v4, v6, name)
  237. name = name or nixio.getnameinfo(v6, nil, 100) or mac
  238. if name and name ~= v6 then
  239. callback(v6, name)
  240. end
  241. end)
  242. else
  243. local rv = { }
  244. _nethints(3, function(mac, v4, v6, name)
  245. name = name or nixio.getnameinfo(v6, nil, 100) or mac
  246. if name and name ~= v6 then
  247. rv[#rv+1] = { v6, name }
  248. end
  249. end)
  250. return rv
  251. end
  252. end
  253. function net.conntrack(callback)
  254. local connt = {}
  255. if fs.access("/proc/net/nf_conntrack", "r") then
  256. for line in io.lines("/proc/net/nf_conntrack") do
  257. line = line:match "^(.-( [^ =]+=).-)%2"
  258. local entry, flags = _parse_mixed_record(line, " +")
  259. if flags[6] ~= "TIME_WAIT" then
  260. entry.layer3 = flags[1]
  261. entry.layer4 = flags[3]
  262. for i=1, #entry do
  263. entry[i] = nil
  264. end
  265. if callback then
  266. callback(entry)
  267. else
  268. connt[#connt+1] = entry
  269. end
  270. end
  271. end
  272. elseif fs.access("/proc/net/ip_conntrack", "r") then
  273. for line in io.lines("/proc/net/ip_conntrack") do
  274. line = line:match "^(.-( [^ =]+=).-)%2"
  275. local entry, flags = _parse_mixed_record(line, " +")
  276. if flags[4] ~= "TIME_WAIT" then
  277. entry.layer3 = "ipv4"
  278. entry.layer4 = flags[1]
  279. for i=1, #entry do
  280. entry[i] = nil
  281. end
  282. if callback then
  283. callback(entry)
  284. else
  285. connt[#connt+1] = entry
  286. end
  287. end
  288. end
  289. else
  290. return nil
  291. end
  292. return connt
  293. end
  294. function net.devices()
  295. local devs = {}
  296. for k, v in ipairs(nixio.getifaddrs()) do
  297. if v.family == "packet" then
  298. devs[#devs+1] = v.name
  299. end
  300. end
  301. return devs
  302. end
  303. function net.deviceinfo()
  304. local devs = {}
  305. for k, v in ipairs(nixio.getifaddrs()) do
  306. if v.family == "packet" then
  307. local d = v.data
  308. d[1] = d.rx_bytes
  309. d[2] = d.rx_packets
  310. d[3] = d.rx_errors
  311. d[4] = d.rx_dropped
  312. d[5] = 0
  313. d[6] = 0
  314. d[7] = 0
  315. d[8] = d.multicast
  316. d[9] = d.tx_bytes
  317. d[10] = d.tx_packets
  318. d[11] = d.tx_errors
  319. d[12] = d.tx_dropped
  320. d[13] = 0
  321. d[14] = d.collisions
  322. d[15] = 0
  323. d[16] = 0
  324. devs[v.name] = d
  325. end
  326. end
  327. return devs
  328. end
  329. -- The following fields are defined for route entry tables:
  330. -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
  331. -- "flags", "device" }
  332. function net.routes(callback)
  333. local routes = { }
  334. for line in io.lines("/proc/net/route") do
  335. local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
  336. dst_mask, mtu, win, irtt = line:match(
  337. "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
  338. "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
  339. )
  340. if dev then
  341. gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
  342. dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
  343. dst_ip = luci.ip.Hex(
  344. dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
  345. )
  346. local rt = {
  347. dest = dst_ip,
  348. gateway = gateway,
  349. metric = tonumber(metric),
  350. refcount = tonumber(refcnt),
  351. usecount = tonumber(usecnt),
  352. mtu = tonumber(mtu),
  353. window = tonumber(window),
  354. irtt = tonumber(irtt),
  355. flags = tonumber(flags, 16),
  356. device = dev
  357. }
  358. if callback then
  359. callback(rt)
  360. else
  361. routes[#routes+1] = rt
  362. end
  363. end
  364. end
  365. return routes
  366. end
  367. -- The following fields are defined for route entry tables:
  368. -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
  369. -- "flags", "device" }
  370. function net.routes6(callback)
  371. if fs.access("/proc/net/ipv6_route", "r") then
  372. local routes = { }
  373. for line in io.lines("/proc/net/ipv6_route") do
  374. local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
  375. metric, refcnt, usecnt, flags, dev = line:match(
  376. "([a-f0-9]+) ([a-f0-9]+) " ..
  377. "([a-f0-9]+) ([a-f0-9]+) " ..
  378. "([a-f0-9]+) ([a-f0-9]+) " ..
  379. "([a-f0-9]+) ([a-f0-9]+) " ..
  380. "([a-f0-9]+) +([^%s]+)"
  381. )
  382. if dst_ip and dst_prefix and
  383. src_ip and src_prefix and
  384. nexthop and metric and
  385. refcnt and usecnt and
  386. flags and dev
  387. then
  388. src_ip = luci.ip.Hex(
  389. src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
  390. )
  391. dst_ip = luci.ip.Hex(
  392. dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
  393. )
  394. nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
  395. local rt = {
  396. source = src_ip,
  397. dest = dst_ip,
  398. nexthop = nexthop,
  399. metric = tonumber(metric, 16),
  400. refcount = tonumber(refcnt, 16),
  401. usecount = tonumber(usecnt, 16),
  402. flags = tonumber(flags, 16),
  403. device = dev,
  404. -- lua number is too small for storing the metric
  405. -- add a metric_raw field with the original content
  406. metric_raw = metric
  407. }
  408. if callback then
  409. callback(rt)
  410. else
  411. routes[#routes+1] = rt
  412. end
  413. end
  414. end
  415. return routes
  416. end
  417. end
  418. function net.pingtest(host)
  419. return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
  420. end
  421. process = {}
  422. function process.info(key)
  423. local s = {uid = nixio.getuid(), gid = nixio.getgid()}
  424. return not key and s or s[key]
  425. end
  426. function process.list()
  427. local data = {}
  428. local k
  429. local ps = luci.util.execi("/bin/busybox top -bn1")
  430. if not ps then
  431. return
  432. end
  433. for line in ps do
  434. local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
  435. "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
  436. )
  437. local idx = tonumber(pid)
  438. if idx then
  439. data[idx] = {
  440. ['PID'] = pid,
  441. ['PPID'] = ppid,
  442. ['USER'] = user,
  443. ['STAT'] = stat,
  444. ['VSZ'] = vsz,
  445. ['%MEM'] = mem,
  446. ['%CPU'] = cpu,
  447. ['COMMAND'] = cmd
  448. }
  449. end
  450. end
  451. return data
  452. end
  453. function process.setgroup(gid)
  454. return nixio.setgid(gid)
  455. end
  456. function process.setuser(uid)
  457. return nixio.setuid(uid)
  458. end
  459. process.signal = nixio.kill
  460. user = {}
  461. -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
  462. user.getuser = nixio.getpw
  463. function user.getpasswd(username)
  464. local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
  465. local pwh = pwe and (pwe.pwdp or pwe.passwd)
  466. if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
  467. return nil, pwe
  468. else
  469. return pwh, pwe
  470. end
  471. end
  472. function user.checkpasswd(username, pass)
  473. local pwh, pwe = user.getpasswd(username)
  474. if pwe then
  475. return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
  476. end
  477. return false
  478. end
  479. function user.setpasswd(username, password)
  480. if password then
  481. password = password:gsub("'", [['"'"']])
  482. end
  483. if username then
  484. username = username:gsub("'", [['"'"']])
  485. end
  486. return os.execute(
  487. "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
  488. "passwd '" .. username .. "' >/dev/null 2>&1"
  489. )
  490. end
  491. wifi = {}
  492. function wifi.getiwinfo(ifname)
  493. local stat, iwinfo = pcall(require, "iwinfo")
  494. if ifname then
  495. local c = 0
  496. local u = uci.cursor_state()
  497. local d, n = ifname:match("^(%w+)%.network(%d+)")
  498. if d and n then
  499. ifname = d
  500. n = tonumber(n)
  501. u:foreach("wireless", "wifi-iface",
  502. function(s)
  503. if s.device == d then
  504. c = c + 1
  505. if c == n then
  506. ifname = s.ifname or s.device
  507. return false
  508. end
  509. end
  510. end)
  511. elseif u:get("wireless", ifname) == "wifi-device" then
  512. u:foreach("wireless", "wifi-iface",
  513. function(s)
  514. if s.device == ifname and s.ifname then
  515. ifname = s.ifname
  516. return false
  517. end
  518. end)
  519. end
  520. local t = stat and iwinfo.type(ifname)
  521. local x = t and iwinfo[t] or { }
  522. return setmetatable({}, {
  523. __index = function(t, k)
  524. if k == "ifname" then
  525. return ifname
  526. elseif x[k] then
  527. return x[k](ifname)
  528. end
  529. end
  530. })
  531. end
  532. end
  533. init = {}
  534. init.dir = "/etc/init.d/"
  535. function init.names()
  536. local names = { }
  537. for name in fs.glob(init.dir.."*") do
  538. names[#names+1] = fs.basename(name)
  539. end
  540. return names
  541. end
  542. function init.index(name)
  543. if fs.access(init.dir..name) then
  544. return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
  545. %{ init.dir, name })
  546. end
  547. end
  548. local function init_action(action, name)
  549. if fs.access(init.dir..name) then
  550. return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
  551. end
  552. end
  553. function init.enabled(name)
  554. return (init_action("enabled", name) == 0)
  555. end
  556. function init.enable(name)
  557. return (init_action("enable", name) == 1)
  558. end
  559. function init.disable(name)
  560. return (init_action("disable", name) == 0)
  561. end
  562. function init.start(name)
  563. return (init_action("start", name) == 0)
  564. end
  565. function init.stop(name)
  566. return (init_action("stop", name) == 0)
  567. end
  568. -- Internal functions
  569. function _parse_mixed_record(cnt, delimiter)
  570. delimiter = delimiter or " "
  571. local data = {}
  572. local flags = {}
  573. for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
  574. for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
  575. local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
  576. if k then
  577. if x == "" then
  578. table.insert(flags, k)
  579. else
  580. data[k] = v
  581. end
  582. end
  583. end
  584. end
  585. return data, flags
  586. end