install and cp fixes (#2398)

testing completed
This commit is contained in:
payonel 2017-05-22 23:10:59 -07:00 committed by GitHub
parent 08bb90faa3
commit f2b5e01730
8 changed files with 245 additions and 272 deletions

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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,
}

View File

@ -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

View File

@ -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