mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-15 02:12:42 -04:00
parent
08bb90faa3
commit
f2b5e01730
@ -4,15 +4,17 @@ local transfer = require("tools/transfer")
|
|||||||
local args, options = shell.parse(...)
|
local args, options = shell.parse(...)
|
||||||
options.h = options.h or options.help
|
options.h = options.h or options.help
|
||||||
if #args < 2 or options.h then
|
if #args < 2 or options.h then
|
||||||
io.write("Usage: cp [-inrv] <from...> <to>\n")
|
io.write([[Usage: cp [OPTIONS] <from...> <to>
|
||||||
io.write(" -i: prompt before overwrite (overrides -n option).\n")
|
-i: prompt before overwrite (overrides -n option).
|
||||||
io.write(" -n: do not overwrite an existing file.\n")
|
-n: do not overwrite an existing file.
|
||||||
io.write(" -r: copy directories recursively.\n")
|
-r: copy directories recursively.
|
||||||
io.write(" -u: copy only when the SOURCE file differs from the destination\n")
|
-u: copy only when the SOURCE file differs from the destination
|
||||||
io.write(" file or when the destination file is missing.\n")
|
file or when the destination file is missing.
|
||||||
io.write(" -P: preserve attributes, e.g. symbolic links.\n")
|
-P: preserve attributes, e.g. symbolic links.
|
||||||
io.write(" -v: verbose output.\n")
|
-v: verbose output.
|
||||||
io.write(" -x: stay on original source file system.\n")
|
-x: stay on original source file system.
|
||||||
|
--skip=P: skip files matching lua regex P
|
||||||
|
]])
|
||||||
return not not options.h
|
return not not options.h
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -27,6 +29,7 @@ options =
|
|||||||
P = options.P,
|
P = options.P,
|
||||||
v = options.v,
|
v = options.v,
|
||||||
x = options.x,
|
x = options.x,
|
||||||
|
skip = options.skip,
|
||||||
}
|
}
|
||||||
|
|
||||||
return transfer.batch(args, options)
|
return transfer.batch(args, options)
|
||||||
|
@ -13,21 +13,23 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not options then return end
|
if not options then return end
|
||||||
local write = io.write
|
|
||||||
|
|
||||||
if computer.freeMemory() < 50000 then
|
if computer.freeMemory() < 50000 then
|
||||||
write("Low memory, collecting garbage\n")
|
print("Low memory, collecting garbage")
|
||||||
for i=1,20 do os.sleep(0) end
|
for i=1,20 do os.sleep(0) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G)
|
local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G)
|
||||||
|
assert(cp, reason)
|
||||||
|
|
||||||
|
local ok, ec = pcall(cp, table.unpack(options.cp_args))
|
||||||
|
assert(ok, ec)
|
||||||
|
|
||||||
local ec = cp(table.unpack(options.cp_args))
|
|
||||||
if ec ~= nil and ec ~= 0 then
|
if ec ~= nil and ec ~= 0 then
|
||||||
return ec
|
return ec
|
||||||
end
|
end
|
||||||
|
|
||||||
write("Installation complete!\n")
|
print("Installation complete!")
|
||||||
|
|
||||||
if options.setlabel then
|
if options.setlabel then
|
||||||
pcall(options.target.dev.setLabel, options.label)
|
pcall(options.target.dev.setLabel, options.label)
|
||||||
@ -36,16 +38,16 @@ end
|
|||||||
if options.setboot then
|
if options.setboot then
|
||||||
local address = options.target.dev.address
|
local address = options.target.dev.address
|
||||||
if computer.setBootAddress(address) then
|
if computer.setBootAddress(address) then
|
||||||
write("Boot address set to " .. address)
|
print("Boot address set to " .. address)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if options.reboot then
|
if options.reboot then
|
||||||
write("Reboot now? [Y/n] ")
|
io.write("Reboot now? [Y/n] ")
|
||||||
if ((io.read() or "n").."y"):match("^%s*[Yy]") then
|
if ((io.read() or "n").."y"):match("^%s*[Yy]") then
|
||||||
write("\nRebooting now!\n")
|
print("\nRebooting now!\n")
|
||||||
computer.shutdown(true)
|
computer.shutdown(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
write("Returning to shell.\n")
|
print("Returning to shell.\n")
|
||||||
|
@ -4,12 +4,13 @@ local transfer = require("tools/transfer")
|
|||||||
local args, options = shell.parse(...)
|
local args, options = shell.parse(...)
|
||||||
options.h = options.h or options.help
|
options.h = options.h or options.help
|
||||||
if #args < 2 or options.h then
|
if #args < 2 or options.h then
|
||||||
io.write([[Usage: mv [-fiv] <from> <to>
|
io.write([[Usage: mv [OPTIONS] <from> <to>
|
||||||
-f overwrite without prompt
|
-f overwrite without prompt
|
||||||
-i prompt before overwriting
|
-i prompt before overwriting
|
||||||
unless -f
|
unless -f
|
||||||
-v verbose
|
-v verbose
|
||||||
-n do not overwrite an existing file
|
-n do not overwrite an existing file
|
||||||
|
--skip=P ignore paths matching lua regex P
|
||||||
-h, --help show this help
|
-h, --help show this help
|
||||||
]])
|
]])
|
||||||
return not not options.h
|
return not not options.h
|
||||||
@ -23,6 +24,7 @@ options =
|
|||||||
i = options.i,
|
i = options.i,
|
||||||
v = options.v,
|
v = options.v,
|
||||||
n = options.n, -- no clobber
|
n = options.n, -- no clobber
|
||||||
|
skip = options.skip,
|
||||||
P = true, -- move operations always preserve
|
P = true, -- move operations always preserve
|
||||||
r = true, -- move is allowed to move entire dirs
|
r = true, -- move is allowed to move entire dirs
|
||||||
x = true, -- cannot move mount points
|
x = true, -- cannot move mount points
|
||||||
|
@ -78,22 +78,21 @@ function require(module)
|
|||||||
return loaded[module]
|
return loaded[module]
|
||||||
elseif not loading[module] then
|
elseif not loading[module] then
|
||||||
loading[module] = true
|
loading[module] = true
|
||||||
local loader, value, errorMsg = nil, nil, {"module '" .. module .. "' not found:"}
|
local loader, errorMsg = nil, {"module '" .. module .. "' not found:"}
|
||||||
for i = 1, #package.searchers do
|
for i = 1, #package.searchers do
|
||||||
-- the pcall is mostly for out of memory errors
|
-- the pcall is mostly for out of memory errors
|
||||||
local ok, f, extra = pcall(package.searchers[i], module)
|
local ok, f = pcall(package.searchers[i], module)
|
||||||
if not ok then
|
if not ok then
|
||||||
table.insert(errorMsg, "\t" .. (f or "nil"))
|
table.insert(errorMsg, "\t" .. (f or "nil"))
|
||||||
elseif f and type(f) ~= "string" then
|
elseif f and type(f) ~= "string" then
|
||||||
loader = f
|
loader = f
|
||||||
value = extra
|
|
||||||
break
|
break
|
||||||
elseif f then
|
elseif f then
|
||||||
table.insert(errorMsg, f)
|
table.insert(errorMsg, f)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if loader then
|
if loader then
|
||||||
local success, result = pcall(loader, module, value)
|
local success, result = pcall(loader, module)
|
||||||
loading[module] = false
|
loading[module] = false
|
||||||
if not success then
|
if not success then
|
||||||
error(result, 2)
|
error(result, 2)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
local fs = require("filesystem")
|
local fs = require("filesystem")
|
||||||
local shell = require("shell")
|
local shell = require("shell")
|
||||||
|
local text = require("text")
|
||||||
local lib = {}
|
local lib = {}
|
||||||
|
|
||||||
local function perr(ops, format, ...)
|
local function perr(ops, format, ...)
|
||||||
@ -31,10 +32,10 @@ end
|
|||||||
|
|
||||||
local function areEqual(path1, path2)
|
local function areEqual(path1, path2)
|
||||||
local f1, f2 = fs.open(path1, "rb")
|
local f1, f2 = fs.open(path1, "rb")
|
||||||
|
local result = true
|
||||||
if f1 then
|
if f1 then
|
||||||
f2 = fs.open(path2, "rb")
|
f2 = fs.open(path2, "rb")
|
||||||
if f2 then
|
if f2 then
|
||||||
local result = true
|
|
||||||
local chunkSize = 4 * 1024
|
local chunkSize = 4 * 1024
|
||||||
repeat
|
repeat
|
||||||
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
|
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
|
||||||
@ -86,10 +87,19 @@ local function stat(path, ops, P)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function lib.recurse(fromPath, toPath, options, origin, top)
|
function lib.recurse(fromPath, toPath, options, origin, top)
|
||||||
|
fromPath = fromPath:gsub("/+", "/")
|
||||||
|
toPath = toPath:gsub("/+", "/")
|
||||||
|
local fromPathFull = shell.resolve(fromPath)
|
||||||
|
local toPathFull = shell.resolve(toPath)
|
||||||
local mv = options.cmd == "mv"
|
local mv = options.cmd == "mv"
|
||||||
|
local verbose = options.v and (not mv or top)
|
||||||
|
if select(2, fromPathFull:find(options.skip)) == #fromPathFull then
|
||||||
|
status(verbose, string.format("skipping %s", fromPath))
|
||||||
|
return true
|
||||||
|
end
|
||||||
local function release(result, reason)
|
local function release(result, reason)
|
||||||
if result and mv and top then
|
if result and mv and top then
|
||||||
local rm_result = not fs.get(fromPath).isReadOnly() and fs.remove(fromPath)
|
local rm_result = not fs.get(fromPathFull).isReadOnly() and fs.remove(fromPathFull)
|
||||||
if not rm_result then
|
if not rm_result then
|
||||||
perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
|
perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
|
||||||
result = false
|
result = false
|
||||||
@ -101,22 +111,22 @@ function lib.recurse(fromPath, toPath, options, origin, top)
|
|||||||
local
|
local
|
||||||
ok,
|
ok,
|
||||||
fromReal,
|
fromReal,
|
||||||
fromError,
|
_, --fromError,
|
||||||
fromIsLink,
|
fromIsLink,
|
||||||
fromLinkTarget,
|
fromLinkTarget,
|
||||||
fromExists,
|
fromExists,
|
||||||
fromFs,
|
fromFs,
|
||||||
fromIsDir = stat(fromPath, options, options.P)
|
fromIsDir = stat(fromPathFull, options, options.P)
|
||||||
if not ok then return nil end
|
if not ok then return nil end
|
||||||
local
|
local
|
||||||
ok,
|
ok,
|
||||||
toReal,
|
toReal,
|
||||||
toError,
|
_,--toError,
|
||||||
toIsLink,
|
toIsLink,
|
||||||
toLinkTarget,
|
_,--toLinkTarget,
|
||||||
toExists,
|
toExists,
|
||||||
toFs,
|
toFs,
|
||||||
toIsDir = stat(toPath, options)
|
toIsDir = stat(toPathFull, options)
|
||||||
if not ok then os.exit(1) end
|
if not ok then os.exit(1) end
|
||||||
if toFs.isReadOnly() then
|
if toFs.isReadOnly() then
|
||||||
perr(options, "cannot create target '%s': filesystem is readonly", toPath)
|
perr(options, "cannot create target '%s': filesystem is readonly", toPath)
|
||||||
@ -124,9 +134,7 @@ function lib.recurse(fromPath, toPath, options, origin, top)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local same_path = fromReal == toReal
|
local same_path = fromReal == toReal
|
||||||
local same_link = fromIsLink and toIsLink and same_path
|
|
||||||
|
|
||||||
local verbose = options.v
|
|
||||||
local same_fs = fromFs == toFs
|
local same_fs = fromFs == toFs
|
||||||
local is_mount = origin[fromReal]
|
local is_mount = origin[fromReal]
|
||||||
|
|
||||||
@ -138,12 +146,12 @@ function lib.recurse(fromPath, toPath, options, origin, top)
|
|||||||
if toExists and options.n then
|
if toExists and options.n then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
fs.remove(toPath)
|
fs.remove(toPathFull)
|
||||||
if toExists then
|
if toExists then
|
||||||
status(verbose, string.format("removed '%s'\n", toPath))
|
status(verbose, string.format("removed '%s'", toPath))
|
||||||
end
|
end
|
||||||
status(verbose, fromPath, toPath)
|
status(verbose, fromPath, toPath)
|
||||||
return release(fs.link(fromLinkTarget, toPath))
|
return release(fs.link(fromLinkTarget, toPathFull))
|
||||||
elseif fromIsDir then
|
elseif fromIsDir then
|
||||||
if not options.r then
|
if not options.r then
|
||||||
status(true, string.format("omitting directory '%s'", fromPath))
|
status(true, string.format("omitting directory '%s'", fromPath))
|
||||||
@ -160,17 +168,20 @@ function lib.recurse(fromPath, toPath, options, origin, top)
|
|||||||
if same_fs then
|
if same_fs then
|
||||||
if (toReal.."/"):find(fromReal.."/",1,true) then
|
if (toReal.."/"):find(fromReal.."/",1,true) then
|
||||||
return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
|
return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
|
||||||
elseif mv then
|
|
||||||
status(verbose, fromPath, toPath)
|
|
||||||
return os.rename(fromPath, toPath)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if mv then
|
||||||
|
if fs.list(toReal)() then -- to is NOT empty
|
||||||
|
return nil, "cannot move '" .. fromPath .. "' to '" .. toPath .. "': Directory not empty"
|
||||||
|
end
|
||||||
|
status(verbose, fromPath, toPath)
|
||||||
|
end
|
||||||
if not toExists then
|
if not toExists then
|
||||||
status(verbose, fromPath, toPath)
|
status(verbose, fromPath, toPath)
|
||||||
fs.makeDirectory(toPath)
|
fs.makeDirectory(toPathFull)
|
||||||
end
|
end
|
||||||
for file in fs.list(fromPath) do
|
for file in fs.list(fromPathFull) do
|
||||||
local result, reason = lib.recurse(fs.concat(fromPath, file), fs.concat(toPath, file), options, origin, false) -- false, no longer top
|
local result, reason = lib.recurse(fromPath .."/".. file, toPath.."/"..file, options, origin, false) -- false, no longer top
|
||||||
if not result then
|
if not result then
|
||||||
return false, reason
|
return false, reason
|
||||||
end
|
end
|
||||||
@ -198,7 +209,7 @@ function lib.recurse(fromPath, toPath, options, origin, top)
|
|||||||
fs.remove(toReal)
|
fs.remove(toReal)
|
||||||
end
|
end
|
||||||
status(verbose, fromPath, toPath)
|
status(verbose, fromPath, toPath)
|
||||||
return release(fs.copy(fromPath, toPath))
|
return release(fs.copy(fromPathFull, toPathFull))
|
||||||
else
|
else
|
||||||
return nil, "'" .. fromPath .. "': No such file or directory"
|
return nil, "'" .. fromPath .. "': No such file or directory"
|
||||||
end
|
end
|
||||||
@ -210,6 +221,7 @@ function lib.batch(args, options)
|
|||||||
-- standardized options
|
-- standardized options
|
||||||
options.i = options.i and not options.f
|
options.i = options.i and not options.f
|
||||||
options.P = options.P or options.r
|
options.P = options.P or options.r
|
||||||
|
options.skip = text.escapeMagic(options.skip or "")
|
||||||
|
|
||||||
local origin = {}
|
local origin = {}
|
||||||
for dev,path in fs.mounts() do
|
for dev,path in fs.mounts() do
|
||||||
@ -217,27 +229,31 @@ function lib.batch(args, options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local toArg = table.remove(args)
|
local toArg = table.remove(args)
|
||||||
local _, toPath = contents_check(toArg, options)
|
local _, ok = contents_check(toArg, options)
|
||||||
if not toPath then
|
if not ok then
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
local originalToIsDir = fs.isDirectory(toPath)
|
local originalToIsDir = fs.isDirectory(ok)
|
||||||
|
|
||||||
for _,arg in ipairs(args) do
|
for _, fromArg in ipairs(args) do
|
||||||
-- a "contents of" copy is where src path ends in . or ..
|
-- a "contents of" copy is where src path ends in . or ..
|
||||||
-- a source path ending with . is not sufficient - could be the source filename
|
-- a source path ending with . is not sufficient - could be the source filename
|
||||||
local contents_of, fromPath = contents_check(arg, options, true)
|
local contents_of
|
||||||
if fromPath then
|
contents_of, ok = contents_check(fromArg, options, true)
|
||||||
|
if ok then
|
||||||
-- we do not append fromPath name to toPath in case of contents_of copy
|
-- we do not append fromPath name to toPath in case of contents_of copy
|
||||||
local nextPath = toPath
|
local toPath = toArg
|
||||||
if contents_of and options.cmd == "mv" then
|
if contents_of and options.cmd == "mv" then
|
||||||
perr(options, "invalid move path '%s'", arg)
|
perr(options, "invalid move path '%s'", fromArg)
|
||||||
else
|
else
|
||||||
if not contents_of and originalToIsDir then
|
if not contents_of and originalToIsDir then
|
||||||
nextPath = fs.concat(nextPath, fs.name(fromPath))
|
local fromName = fs.name(fromArg)
|
||||||
|
if fromName then
|
||||||
|
toPath = toPath .. "/" .. fromName
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local result, reason = lib.recurse(fromPath, nextPath, options, origin, true)
|
local result, reason = lib.recurse(fromArg, toPath, options, origin, true)
|
||||||
|
|
||||||
if not result then
|
if not result then
|
||||||
perr(options, reason)
|
perr(options, reason)
|
||||||
|
@ -1,23 +1,11 @@
|
|||||||
local computer = require("computer")
|
local computer = require("computer")
|
||||||
local shell = require("shell")
|
local shell = require("shell")
|
||||||
local component = require("component")
|
|
||||||
local event = require("event")
|
|
||||||
local fs = require("filesystem")
|
local fs = require("filesystem")
|
||||||
local unicode = require("unicode")
|
|
||||||
local text = require("text")
|
|
||||||
|
|
||||||
local write = io.write
|
|
||||||
|
|
||||||
local args, options = shell.parse(...)
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
options.sources = {}
|
|
||||||
options.targets = {}
|
|
||||||
options.source_label = args[1]
|
|
||||||
|
|
||||||
local root_exception
|
|
||||||
|
|
||||||
if options.help then
|
if options.help then
|
||||||
write([[Usage: install [OPTION]...
|
io.write([[Usage: install [OPTION]...
|
||||||
--from=ADDR install filesystem at ADDR
|
--from=ADDR install filesystem at ADDR
|
||||||
default: builds list of
|
default: builds list of
|
||||||
candidates and prompts user
|
candidates and prompts user
|
||||||
@ -34,176 +22,161 @@ if options.help then
|
|||||||
return nil -- exit success
|
return nil -- exit success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local utils_path = "/opt/core/install_utils.lua"
|
||||||
|
local utils
|
||||||
|
|
||||||
local rootfs = fs.get("/")
|
local rootfs = fs.get("/")
|
||||||
if not rootfs then
|
if not rootfs then
|
||||||
io.stderr:write("no root filesystem, aborting\n");
|
io.stderr:write("no root filesystem, aborting\n");
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function up_deprecate(old_key, new_key)
|
local label = args[1]
|
||||||
if options[new_key] == nil then
|
options.label = label
|
||||||
options[new_key] = options[old_key]
|
|
||||||
end
|
|
||||||
options[old_key] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function cleanPath(path)
|
local source_filter = options.from
|
||||||
if path then
|
local source_filter_dev
|
||||||
local rpath = shell.resolve(path)
|
if source_filter then
|
||||||
if fs.isDirectory(rpath) then
|
local from_path = shell.resolve(source_filter)
|
||||||
return fs.canonical(rpath) .. '/'
|
if fs.isDirectory(from_path) then
|
||||||
end
|
source_filter_dev = fs.get(from_path)
|
||||||
|
source_filter = source_filter_dev.address
|
||||||
|
options.from = from_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local rootAddress = rootfs.address
|
local target_filter = options.to
|
||||||
-- if the rootfs is read only, it is probably the loot disk!
|
local target_filter_dev
|
||||||
root_exception = rootAddress
|
if target_filter then
|
||||||
if rootfs.isReadOnly() then
|
local to_path = shell.resolve(target_filter)
|
||||||
root_exception = nil
|
if fs.isDirectory(target_filter) then
|
||||||
end
|
target_filter_dev = fs.get(to_path)
|
||||||
|
target_filter = target_filter_dev.address
|
||||||
-- this may be OpenOS specific, default to "" in case no /dev mount point
|
options.to = to_path
|
||||||
local devfsAddress = (fs.get("/dev/") or {}).address or ""
|
|
||||||
|
|
||||||
-- tmp is only valid if specified as an option
|
|
||||||
local tmpAddress = computer.tmpAddress()
|
|
||||||
|
|
||||||
----- For now, I am allowing source==target -- cp can handle it if the user prepares conditions correctly
|
|
||||||
----- in other words, install doesn't need to filter this scenario:
|
|
||||||
--if #options.targets == 1 and #options.sources == 1 and options.targets[1] == options.sources[1] then
|
|
||||||
-- io.stderr:write("It is not the intent of install to use the same source and target filesystem.\n")
|
|
||||||
-- return 1
|
|
||||||
--end
|
|
||||||
|
|
||||||
------ load options
|
|
||||||
up_deprecate('noboot', 'nosetboot')
|
|
||||||
up_deprecate('nolabelset', 'nosetlabel')
|
|
||||||
up_deprecate('name', 'label')
|
|
||||||
|
|
||||||
options.source_root = cleanPath(options.from)
|
|
||||||
options.target_root = cleanPath(options.to)
|
|
||||||
|
|
||||||
options.target_dir = fs.canonical(options.root or options.toDir or "")
|
|
||||||
|
|
||||||
options.update = options.u or options.update
|
|
||||||
|
|
||||||
local function path_to_dev(path)
|
|
||||||
return path and fs.isDirectory(path) and not fs.isLink(path) and fs.get(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
local source_dev = path_to_dev(options.source_root)
|
|
||||||
local target_dev = path_to_dev(options.target_root)
|
|
||||||
|
|
||||||
-- use a single for loop of all filesystems to build the list of candidates of sources and targets
|
|
||||||
local function validDevice(candidate, exceptions, specified, existing)
|
|
||||||
local address = candidate.dev.address
|
|
||||||
|
|
||||||
for _,e in ipairs(existing) do
|
|
||||||
if e.dev.address == address then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if specified then
|
|
||||||
if address:find(specified, 1, true) == 1 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for _,e in ipairs(exceptions) do
|
|
||||||
if e == address then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local sources = {}
|
||||||
|
local targets = {}
|
||||||
|
|
||||||
|
-- tmpfs is not a candidate unless it is specified
|
||||||
|
|
||||||
|
local devices = {}
|
||||||
|
|
||||||
for dev, path in fs.mounts() do
|
for dev, path in fs.mounts() do
|
||||||
local candidate = {dev=dev, path=path:gsub("/+$","")..'/'}
|
devices[dev] = path
|
||||||
|
end
|
||||||
|
|
||||||
if validDevice(candidate, {devfsAddress, tmpAddress, root_exception}, source_dev and source_dev.address or options.from, options.sources) then
|
devices[fs.get("/dev/") or false] = nil
|
||||||
-- root path is either the command line path given for this dev or its natural mount point
|
local tmpAddress = computer.tmpAddress()
|
||||||
local root_path = source_dev == dev and options.source_root or path
|
|
||||||
if (options.from or fs.list(root_path)()) then -- ignore empty sources unless specified
|
|
||||||
local prop = fs.open(root_path .. '/.prop')
|
|
||||||
if prop then
|
|
||||||
local prop_data = prop:read(math.huge)
|
|
||||||
prop:close()
|
|
||||||
prop = prop_data
|
|
||||||
end
|
|
||||||
candidate.prop = prop and load('return ' .. prop)() or {}
|
|
||||||
if not candidate.prop.ignore then
|
|
||||||
if not options.source_label or options.source_label:lower() == (candidate.prop.label or (dev.getLabel() or "")):lower() then
|
|
||||||
table.insert(options.sources, candidate)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- in case candidate is valid for BOTH, we want a new table
|
for dev, path in pairs(devices) do
|
||||||
candidate = {dev=candidate.dev, path=candidate.path} -- but not the prop
|
local address = dev.address
|
||||||
|
local install_path = dev == target_filter_dev and options.to or path
|
||||||
|
local specified = target_filter and address:find(target_filter, 1, true) == 1
|
||||||
|
|
||||||
if validDevice(candidate, {devfsAddress, tmpAddress}, target_dev and target_dev.address or options.to, options.targets) then
|
if dev.isReadOnly() then
|
||||||
if not dev.isReadOnly() then
|
if specified then
|
||||||
table.insert(options.targets, candidate)
|
|
||||||
elseif options.to then
|
|
||||||
io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
|
io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
elseif specified or
|
||||||
|
not target_filter and
|
||||||
|
address ~= tmpAddress then
|
||||||
|
table.insert(targets, {dev=dev, path=install_path, specified=specified})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local source = options.sources[1]
|
local target = targets[1]
|
||||||
local target = options.targets[1]
|
if #targets ~= 1 then
|
||||||
local utils_path = "/opt/core/install_utils.lua"
|
utils = loadfile(utils_path, "bt", _G)
|
||||||
|
target = utils("select", "targets", options, targets)
|
||||||
|
end
|
||||||
|
if not target then return end
|
||||||
|
devices[target.dev] = nil
|
||||||
|
|
||||||
if #options.sources ~= 1 or #options.targets ~= 1 then
|
for dev, path in pairs(devices) do
|
||||||
source, target = loadfile(utils_path, "bt", _G)('select', options)
|
local address = dev.address
|
||||||
|
local install_path = dev == source_filter_dev and options.from or path
|
||||||
|
local specified = source_filter and address:find(source_filter, 1, true) == 1
|
||||||
|
|
||||||
|
if fs.list(install_path)()
|
||||||
|
and (specified or
|
||||||
|
not source_filter and
|
||||||
|
address ~= tmpAddress and
|
||||||
|
not (address == rootfs.address and not rootfs.isReadOnly())) then
|
||||||
|
local prop = {}
|
||||||
|
local prop_path = path .. "/.prop"
|
||||||
|
local prop_file = fs.open(prop_path)
|
||||||
|
if prop_file then
|
||||||
|
local prop_data = prop_file:read(math.huge)
|
||||||
|
prop_file:close()
|
||||||
|
local prop_load = load("return " .. prop_data)
|
||||||
|
prop = prop_load and prop_load()
|
||||||
|
if not prop then
|
||||||
|
io.stderr:write("Ignoring " .. path .. " due to malformed prop file\n")
|
||||||
|
prop = {ignore = true}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not prop.ignore then
|
||||||
|
if not label or label:lower() == (prop.label or dev.getLabel() or ""):lower() then
|
||||||
|
table.insert(sources, {dev=dev, path=install_path, prop=prop, specified=specified})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local source = sources[1]
|
||||||
|
if #sources ~= 1 then
|
||||||
|
utils = utils or loadfile(utils_path, "bt", _G)
|
||||||
|
source = utils("select", "sources", options, sources)
|
||||||
|
end
|
||||||
if not source then return end
|
if not source then return end
|
||||||
options.source_root = options.source_root or source.path
|
|
||||||
|
|
||||||
if not target then return end
|
options =
|
||||||
options.target_root = options.target_root or target.path
|
{
|
||||||
|
from = source.path .. '/',
|
||||||
-- now that source is selected, we can update options
|
to = target.path .. '/',
|
||||||
options.label = options.label or source.prop.label
|
fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""),
|
||||||
options.setlabel = source.prop.setlabel and not options.nosetlabel
|
root = fs.canonical(options.root or options.toDir or source.prop.root or ""),
|
||||||
options.setboot = source.prop.setboot and not options.nosetboot
|
update = options.update or options.u,
|
||||||
options.reboot = source.prop.reboot and not options.noreboot
|
label = source.prop.label or label,
|
||||||
options.source_dir = fs.canonical(source.prop.fromDir or options.fromDir or "") .. '/.'
|
setlabel = not (options.nosetlabel or options.nolabelset) and source.prop.setlabel,
|
||||||
|
setboot = not (options.nosetboot or options.noboot) and source.prop.setboot,
|
||||||
|
reboot = not options.noreboot and source.prop.reboot,
|
||||||
|
}
|
||||||
|
|
||||||
local cp_args =
|
local cp_args =
|
||||||
{
|
{
|
||||||
"-vrx" .. (options.update and "ui" or ""),
|
"-vrx" .. (options.update and "ui" or ""),
|
||||||
options.source_root .. options.source_dir,
|
"--skip=.prop",
|
||||||
options.target_root:gsub("//","/") .. options.target_dir
|
fs.concat(options.from, options.fromDir) .. "/.",
|
||||||
|
fs.concat(options.to , options.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
local source_display = (source.prop or {}).label or source.dev.getLabel() or source.path
|
local source_display = options.label or source.dev.getLabel() or source.path
|
||||||
local special_target = ""
|
local special_target = ""
|
||||||
if #options.targets > 1 or options.to then
|
if #targets > 1 or target_filter then
|
||||||
special_target = " to " .. cp_args[3]
|
special_target = " to " .. cp_args[3]
|
||||||
end
|
end
|
||||||
|
|
||||||
io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
|
io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
|
||||||
if not ((io.read() or "n").."y"):match("^%s*[Yy]") then
|
if not ((io.read() or "n").."y"):match("^%s*[Yy]") then
|
||||||
write("Installation cancelled\n")
|
io.write("Installation cancelled\n")
|
||||||
os.exit()
|
os.exit()
|
||||||
end
|
end
|
||||||
|
|
||||||
local installer_path = options.source_root .. "/.install"
|
local installer_path = options.from .. "/.install"
|
||||||
if fs.exists(installer_path) then
|
if fs.exists(installer_path) then
|
||||||
os.exit(loadfile(utils_path, "bt", _G)('install', options))
|
local installer, reason = loadfile(installer_path, "bt", setmetatable({install=options}, {__index = _G}))
|
||||||
|
if not installer then
|
||||||
|
io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n')
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
os.exit(installer())
|
||||||
end
|
end
|
||||||
|
|
||||||
return
|
options.cp_args = cp_args
|
||||||
{
|
options.target = target
|
||||||
setlabel = options.setlabel,
|
|
||||||
label = options.label,
|
return options
|
||||||
setboot = options.setboot,
|
|
||||||
reboot = options.reboot,
|
|
||||||
target = target,
|
|
||||||
cp_args = cp_args,
|
|
||||||
}
|
|
||||||
|
@ -1,48 +1,51 @@
|
|||||||
local cmd, options = ...
|
local cmd, arg, options, devices = ...
|
||||||
|
|
||||||
local function select_prompt(devs, prompt)
|
local function select_prompt(devs, prompt)
|
||||||
table.sort(devs, function(a, b) return a.path<b.path end)
|
table.sort(devs, function(a, b) return a.path<b.path end)
|
||||||
|
local num_devs = #devs
|
||||||
|
|
||||||
|
if num_devs < 2 then
|
||||||
|
return devs[1]
|
||||||
|
end
|
||||||
|
|
||||||
local choice = devs[1]
|
|
||||||
if #devs > 1 then
|
|
||||||
io.write(prompt,'\n')
|
io.write(prompt,'\n')
|
||||||
|
|
||||||
for i = 1, #devs do
|
for i = 1, num_devs do
|
||||||
local src = devs[i]
|
local src = devs[i]
|
||||||
local label = src.dev.getLabel()
|
local dev = src.dev
|
||||||
if label then
|
local selection_label = src.prop.label or dev.getLabel()
|
||||||
label = label .. " (" .. src.dev.address:sub(1, 8) .. "...)"
|
if selection_label then
|
||||||
|
selection_label = string.format("%s (%s...)", selection_label, dev.address:sub(1, 8))
|
||||||
else
|
else
|
||||||
label = src.dev.address
|
selection_label = dev.address
|
||||||
end
|
end
|
||||||
io.write(i .. ") " .. label .. " at " .. src.path .. '\n')
|
io.write(string.format("%d) %s at %s [r%s]\n", i, selection_label, src.path, dev.isReadOnly() and 'o' or 'w'))
|
||||||
end
|
end
|
||||||
|
|
||||||
io.write("Please enter a number between 1 and " .. #devs .. '\n')
|
io.write("Please enter a number between 1 and " .. num_devs .. '\n')
|
||||||
io.write("Enter 'q' to cancel the installation: ")
|
io.write("Enter 'q' to cancel the installation: ")
|
||||||
choice = nil
|
for _=1,5 do
|
||||||
while not choice do
|
local result = io.read() or "q"
|
||||||
result = io.read() or "q"
|
|
||||||
if result == "q" then
|
if result == "q" then
|
||||||
os.exit()
|
os.exit()
|
||||||
end
|
end
|
||||||
local number = tonumber(result)
|
local number = tonumber(result)
|
||||||
if number and number > 0 and number <= #devs then
|
if number and number > 0 and number <= num_devs then
|
||||||
choice = devs[number]
|
return devs[number]
|
||||||
else
|
else
|
||||||
io.write("Invalid input, please try again: ")
|
io.write("Invalid input, please try again: ")
|
||||||
os.sleep(0)
|
os.sleep(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
print("\ntoo many bad inputs, aborting")
|
||||||
|
os.exit(1)
|
||||||
return choice
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if cmd == 'select' then
|
if cmd == "select" then
|
||||||
if #options.sources == 0 then
|
if arg == "sources" then
|
||||||
if options.source_label then
|
if #devices == 0 then
|
||||||
io.stderr:write("Nothing to install labeled: " .. options.source_label .. '\n')
|
if options.label then
|
||||||
|
io.stderr:write("Nothing to install labeled: " .. options.label .. '\n')
|
||||||
elseif options.from then
|
elseif options.from then
|
||||||
io.stderr:write("Nothing to install from: " .. options.from .. '\n')
|
io.stderr:write("Nothing to install from: " .. options.from .. '\n')
|
||||||
else
|
else
|
||||||
@ -50,8 +53,9 @@ if cmd == 'select' then
|
|||||||
end
|
end
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
return select_prompt(devices, "What do you want to install?")
|
||||||
if #options.targets == 0 then
|
elseif arg == "targets" then
|
||||||
|
if #devices == 0 then
|
||||||
if options.to then
|
if options.to then
|
||||||
io.stderr:write("No such target to install to: " .. options.to .. '\n')
|
io.stderr:write("No such target to install to: " .. options.to .. '\n')
|
||||||
else
|
else
|
||||||
@ -60,32 +64,6 @@ if cmd == 'select' then
|
|||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local source = select_prompt(options.sources, "What do you want to install?")
|
return select_prompt(devices, "Where do you want to install to?")
|
||||||
if #options.sources > 1 and #options.targets > 1 then
|
|
||||||
io.write('\n')
|
|
||||||
end
|
|
||||||
local target = select_prompt(options.targets, "Where do you want to install to?")
|
|
||||||
|
|
||||||
return source, target
|
|
||||||
|
|
||||||
elseif cmd == 'install' then
|
|
||||||
local installer_path = options.source_root .. "/.install"
|
|
||||||
local installer, reason = loadfile(installer_path, "bt", setmetatable({install=
|
|
||||||
{
|
|
||||||
from=options.source_root,
|
|
||||||
to=options.target_root,
|
|
||||||
fromDir=options.source_dir,
|
|
||||||
root=options.target_dir,
|
|
||||||
update=options.update,
|
|
||||||
label=options.label,
|
|
||||||
setlabel=options.setlabel,
|
|
||||||
setboot=options.setboot,
|
|
||||||
reboot=options.reboot,
|
|
||||||
}}, {__index=_G}))
|
|
||||||
if not installer then
|
|
||||||
io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n')
|
|
||||||
os.exit(1)
|
|
||||||
else
|
|
||||||
return installer()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -65,7 +65,7 @@ The following can override settings defined in .prop in the source filesystem.
|
|||||||
.install ENVIRONMENT
|
.install ENVIRONMENT
|
||||||
A loot disc can optionally provide a custom installation script at the root of the source filesytem selected for installation. The script must be named ".install"
|
A loot disc can optionally provide a custom installation script at the root of the source filesytem selected for installation. The script must be named ".install"
|
||||||
When provided, the default install action is replaced by executation of this script. The default action is to copy all source files to the destination
|
When provided, the default install action is replaced by executation of this script. The default action is to copy all source files to the destination
|
||||||
An _ENV.install table is set in the environment of '.install' when loaded
|
A table of configuration options, named `install`, is provided in _ENV
|
||||||
These are the keys and their descriptions of that table
|
These are the keys and their descriptions of that table
|
||||||
|
|
||||||
_ENV.install.from:
|
_ENV.install.from:
|
||||||
@ -76,7 +76,7 @@ The following can override settings defined in .prop in the source filesystem.
|
|||||||
This is the path of the selected target filesystem to install to.
|
This is the path of the selected target filesystem to install to.
|
||||||
example: "/"
|
example: "/"
|
||||||
|
|
||||||
_ENV.install.fromdir
|
_ENV.install.fromDir
|
||||||
This is the relative path to use in the source filesystem as passed by command line to install. If unspecified to install it defaults to "."
|
This is the relative path to use in the source filesystem as passed by command line to install. If unspecified to install it defaults to "."
|
||||||
example: Perhaps the user executed `install --fromDir="bin"` with the intention that only files under /mnt/ABC/bin would be copied to their rootfs
|
example: Perhaps the user executed `install --fromDir="bin"` with the intention that only files under /mnt/ABC/bin would be copied to their rootfs
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user