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

Sandbox

Started by Microwarrior, 09 June 2015 - 01:25 AM
Microwarrior #1
Posted 09 June 2015 - 03:25 AM
I want to create a login system that puts each user in a different sandbox in different folders in the system that each act like the root file system. Is there any way to do this without re-writing all of the API directing to different folders?
KingofGamesYami #2
Posted 09 June 2015 - 04:00 AM
Really, the only way to do this would be to modify certain functions of the fs API. You don't have to modify IO because it uses fs internally.

You wouldn't rewrite the API, just override the functions with your own.


local _fs = fs --#store the old functions
fs.open = function( path, mode )
  --#modify the path somewhere in here
  return _fs.open( modifiedpath, mode )
end
Bomb Bloke #3
Posted 09 June 2015 - 08:14 AM
local _fs = fs --#store the old functions

Immediately after that, you'd then want to do something like:

fs = {}

… or else the new functions you created would go into the same table _fs points to. Don't forget you're just copying the table pointer! Alternatively, _fs would be a new table with all the old function pointers copied into it with a pairs loop:

local _fs = {}

for key, value in pairs(fs) do
  _fs[key] = value
end

fs.open = etc
Microwarrior #4
Posted 09 June 2015 - 11:44 PM
Thanks for the response, but when I try to use this code, the program it is in works correctly but when it finishes running, any program that uses the fs API breaks. The goal would be to make fs return to normal after the program finishes and while it is running, make any program started by it use the modified fs. Any ideas?
Edited on 09 June 2015 - 09:47 PM
biggest yikes #5
Posted 09 June 2015 - 11:52 PM
Thanks for the response, but when I try to use this code, the program it is in works correctly but when it finishes running, any program that uses the fs API breaks. The goal would be to make fs return to normal after the program finishes and while it is running, make any program started by it use the modified fs. Any ideas?
at the end of your program, you would probably want to restore the filesystem API

fs = _fs
Edited on 09 June 2015 - 09:52 PM
Microwarrior #6
Posted 10 June 2015 - 04:14 AM
I am trying to run the edit program in the 'wow' directory with the code below, but it keeps giving me an error on line 10 of the edit program about an attempt to call nil. Please help!


local _fs = fs --#store the old functions
fs = {}
fs.open = function( path, mode )
  --#modify the path somewhere in here
  print(2)
  return fs.open( "wow/"..path, mode )
end
shell.run("edit","testfile")
fs = _fs
KingofGamesYami #7
Posted 10 June 2015 - 04:40 AM
Change line two to this:

fs = setmetatable( {}, {__index = _fs} )

This will automatically add any fs functions you've not overwritten back into the fs API.
Bomb Bloke #8
Posted 10 June 2015 - 04:41 AM
You'll also need to create new versions of every other function in the fs table, if you're going with the "fs = {}" method. If you don't want to do that, use the pairs loop I provided above instead, to copy the function pointers to _fs (instead of moving them). (Edit: Or use the line KoGY ninja'd me with :P/> )

Either way, you'll need to fix this line like so:

return _fs.open( "wow/"..path, mode )
Edited on 10 June 2015 - 02:42 AM
Microwarrior #9
Posted 10 June 2015 - 05:50 PM
Its getting closer, but there is still a problem. when I run fs.open in the file with this code in it, it works. but when I run another program, like edit from this file, it does not work.


local _fs = fs --#store the old functions
fs = setmetatable( {}, {__index = _fs} )
fs.open = function( path, mode )
  --#modify the path somewhere in here
  print(2)
  return _fs.open( "wow/"..path, mode )
end
--run the edit program to test if other programs will open under the 'wow' directory (does not work)
shell.run("edit","hmm")
--testing fs.open from THIS file (works)
fileTable = fs.open("hmm", "r" )
serialRead = fileTable.readAll()
print(serialRead)
fs = _fs
KingofGamesYami #10
Posted 10 June 2015 - 06:28 PM
What do you mean by it does not work? If it errors, post the error message. Otherwise, tell me what did happen.
Microwarrior #11
Posted 10 June 2015 - 06:55 PM
I meant that when edit opens the test file, it opens from the root directory of the computer, now from the 'wow' directory. Sorry i didnt make that clear before.
TheOddByte #12
Posted 10 June 2015 - 07:48 PM
I meant that when edit opens the test file, it opens from the root directory of the computer, now from the 'wow' directory. Sorry i didnt make that clear before.
Well do you have a program that runs the code above? Then you try to run the edit program after you've ran the program?
Because at the end of the current code you restore the original fs api at the end, which will cause it to work as it normally does, and it won't point it to the "wow" directory or whatever
KingofGamesYami #13
Posted 10 June 2015 - 08:46 PM
I meant that when edit opens the test file, it opens from the root directory of the computer, now from the 'wow' directory. Sorry i didnt make that clear before.

