local computer = require("computer") local shell = require("shell") local fs = require("filesystem") local args, options = shell.parse(...) if options.help then io.write([[Usage: install [OPTION]... --from=ADDR install filesystem at ADDR default: builds list of candidates and prompts user --to=ADDR same as --from but for target --fromDir=PATH install PATH from source --root=PATH same as --fromDir but target --toDir=PATH same as --root -u, --update update files interactively --label override label from .prop --nosetlabel do not label target --nosetboot do not use target for boot --noreboot do not reboot after install ]]) return nil -- exit success end local utils_path = "/lib/core/install_utils.lua" local utils local rootfs = fs.get("/") if not rootfs then io.stderr:write("no root filesystem, aborting\n") os.exit(1) end local label = args[1] options.label = label local source_filter = options.from local source_filter_dev if source_filter then local from_path = shell.resolve(source_filter) if fs.isDirectory(from_path) then source_filter_dev = fs.get(from_path) source_filter = source_filter_dev.address options.from = from_path end end local target_filter = options.to local target_filter_dev if target_filter then local to_path = shell.resolve(target_filter) if fs.isDirectory(target_filter) then target_filter_dev = fs.get(to_path) target_filter = target_filter_dev.address options.to = to_path end end local sources = {} local targets = {} -- tmpfs is not a candidate unless it is specified local comps = require("component").list("filesystem") local devices = {} -- not all mounts are components, only use components for dev, path in fs.mounts() do if comps[dev.address] then local known = devices[dev] devices[dev] = known and #known < #path and known or path end end local dev_dev = fs.get("/dev") devices[dev_dev == rootfs or dev_dev] = nil local tmpAddress = computer.tmpAddress() for dev, path in pairs(devices) do 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 dev.isReadOnly() then if specified then io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n") os.exit(1) end elseif specified or not (source_filter and address:find(source_filter, 1, true) == 1) and -- specified for source not target_filter and address ~= tmpAddress then table.insert(targets, {dev = dev, path = install_path, specified = specified}) end end local target = targets[1] -- if there is only 1 target, the source selection cannot include it if #targets == 1 then devices[targets[1].dev] = nil end for dev, path in pairs(devices) do 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.maxinteger or 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 -- Ask the user to select a source local source = sources[1] if #sources ~= 1 then utils = loadfile(utils_path, "bt", _G) source = utils("select", "sources", options, sources) end if not source then return end options = { from = source.path .. "/", fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""), root = fs.canonical(options.root or options.toDir or source.prop.root or ""), update = options.update or options.u, label = source.prop.label or label, 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 source_display = options.label or source.dev.getLabel() or source.path -- Remove the source from the target options for index, entry in ipairs(targets) do if entry.dev == source.dev then table.remove(targets, index) target = targets[1] end end -- Ask the user to select a target if #targets ~= 1 then if #sources == 1 then io.write(source_display, " selected for install\n") end utils = utils or loadfile(utils_path, "bt", _G) target = utils("select", "targets", options, targets) end if not target then return end options.to = target.path .. "/" local function resolveFrom(path) return fs.concat(options.from, options.fromDir) .. "/" .. path end local fullTargetPath = fs.concat(options.to, options.root) local transfer_args = { { {resolveFrom("."), fullTargetPath}, { cmd = "cp", r = true, v = true, x = true, u = options.update, i = options.update, skip = {resolveFrom(".prop")}, } } } if source.prop.noclobber and #source.prop.noclobber > 0 then local noclobber_opts = {cmd = "cp", v = true, n = true} for _, noclobber in ipairs(source.prop.noclobber or {}) do local noclobberFrom = resolveFrom(noclobber) local noclobberTo = fs.concat(fullTargetPath, noclobber) table.insert(transfer_args[1][2].skip, noclobberFrom) table.insert(transfer_args, {{noclobberFrom, noclobberTo}, noclobber_opts}) end end local special_target = "" if #targets > 1 or target_filter or source_filter then special_target = " to " .. transfer_args[1][1][2] end io.write("Install " .. source_display .. special_target .. "? [Y/n] ") if not ((io.read() or "n") .. "y"):match("^%s*[Yy]") then io.write("Installation cancelled\n") os.exit() end local installer_path = options.from .. "/.install" if fs.exists(installer_path) then 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 options.cp_args = transfer_args options.target = target return options