sys.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  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. luci.ip.neighbors(nil, function(neigh)
  140. if neigh.mac and neigh.family == 4 then
  141. _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
  142. elseif neigh.mac and neigh.family == 6 then
  143. _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
  144. end
  145. end)
  146. if fs.access("/etc/ethers") then
  147. for e in io.lines("/etc/ethers") do
  148. mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
  149. if mac and ip then
  150. _add(what, mac:upper(), ip, nil, nil)
  151. end
  152. end
  153. end
  154. cur:foreach("dhcp", "dnsmasq",
  155. function(s)
  156. if s.leasefile and fs.access(s.leasefile) then
  157. for e in io.lines(s.leasefile) do
  158. mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
  159. if mac and ip then
  160. _add(what, mac:upper(), ip, nil, name ~= "*" and name)
  161. end
  162. end
  163. end
  164. end
  165. )
  166. cur:foreach("dhcp", "host",
  167. function(s)
  168. for mac in luci.util.imatch(s.mac) do
  169. _add(what, mac:upper(), s.ip, nil, s.name)
  170. end
  171. end)
  172. for _, e in ipairs(nixio.getifaddrs()) do
  173. if e.name ~= "lo" then
  174. ifn[e.name] = ifn[e.name] or { }
  175. if e.family == "packet" and e.addr and #e.addr == 17 then
  176. ifn[e.name][1] = e.addr:upper()
  177. elseif e.family == "inet" then
  178. ifn[e.name][2] = e.addr
  179. elseif e.family == "inet6" then
  180. ifn[e.name][3] = e.addr
  181. end
  182. end
  183. end
  184. for _, e in pairs(ifn) do
  185. if e[what] and (e[2] or e[3]) then
  186. _add(what, e[1], e[2], e[3], e[4])
  187. end
  188. end
  189. for _, e in luci.util.kspairs(hosts) do
  190. callback(e[1], e[2], e[3], e[4])
  191. end
  192. end
  193. -- Each entry contains the values in the following order:
  194. -- [ "mac", "name" ]
  195. function net.mac_hints(callback)
  196. if callback then
  197. _nethints(1, function(mac, v4, v6, name)
  198. name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
  199. if name and name ~= mac then
  200. callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
  201. end
  202. end)
  203. else
  204. local rv = { }
  205. _nethints(1, function(mac, v4, v6, name)
  206. name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
  207. if name and name ~= mac then
  208. rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
  209. end
  210. end)
  211. return rv
  212. end
  213. end
  214. -- Each entry contains the values in the following order:
  215. -- [ "ip", "name" ]
  216. function net.ipv4_hints(callback)
  217. if callback then
  218. _nethints(2, function(mac, v4, v6, name)
  219. name = name or nixio.getnameinfo(v4, nil, 100) or mac
  220. if name and name ~= v4 then
  221. callback(v4, name)
  222. end
  223. end)
  224. else
  225. local rv = { }
  226. _nethints(2, function(mac, v4, v6, name)
  227. name = name or nixio.getnameinfo(v4, nil, 100) or mac
  228. if name and name ~= v4 then
  229. rv[#rv+1] = { v4, name }
  230. end
  231. end)
  232. return rv
  233. end
  234. end
  235. -- Each entry contains the values in the following order:
  236. -- [ "ip", "name" ]
  237. function net.ipv6_hints(callback)
  238. if callback then
  239. _nethints(3, function(mac, v4, v6, name)
  240. name = name or nixio.getnameinfo(v6, nil, 100) or mac
  241. if name and name ~= v6 then
  242. callback(v6, name)
  243. end
  244. end)
  245. else
  246. local rv = { }
  247. _nethints(3, function(mac, v4, v6, name)
  248. name = name or nixio.getnameinfo(v6, nil, 100) or mac
  249. if name and name ~= v6 then
  250. rv[#rv+1] = { v6, name }
  251. end
  252. end)
  253. return rv
  254. end
  255. end
  256. function net.host_hints(callback)
  257. if callback then
  258. _nethints(1, function(mac, v4, v6, name)
  259. if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
  260. callback(mac, v4, v6, name)
  261. end
  262. end)
  263. else
  264. local rv = { }
  265. _nethints(1, function(mac, v4, v6, name)
  266. if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
  267. local e = { }
  268. if v4 then e.ipv4 = v4 end
  269. if v6 then e.ipv6 = v6 end
  270. if name then e.name = name end
  271. rv[mac] = e
  272. end
  273. end)
  274. return rv
  275. end
  276. end
  277. function net.conntrack(callback)
  278. local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
  279. if not ok or not nfct then
  280. return nil
  281. end
  282. local line, connt = nil, (not callback) and { }
  283. for line in nfct do
  284. local fam, l3, l4, timeout, tuples =
  285. line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
  286. if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
  287. l4 = nixio.getprotobynumber(l4)
  288. local entry = {
  289. bytes = 0,
  290. packets = 0,
  291. layer3 = fam,
  292. layer4 = l4 and l4.name or "unknown",
  293. timeout = tonumber(timeout, 10)
  294. }
  295. local key, val
  296. for key, val in tuples:gmatch("(%w+)=(%S+)") do
  297. if key == "bytes" or key == "packets" then
  298. entry[key] = entry[key] + tonumber(val, 10)
  299. elseif key == "src" or key == "dst" then
  300. if entry[key] == nil then
  301. entry[key] = luci.ip.new(val):string()
  302. end
  303. elseif key == "sport" or key == "dport" then
  304. if entry[key] == nil then
  305. entry[key] = val
  306. end
  307. elseif val then
  308. entry[key] = val
  309. end
  310. end
  311. if callback then
  312. callback(entry)
  313. else
  314. connt[#connt+1] = entry
  315. end
  316. end
  317. end
  318. return callback and true or connt
  319. end
  320. function net.devices()
  321. local devs = {}
  322. for k, v in ipairs(nixio.getifaddrs()) do
  323. if v.family == "packet" then
  324. devs[#devs+1] = v.name
  325. end
  326. end
  327. return devs
  328. end
  329. function net.deviceinfo()
  330. local devs = {}
  331. for k, v in ipairs(nixio.getifaddrs()) do
  332. if v.family == "packet" then
  333. local d = v.data
  334. d[1] = d.rx_bytes
  335. d[2] = d.rx_packets
  336. d[3] = d.rx_errors
  337. d[4] = d.rx_dropped
  338. d[5] = 0
  339. d[6] = 0
  340. d[7] = 0
  341. d[8] = d.multicast
  342. d[9] = d.tx_bytes
  343. d[10] = d.tx_packets
  344. d[11] = d.tx_errors
  345. d[12] = d.tx_dropped
  346. d[13] = 0
  347. d[14] = d.collisions
  348. d[15] = 0
  349. d[16] = 0
  350. devs[v.name] = d
  351. end
  352. end
  353. return devs
  354. end
  355. -- The following fields are defined for route entry tables:
  356. -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
  357. -- "flags", "device" }
  358. function net.routes(callback)
  359. local routes = { }
  360. for line in io.lines("/proc/net/route") do
  361. local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
  362. dst_mask, mtu, win, irtt = line:match(
  363. "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
  364. "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
  365. )
  366. if dev then
  367. gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
  368. dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
  369. dst_ip = luci.ip.Hex(
  370. dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
  371. )
  372. local rt = {
  373. dest = dst_ip,
  374. gateway = gateway,
  375. metric = tonumber(metric),
  376. refcount = tonumber(refcnt),
  377. usecount = tonumber(usecnt),
  378. mtu = tonumber(mtu),
  379. window = tonumber(window),
  380. irtt = tonumber(irtt),
  381. flags = tonumber(flags, 16),
  382. device = dev
  383. }
  384. if callback then
  385. callback(rt)
  386. else
  387. routes[#routes+1] = rt
  388. end
  389. end
  390. end
  391. return routes
  392. end
  393. -- The following fields are defined for route entry tables:
  394. -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
  395. -- "flags", "device" }
  396. function net.routes6(callback)
  397. if fs.access("/proc/net/ipv6_route", "r") then
  398. local routes = { }
  399. for line in io.lines("/proc/net/ipv6_route") do
  400. local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
  401. metric, refcnt, usecnt, flags, dev = line:match(
  402. "([a-f0-9]+) ([a-f0-9]+) " ..
  403. "([a-f0-9]+) ([a-f0-9]+) " ..
  404. "([a-f0-9]+) ([a-f0-9]+) " ..
  405. "([a-f0-9]+) ([a-f0-9]+) " ..
  406. "([a-f0-9]+) +([^%s]+)"
  407. )
  408. if dst_ip and dst_prefix and
  409. src_ip and src_prefix and
  410. nexthop and metric and
  411. refcnt and usecnt and
  412. flags and dev
  413. then
  414. src_ip = luci.ip.Hex(
  415. src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
  416. )
  417. dst_ip = luci.ip.Hex(
  418. dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
  419. )
  420. nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
  421. local rt = {
  422. source = src_ip,
  423. dest = dst_ip,
  424. nexthop = nexthop,
  425. metric = tonumber(metric, 16),
  426. refcount = tonumber(refcnt, 16),
  427. usecount = tonumber(usecnt, 16),
  428. flags = tonumber(flags, 16),
  429. device = dev,
  430. -- lua number is too small for storing the metric
  431. -- add a metric_raw field with the original content
  432. metric_raw = metric
  433. }
  434. if callback then
  435. callback(rt)
  436. else
  437. routes[#routes+1] = rt
  438. end
  439. end
  440. end
  441. return routes
  442. end
  443. end
  444. function net.pingtest(host)
  445. return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
  446. end
  447. process = {}
  448. function process.info(key)
  449. local s = {uid = nixio.getuid(), gid = nixio.getgid()}
  450. return not key and s or s[key]
  451. end
  452. function process.list()
  453. local data = {}
  454. local k
  455. local ps = luci.util.execi("/bin/busybox top -bn1")
  456. if not ps then
  457. return
  458. end
  459. for line in ps do
  460. local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
  461. "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
  462. )
  463. local idx = tonumber(pid)
  464. if idx then
  465. data[idx] = {
  466. ['PID'] = pid,
  467. ['PPID'] = ppid,
  468. ['USER'] = user,
  469. ['STAT'] = stat,
  470. ['VSZ'] = vsz,
  471. ['%MEM'] = mem,
  472. ['%CPU'] = cpu,
  473. ['COMMAND'] = cmd
  474. }
  475. end
  476. end
  477. return data
  478. end
  479. function process.setgroup(gid)
  480. return nixio.setgid(gid)
  481. end
  482. function process.setuser(uid)
  483. return nixio.setuid(uid)
  484. end
  485. process.signal = nixio.kill
  486. user = {}
  487. -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
  488. user.getuser = nixio.getpw
  489. function user.getpasswd(username)
  490. local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
  491. local pwh = pwe and (pwe.pwdp or pwe.passwd)
  492. if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
  493. return nil, pwe
  494. else
  495. return pwh, pwe
  496. end
  497. end
  498. function user.checkpasswd(username, pass)
  499. local pwh, pwe = user.getpasswd(username)
  500. if pwe then
  501. return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
  502. end
  503. return false
  504. end
  505. function user.setpasswd(username, password)
  506. if password then
  507. password = password:gsub("'", [['"'"']])
  508. end
  509. if username then
  510. username = username:gsub("'", [['"'"']])
  511. end
  512. return os.execute(
  513. "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
  514. "passwd '" .. username .. "' >/dev/null 2>&1"
  515. )
  516. end
  517. wifi = {}
  518. function wifi.getiwinfo(ifname)
  519. local stat, iwinfo = pcall(require, "iwinfo")
  520. if ifname then
  521. local d, n = ifname:match("^(%w+)%.network(%d+)")
  522. local wstate = luci.util.ubus("network.wireless", "status") or { }
  523. d = d or ifname
  524. n = n and tonumber(n) or 1
  525. if type(wstate[d]) == "table" and
  526. type(wstate[d].interfaces) == "table" and
  527. type(wstate[d].interfaces[n]) == "table" and
  528. type(wstate[d].interfaces[n].ifname) == "string"
  529. then
  530. ifname = wstate[d].interfaces[n].ifname
  531. else
  532. ifname = d
  533. end
  534. local t = stat and iwinfo.type(ifname)
  535. local x = t and iwinfo[t] or { }
  536. return setmetatable({}, {
  537. __index = function(t, k)
  538. if k == "ifname" then
  539. return ifname
  540. elseif x[k] then
  541. return x[k](ifname)
  542. end
  543. end
  544. })
  545. end
  546. end
  547. init = {}
  548. init.dir = "/etc/init.d/"
  549. function init.names()
  550. local names = { }
  551. for name in fs.glob(init.dir.."*") do
  552. names[#names+1] = fs.basename(name)
  553. end
  554. return names
  555. end
  556. function init.index(name)
  557. if fs.access(init.dir..name) then
  558. return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
  559. %{ init.dir, name })
  560. end
  561. end
  562. local function init_action(action, name)
  563. if fs.access(init.dir..name) then
  564. return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
  565. end
  566. end
  567. function init.enabled(name)
  568. return (init_action("enabled", name) == 0)
  569. end
  570. function init.enable(name)
  571. return (init_action("enable", name) == 1)
  572. end
  573. function init.disable(name)
  574. return (init_action("disable", name) == 0)
  575. end
  576. function init.start(name)
  577. return (init_action("start", name) == 0)
  578. end
  579. function init.stop(name)
  580. return (init_action("stop", name) == 0)
  581. end