This is a read-only snapshot of the ComputerCraft forums,
taken in April 2020.
How can I make a file read-only?
Started by MarcoPolo0306, 03 September 2016 - 01:23 PMPosted 03 September 2016 - 03:23 PM
I am wondering how I make a file read-only. Please help!
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.
Here is a example, if you need it.
Edited on 03 September 2016 - 01:25 PM
Posted 03 September 2016 - 04:07 PM
You could overwrite fs.open like this:
Note: I haven't tested 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
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:
Metatable code:
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…
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
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"?
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
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
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.
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