1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
|
-- misc utils
-- aim is to move a lot of stuff that is not strictly syscalls out of main code to modularise better
-- most code here is man(1) or man(3) or misc helpers for common tasks.
-- TODO rework so that items can be methods on fd again, for eventfd, timerfd, signalfd and tty
local require, error, assert, tonumber, tostring,
setmetatable, pairs, ipairs, unpack, rawget, rawset,
pcall, type, table, string =
require, error, assert, tonumber, tostring,
setmetatable, pairs, ipairs, unpack, rawget, rawset,
pcall, type, table, string
local function init(S)
local abi, types, c = S.abi, S.types, S.c
local t, pt, s = types.t, types.pt, types.s
local h = require "syscall.helpers"
local ffi = require "ffi"
local bit = require "syscall.bit"
local octal = h.octal
-- TODO move to helpers? see notes in syscall.lua about reworking though
local function istype(tp, x)
if ffi.istype(tp, x) then return x end
return false
end
local util = {}
local mt = {}
local function if_nametoindex(name, s)
local ifr = t.ifreq{name = name}
local ret, err = S.ioctl(s, "SIOCGIFINDEX", ifr)
if not ret then return nil, err end
return ifr.ivalue
end
function util.if_nametoindex(name) -- standard function in some libc versions
local s, err = S.socket(c.AF.LOCAL, c.SOCK.STREAM, 0)
if not s then return nil, err end
local i, err = if_nametoindex(name, s)
if not i then
S.close(s)
return nil, err
end
local ok, err = S.close(s)
if not ok then return nil, err end
return i
end
-- bridge functions.
local function bridge_ioctl(io, name)
local s, err = S.socket(c.AF.LOCAL, c.SOCK.STREAM, 0)
if not s then return nil, err end
local ret, err = S.ioctl(s, io, name)
if not ret then
s:close()
return nil, err
end
local ok, err = s:close()
if not ok then return nil, err end
return ret
end
function util.bridge_add(name) return bridge_ioctl("SIOCBRADDBR", name) end
function util.bridge_del(name) return bridge_ioctl("SIOCBRDELBR", name) end
local function bridge_if_ioctl(io, name, dev)
local err, s, ifr, len, ret, ok
s, err = S.socket(c.AF.LOCAL, c.SOCK.STREAM, 0)
if not s then return nil, err end
if type(dev) == "string" then
dev, err = if_nametoindex(dev, s)
if not dev then return nil, err end
end
ifr = t.ifreq{name = name, ivalue = dev}
ret, err = S.ioctl(s, io, ifr);
if not ret then
s:close()
return nil, err
end
ok, err = s:close()
if not ok then return nil, err end
return ret
end
function util.bridge_add_interface(bridge, dev) return bridge_if_ioctl(c.SIOC.BRADDIF, bridge, dev) end
function util.bridge_add_interface(bridge, dev) return bridge_if_ioctl(c.SIOC.BRDELIF, bridge, dev) end
local function brinfo(d) -- can be used as subpart of general interface info
local bd = "/sys/class/net/" .. d .. "/" .. c.SYSFS_BRIDGE_ATTR
if not S.stat(bd) then return nil end
local bridge = {}
for fn, f in util.ls(bd) do
local s = util.readfile(bd .. "/" .. fn)
if s then
s = s:sub(1, #s - 1) -- remove newline at end
if fn == "group_addr" or fn == "root_id" or fn == "bridge_id" then -- string values
bridge[fn] = s
elseif f == "stp_state" then -- bool
bridge[fn] = s == 1
elseif fn ~= "." and fn ~=".." then
bridge[fn] = tonumber(s) -- not quite correct, most are timevals TODO
end
end
end
local brif, err = util.dirtable("/sys/class/net/" .. d .. "/" .. c.SYSFS_BRIDGE_PORT_SUBDIR, true)
if not brif then return nil end
local fdb = "/sys/class/net/" .. d .. "/" .. c.SYSFS_BRIDGE_FDB
if not S.stat(fdb) then return nil end
local sl = 2048
local buffer = t.buffer(sl)
local fd = S.open(fdb, "rdonly")
if not fd then return nil end
local brforward = {}
repeat
local n = S.read(fd, buffer, sl)
if not n then return nil end
local fdbs = pt.fdb_entry(buffer)
for i = 1, bit.rshift(n, 4) do -- fdb_entry is 16 bytes
local fdb = fdbs[i - 1]
local mac = t.macaddr()
ffi.copy(mac, fdb.mac_addr, s.macaddr)
-- TODO ageing_timer_value is not an int, time, float
brforward[#brforward + 1] = {
mac_addr = mac, port_no = tonumber(fdb.port_no),
is_local = fdb.is_local ~= 0,
ageing_timer_value = tonumber(fdb.ageing_timer_value)
}
end
until n == 0
if not fd:close() then return nil end
return {bridge = bridge, brif = brif, brforward = brforward}
end
function util.bridge_list()
local b = {}
for d in util.ls("/sys/class/net") do
if d ~= "." and d ~= ".." then b[d] = brinfo(d) end
end
return b
end
-- eventfd read and write helpers, as in glibc but Lua friendly. Note returns 0 for EAGAIN, as 0 never returned directly
-- returns Lua number - if you need all 64 bits, pass your own value in and use that for the exact result
function util.eventfd_read(fd, value)
if not value then value = t.uint64_1() end
local ret, err = S.read(fd, value, 8)
if err and err.AGAIN then
value[0] = 0
return 0
end
if not ret then return nil, err end
return tonumber(value[0])
end
function util.eventfd_write(fd, value)
if not value then value = 1 end
if type(value) == "number" then value = t.uint64_1(value) end
local ret, err = S.write(fd, value, 8)
if not ret then return nil, err end
return true
end
function util.signalfd_read(fd, ss)
ss = istype(t.siginfos, ss) or t.siginfos(ss or 8)
local ret, err = S.read(fd, ss.sfd, ss.bytes)
if ret == 0 or (err and err.AGAIN) then return {} end
if not ret then return nil, err end
ss.count = ret / s.signalfd_siginfo -- may not be full length
return ss
end
function util.timerfd_read(fd, buffer)
if not buffer then buffer = t.uint64_1() end
local ret, err = S.read(fd, buffer, 8)
if not ret and err.AGAIN then return 0 end -- will never actually return 0
if not ret then return nil, err end
return tonumber(buffer[0])
end
local auditarch_le = {
x86 = "I386",
x64 = "X86_64",
arm = "ARM",
arm64 = "AARCH64",
mipsel = "MIPSEL",
ppc64le = "PPC64LE",
}
local auditarch_be = {
ppc = "PPC",
arm = "ARMEB",
arm64 = "AARCH64",
mips = "MIPS",
}
function util.auditarch()
if abi.le then return c.AUDIT_ARCH[auditarch_le[abi.arch]] else return c.AUDIT_ARCH[auditarch_be[abi.arch]] end
end
-- file system capabilities
local seccap = "security.capability"
function util.capget(f)
local attr, err
if type(f) == "string" then attr, err = S.getxattr(f, seccap) else attr, err = f:getxattr(seccap) end
if not attr then return nil, err end
local vfs = pt.vfs_cap_data(attr)
local magic_etc = h.convle32(vfs.magic_etc)
local version = bit.band(c.VFS_CAP.REVISION_MASK, magic_etc)
-- TODO if you need support for version 1 filesystem caps add here, fairly simple
assert(version == c.VFS_CAP.REVISION_2, "FIXME: Currently only support version 2 filesystem capabilities")
local cap = t.capabilities()
cap.permitted.cap[0], cap.permitted.cap[1] = h.convle32(vfs.data[0].permitted), h.convle32(vfs.data[1].permitted)
cap.inheritable.cap[0], cap.inheritable.cap[1] = h.convle32(vfs.data[0].inheritable), h.convle32(vfs.data[1].inheritable)
if bit.band(magic_etc, c.VFS_CAP_FLAGS.EFFECTIVE) ~= 0 then
cap.effective.cap[0] = bit.bor(cap.permitted.cap[0], cap.inheritable.cap[0])
cap.effective.cap[1] = bit.bor(cap.permitted.cap[1], cap.inheritable.cap[1])
end
return cap
end
function util.capset(f, cap, flags)
cap = istype(t.capabilities, cap) or t.capabilities(cap)
local vfsflags = 0
-- is this the correct way to do this? TODO check
for k, _ in pairs(c.CAP) do if cap.effective[k] then vfsflags = c.VFS_CAP_FLAGS.EFFECTIVE end end
local vfs = t.vfs_cap_data()
vfs.magic_etc = h.convle32(c.VFS_CAP.REVISION_2 + vfsflags)
vfs.data[0].permitted, vfs.data[1].permitted = h.convle32(cap.permitted.cap[0]), h.convle32(cap.permitted.cap[1])
vfs.data[0].inheritable, vfs.data[1].inheritable = h.convle32(cap.inheritable.cap[0]), h.convle32(cap.inheritable.cap[1])
if type(f) == "string" then return S.setxattr(f, seccap, vfs, flags) else return f:getxattr(seccap, vfs, flags) end
end
-- TODO could add umount method.
mt.mount = {
__tostring = function(m) return m.source .. " on " .. m.target .. " type " .. m.type .. " (" .. m.flags .. ")" end,
}
mt.mounts = {
__tostring = function(ms)
local rs = ""
for i = 1, #ms do
rs = rs .. tostring(ms[i]) .. '\n'
end
return rs
end
}
-- will work on netbsd with Linux compat, but should use getvfsstat()
function util.mounts(file)
local mf, err = util.readfile(file or "/proc/mounts")
if not mf then return nil, err end
local mounts = {}
for line in mf:gmatch("[^\r\n]+") do
local l = {}
local parts = {"source", "target", "type", "flags", "freq", "passno"}
local p = 1
for word in line:gmatch("%S+") do
l[parts[p]] = word
p = p + 1
end
mounts[#mounts + 1] = setmetatable(l, mt.mount)
end
-- TODO some of the options you get in /proc/mounts are file system specific and should be moved to l.data
-- idea is you can round-trip this data
-- a lot of the fs specific options are key=value so easier to recognise
return setmetatable(mounts, mt.mounts)
end
-- table based mount, more cross OS compatible
function util.mount(tab)
local source = tab.source or "none" -- standard default
local target = tab.target or tab.dir -- netbsd compatible
local filesystemtype = tab.type
local mountflags = tab.flags
local data = tab.data
return S.mount(source, target, filesystemtype, mountflags, data)
end
function util.sendcred(fd, pid, uid, gid)
if not pid then pid = S.getpid() end
if not uid then uid = S.getuid() end
if not gid then gid = S.getgid() end
local ucred = t.ucred{pid = pid, uid = uid, gid = gid}
local buf1 = t.buffer(1) -- need to send one byte
local io = t.iovecs{{buf1, 1}}
local cmsg = t.cmsghdr("socket", "credentials", ucred)
local msg = t.msghdr{iov = io, control = cmsg}
return S.sendmsg(fd, msg, 0)
end
return util
end
return {init = init}
|