I have to ask, how are you testing this? Please remember, the edit program will not say "saved to wow/hmm", because it has been tricked into thinking /wow is the root directory.
Microwarrior #14
Posted 11 June 2015 - 12:08 AM
I have a file named 'hmm' in my root directory, and a file names 'hmm' in the wow directory, each containing different text. I also restart the computer after every test.
Bomb Bloke #15
Posted 11 June 2015 - 03:08 AM
It's because the edit script calls on the io API, and the io API is executed (in order to load its functions into RAM) before your script runs.

When you run a script, it runs from top to bottom. Whenever the VM hits a function definition it compiles it up and stores a pointer (like a shortcut/alias) to the new function in whatever variable you specified. Eg:

local function thingy() ... end  -- Function pointer gets stored against the "thingy" variable.

Tables (and coroutines) work in a similar manner - your variable ends up holding a pointer. You can copy this pointer into other variables, but that doesn't copy the function/table, it only copies the pointer.

When you load an API, most every function that was defined in the API's script file gets compiled up, then the os.loadAPI() function dumps all the pointers gathered into a table named after the API, and dumps that table into _G (where all your other scripts can then access it).

In the io API, around line 64, we have:

function open( _sPath, _sMode )
	local sMode = _sMode or "r"
	local file = fs.open( _sPath, sMode )
	-- etc

This is the function that io.open() will point to once os.loadAPI() is done processing the script file.

That third line there, which calls fs.open(), is saying "index into the fs table, get the pointer to the open function, and execute it". Every time io.open is called it'll perform the index, meaning that if you change the open function in the fs table then that'll change the function io.open calls.

However, what you can't change is which fs table io.open will index into. That is set at the time at which io.open was compiled (during the computer's boot process), so even if you create a new fs table and start messing around with it, io.open will get the open function from within the original fs table. That's the function pointer you'll need to change.

What this boils down to is that you're going to want to use this structure:

local _fs = {}
for key, value in pairs(fs) do _fs[key] = value end

fs.open = etc

Edit: You're also going to need to do the same thing in reverse in order to undo your changes. "fs = _fs" won't cut it.

for key, value in pairs(_fs) do fs[key] = value end  -- To undo.
Edited on 11 June 2015 - 01:22 AM
Lyqyd #16
Posted 11 June 2015 - 04:04 AM
However, what you can't change is which fs table io.open will index into. That is set at the time at which io.open was compiled (during the computer's boot process), so even if you create a new fs table and start messing around with it, io.open will get the open function from within the original fs table. That's the function pointer you'll need to change.

This is actually not true. The problem is that the newly created fs table is going in to the current program's environment table. The io API is indexing _G looking for fs (before indexing that table for open), so it will find the old version, which was not replaced, since the new fs table was in a program's environment. Declaring a new fs table in _G does cause io (and everything else) to use values from the new table instead.

Reversing it is as simple as putting the original table back in the right place, with this method.
Bomb Bloke #17
Posted 11 June 2015 - 04:20 AM
Ooh, good point!
TheOddByte #18
Posted 11 June 2015 - 11:53 AM
Something that's really helpful with a pairs loop is that you can automatically override all the functinons in the fs table and make them point to the new path

local sPath = "sandbox_directory"

local _fs = {}
for k, v in pairs( fs ) do
	_fs[k] = v								   --# Store the old, unmodified version of the function
	fs[k] = function( path, ... )				--# Override the function
		_fs[k]( _fs.combine( sPath, path ), ... ) --# And make it point to the 'sandbox_directory'
	end
end



-- Code here



--# Restore the native functions
for k, v in pairs( _fs ) do
	fs[k] = v
end
Edited on 11 June 2015 - 09:53 AM
flaghacker #19
Posted 11 June 2015 - 12:58 PM
Something that's really helpful with a pairs loop is that you can automatically override all the functinons in the fs table and make them point to the new path

local sPath = "sandbox_directory"

local _fs = {}
for k, v in pairs( fs ) do
	_fs[k] = v								   --# Store the old, unmodified version of the function
	fs[k] = function( path, ... )				--# Override the function
		_fs[k]( _fs.combine( sPath, path ), ... ) --# And make it point to the 'sandbox_directory'
	end
end



-- Code here



--# Restore the native functions
for k, v in pairs( _fs ) do
	fs[k] = v
end

