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

Quarantine program not functioning

Started by ViperLordX, 06 November 2015 - 12:03 AM
ViperLordX #1
Posted 06 November 2015 - 01:03 AM
I've been writing a program designed to move everything to a folder, q, and make it virtually undetectable. When it runs, it will move all files other than itself and q to q, and it modifies the fs api so that everything appears normal. The problem is that if you try to run a program in the q folder while quarantined, it won't run. However, if you try running q/[program], it will. Can anyone explain this? fs.exists, fs.open, and everything else still work like I made them work. I don't know what shell is doing to break out.

function main()
  local es = fs
  allowed = {"rom", "disk[1-6]"}
  function cover(file)
    file = es.combine(file, "")
    file = file:gsub("%.%.", "")
    permitted = false
    for _, v in pairs(allowed) do
	  if (string.find(file, v)) then
	    permitted = true
	  end
    end
    if (not (permitted)) then
	  file = "q/"..file
    end
    return file
  end
  function uncover(file)
    file = es.combine(file, "")
    file = string.gsub(file, "^q/", "")
    return file
  end
  if (not es.exists("q")) then
    es.makeDir("q")
  end
  files = es.list("/")
  for k, v in pairs(files) do
    if (not (v == "start") and not (v == "q")) then
	  if (not (es.exists(cover(v)))) then
	    es.copy(v, cover(v))
	  end
	  if (not (es.isReadOnly(v)) and not (v == "q")) then
	    es.delete(v)
	  end
    end
  end
  local vfs = {}
  function vfs.list(path)
    r = es.list(cover(path))
    if (path == "") then
	  r[#r + 1] = "rom"
    end
    for k, v in pairs(r) do
	  r[k] = uncover(v)
    end
    return r
  end
  function vfs.exists(path)
    return es.exists(cover(path))
  end
  function vfs.isDir(path)
    return es.isDir(cover(path))
  end
  function vfs.isReadOnly(path)
    return es.isReadOnly(cover(path))
  end
  function vfs.getDrive(path)
    return es.getDrive(cover(path))
  end
  function vfs.getSize(path)
    return es.getSize(cover(path))
  end
  function vfs.getFreeSpace(path)
    return es.getFreeSpace(cover(path))
  end
  function vfs.makeDir(path)
    es.makeDir(cover(path))
  end
  function vfs.move(fromPath, toPath)
    es.move(cover(fromPath), cover(toPath))
  end
  function vfs.copy(fromPath, toPath)
    es.copy(cover(fromPath), cover(toPath))
  end
  function vfs.delete(path)
    es.delete(cover(path))
  end
  function vfs.open(path, mode)
    return es.open(cover(path), mode)
  end
  function vfs.find(wildcard)
    value = es.find(cover(wildcard))
    for k, v in pairs(value) do
	  value[k] = uncover(v)
    end
    return value
  end
  vfs.complete = es.complete
  vfs.combine = es.combine
  vfs.getName = es.getName
  fs = vfs
end
main()
valithor #2
Posted 06 November 2015 - 02:56 PM
I do not really have time to look into it to help, but in the few minutes I had to look over it, I found that your problem is the shell.resolveProgram function, which is called inside shell.run. In my test, passing the file name only to shell.resolveProgram causes it to return nil (is what is being passed to the function), but passing the q/..filename caused it to return the correct file name. Would be worth looking into what causes that function to think the file does not exist. I would do it myself, but I do not have time.

Alternatively, you could make a alias through shell for each of the files you move.
Edited on 06 November 2015 - 01:57 PM
ViperLordX #3
Posted 06 November 2015 - 09:18 PM
Ok, I'll try it. Thanks!
ViperLordX #4
Posted 06 November 2015 - 09:38 PM
When I overwrite shell.resolve() or shell.resolveProgram(), the entire computer freezes, including the event queue but not the cursor, and then shuts down after a while.

Ok, I tested it, and it only seems to freeze shell, and nothing else. If I use lua to manually overwrite it, then exit lua, the shell freezes. It also happens if I make shell.resolve() return the value of blah.resolve() where blah = shell.
H4X0RZ #5
Posted 07 November 2015 - 01:07 AM
Are you simply doing

blah = shell
shell.resolve = blah.resolve
because that doesn't copy the table, it just adds a "pointer" for that table.
ViperLordX #6
Posted 07 November 2015 - 02:47 AM
No, I did this:

function shell.resolve(name)
  return blah.resolve(name)
end
valithor #7
Posted 07 November 2015 - 02:51 AM
No, I did this:

function shell.resolve(name)
  return blah.resolve(name)
end

What he meant by that was, are you doing this to copy the table:

blah = shell

The second part really wasn't needed to ask what he was asking.
Edited on 07 November 2015 - 01:52 AM
ViperLordX #8
Posted 07 November 2015 - 02:52 AM
Yes, I am
valithor #9
Posted 07 November 2015 - 02:55 AM
Yes, I am

The reason it is crashing then is due to the way lua tables work. When you do "blah = shell" you are not making a new table with the same contents as shell, you are creating a variable that points to the same table as the shell variable points to. So, in short there is 1 table, and the function you overwrote shell.resolveProgram with, is just calling itself over and over (CC limits recursion to 255 times, which eventually causes a crash).
ViperLordX #10
Posted 07 November 2015 - 02:55 AM
So how do I avoid this?
valithor #11
Posted 07 November 2015 - 02:57 AM
So how do I avoid this?

Right now I am starting minecraft in order to play around with it and help you find a answer to your original problem, but in order to overcome the problem you are having right now is fairly easy.

Instead of copying the entire table, just copy the single function you are going to overwrite:

local oldResolveProgram = shell.resolveProgram

shell.resolveProgram = function(...)
  return oldResolveProgram(...)
end
Edited on 07 November 2015 - 06:52 PM
KingofGamesYami #12
Posted 07 November 2015 - 03:04 AM
Whenever modifying APIs, it's a good idea to back-up the API using a for loop.


local oldShell = {}
for k, v in pairs( shell )
  oldShell[ k ] = v
end

shell.resolveProgram = function( ... )
  return oldShell.resolveProgram( ... )
end

Edit: Clarification on this issue:

Variables pointing to tables really contain a pointer to the table.

This can be demonstrated with the following:


local t = {foo="bar"}
print( t.foo )
local _t = t
_t.foo = "foo"
print( t.foo )

As you can see, even though we assigned _t.foo a variable instead of t.foo, t.foo was also changed. This is because, in reality, both of these variables are simply labels - and they label the same table.
Edited on 07 November 2015 - 02:08 AM
valithor #13
Posted 07 November 2015 - 03:08 AM
A quick little fix that will fix your problem:


local oldResolveProgram = shell.resolveProgram

shell.resolveProgram = function(path)
  if oldResolveProgram(path) ~= nil then --# checking if it is nil without the "q/" added on.  Essentially if the path is for rom, or a disk
	return oldResolveProgram(path) --# returning it sense it isn't nil
  else
	return oldResolveProgram("q/"..path) --# returning the "q/" path
  end
end
Edited on 07 November 2015 - 04:56 PM
ViperLordX #14
Posted 07 November 2015 - 05:01 AM
Come to think of it, I could just do blah = {} then set blah.resolve(), then setmetatable(blah, {__index = shell}) shell = blah
ViperLordX #15
Posted 07 November 2015 - 06:18 AM
My new code is below, and it's still not working. Now it thinks that anything within q does not exist, and it also errors if I do "edit ___" where ___ is anything, saying that it's too long without yielding on line 16 of all things: file = es.combine(file, ""). It's not even in a loop!

local function contains(table, value)
  for k, v in pairs(table) do
    if (v == value) then
	  return true;
    end
  end
  return false;
end
function main()
  local es = {}
  for k, v in pairs(fs) do
    es[k] = v
  end
  allowed = {"rom", "disk[1-6]"}
  function cover(file)
    file = es.combine(file, "")
    file = string.gsub(file, "%.%.", "")
    permitted = false
    for _, v in pairs(allowed) do
	  if (string.find(file, v)) then
	    permitted = true
	  end
    end
    if (not (permitted)) then
	  file = "q/"..file
    end
    return file
  end
  function uncover(file)
    file = es.combine(file, "")
    file = string.gsub(file, "^q/", "")
    return file
  end
  if (not es.exists("q")) then
    es.makeDir("q")
  end
  files = es.list("/")
  for k, v in pairs(files) do
    if (not (v == "start") and not (v == "q")) then
	  if (not (es.exists(cover(v)))) then
	    es.copy(v, cover(v))
	  end
	  if (not (es.isReadOnly(v)) and not (v == "q")) then
	    es.delete(v)
	  end
    end
  end
  local vfs = {}
  function vfs.list(path)
    r = es.list(cover(path))
    if (path == "") then
	  r[#r + 1] = "rom"
    end
    for k, v in pairs(r) do
	  r[k] = uncover(v)
    end
    return r
  end
  function vfs.exists(path)
    return es.exists(cover(path))
  end
  function vfs.isDir(path)
    return es.isDir(cover(path))
  end
  function vfs.isReadOnly(path)
    return es.isReadOnly(cover(path))
  end
  function vfs.getDrive(path)
    return es.getDrive(cover(path))
  end
  function vfs.getSize(path)
    return es.getSize(cover(path))
  end
  function vfs.getFreeSpace(path)
    return es.getFreeSpace(cover(path))
  end
  function vfs.makeDir(path)
    es.makeDir(cover(path))
  end
  function vfs.move(fromPath, toPath)
    es.move(cover(fromPath), cover(toPath))
  end
  function vfs.copy(fromPath, toPath)
    es.copy(cover(fromPath), cover(toPath))
  end
  function vfs.delete(path)
    es.delete(cover(path))
  end
  function vfs.open(path, mode)
    return es.open(cover(path), mode)
  end
  function vfs.find(wildcard)
    value = es.find(cover(wildcard))
    for k, v in pairs(value) do
	  value[k] = uncover(v)
    end
    return value
  end
  vfs.complete = es.complete
  vfs.combine = es.combine
  vfs.getName = es.getName
  fs = vfs
  local hell = {}
  function hell.resolve(name)
    return hell.resolve(cover(name))
  end
    function hell.resolveProgram(name)
    return hell.resolveProgram(cover(name))
  end
  setmetatable(hell, {__index = shell})
  shell = hell
end
main()
valithor #16
Posted 07 November 2015 - 05:58 PM
If you use the fix I gave you instead of the one you came up with it would work and would be much more efficient.

Also, when you do eventually switch over to the one I gave you, you will realize you are getting a error on line 65 of edit. In which case you will need to overwrite io.open in the same way you overwrote fs.open.


local oldResolveProgram = shell.resolveProgram

shell.resolveProgram = function(path)
  if oldResolveProgram(path) ~= nil then --# checking if it is nil without the "q/" added on.  Essentially if the path is for rom, or a disk
        return oldResolveProgram(path) --# returning it sense it isn't nil
  else
        return oldResolveProgram("q/"..path) --# returning the "q/" path
  end
end

local oldIoOpen = io.open

io.open = function(path,mode)
  return oldIoOpen(cover(path),mode)
end
KingofGamesYami #17
Posted 07 November 2015 - 06:10 PM
@valithor - that'd be a terrible idea, as overwriting fs already overwrites io. io uses fs.
Lyqyd #18
Posted 07 November 2015 - 06:12 PM
In ComputerCraft, io.open uses fs.open internally, so if you're overriding fs, you probably don't need to touch io.
ViperLordX #19
Posted 07 November 2015 - 06:22 PM
So what is the issue with the code? Why is it saying that it's too long without yielding when it's not even in a loop, and it's the first line in the function?
valithor #20
Posted 07 November 2015 - 06:45 PM
@valithor - that'd be a terrible idea, as overwriting fs already overwrites io. io uses fs.
In ComputerCraft, io.open uses fs.open internally, so if you're overriding fs, you probably don't need to touch io.

Apparently overwriting the fs api did not change the io api. It might be how he overwrote it, but when I was testing the program in a emulator the fs.exists check on line 63 of the edit program was returning true, but line 65 was erroring with attempt to call nil, aka io.opening the file that fs.exist said exist was returning nil. I came to the conclusion that io.open was still using the unmodified version of fs.open, which made no sense to me, but doing the overwrite i posted fixed it.

Put shortly. I thought the exact same as both of you, but it doesn't work without making that modification from my tests, and yes I do understand how the io api works internally.
Edited on 07 November 2015 - 05:51 PM
Lyqyd #21
Posted 07 November 2015 - 07:26 PM
The fs = vfs line is the problem. That's creating an fs table in the current environment rather than replacing the fs table in _G.
ViperLordX #22
Posted 07 November 2015 - 10:35 PM
Ah, that makes sense. I'll try it.

Still not working, getting the same exact error at line 16.
valithor #23
Posted 08 November 2015 - 04:57 AM
Ah, that makes sense. I'll try it.

Still not working, getting the same exact error at line 16.

If you use the fix I gave you instead of the one you came up with it would work and would be much more efficient.

Also, when you do eventually switch over to the one I gave you, you will realize you are getting a error on line 65 of edit. In which case you will need to overwrite io.open in the same way you overwrote fs.open.


local oldResolveProgram = shell.resolveProgram

shell.resolveProgram = function(path)
  if oldResolveProgram(path) ~= nil then --# checking if it is nil without the "q/" added on.  Essentially if the path is for rom, or a disk
		return oldResolveProgram(path) --# returning it sense it isn't nil
  else
		return oldResolveProgram("q/"..path) --# returning the "q/" path
  end
end

local oldIoOpen = io.open

io.open = function(path,mode)
  return oldIoOpen(cover(path),mode)
end

Just replace your shell overwrite with this… If it doesn't work then come back and ask for help. It doesn't even appear you have tested it since so you have not said anything about it though I have posted 3 times.
Edited on 08 November 2015 - 03:59 AM
ViperLordX #24
Posted 08 November 2015 - 06:50 PM
Didn't you admit that I shouldn't overwrite io.open?
valithor #25
Posted 08 November 2015 - 07:27 PM
Didn't you admit that I shouldn't overwrite io.open?

No… I agreed with them in the respect you shouldn't need to overwrite it, however, with the code you posted here and how you overwrite the fs api it is necessary. If you were to change how you overwrite the fs api then no you shouldn't need to overwrite io.open.

Edit:

Chances are… You just need to fix how you overwrite the fs api, and then you wouldn't need to overwrite anything in shell (the way you overwrote shell caused the too long without yielding error).
Edited on 08 November 2015 - 06:30 PM
ViperLordX #26
Posted 08 November 2015 - 07:36 PM
Could you explain how it causes the too long without yielding error to me and tell me how to fix it?

Oh, does fs.combine() use shell.resolve() so when I do cover() in shell.resolve(), it calls itself recursively?
Edited on 08 November 2015 - 06:37 PM
valithor #27
Posted 08 November 2015 - 09:17 PM
Could you explain how it causes the too long without yielding error to me and tell me how to fix it?

Oh, does fs.combine() use shell.resolve() so when I do cover() in shell.resolve(), it calls itself recursively?


function hell.resolveProgram(name)
  return hell.resolveProgram(cover(name))
end

That code is taken from what you posted. As you can see it defines hell.resolveProgram, and then the only thing that the function does is call hell.resolveProgram… It is calling itself over and over. It does not crash from the CC recursive limit due to the fact you are returning the function, hence the reason it crashes without too long without yielding.

How to fix it:

Completely remove the shell overwrite, and change the line:


fs = vfs

to


_G.fs = vfs

As lyqyd said.

Just going to say… the fix I posted 3 times actually did fix your problem. Very annoyed you did not even try it, and continued to say your code did not work although I provided a working solution.
Edited on 08 November 2015 - 08:18 PM
ViperLordX #28
Posted 09 November 2015 - 11:53 PM
I did change it to _G.fs = vfs, and got the same error. I'll try your fix now, I thought I had done shell.resolve instead of hell.resolve.
ViperLordX #29
Posted 10 November 2015 - 12:00 AM
Sorry about not trying your code, I didn't try it because I thought it was the same as mine… If you look at the cover() function, it's doing pretty much the same thing. Your way works now, but why? What's the difference?
valithor #30
Posted 11 November 2015 - 04:38 AM
I did change it to _G.fs = vfs, and got the same error. I'll try your fix now, I thought I had done shell.resolve instead of hell.resolve.

If you change it to _G.fs = vfs, then you must remove the shell overwrite as well. The entire reason the shell overwrite was every needed was because the fs api used in shell.resolveProgram was not being changed (explained by lyqyd). I tested it without the shell overwrite, and it did exactly what you wanted it to do.

Sorry about not trying your code, I didn't try it because I thought it was the same as mine… If you look at the cover() function, it's doing pretty much the same thing. Your way works now, but why? What's the difference?

I am not going to pretend I have a lot of experience with LUA metatables… because I do not. However, if I understand correctly, then overwriting the shell api that way is almost exactly the same as the code below (the test I did also supports this statement):


local hell = {}
hell.resolveProgram = function(...)
  return shell.resolveProgram(cover(...))
end

shell = hell

I think I explained why this won't work earlier, and from my tests this is basically what your overwrite was doing. This is the main difference between our ways of overwriting it.

If you want a better way to overwrite it, then remove the shell overwrite (and io overwrite), and just change that one line. As I admitted in my first post, I did not spend much time actually looking at the code, as I was in class when I made that first post. I just narrowed it down hoping someone else would pick it up and continue to help. As such the solution I provided was aimed at the effect instead of the cause of your problem, so although it works it is not the most efficient way to fix the problem.