sys.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  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 ntm = require "luci.model.network"
  10. local luci = {}
  11. luci.util = require "luci.util"
  12. luci.ip = require "luci.ip"
  13. local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack =
  14. tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack
  15. module "luci.sys"
  16. function call(...)
  17. return os.execute(...) / 256
  18. end
  19. exec = luci.util.exec
  20. function mounts()
  21. local data = {}
  22. local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
  23. local ps = luci.util.execi("df")
  24. if not ps then
  25. return
  26. else
  27. ps()
  28. end
  29. for line in ps do
  30. local row = {}
  31. local j = 1
  32. for value in line:gmatch("[^%s]+") do
  33. row[k[j]] = value
  34. j = j + 1
  35. end
  36. if row[k[1]] then
  37. -- this is a rather ugly workaround to cope with wrapped lines in
  38. -- the df output:
  39. --
  40. -- /dev/scsi/host0/bus0/target0/lun0/part3
  41. -- 114382024 93566472 15005244 86% /mnt/usb
  42. --
  43. if not row[k[2]] then
  44. j = 2
  45. line = ps()
  46. for value in line:gmatch("[^%s]+") do
  47. row[k[j]] = value
  48. j = j + 1
  49. end
  50. end
  51. table.insert(data, row)
  52. end
  53. end
  54. return data
  55. end
  56. function mtds()
  57. local data = {}
  58. if fs.access("/proc/mtd") then
  59. for l in io.lines("/proc/mtd") do
  60. local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
  61. if s and n then
  62. local d = {}
  63. d.size = tonumber(s, 16)
  64. d.name = n
  65. table.insert(data, d)
  66. end
  67. end
  68. end
  69. return data
  70. end
  71. -- containing the whole environment is returned otherwise this function returns
  72. -- the corresponding string value for the given name or nil if no such variable
  73. -- exists.
  74. getenv = nixio.getenv
  75. function hostname(newname)
  76. if type(newname) == "string" and #newname > 0 then
  77. fs.writefile( "/proc/sys/kernel/hostname", newname )
  78. return newname
  79. else
  80. return nixio.uname().nodename
  81. end
  82. end
  83. function httpget(url, stream, target)
  84. if not target then
  85. local source = stream and io.popen or luci.util.exec
  86. return source("wget -qO- %s" % luci.util.shellquote(url))
  87. else
  88. return os.execute("wget -qO %s %s" %
  89. {luci.util.shellquote(target), luci.util.shellquote(url)})
  90. end
  91. end
  92. function reboot()
  93. return os.execute("reboot >/dev/null 2>&1")
  94. end
  95. function syslog()
  96. return luci.util.exec("logread")
  97. end
  98. function dmesg()
  99. return luci.util.exec("dmesg")
  100. end
  101. function uniqueid(bytes)
  102. local rand = fs.readfile("/dev/urandom", bytes)
  103. return rand and nixio.bin.hexlify(rand)
  104. end
  105. function uptime()
  106. return nixio.sysinfo().uptime
  107. end
  108. net = {}
  109. local function _nethints(what, callback)
  110. local _, k, e, mac, ip, name, duid, iaid
  111. local cur = uci.cursor()
  112. local ifn = { }
  113. local hosts = { }
  114. local lookup = { }
  115. local function _add(i, ...)
  116. local k = select(i, ...)
  117. if k then
  118. if not hosts[k] then hosts[k] = { } end
  119. hosts[k][1] = select(1, ...) or hosts[k][1]
  120. hosts[k][2] = select(2, ...) or hosts[k][2]
  121. hosts[k][3] = select(3, ...) or hosts[k][3]
  122. hosts[k][4] = select(4, ...) or hosts[k][4]
  123. end
  124. end
  125. luci.ip.neighbors(nil, function(neigh)
  126. if neigh.mac and neigh.family == 4 then
  127. _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
  128. elseif neigh.mac and neigh.family == 6 then
  129. _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
  130. end
  131. end)
  132. if fs.access("/etc/ethers") then
  133. for e in io.lines("/etc/ethers") do
  134. mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
  135. mac = luci.ip.checkmac(mac)
  136. if mac and name then
  137. if luci.ip.checkip4(name) then
  138. _add(what, mac, name, nil, nil)
  139. else
  140. _add(what, mac, nil, nil, name)
  141. end
  142. end
  143. end
  144. end
  145. cur:foreach("dhcp", "dnsmasq",
  146. function(s)
  147. if s.leasefile and fs.access(s.leasefile) then
  148. for e in io.lines(s.leasefile) do
  149. mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
  150. mac = luci.ip.checkmac(mac)
  151. if mac and ip then
  152. _add(what, mac, ip, nil, name ~= "*" and name)
  153. end
  154. end
  155. end
  156. end
  157. )
  158. cur:foreach("dhcp", "odhcpd",
  159. function(s)
  160. if type(s.leasefile) == "string" and fs.access(s.leasefile) then
  161. for e in io.lines(s.leasefile) do
  162. duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+")
  163. mac = net.duid_to_mac(duid)
  164. if mac then
  165. if ip and iaid == "ipv4" then
  166. _add(what, mac, ip, nil, name ~= "*" and name)
  167. elseif ip then
  168. _add(what, mac, nil, ip, name ~= "*" and name)
  169. end
  170. end
  171. end
  172. end
  173. end
  174. )
  175. cur:foreach("dhcp", "host",
  176. function(s)
  177. for mac in luci.util.imatch(s.mac) do
  178. mac = luci.ip.checkmac(mac)
  179. if mac then
  180. _add(what, mac, s.ip, nil, s.name)
  181. end
  182. end
  183. end)
  184. for _, e in ipairs(nixio.getifaddrs()) do
  185. if e.name ~= "lo" then
  186. ifn[e.name] = ifn[e.name] or { }
  187. if e.family == "packet" and e.addr and #e.addr == 17 then
  188. ifn[e.name][1] = e.addr:upper()
  189. elseif e.family == "inet" then
  190. ifn[e.name][2] = e.addr
  191. elseif e.family == "inet6" then
  192. ifn[e.name][3] = e.addr
  193. end
  194. end
  195. end
  196. for _, e in pairs(ifn) do
  197. if e[what] and (e[2] or e[3]) then
  198. _add(what, e[1], e[2], e[3], e[4])
  199. end
  200. end
  201. for _, e in pairs(hosts) do
  202. lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
  203. end
  204. if #lookup > 0 then
  205. lookup = luci.util.ubus("network.rrdns", "lookup", {
  206. addrs = lookup,
  207. timeout = 250,
  208. limit = 1000
  209. }) or { }
  210. end
  211. for _, e in luci.util.kspairs(hosts) do
  212. callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
  213. end
  214. end
  215. -- Each entry contains the values in the following order:
  216. -- [ "mac", "name" ]
  217. function net.mac_hints(callback)
  218. if callback then
  219. _nethints(1, function(mac, v4, v6, name)
  220. name = name or v4
  221. if name and name ~= mac then
  222. callback(mac, name or v4)
  223. end
  224. end)
  225. else
  226. local rv = { }
  227. _nethints(1, function(mac, v4, v6, name)
  228. name = name or v4
  229. if name and name ~= mac then
  230. rv[#rv+1] = { mac, name or v4 }
  231. end
  232. end)
  233. return rv
  234. end
  235. end
  236. -- Each entry contains the values in the following order:
  237. -- [ "ip", "name" ]
  238. function net.ipv4_hints(callback)
  239. if callback then
  240. _nethints(2, function(mac, v4, v6, name)
  241. name = name or mac
  242. if name and name ~= v4 then
  243. callback(v4, name)
  244. end
  245. end)
  246. else
  247. local rv = { }
  248. _nethints(2, function(mac, v4, v6, name)
  249. name = name or mac
  250. if name and name ~= v4 then
  251. rv[#rv+1] = { v4, name }
  252. end
  253. end)
  254. return rv
  255. end
  256. end
  257. -- Each entry contains the values in the following order:
  258. -- [ "ip", "name" ]
  259. function net.ipv6_hints(callback)
  260. if callback then
  261. _nethints(3, function(mac, v4, v6, name)
  262. name = name or mac
  263. if name and name ~= v6 then
  264. callback(v6, name)
  265. end
  266. end)
  267. else
  268. local rv = { }
  269. _nethints(3, function(mac, v4, v6, name)
  270. name = name or mac
  271. if name and name ~= v6 then
  272. rv[#rv+1] = { v6, name }
  273. end
  274. end)
  275. return rv
  276. end
  277. end
  278. function net.host_hints(callback)
  279. if callback then
  280. _nethints(1, function(mac, v4, v6, name)
  281. if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
  282. callback(mac, v4, v6, name)
  283. end
  284. end)
  285. else
  286. local rv = { }
  287. _nethints(1, function(mac, v4, v6, name)
  288. if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
  289. local e = { }
  290. if v4 then e.ipv4 = v4 end
  291. if v6 then e.ipv6 = v6 end
  292. if name then e.name = name end
  293. rv[mac] = e
  294. end
  295. end)
  296. return rv
  297. end
  298. end
  299. function net.conntrack(callback)
  300. local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
  301. if not ok or not nfct then
  302. return nil
  303. end
  304. local line, connt = nil, (not callback) and { }
  305. for line in nfct do
  306. local fam, l3, l4, timeout, tuples =
  307. line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
  308. if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
  309. l4 = nixio.getprotobynumber(l4)
  310. local entry = {
  311. bytes = 0,
  312. packets = 0,
  313. layer3 = fam,
  314. layer4 = l4 and l4.name or "unknown",
  315. timeout = tonumber(timeout, 10)
  316. }
  317. local key, val
  318. for key, val in tuples:gmatch("(%w+)=(%S+)") do
  319. if key == "bytes" or key == "packets" then
  320. entry[key] = entry[key] + tonumber(val, 10)
  321. elseif key == "src" or key == "dst" then
  322. if entry[key] == nil then
  323. entry[key] = luci.ip.new(val):string()
  324. end
  325. elseif key == "sport" or key == "dport" then
  326. if entry[key] == nil then
  327. entry[key] = val
  328. end
  329. elseif val then
  330. entry[key] = val
  331. end
  332. end
  333. if callback then
  334. callback(entry)
  335. else
  336. connt[#connt+1] = entry
  337. end
  338. end
  339. end
  340. return callback and true or connt
  341. end
  342. function net.devices()
  343. local devs = {}
  344. local seen = {}
  345. for k, v in ipairs(nixio.getifaddrs()) do
  346. if v.name and not seen[v.name] then
  347. seen[v.name] = true
  348. devs[#devs+1] = v.name
  349. end
  350. end
  351. return devs
  352. end
  353. function net.duid_to_mac(duid)
  354. local b1, b2, b3, b4, b5, b6
  355. if type(duid) == "string" then
  356. -- DUID-LLT / Ethernet
  357. if #duid == 28 then
  358. b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$")
  359. -- DUID-LL / Ethernet
  360. elseif #duid == 20 then
  361. b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
  362. -- DUID-LL / Ethernet (Without Header)
  363. elseif #duid == 12 then
  364. b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
  365. end
  366. end
  367. return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
  368. end
  369. process = {}
  370. function process.info(key)
  371. local s = {uid = nixio.getuid(), gid = nixio.getgid()}
  372. return not key and s or s[key]
  373. end
  374. function process.list()
  375. local data = {}
  376. local k
  377. local ps = luci.util.execi("/bin/busybox top -bn1")
  378. if not ps then
  379. return
  380. end
  381. for line in ps do
  382. local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
  383. "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][<NW ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
  384. )
  385. local idx = tonumber(pid)
  386. if idx and not cmd:match("top %-bn1") then
  387. data[idx] = {
  388. ['PID'] = pid,
  389. ['PPID'] = ppid,
  390. ['USER'] = user,
  391. ['STAT'] = stat,
  392. ['VSZ'] = vsz,
  393. ['%MEM'] = mem,
  394. ['%CPU'] = cpu,
  395. ['COMMAND'] = cmd
  396. }
  397. end
  398. end
  399. return data
  400. end
  401. function process.setgroup(gid)
  402. return nixio.setgid(gid)
  403. end
  404. function process.setuser(uid)
  405. return nixio.setuid(uid)
  406. end
  407. process.signal = nixio.kill
  408. local function xclose(fd)
  409. if fd and fd:fileno() > 2 then
  410. fd:close()
  411. end
  412. end
  413. function process.exec(command, stdout, stderr, nowait)
  414. local out_r, out_w, err_r, err_w
  415. if stdout then out_r, out_w = nixio.pipe() end
  416. if stderr then err_r, err_w = nixio.pipe() end
  417. local pid = nixio.fork()
  418. if pid == 0 then
  419. nixio.chdir("/")
  420. local null = nixio.open("/dev/null", "w+")
  421. if null then
  422. nixio.dup(out_w or null, nixio.stdout)
  423. nixio.dup(err_w or null, nixio.stderr)
  424. nixio.dup(null, nixio.stdin)
  425. xclose(out_w)
  426. xclose(out_r)
  427. xclose(err_w)
  428. xclose(err_r)
  429. xclose(null)
  430. end
  431. nixio.exec(unpack(command))
  432. os.exit(-1)
  433. end
  434. local _, pfds, rv = nil, {}, { code = -1, pid = pid }
  435. xclose(out_w)
  436. xclose(err_w)
  437. if out_r then
  438. pfds[#pfds+1] = {
  439. fd = out_r,
  440. cb = type(stdout) == "function" and stdout,
  441. name = "stdout",
  442. events = nixio.poll_flags("in", "err", "hup")
  443. }
  444. end
  445. if err_r then
  446. pfds[#pfds+1] = {
  447. fd = err_r,
  448. cb = type(stderr) == "function" and stderr,
  449. name = "stderr",
  450. events = nixio.poll_flags("in", "err", "hup")
  451. }
  452. end
  453. while #pfds > 0 do
  454. local nfds, err = nixio.poll(pfds, -1)
  455. if not nfds and err ~= nixio.const.EINTR then
  456. break
  457. end
  458. local i
  459. for i = #pfds, 1, -1 do
  460. local rfd = pfds[i]
  461. if rfd.revents > 0 then
  462. local chunk, err = rfd.fd:read(4096)
  463. if chunk and #chunk > 0 then
  464. if rfd.cb then
  465. rfd.cb(chunk)
  466. else
  467. rfd.buf = rfd.buf or {}
  468. rfd.buf[#rfd.buf + 1] = chunk
  469. end
  470. else
  471. table.remove(pfds, i)
  472. if rfd.buf then
  473. rv[rfd.name] = table.concat(rfd.buf, "")
  474. end
  475. rfd.fd:close()
  476. end
  477. end
  478. end
  479. end
  480. if not nowait then
  481. _, _, rv.code = nixio.waitpid(pid)
  482. end
  483. return rv
  484. end
  485. user = {}
  486. -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
  487. user.getuser = nixio.getpw
  488. function user.getpasswd(username)
  489. local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
  490. local pwh = pwe and (pwe.pwdp or pwe.passwd)
  491. if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
  492. return nil, pwe
  493. else
  494. return pwh, pwe
  495. end
  496. end
  497. function user.checkpasswd(username, pass)
  498. local pwh, pwe = user.getpasswd(username)
  499. if pwe then
  500. return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
  501. end
  502. return false
  503. end
  504. function user.setpasswd(username, password)
  505. return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
  506. luci.util.shellquote(password),
  507. luci.util.shellquote(password),
  508. luci.util.shellquote(username)
  509. })
  510. end
  511. wifi = {}
  512. function wifi.getiwinfo(ifname)
  513. ntm.init()
  514. local wnet = ntm:get_wifinet(ifname)
  515. if wnet and wnet.iwinfo then
  516. return wnet.iwinfo
  517. end
  518. local wdev = ntm:get_wifidev(ifname)
  519. if wdev and wdev.iwinfo then
  520. return wdev.iwinfo
  521. end
  522. return { ifname = ifname }
  523. end
  524. init = {}
  525. init.dir = "/etc/init.d/"
  526. function init.names()
  527. local names = { }
  528. for name in fs.glob(init.dir.."*") do
  529. names[#names+1] = fs.basename(name)
  530. end
  531. return names
  532. end
  533. function init.index(name)
  534. if fs.access(init.dir..name) then
  535. return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
  536. %{ init.dir, name })
  537. end
  538. end
  539. local function init_action(action, name)
  540. if fs.access(init.dir..name) then
  541. return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
  542. end
  543. end
  544. function init.enabled(name)
  545. return (init_action("enabled", name) == 0)
  546. end
  547. function init.enable(name)
  548. return (init_action("enable", name) == 1)
  549. end
  550. function init.disable(name)
  551. return (init_action("disable", name) == 0)
  552. end
  553. function init.start(name)
  554. return (init_action("start", name) == 0)
  555. end
  556. function init.stop(name)
  557. return (init_action("stop", name) == 0)
  558. end