Dont you have to unpack () the … varargs?
KingofGamesYami #20
Posted 11 June 2015 - 04:06 PM
@flaghacker - they aren't in a table, so no. {…} would need unpacked, just … does not.

@TheOddByte - wouldn't this method screw up fs.combine? Because fs.combine would add the path, then when that generated path is used later, the path is added again, ending up in /yourpath/yourpath/whatever, instead of /yourpath/whatever.
Edited on 11 June 2015 - 02:08 PM
Microwarrior #21
Posted 11 June 2015 - 05:58 PM
Thanks for the code TheOddByte. I tried it and it works great when creating new files with fs.open, but when i try to view the contents of a file like this:

local sPath = "wow"
local _fs = {}
for k, v in pairs( fs ) do
		_fs[k] = v																 --# Store the old, unmodified version of the function
		fs[k] = function( path, ... )						   --# Override the function
				_fs[k]( _fs.combine( sPath, path ), ... ) --# And make it point to the 'sandbox_directory'
		end
end
--The file making code
fs.open("fileToBeMade","w")
--The file viewing code
h = fs.open("hmm","r")
read = h.readLine()
print(read)

--# Restore the native functions
for k, v in pairs( _fs ) do
		fs[k] = v
end
The computer throws this error "{programName}:16:attempt to index ?(a nil value)". I assume this is because the 'file value was never created for some reason.

While im here, the way commands like 'fs' works is fs is actually a table containing paired values, and the 'open' part refers to a certain first value, which once found, executes the second value which is a function? Is that correct?
Edited on 11 June 2015 - 05:45 PM
KingofGamesYami #22
Posted 11 June 2015 - 07:46 PM
While im here, the way commands like 'fs' works is fs is actually a table containing paired values, and the 'open' part refers to a certain first value, which once found, executes the second value which is a function?

Yes, all APIs are tables. Any time you write a dot (.) that is not creating a decimal number (10.1), it's indexing a table. For example, you could use fs["open"]( "hi", 'r' ) in place of fs.open( "hi", 'r' ).

FYI, when using fs, ALWAYS close your handles.


fs.open("fileToBeMade","w")

…loads a file into the RAM without any pointer. You'll have to reboot the computer before anything can access the file.


local file = fs.open( "fileToBeMade", "w" )
file.write( "Hi" )
file.close()
..loads the file into the handle 'file', writes "hi" to that handle, then saves and unloads the file.
Microwarrior #23
Posted 15 June 2015 - 04:55 PM
Thank you for the response, I didnt know that was how it worked. Got any ideas on how to fix the code from the earlier post?
Bomb Bloke #24
Posted 16 June 2015 - 03:05 AM
Try "return _fs[k]( _fs.combine( sPath, path ), … )".
Microwarrior #25
Posted 16 June 2015 - 10:00 PM
Thank you for all the help! The code works well.
Here is the code for anyone who wants it:

local sPath = "wow"

local _fs = {}
for k, v in pairs( fs ) do
		_fs[k] = v											       --# Store the old, unmodified version of the function
		fs[k] = function( path, ... )						   --# Override the function
				_fs[k]( _fs.combine( sPath, path ), ... ) --# And make it point to the 'sandbox_directory'
		return _fs[k]( _fs.combine( sPath, path ), ... )
  end
end

-- Code here

--# Restore the native functions
for k, v in pairs( _fs ) do
		fs[k] = v
end
Edited on 16 June 2015 - 08:01 PM
Bomb Bloke #26
Posted 17 June 2015 - 03:22 AM
Er, not quite what I meant; just add the "return" keyword to your existing line, don't add a whole new line.

KoGY also raised an important point here:

@TheOddByte - wouldn't this method screw up fs.combine? Because fs.combine would add the path, then when that generated path is used later, the path is added again, ending up in /yourpath/yourpath/whatever, instead of /yourpath/whatever.

… which I believe you should be able to deal with by restoring the native version of fs.combine immediately after your "sandboxed"-function-generating loop completes.

fs.getName() and fs.getDir() may also need the same treatment.
Edited on 17 June 2015 - 03:12 AM
MKlegoman357 #27
Posted 17 June 2015 - 10:04 AM
And don't forget about fs.copy and fs.move! They take two paths as arguments.
The Crazy Phoenix #28
Posted 17 June 2015 - 07:59 PM
There is a different way of building the sandbox environment. Just create a table, set the metatable's __index = _G and add all methods inside that table. Once you're done, set the function you're calling's environment by calling setfenv(func, env) or by using os.run. For the latter, you don't need to set the metatable since it will do that for you.

The advantage with this is that you don't need to worry about calling an overwritten function on accident, it also allows you to preserve your program's _ENV.