Added -u flag to cp.lua, added -u flag to install.lua. Closes #1092.

Run `/mnt/???/bin/install.lua -u` to update OpenOS more comfortably now, asks for each changed file, to avoid blindly overwriting user-changed files.
Made install.lua a bit more clever, looking for an actual mount point for an address, not blindly hoping that `/mnt/(first three chars of address)` exists. Also made it use the cp.lua on its own file system (since that's needed for updating the first time, e.g.).
This commit is contained in:
Florian Nücke 2015-05-01 10:46:13 +02:00
parent a4214910c4
commit d77bc3d3d7
3 changed files with 82 additions and 33 deletions

View File

@ -7,6 +7,8 @@ if #args < 2 then
io.write(" -i: prompt before overwrite (overrides -n option).\n")
io.write(" -n: do not overwrite an existing file.\n")
io.write(" -r: copy directories recursively.\n")
io.write(" -u: copy only when the SOURCE file differs from the destination\n")
io.write(" file or when the destination file is missing.\n")
io.write(" -v: verbose output.")
return
end
@ -19,7 +21,7 @@ local to = shell.resolve(args[#args])
local function status(from, to)
if options.v then
print(from .. " -> " .. to)
io.write(from .. " -> " .. to .. "\n")
end
os.sleep(0) -- allow interrupting
end
@ -27,9 +29,33 @@ end
local result, reason
local function prompt(message)
io.write(message .. " ")
io.write(message .. " [Y/n]\n")
local result = io.read()
return result and result:sub(1, 1):lower() == "y"
return result and (result == "" or result:sub(1, 1):lower() == "y")
end
local function areEqual(path1, path2)
local f1 = io.open(path1, "rb")
if not f1 then
return nil, "could not open `" .. path1 .. "' for update test"
end
local f2 = io.open(path2, "rb")
if not f2 then
f1:close()
return nil, "could not open `" .. path2 .. "' for update test"
end
local result = true
local chunkSize = 4 * 1024
repeat
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
if s1 ~= s2 then
result = false
break
end
until not s1 or not s2
f1:close()
f2:close()
return result
end
local function recurse(fromPath, toPath)
@ -70,6 +96,11 @@ local function recurse(fromPath, toPath)
return nil, "cannot overwrite directory `" .. toPath .. "' with non-directory"
end
else
if options.u then
if areEqual(fromPath, toPath) then
return true
end
end
if options.i then
if not prompt("overwrite `" .. toPath .. "'?") then
return true

View File

@ -1,22 +1,24 @@
local component = require("component")
local computer = require("computer")
local event = require("event")
local filesystem = require("filesystem")
local unicode = require("unicode")
local shell = require("shell")
local args, options = shell.parse(...)
local fromAddress = options.from and component.get(options.from) or filesystem.get(os.getenv("_")).address
local candidates = {}
for address in component.list("filesystem") do
local dev = component.proxy(address)
if not dev.isReadOnly() and dev.address ~= computer.tmpAddress() then
if not dev.isReadOnly() and dev.address ~= computer.tmpAddress() and dev.address ~= fromAddress then
table.insert(candidates, dev)
end
end
if #candidates == 0 then
print("No writable disks found, aborting.")
return
io.write("No writable disks found, aborting.\n")
os.exit()
end
for i = 1, #candidates do
@ -26,45 +28,53 @@ for i = 1, #candidates do
else
label = candidates[i].address
end
print(i .. ") " .. label)
io.write(i .. ") " .. label .. "\n")
end
print("To select the device to install to, please enter a number between 1 and " .. #candidates .. ".")
print("Press 'q' to cancel the installation.")
io.write("To select the device to install to, please enter a number between 1 and " .. #candidates .. ".\n")
io.write("Press 'q' to cancel the installation.\n")
local choice
while not choice do
result = io.read()
if result:sub(1, 1):lower() == "q" then
return
os.exit()
end
local number = tonumber(result)
if number and number > 0 and number <= #candidates then
choice = candidates[number]
else
print("Invalid input, please try again.")
io.write("Invalid input, please try again.\n")
end
end
local function findMount(address)
for fs, path in filesystem.mounts() do
if fs.address == component.get(address) then
return path
end
end
end
candidates = nil
local name = options.name or "OpenOS"
print("Installing " .. name .." to device " .. (choice.getLabel() or choice.address))
io.write("Installing " .. name .." to device " .. (choice.getLabel() or choice.address) .. "\n")
os.sleep(0.25)
local origin = options.from and options.from:sub(1,3) or computer.getBootAddress():sub(1, 3)
local fromDir = options.fromDir or "/"
local mnt = choice.address:sub(1, 3)
local result, reason = os.execute("/bin/cp -vr /mnt/" .. origin .. fromDir .. "* /mnt/" .. mnt .. "/")
local cpPath = filesystem.concat(findMount(filesystem.get(os.getenv("_")).address), "bin/cp")
local cpOptions = "-vr" .. (options.u and "ui " or "")
local cpSource = filesystem.concat(findMount(fromAddress), options.fromDir or "/", "*")
local cpDest = findMount(choice.address) .. "/"
local result, reason = os.execute(cpPath .. " " .. cpOptions .. " " .. cpSource .. " " .. cpDest)
if not result then
error(reason, 0)
end
if not options.nolabelset then pcall(choice.setLabel, name) end
if not options.noreboot then
print("All done! " .. ((not options.noboot) and "Set as boot device and r" or "R") .. "eboot now? [Y/n]")
io.write("All done! " .. ((not options.noboot) and "Set as boot device and r" or "R") .. "eboot now? [Y/n]\n")
local result = io.read()
if not result or result == "" or result:sub(1, 1):lower() == "y" then
if not options.noboot then computer.setBootAddress(choice.address)end
print("\nRebooting now!")
io.write("\nRebooting now!\n")
computer.shutdown(true)
end
end
print("Returning to shell.")
io.write("Returning to shell.\n")

View File

@ -17,6 +17,9 @@ function memoryStream:close()
end
function memoryStream:seek()
if self.closed then
error("attempt to use a closed stream")
end
return nil, "bad file descriptor"
end
@ -24,8 +27,9 @@ function memoryStream:read(n)
if self.closed then
if self.buffer == "" and self.redirect.read then
return self.redirect.read:read(n)
else
error("attempt to use a closed stream")
end
return nil -- eof
end
if self.buffer == "" then
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
@ -36,19 +40,24 @@ function memoryStream:read(n)
end
function memoryStream:write(value)
local ok
if not self.redirect.write and self.closed then
error("attempt to use a closed stream")
end
if self.redirect.write then
ok = self.redirect.write:write(value)
self.redirect.write:write(value)
end
if not self.closed then
self.buffer = self.buffer .. value
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
ok = true
if coroutine.status(self.next) == "dead" then
self:close()
end
if not self.result[1] then
error(self.result[2], 0)
end
table.remove(self.result, 1)
end
if ok then
return true
end
return nil, "stream is closed"
return true
end
function memoryStream.new()
@ -328,16 +337,15 @@ local function execute(env, command, ...)
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
if coroutine.status(threads[i]) ~= "dead" then
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
elseif not args[1] then
args[2] = debug.traceback(threads[i], args[2])
end
end
if pipes[i] then
pipes[i]:close()
pcall(pipes[i].close, pipes[i])
end
if not result[1] then
if type(result[2]) == "table" and result[2].reason == "terminated" then
if result[2].code then
result[1] = true
result.n = 1
else
result[2] = "terminated"
@ -473,7 +481,7 @@ if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then
term.write("\n")
end
if not result then
io.stderr:write((tostring(reason) or "unknown error").. "\n")
io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n")
end
end
end
@ -491,7 +499,7 @@ elseif #args == 0 and (io.input() ~= io.stdin) then
elseif command ~= "" then
local result, reason = os.execute(command)
if not result then
io.stderr:write((tostring(reason) or "unknown error").. "\n")
io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n")
end
end
end