OC-KittenOS/code/apps/sys-icecap.lua

329 lines
9.6 KiB
Lua

-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- s-icecap : Responsible for x.neo.pub API, crash dialogs, and security policy that isn't "sys- has ALL access, anything else has none"
-- In general, this is what userspace will be interacting with in some way or another to get stuff done
local rootAccess = neo.requireAccess("k.root", "installing GUI integration")
local settings = neo.requireAccess("x.neo.sys.manage", "security sysconf access")
local donkonitDFProvider = neo.requireAccess("r.neo.pub.base", "creating basic NEO APIs")
local targsDH = {} -- data disposal
local todo = {}
-- Specific registration callbacks
local onReg = {}
local everestWindows = {}
local nexus
local theEventHandler
local function addOnReg(p, f)
onReg[p] = onReg[p] or {}
table.insert(onReg[p], f)
end
nexus = {
create = function (w, h, t, c)
local function cb()
local e = neo.requestAccess("x.neo.pub.window", theEventHandler)
if e then
if onReg["x.neo.pub.window"] then
neo.emergency("icecap nexus prereg issue")
theEventHandler("k.registration", "x.neo.pub.window")
end
local dwo, dw = pcall(e, w, h, t)
if not dwo then
addOnReg("x.neo.pub.window", cb)
return
end
c(dw)
everestWindows[dw.id] = function (...)
return c(dw, ...)
end
else
addOnReg("x.neo.pub.window", cb)
end
end
cb()
end,
windows = everestWindows,
startDialog = function (tx, ti)
local txl = require("fmttext").fmtText(unicode.safeTextFormat(tx), 40)
nexus.create(40, #txl, ti, function (w, ev, a)
if ev == "line" then
if not pcall(w.span, 1, a, txl[a], 0xFFFFFF, 0) then
everestWindows[w.id] = nil
end
elseif ev == "close" then
w.close()
everestWindows[w.id] = nil
end
end)
end
}
local function getPfx(xd, pkg)
-- This is to ensure the prefix naming scheme is FOLLOWED!
-- sys- : System, part of KittenOS NEO and thus tries to present a "unified fragmented interface" in 'neo'
-- app- : Application - these can have ad-hoc relationships. It is EXPECTED these have a GUI
-- svc- : Service - Same as Application but with no expectation of desktop usability
-- Libraries "have no rights" as they are essentially loadable blobs of Lua code.
-- They have access via the calling program, and have a subset of the NEO Kernel API
-- Apps can register with their own name, w/ details
local pfx = nil
if pkg:sub(1, 4) == "app-" then pfx = "app" end
if pkg:sub(1, 4) == "svc-" then pfx = "svc" end
if pfx then
return xd .. pfx .. "." .. pkg:sub(5)
end
end
local function splitAC(ac)
local sb = ac:match("/[a-z0-9/%.]*$")
if sb then
return ac:sub(1, #ac - #sb), sb
end
return ac
end
donkonitDFProvider(function (pkg, pid, sendSig)
local prefixNS = "data/" .. pkg
local prefixWS = prefixNS .. "/"
local fs = rootAccess.primaryDisk
fs.makeDirectory(prefixNS)
local openHandles = {}
targsDH[pid] = function ()
for k, v in pairs(openHandles) do
v()
end
end
return {
showFileDialogAsync = function (forWrite, defName)
if not rawequal(forWrite, nil) then
require("sys-filewrap").ensureMode(forWrite)
end
if not rawequal(defName, nil) then
defName = tostring(defName)
end
-- Not hooked into the event API, so can't safely interfere
-- Thus, this is async and uses a return event.
local tag = {}
neo.scheduleTimer(0)
table.insert(todo, function ()
-- sys-filedialog is yet another "library to control memory usage".
local closer = require("sys-filedialog")(event, nexus, function (res) openHandles[tag] = nil sendSig("filedialog", tag, res) end, neo.requireAccess("c.filesystem", "file managers"), pkg, forWrite, defName)
openHandles[tag] = closer
end)
return tag
end,
myApi = getPfx("", pkg),
lockPerm = function (perm)
-- Are we allowed to?
local permPfx, detail = splitAC(perm)
if getPfx("x.", pkg) ~= permPfx then
return false, "You don't own this permission."
end
local set = "perm|*|" .. perm
if settings.getSetting(set) then
-- Silently ignored, to stop apps trying to sense this & be annoying.
-- The user is allowed to choose.
-- You are only allowed to suggest.
return true
end
settings.setSetting(set, "ask")
return true
end,
-- Paths must begin with / implicitly
list = function (path)
neo.ensureType(path, "string")
path = prefixNS .. path
neo.ensurePath(path, prefixWS)
if path:sub(#path, #path) ~= "/" then error("Expected / at end") end
return fs.list(path:sub(1, #path - 1))
end,
makeDirectory = function (path)
neo.ensureType(path, "string")
path = prefixNS .. path
neo.ensurePath(path, prefixWS)
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
return fs.makeDirectory(path)
end,
rename = function (path1, path2)
neo.ensureType(path1, "string")
neo.ensureType(path2, "string")
path1 = prefixNS .. path1
path2 = prefixNS .. path2
neo.ensurePath(path1, prefixWS)
neo.ensurePath(path2, prefixWS)
if path:sub(#path1, #path1) == "/" then error("Expected no / at end") end
if path:sub(#path2, #path2) == "/" then error("Expected no / at end") end
return fs.rename(path1, path2)
end,
open = function (path, mode)
neo.ensureType(path, "string")
-- mode verified by filewrap
path = prefixNS .. path
neo.ensurePath(path, prefixWS)
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
local fw, closer = require("sys-filewrap").create(fs, path, mode)
if not fw then return nil, closer end
local oc = fw.close
fw.close = function ()
oc()
openHandles[fw] = nil
end
openHandles[fw] = closer
return fw
end,
remove = function (path)
neo.ensureType(path, "string")
path = prefixNS .. path
neo.ensurePath(path, prefixWS)
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
return fs.remove(path)
end,
stat = function (path)
neo.ensureType(path, "string")
path = prefixNS .. path
neo.ensurePath(path, prefixWS)
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
if not fs.exists(path) then return nil end
return {
fs.isDirectory(path),
fs.size(path),
fs.lastModified(path)
}
end,
-- getLabel/setLabel have nothing to do with this
spaceUsed = fs.spaceUsed,
spaceTotal = fs.spaceTotal,
isReadOnly = fs.isReadOnly
}
end)
local function secPolicyStage2(pid, proc, perm, req)
local def = proc.pkg:sub(1, 4) == "sys-"
local secpol, err = require("sys-secpolicy")
if not secpol then
-- Failsafe.
neo.emergency("Used fallback policy because of load-err: " .. err)
req(def)
return
end
-- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context
neo.scheduleTimer(0)
table.insert(todo, function ()
local fPerm = perm
if fPerm:sub(1, 2) == "r." then
fPerm = splitAC(fPerm)
end
local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, fPerm, req, getPfx("", proc.pkg))
if not ok then
neo.emergency("Used fallback policy because of run-err: " .. err)
req(def)
end
end)
end
-- Connect in security policy now
local backup = rootAccess.securityPolicyINIT or rootAccess.securityPolicy
rootAccess.securityPolicyINIT = backup
rootAccess.securityPolicy = function (pid, proc, perm, req)
if neo.dead then
return backup(pid, proc, perm, req)
end
local function finish()
secPolicyStage2(pid, proc, perm, req)
end
-- Do we need to start it?
if perm:sub(1, 6) == "x.svc." and not neo.usAccessExists(perm) then
local appAct = splitAC(perm:sub(7))
-- Prepare for success
onReg[perm] = onReg[perm] or {}
local orp = onReg[perm]
local function kme()
if finish then
finish()
finish = nil
end
end
table.insert(orp, kme)
pcall(neo.executeAsync, "svc-" .. appAct)
-- Fallback "quit now"
local time = os.uptime() + 30
neo.scheduleTimer(time)
local f
function f()
if finish then
if os.uptime() >= time then
-- we've given up
if onReg[perm] == orp then
for k, v in ipairs(orp) do
if v == kme then
table.remove(orp, k)()
break
end
end
end
else
table.insert(todo, f)
end
end
end
table.insert(todo, f)
return
else
finish()
end
end
local function dcall(c, ...)
local ok, e = pcall(...)
if not ok then
nexus.startDialog(tostring(e), c .. "err")
end
end
function theEventHandler(...)
local ev = {...}
if ev[1] == "k.procdie" then
local _, pkg, pid, reason = table.unpack(ev)
if targsDH[pid] then
targsDH[pid]()
end
targsDH[pid] = nil
if reason then
nexus.startDialog(string.format("%s/%i died:\n%s", pkg, pid, reason), "error")
end
elseif ev[1] == "k.timer" then
local nt = todo
todo = {}
for _, v in ipairs(nt) do
dcall("t", v)
end
elseif ev[1] == "k.registration" then
if onReg[ev[2]] then
local tmp = onReg[ev[2]]
onReg[ev[2]] = nil
for _, v in ipairs(tmp) do
dcall("r", v)
end
end
elseif ev[1] == "x.neo.pub.window" then
local v = everestWindows[ev[2]]
if v then
dcall("w", v, table.unpack(ev, 3))
end
end
end
while true do
theEventHandler(coroutine.yield())
end