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

How can I make a file read-only?

Started by MarcoPolo0306, 03 September 2016 - 01:23 PM
MarcoPolo0306 #1
Posted 03 September 2016 - 03:23 PM
I am wondering how I make a file read-only. Please help!
Admicos #2
Posted 03 September 2016 - 03:24 PM
you override fs.isReadOnly(file) and make it so if "file" argument is your file, you return true.

Here is a example, if you need it.
Edited on 03 September 2016 - 01:25 PM
Blue #3
Posted 03 September 2016 - 04:07 PM
You could overwrite fs.open like this:

local oldFs = fs
local oldOs = os
fs.open = function(file,method)
  if method == 'w'  and file == 'your_read_only_file_here' then
	error('File is read only!')
  else
	return oldFs.open(file,method)
  end
end
os.run = function(env,path,...)
  local args = {...}
  env.fs = fs
  env.os = os
  oldOs.run(env,path,...)
end

Note: I haven't tested this
Exerro #4
Posted 03 September 2016 - 06:55 PM
That won't quite work. Because you write 'oldFs = fs', they both point to the same table: changing an index in one will change it in the other. To get around this, you could shallow copy 'fs' or give 'oldFs' a metatable with its __index pointing to 'fs'. The same goes for 'oldOs'/'os'

You might want to normalise the path too, as you could get around that by opening './myfile.txt' in write mode to write to 'myfile.txt' (even if you test if it's 'myfile.txt').

Shallow copy code:

local oldFs = {}
for k, v in pairs( fs ) do
		oldFs[k] = v
end

Metatable code:

local oldFs = setmetatable( {}, { __index = fs } )

Edit: ignore the metatable solution, it won't work. You're changing the 'fs' table rather than creating a new, modified table and using that. Because of this, you don't even need to modify os.run(). You could even get away with just keeping a copy of the old 'fs.open()' rather than the whole 'fs' table.

Edit 2: I may as well post the code for what I mean…

local function normalise( path )
	return path
	:gsub( "//+", "/" ) --# remove multiple slashes
	:gsub( "/%./", "/" ) --# remove /./ (has no effect on path)
	:gsub( "^%./", "" ) --# remove ./ at start (same as above)
	:gsub( "(/?)[^/]+/%.%./", "%1" ) --# remove /X/../
	:gsub( "(/?)[^/]+/%.%.$", "%1" ) --# remove /X/.. at end
	:gsub( "^%.%./", "" ) --# remove ../ at start
	:gsub( "^[^/]+/%.%./", "/" ) --# remove X/.. at start
	:gsub( "^/", "" ):gsub( "/$", "" ) --# remove trailing slashes
end

local oldOpen = fs.open
local blacklist = {
	["my/readonly/path"] = true;
}

function fs.open( path, mode )
	if mode ~= "r" and mode ~= "rb" and blacklist[normalise( path )] then
		return error( "attempt to open read-only file in '" .. mode .. "' mode" )
	else
		return oldOpen( path, mode )
	end
end
Edited on 03 September 2016 - 05:07 PM
H4X0RZ #5
Posted 03 September 2016 - 09:35 PM
That won't quite work. Because you write 'oldFs = fs', they both point to the same table: changing an index in one will change it in the other. To get around this, you could shallow copy 'fs' or give 'oldFs' a metatable with its __index pointing to 'fs'. The same goes for 'oldOs'/'os'

You might want to normalise the path too, as you could get around that by opening './myfile.txt' in write mode to write to 'myfile.txt' (even if you test if it's 'myfile.txt').

Shallow copy code:

local oldFs = {}
for k, v in pairs( fs ) do
		oldFs[k] = v
end

Metatable code:

local oldFs = setmetatable( {}, { __index = fs } )

Edit: ignore the metatable solution, it won't work. You're changing the 'fs' table rather than creating a new, modified table and using that. Because of this, you don't even need to modify os.run(). You could even get away with just keeping a copy of the old 'fs.open()' rather than the whole 'fs' table.

Edit 2: I may as well post the code for what I mean…

local function normalise( path )
	return path
	:gsub( "//+", "/" ) --# remove multiple slashes
	:gsub( "/%./", "/" ) --# remove /./ (has no effect on path)
	:gsub( "^%./", "" ) --# remove ./ at start (same as above)
	:gsub( "(/?)[^/]+/%.%./", "%1" ) --# remove /X/../
	:gsub( "(/?)[^/]+/%.%.$", "%1" ) --# remove /X/.. at end
	:gsub( "^%.%./", "" ) --# remove ../ at start
	:gsub( "^[^/]+/%.%./", "/" ) --# remove X/.. at start
	:gsub( "^/", "" ):gsub( "/$", "" ) --# remove trailing slashes
end

local oldOpen = fs.open
local blacklist = {
	["my/readonly/path"] = true;
}

function fs.open( path, mode )
	if mode ~= "r" and mode ~= "rb" and blacklist[normalise( path )] then
		return error( "attempt to open read-only file in '" .. mode .. "' mode" )
	else
		return oldOpen( path, mode )
	end
end

can't you just let "shell.resolve" handle the "normalizing"?
Sewbacca #6
Posted 04 September 2016 - 10:06 AM

local function normalise( path )
	return path
	:gsub( "//+", "/" ) --# remove multiple slashes
	:gsub( "/%./", "/" ) --# remove /./ (has no effect on path)
	:gsub( "^%./", "" ) --# remove ./ at start (same as above)
	:gsub( "(/?)[^/]+/%.%./", "%1" ) --# remove /X/../
	:gsub( "(/?)[^/]+/%.%.$", "%1" ) --# remove /X/.. at end
	:gsub( "^%.%./", "" ) --# remove ../ at start
	:gsub( "^[^/]+/%.%./", "/" ) --# remove X/.. at start
	:gsub( "^/", "" ):gsub( "/$", "" ) --# remove trailing slashes
end
can't you just let "shell.resolve" handle the "normalizing"?

shell.resolve, resolves the path from the current directory.
For example:
current directory: rom
path: rom/../rom/apis
result: rom/rom/apis

local sPath = shell.resolve('rom/../rom/apis') --> rom/rom/apis

fs.combine would give the excepted result:

current directroy: rom
path: rom/../rom/apis
result: rom/apis

local sPath = fs.combine('/', 'rom/../rom/apis') -- rom/apis

-- example resolve function:
local resolve = function(sPath)
  return fs.combine('/', sPath)
end
Edited on 04 September 2016 - 08:08 AM
TheOddByte #7
Posted 04 September 2016 - 12:26 PM
Simplest solution as suggested at the top

--# The files that you'd like to be read-only
local paths = {
    ["startup"] = true;
}

--# Create a backup of the fs table
local _fs = fs 

--# Override the function
fs.isReadOnly = function( sPath )
    if paths[sPath] then 
        return true --# If the path matches one of the paths in the table it will return true
    else
        return _fs.isReadOnly( sPath ) --# If it doesn't then let the native function handle it
    end
end
Admicos #8
Posted 04 September 2016 - 12:30 PM
Simplest solution as suggested at the top

--# The files that you'd like to be read-only
local paths = {
	["startup"] = true;
}

--# Create a backup of the fs table
local _fs = fs

--# Override the function
fs.isReadOnly = function( sPath )
	if paths[sPath] then
		return true --# If the path matches one of the paths in the table it will return true
	else
		return _fs.isReadOnly( sPath ) --# If it doesn't then let the native function handle it
	end
end

Just want to point it out that if you use this, you need to normalize the path, otherwise editing /rom/../startup will probably work.
Sewbacca #9
Posted 05 September 2016 - 08:14 PM
Simplest solution as suggested at the top

--# The files that you'd like to be read-only
local paths = {
	["startup"] = true;
}

--# Create a backup of the fs table
local _fs = fs

--# Override the function
fs.isReadOnly = function( sPath )
	if paths[sPath] then
		return true --# If the path matches one of the paths in the table it will return true
	else
		return _fs.isReadOnly( sPath ) --# If it doesn't then let the native function handle it
	end
end
Just want to point it out that if you use this, you need to normalize the path, otherwise editing /rom/../startup will probably work.

It doesn't work correctly. Ignoring the point, that the backup _fs == fs (because they are the same objects) –> true, just edit or paint or other programs who uses fs.isReadOnly() would follow your rules, but fs.open() doesn't use fs.isReadOnly(),
so everyone, who don't take care of fs.isReadOnly() can edit the startup with fs.open(). If you want to prevent writing on a file, try this code:

local tReadOnly = {
  '^startup' -- a pattern, working for all strings, containing startup at the start of the string
}

local native_fs = {}

for key, value in pairs(fs) do
  native_fs[key] = value -- copying all functions of fs into a new table
end

local isReadOnly = function (sPath) -- modulate the program for a better handling
  for i = 1, #tReadOnly do
	if sPath:match(tReadOnly[i]) then
	  return true
	end
  end
  return false
end

fs.isReadOnly = function (sPath) -- For programs who take care of fs.isReadOnly()
  return isReadOnly(sPath) and native_fs.isReadOnly(sPath)
end

fs.open = function (sPath, sMode)
  if sMode:match('w') or sMode:match('a') and isReadOnly(sPath) then
	return nil -- return nil, like the native fs, if the file is read only
  end
  return native_fs.open(sPath, sMode)
end

-- Note that you have to add fs.delete() and fs.move() here here
(Note, the Code is untested)
Edited on 05 September 2016 - 06:18 PM