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 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={} local comps=require("component").list("filesystem") local devices={} 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 not target_filter and address~=tmpAddress then table.insert(targets,{dev=dev,path=install_path,specified=specified}) end end local target=targets[1] 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=install_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 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 for index,entry in ipairs(targets)do if entry.dev==source.dev then table.remove(targets,index) target=targets[1] end end 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