This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Ajt86's profile picture

[Solved] Unload all APIs

Started by Ajt86, 16 January 2016 - 10:36 PM
Ajt86 #1
Posted 16 January 2016 - 11:36 PM
I'd like to sandbox all functions by embedding each of them inside a function where _ENV is set to the sandbox table.

Unfortunately, APIs often declare variables outside the function, which get excluded by my idea of using _ENV (and setfenv wouldn't help either).
My plan to work around this is to unload all APIs and reload them in the sandbox table.

Unfortunately, if I try to reload all of them (except term, window and those the computer can't access), the computer just shuts down.

How would I reload all the available APIs without the computer shutting down?
Edited on 17 January 2016 - 11:32 AM
Bomb Bloke #2
Posted 16 January 2016 - 11:44 PM
When an API is loaded, it gets its own environment table - they never share the environment your code is running in. Given this, I don't see how your sandbox creates a problem…?

Are you aware of the differences between _ENV and _G?
Lyqyd #3
Posted 16 January 2016 - 11:47 PM
I think he's talking about APIs that use some sort of internal state tracking, so that if two different programs were trying to use an API that did different things depending on the internal hidden state, they might get unexpected results.
Ajt86 #4
Posted 16 January 2016 - 11:50 PM
When an API is loaded, it gets its own environment table - they never share the environment your code is running in. Given this, I don't see how your sandbox creates a problem…?

Are you aware of the differences between _ENV and _G?

I'm enclosing each function in a function whose _ENV is equal to the sandbox. Kind of like this:


sandbox[function] = function(...)
  _ENV = sandbox
  return _G[function](...)
end

However, APIs that define variables outside the function definition can no longer access those variables. And they do use the sandbox environment, functions inherit their environment from the function above them in the stack.
Edited on 16 January 2016 - 10:51 PM
Bomb Bloke #5
Posted 17 January 2016 - 12:34 AM
Here's the source of os.loadAPI() (as defined in bios.lua):

Spoiler
local tAPIsLoading = {}
function os.loadAPI( _sPath )
    local sName = fs.getName( _sPath )
    if tAPIsLoading[sName] == true then
        printError( "API "..sName.." is already being loaded" )
        return false
    end
    tAPIsLoading[sName] = true

    local tEnv = {}
    setmetatable( tEnv, { __index = _G } )
    local fnAPI, err = loadfile( _sPath, tEnv )
    if fnAPI then
        local ok, err = pcall( fnAPI )
        if not ok then
            printError( err )
            tAPIsLoading[sName] = nil
            return false
        end
    else
        printError( err )
        tAPIsLoading[sName] = nil
        return false
    end

    local tAPI = {}
    for k,v in pairs( tEnv ) do
        if k ~= "_ENV" then
            tAPI[k] =  v
        end
    end

    _G[sName] = tAPI    
    tAPIsLoading[sName] = nil
    return true
end

As you can see, the API is loaded as a function, and then the environment of that function is replaced with a custom one. The whole point of this is so that any globals the API defines can then be extracted and dumped into the API table which finally ends up in _G (that being the "loaded API").

That is to say, APIs can't touch _ENV. This is why they can't call "shell" or "multishell" functions: Those are loaded into the global environment area all our scripts use, _ENV, as opposed to the table APIs generally go into, _G.

(Edit: Point is, everything an API does is supposed to apply on a system-wide basis. If two different scripts asking one API to do something results in a conflict, then that's the fault of the API, and it's not your problem to fix. There's a reason why you can't define windows using the term API as a parent, for example.)
Edited on 16 January 2016 - 11:48 PM
Ajt86 #6
Posted 17 January 2016 - 06:58 AM
Here's the source of os.loadAPI() (as defined in bios.lua):

Spoiler
local tAPIsLoading = {}
function os.loadAPI( _sPath )
	local sName = fs.getName( _sPath )
	if tAPIsLoading[sName] == true then
		printError( "API "..sName.." is already being loaded" )
		return false
	end
	tAPIsLoading[sName] = true

	local tEnv = {}
	setmetatable( tEnv, { __index = _G } )
	local fnAPI, err = loadfile( _sPath, tEnv )
	if fnAPI then
		local ok, err = pcall( fnAPI )
		if not ok then
			printError( err )
			tAPIsLoading[sName] = nil
			return false
		end
	else
		printError( err )
		tAPIsLoading[sName] = nil
		return false
	end

	local tAPI = {}
	for k,v in pairs( tEnv ) do
		if k ~= "_ENV" then
			tAPI[k] =  v
		end
	end

	_G[sName] = tAPI	
	tAPIsLoading[sName] = nil
	return true
end

As you can see, the API is loaded as a function, and then the environment of that function is replaced with a custom one. The whole point of this is so that any globals the API defines can then be extracted and dumped into the API table which finally ends up in _G (that being the "loaded API").

That is to say, APIs can't touch _ENV. This is why they can't call "shell" or "multishell" functions: Those are loaded into the global environment area all our scripts use, _ENV, as opposed to the table APIs generally go into, _G.

(Edit: Point is, everything an API does is supposed to apply on a system-wide basis. If two different scripts asking one API to do something results in a conflict, then that's the fault of the API, and it's not your problem to fix. There's a reason why you can't define windows using the term API as a parent, for example.)

There's a problem with that. I want to unload all the APIs bios.lua initialized and I want to reload those in the sandboxed filesystem.
I've already sandboxed os.loadAPI to load APIs into the sandboxed environment.
Bomb Bloke #7
Posted 17 January 2016 - 11:01 AM
I still don't get it. Even if you wanted to load fresh copies of all APIs in your sandboxed environment (what's this about a filesystem?), why would you want to unload the original APIs? Why not just leave them in the original _G table?

All that aside, if you want to know what's wrong with your code: post it. We can't very well comment on what we can't see. If you don't want to post the whole project, that's fine: just cobble together the bits that're causing you problems, make sure they're still causing said problems, and post that.
Edited on 17 January 2016 - 10:08 AM
Ajt86 #8
Posted 17 January 2016 - 12:32 PM
Somehow I got it to work… I guess unloading the APIs really is a bad idea.

The only API I had to reload was the IO API.
I have no idea how I got the others to work.

Here's the method I used (worked)


local sandbox = {--[[ Generate environment here ]]}    -- Your sandbox environment
sandbox._G = sandbox
sandbox.func = function(...)	 -- Do this for each function
  _ENV = sandbox
  return func(...)
end

Thanks for your help.