2021-01-12 23:11:00 +11:00
-- 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.
2018-03-19 10:10:54 +11:00
-- 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
2018-04-27 02:38:33 +10:00
local rootAccess = neo.requireAccess ( " k.root " , " installing GUI integration " )
2018-03-19 14:08:09 +11:00
local settings = neo.requireAccess ( " x.neo.sys.manage " , " security sysconf access " )
local donkonitDFProvider = neo.requireAccess ( " r.neo.pub.base " , " creating basic NEO APIs " )
2018-03-19 10:10:54 +11:00
local targsDH = { } -- data disposal
2018-03-30 22:36:48 +11:00
local todo = { }
2018-04-23 04:40:08 +10:00
-- Specific registration callbacks
local onReg = { }
2018-03-30 22:36:48 +11:00
local everestWindows = { }
local nexus
2018-04-23 04:40:08 +10:00
local theEventHandler
local function addOnReg ( p , f )
onReg [ p ] = onReg [ p ] or { }
table.insert ( onReg [ p ] , f )
end
2018-03-30 22:36:48 +11:00
nexus = {
2018-04-26 07:57:25 +10:00
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 " )
2018-03-30 22:36:48 +11:00
end
2018-05-30 08:47:20 +10:00
local dwo , dw = pcall ( e , w , h , t )
if not dwo then
addOnReg ( " x.neo.pub.window " , cb )
return
end
2018-04-26 07:57:25 +10:00
c ( dw )
everestWindows [ dw.id ] = function ( ... )
return c ( dw , ... )
end
2018-05-30 08:47:20 +10:00
else
addOnReg ( " x.neo.pub.window " , cb )
2018-03-30 22:36:48 +11:00
end
end
2018-05-30 08:47:20 +10:00
cb ( )
2018-03-30 22:36:48 +11:00
end ,
2018-04-26 07:57:25 +10:00
windows = everestWindows ,
2018-03-30 22:36:48 +11:00
startDialog = function ( tx , ti )
2018-04-26 07:57:25 +10:00
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
2018-04-26 22:50:51 +10:00
everestWindows [ w.id ] = nil
2018-03-30 22:36:48 +11:00
end
2018-04-26 07:57:25 +10:00
elseif ev == " close " then
w.close ( )
2018-04-26 22:50:51 +10:00
everestWindows [ w.id ] = nil
2018-03-30 22:36:48 +11:00
end
end )
end
}
2018-04-12 09:04:16 +10:00
local function getPfx ( xd , pkg )
2018-04-09 09:04:40 +10:00
-- 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
2018-04-12 09:04:16 +10:00
-- Apps can register with their own name, w/ details
2018-04-09 09:04:40 +10:00
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
2018-04-12 09:04:16 +10:00
return xd .. pfx .. " . " .. pkg : sub ( 5 )
end
end
2020-04-02 09:21:36 +11:00
local function splitAC ( ac )
local sb = ac : match ( " /[a-z0-9/%.]*$ " )
if sb then
return ac : sub ( 1 , # ac - # sb ) , sb
2018-04-09 09:04:40 +10:00
end
2020-04-02 09:21:36 +11:00
return ac
2018-04-09 09:04:40 +10:00
end
2018-03-19 10:10:54 +11:00
donkonitDFProvider ( function ( pkg , pid , sendSig )
local prefixNS = " data/ " .. pkg
2018-04-27 02:38:33 +10:00
local prefixWS = prefixNS .. " / "
local fs = rootAccess.primaryDisk
fs.makeDirectory ( prefixNS )
2018-03-19 10:10:54 +11:00
local openHandles = { }
targsDH [ pid ] = function ( )
for k , v in pairs ( openHandles ) do
v ( )
end
end
return {
2018-12-24 10:51:30 +11:00
showFileDialogAsync = function ( forWrite , defName )
2018-04-12 09:04:16 +10:00
if not rawequal ( forWrite , nil ) then
require ( " sys-filewrap " ) . ensureMode ( forWrite )
end
2018-12-24 10:51:30 +11:00
if not rawequal ( defName , nil ) then
defName = tostring ( defName )
end
2018-03-19 10:10:54 +11:00
-- Not hooked into the event API, so can't safely interfere
-- Thus, this is async and uses a return event.
local tag = { }
2018-03-30 22:36:48 +11:00
neo.scheduleTimer ( 0 )
table.insert ( todo , function ( )
2018-03-19 10:10:54 +11:00
-- sys-filedialog is yet another "library to control memory usage".
2018-12-24 10:51:30 +11:00
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 )
2018-03-19 10:10:54 +11:00
openHandles [ tag ] = closer
end )
return tag
end ,
2018-04-12 09:04:16 +10:00
myApi = getPfx ( " " , pkg ) ,
2018-04-09 09:04:40 +10:00
lockPerm = function ( perm )
-- Are we allowed to?
2020-04-02 09:21:36 +11:00
local permPfx , detail = splitAC ( perm )
if getPfx ( " x. " , pkg ) ~= permPfx then
2018-04-09 09:04:40 +10:00
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 ,
2018-03-19 10:10:54 +11:00
-- Paths must begin with / implicitly
list = function ( path )
2018-04-12 09:04:16 +10:00
neo.ensureType ( path , " string " )
2018-03-19 10:10:54 +11:00
path = prefixNS .. path
neo.ensurePath ( path , prefixWS )
if path : sub ( # path , # path ) ~= " / " then error ( " Expected / at end " ) end
2018-04-27 02:38:33 +10:00
return fs.list ( path : sub ( 1 , # path - 1 ) )
2018-03-19 10:10:54 +11:00
end ,
makeDirectory = function ( path )
2018-04-12 09:04:16 +10:00
neo.ensureType ( path , " string " )
2018-03-19 10:10:54 +11:00
path = prefixNS .. path
neo.ensurePath ( path , prefixWS )
if path : sub ( # path , # path ) == " / " then error ( " Expected no / at end " ) end
2018-04-27 02:38:33 +10:00
return fs.makeDirectory ( path )
2018-03-19 10:10:54 +11:00
end ,
rename = function ( path1 , path2 )
2018-04-12 09:04:16 +10:00
neo.ensureType ( path1 , " string " )
neo.ensureType ( path2 , " string " )
2018-03-19 10:10:54 +11:00
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
2018-04-27 02:38:33 +10:00
return fs.rename ( path1 , path2 )
2018-03-19 10:10:54 +11:00
end ,
open = function ( path , mode )
2018-04-12 09:04:16 +10:00
neo.ensureType ( path , " string " )
-- mode verified by filewrap
2018-03-19 10:10:54 +11:00
path = prefixNS .. path
neo.ensurePath ( path , prefixWS )
if path : sub ( # path , # path ) == " / " then error ( " Expected no / at end " ) end
2018-04-27 02:38:33 +10:00
local fw , closer = require ( " sys-filewrap " ) . create ( fs , path , mode )
2018-04-22 20:54:47 +10:00
if not fw then return nil , closer end
2018-03-19 10:10:54 +11:00
local oc = fw.close
fw.close = function ( )
oc ( )
openHandles [ fw ] = nil
end
openHandles [ fw ] = closer
return fw
end ,
remove = function ( path )
2018-04-12 09:04:16 +10:00
neo.ensureType ( path , " string " )
2018-03-19 10:10:54 +11:00
path = prefixNS .. path
neo.ensurePath ( path , prefixWS )
if path : sub ( # path , # path ) == " / " then error ( " Expected no / at end " ) end
2018-04-27 02:38:33 +10:00
return fs.remove ( path )
2018-03-19 10:10:54 +11:00
end ,
stat = function ( path )
2018-04-12 09:04:16 +10:00
neo.ensureType ( path , " string " )
2018-03-19 10:10:54 +11:00
path = prefixNS .. path
neo.ensurePath ( path , prefixWS )
if path : sub ( # path , # path ) == " / " then error ( " Expected no / at end " ) end
2018-04-27 02:38:33 +10:00
if not fs.exists ( path ) then return nil end
2018-03-19 10:10:54 +11:00
return {
2018-04-27 02:38:33 +10:00
fs.isDirectory ( path ) ,
fs.size ( path ) ,
fs.lastModified ( path )
2018-03-19 10:10:54 +11:00
}
end ,
-- getLabel/setLabel have nothing to do with this
2018-04-27 02:38:33 +10:00
spaceUsed = fs.spaceUsed ,
spaceTotal = fs.spaceTotal ,
isReadOnly = fs.isReadOnly
2018-03-19 10:10:54 +11:00
}
end )
2018-04-24 07:18:18 +10:00
local function secPolicyStage2 ( pid , proc , perm , req )
2018-03-28 00:40:05 +11:00
local def = proc.pkg : sub ( 1 , 4 ) == " sys- "
2018-03-19 10:10:54 +11:00
local secpol , err = require ( " sys-secpolicy " )
if not secpol then
2018-03-19 14:08:09 +11:00
-- Failsafe.
neo.emergency ( " Used fallback policy because of load-err: " .. err )
2018-03-28 00:40:05 +11:00
req ( def )
2018-03-19 14:08:09 +11:00
return
2018-03-19 10:10:54 +11:00
end
2018-03-30 22:36:48 +11:00
-- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context
neo.scheduleTimer ( 0 )
table.insert ( todo , function ( )
2020-04-02 09:21:36 +11:00
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 ) )
2018-03-19 14:08:09 +11:00
if not ok then
neo.emergency ( " Used fallback policy because of run-err: " .. err )
2018-03-28 00:40:05 +11:00
req ( def )
2018-03-19 14:08:09 +11:00
end
end )
end
2018-03-19 10:10:54 +11:00
2018-04-24 07:18:18 +10:00
-- 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?
2018-04-26 04:52:23 +10:00
if perm : sub ( 1 , 6 ) == " x.svc. " and not neo.usAccessExists ( perm ) then
2020-04-22 06:39:09 +10:00
local appAct = splitAC ( perm : sub ( 7 ) )
2018-04-26 04:52:23 +10:00
-- Prepare for success
onReg [ perm ] = onReg [ perm ] or { }
2018-04-26 04:54:37 +10:00
local orp = onReg [ perm ]
local function kme ( )
if finish then
finish ( )
finish = nil
end
end
table.insert ( orp , kme )
2018-04-26 04:52:23 +10:00
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
2018-04-26 04:54:37 +10:00
-- 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
2018-04-26 04:52:23 +10:00
else
table.insert ( todo , f )
2018-04-24 07:18:18 +10:00
end
end
end
2018-04-26 04:52:23 +10:00
table.insert ( todo , f )
return
2018-04-24 07:18:18 +10:00
else
finish ( )
end
end
2018-04-26 08:58:31 +10:00
local function dcall ( c , ... )
local ok , e = pcall ( ... )
if not ok then
nexus.startDialog ( tostring ( e ) , c .. " err " )
end
end
2018-04-23 04:40:08 +10:00
function theEventHandler ( ... )
local ev = { ... }
2018-03-30 22:36:48 +11:00
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
2018-04-26 08:58:31 +10:00
dcall ( " t " , v )
2018-03-30 22:36:48 +11:00
end
elseif ev [ 1 ] == " k.registration " then
2018-04-23 04:40:08 +10:00
if onReg [ ev [ 2 ] ] then
local tmp = onReg [ ev [ 2 ] ]
onReg [ ev [ 2 ] ] = nil
for _ , v in ipairs ( tmp ) do
2018-04-26 08:58:31 +10:00
dcall ( " r " , v )
2018-03-30 22:36:48 +11:00
end
end
elseif ev [ 1 ] == " x.neo.pub.window " then
local v = everestWindows [ ev [ 2 ] ]
if v then
2018-04-26 08:58:31 +10:00
dcall ( " w " , v , table.unpack ( ev , 3 ) )
2018-03-30 22:36:48 +11:00
end
end
2018-03-19 10:10:54 +11:00
end
2018-04-23 04:40:08 +10:00
while true do
theEventHandler ( coroutine.yield ( ) )
end