Skip to content

Commit

Permalink
Implemented error handling: removal of half-written installs and roll…
Browse files Browse the repository at this point in the history
…back of

 existing files.
  • Loading branch information
icculus committed May 7, 2007
1 parent 2bb869d commit 322d3a8
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 54 deletions.
26 changes: 26 additions & 0 deletions lua_glue.c
Expand Up @@ -300,6 +300,31 @@ void MojoLua_debugger(void)
} // MojoLua_debugger


boolean MojoLua_callProcedure(const char *funcname)
{
boolean called = false;
lua_State *L = luaState;
int popcount = 0;

if (L != NULL)
{
lua_getglobal(L, MOJOSETUP_NAMESPACE); popcount++;
if (lua_istable(L, -1)) // namespace is sane?
{
lua_getfield(L, -1, funcname); popcount++;
if (lua_isfunction(L, -1))
{
lua_call(L, 0, 0);
called = true;
} // if
} // if
lua_pop(L, popcount);
} // if

return called;
} // MojoLua_callProcedure


boolean MojoLua_runFile(const char *name)
{
MojoArchive *ar = GBaseArchive; // in case we want to generalize later.
Expand Down Expand Up @@ -426,6 +451,7 @@ const char *translate(const char *str)
// Use this instead of Lua's error() function if you don't have a
// programatic error, so you don't get stack callback stuff:
// MojoSetup.fatal("You need the base game to install this expansion pack.")
// This will also handle cleanup of half-written installations.
// Doesn't actually return.
static int luahook_fatal(lua_State *L)
{
Expand Down
8 changes: 8 additions & 0 deletions lua_glue.h
Expand Up @@ -28,6 +28,14 @@ boolean MojoLua_initialized(void);
// chunk, as it will call fatal() instead.
boolean MojoLua_runFile(const char *fname);

// Call a function in Lua. This calls MojoSetup.funcname, if it exists and
// is a function. It will not pass any parameters and it will not return
// any values. The call is made unprotected, so if Lua triggers an error,
// this C function will not return. Don't use this if you don't know what
// you're doing.
// Returns true if function was called, false otherwise.
boolean MojoLua_callProcedure(const char *funcname);

// Set a Lua variable in the MojoSetup namespace to a string:
// MojoLua_setString("bob", "name");
// in Lua: print(MojoSetup.name) -- outputs: bob
Expand Down
1 change: 1 addition & 0 deletions mojosetup.c
Expand Up @@ -350,6 +350,7 @@ int fatal(const char *fmt, ...)

//GGui->status(_("There were errors. Click 'OK' to clean up and exit."));
//MojoLua_runFunction("errorcleanup");
MojoLua_callProcedure("revertinstall");

deinitEverything();
exit(23);
Expand Down
4 changes: 2 additions & 2 deletions platform.h
Expand Up @@ -22,8 +22,8 @@ uint32 MojoPlatform_ticks(void);
// avoid calling this.
void MojoPlatform_die(void);

// Delete a file from the physical filesystem. Returns true on success, false
// on failure.
// Delete a file from the physical filesystem. This should remove empty
// directories as well as files. Returns true on success, false on failure.
boolean MojoPlatform_unlink(const char *fname);

// Resolve symlinks, relative paths, etc. Caller free()'s buffer. Returns
Expand Down
11 changes: 10 additions & 1 deletion platform/unix.c
Expand Up @@ -447,7 +447,16 @@ void MojoPlatform_die(void)

boolean MojoPlatform_unlink(const char *fname)
{
return (unlink(fname) == 0);
boolean retval = false;
struct stat statbuf;
if (stat(fname, &statbuf) != -1)
{
if (S_ISDIR(statbuf.st_mode))
retval = (rmdir(fname) == 0);
else
retval = (unlink(fname) == 0);
} // if
return retval;
} // MojoPlatform_unlink


Expand Down
13 changes: 0 additions & 13 deletions scripts/mojosetup_init.lua
Expand Up @@ -37,19 +37,6 @@ function MojoSetup.spliturl(url)
end


-- This gets called by fatal()...
function MojoSetup.shutdown ()
if MojoSetup.installed_files ~= nil then
for k,v in ipairs(MojoSetup.installed_files) do
MojoSetup.loginfo("Deleting " .. v)
-- unlink(v)
end
--- !!! FIXME: delete downloads.
--- !!! FIXME: do rollbacks.
end
end


-- This is handy for debugging.
function MojoSetup.dumptable(tabname, tab, depth)
if depth == nil then -- first call, before any recursion?
Expand Down
141 changes: 103 additions & 38 deletions scripts/mojosetup_mainline.lua
Expand Up @@ -7,9 +7,75 @@
-- This is just for convenience.
local _ = MojoSetup.translate

-- This dumps the table built from the user's config script using logdebug,
-- so it only spits out crap if debug-level logging is enabled.
MojoSetup.dumptable("MojoSetup.installs", MojoSetup.installs)
local function do_delete(fname)
local retval = false
if fname == nil then
retval = true
else
if MojoSetup.platform.exists(fname) then
if MojoSetup.platform.unlink(fname) then
MojoSetup.loginfo("Deleted '" .. fname .. "'")
retval = true
end
end
end
return retval
end

local function delete_files(filelist, callback, error_is_fatal)
if filelist ~= nil then
local max = #filelist
for i = max,1,-1 do
local fname = filelist[i]
if not do_delete(fname) and error_is_fatal then
-- !!! FIXME: formatting
MojoSetup.fatal(_("Deletion failed!"))
end

if callback ~= nil then
callback(i, max)
end
end
end
end

local function delete_scratchdirs()
do_delete(MojoSetup.downloaddir)
do_delete(MojoSetup.rollbackdir)
do_delete(MojoSetup.scratchdir) -- must be last!
end


local function do_rollbacks()
if MojoSetup.rollbacks == nil then
return
end

local max = #MojoSetup.rollbacks
for id = max,1,-1 do
local src = MojoSetup.rollbackdir .. "/" .. id
local dest = MojoSetup.rollbacks[id]
if not MojoSetup.movefile(src, dest) then
-- we're already in fatal(), so we can only throw up a msgbox...
-- !!! FIXME: formatting
MojoSetup.msgbox(_("Serious problem"),
_("Couldn't restore some files. Your existing installation is likely damaged."))
end
MojoSetup.loginfo("Restored rollback #" .. id .. ": '" .. src .. "' -> '" .. dest .. "'")
end
end


-- This gets called by fatal()...must be a global function.
function MojoSetup.revertinstall()
MojoSetup.loginfo("Cleaning up half-finished installation...");

delete_files(MojoSetup.downloads)
delete_files(MojoSetup.installed_files)
do_rollbacks()
delete_scratchdirs();
end


local function calc_percent(current, total)
if total == 0 then
Expand Down Expand Up @@ -90,22 +156,6 @@ local function drill_for_archive(archive, path, arclist)
end


local function delete_files(filelist, callback, error_is_fatal)
local max = #filelist
for i,v in ipairs(filelist) do
if not MojoSetup.platform.unlink(v) and error_is_fatal then
-- !!! FIXME: formatting
MojoSetup.fatal(_("Deletion failed!"))
end
MojoSetup.loginfo("Deleted '" .. v .. "'")

if callback ~= nil then
callback(i, max)
end
end
end


local function install_file(path, archive, file, option)
-- Upvalued so we don't look these up each time...
local fname = string.gsub(path, "^.*/", "", 1) -- chop the dirs off...
Expand All @@ -119,32 +169,32 @@ local function install_file(path, archive, file, option)
return MojoSetup.gui.progress(ptype, component, percent, item)
end

MojoSetup.installed_files[#MojoSetup.installed_files+1] = path
if not MojoSetup.writefile(archive, path, callback) then
-- !!! FIXME: formatting!
MojoSetup.fatal(_("file creation failed!"))
end
MojoSetup.loginfo("Created file '" .. path .. "'")
MojoSetup.installed_files[#MojoSetup.installed_files+1] = path
end


local function install_symlink(path, lndest)
MojoSetup.installed_files[#MojoSetup.installed_files+1] = path
if not MojoSetup.platform.symlink(path, lndest) then
-- !!! FIXME: formatting!
MojoSetup.fatal(_("symlink creation failed!"))
end
MojoSetup.loginfo("Created symlink '" .. path .. "' -> '" .. lndest .. "'")
MojoSetup.installed_files[#MojoSetup.installed_files+1] = path
end


local function install_directory(path)
MojoSetup.installed_files[#MojoSetup.installed_files+1] = path
if not MojoSetup.platform.mkdir(path) then
-- !!! FIXME: formatting
MojoSetup.fatal(_("mkdir failed"))
end
MojoSetup.loginfo("Created directory '" .. path .. "'")
MojoSetup.installed_files[#MojoSetup.installed_files+1] = path
end


Expand Down Expand Up @@ -181,16 +231,14 @@ local function permit_write(dest, entinfo, file)

if allowoverwrite then
local id = #MojoSetup.rollbacks + 1
-- !!! FIXME: dirname may clash.
local f = MojoSetup.destination .. "/.mojosetup_tmp/rollback"
f = f .. "/" .. id
local f = MojoSetup.rollbackdir .. "/" .. id
-- !!! FIXME: don't add (f) to the installed_files table...
install_parent_dirs(f)
MojoSetup.rollbacks[id] = dest
if not MojoSetup.movefile(dest, f) then
-- !!! FIXME: formatting
MojoSetup.fatal(_("Couldn't backup file for rollback"))
end
MojoSetup.rollbacks[id] = dest
MojoSetup.loginfo("Moved rollback #" .. id .. ": '" .. dest .. "' -> '" .. f .. "'")
end
end
Expand Down Expand Up @@ -357,6 +405,16 @@ local function install_basepath(basepath, file, option)
end


local function set_destination(dest)
-- !!! FIXME: ".mojosetup_tmp" dirname may clash with install...?
MojoSetup.loginfo("Install dest: '" .. dest .. "'")
MojoSetup.destination = dest
MojoSetup.scratchdir = MojoSetup.destination .. "/.mojosetup_tmp"
MojoSetup.rollbackdir = MojoSetup.scratchdir .. "/rollbacks"
MojoSetup.downloaddir = MojoSetup.scratchdir .. "/downloads"
end


local function do_install(install)
MojoSetup.written = 0
MojoSetup.totalwrite = 0
Expand Down Expand Up @@ -433,9 +491,8 @@ local function do_install(install)
-- The config file can force a destination if it has a really good reason
-- (system drivers that have to go in a specific place, for example),
-- but really really shouldn't in 99% of the cases.
MojoSetup.destination = install.destination
if MojoSetup.destination ~= nil then
MojoSetup.loginfo("Install dest: '" .. install.destination .. "'")
if install.destination ~= nil then
set_destination(install.destination)
else
local recommend = install.recommended_destinations
-- (recommend) becomes an upvalue in this function.
Expand All @@ -444,8 +501,7 @@ local function do_install(install)
if x == nil then
return false -- go back
end
MojoSetup.destination = x
MojoSetup.loginfo("Install dest: '" .. x .. "'")
set_destination(x)
return true
end
end
Expand Down Expand Up @@ -544,8 +600,7 @@ local function do_install(install)
-- !!! FIXME: id will chop filename extension
local id = 0
for file,option in pairs(MojoSetup.files.downloads) do
local f = MojoSetup.destination .. "/.mojosetup_tmp/downloads"
f = f .. "/" .. id
local f = MojoSetup.downloaddir .. "/" .. id
-- !!! FIXME: don't add (f) to the installed_files table...
install_parent_dirs(f)
id = id + 1
Expand Down Expand Up @@ -616,8 +671,7 @@ local function do_install(install)
if MojoSetup.files.downloads ~= nil then
local id = 0
for file,option in pairs(MojoSetup.files.downloads) do
local f = MojoSetup.destination .. "/.mojosetup_tmp/downloads"
f = f .. "/" .. id
local f = MojoSetup.downloaddir .. "/" .. id
id = id + 1
install_basepath(f, file, option)
end
Expand Down Expand Up @@ -686,8 +740,7 @@ MojoSetup.crash();
-- Successful install, so delete conflicts we no longer need to rollback.
delete_files(MojoSetup.rollbacks)
delete_files(MojoSetup.downloads)

-- !!! FIXME: nuke scratch files (downloaded files, rollbacks, etc)...
delete_scratchdirs()

-- Don't let future errors delete files from successful installs...
MojoSetup.installed_files = nil
Expand All @@ -697,16 +750,28 @@ MojoSetup.crash();
MojoSetup.gui.stop()

-- Done with these things. Make them eligible for garbage collection.
MojoSetup.destination = nil
MojoSetup.scratchdir = nil
MojoSetup.rollbackdir = nil
MojoSetup.downloaddir = nil
MojoSetup.stages = nil
MojoSetup.files = nil
MojoSetup.media = nil
MojoSetup.destination = nil
MojoSetup.written = 0
MojoSetup.totalwrite = 0
MojoSetup.downloaded = 0
MojoSetup.totaldownload = 0
end



-- Mainline.


-- This dumps the table built from the user's config script using logdebug,
-- so it only spits out crap if debug-level logging is enabled.
MojoSetup.dumptable("MojoSetup.installs", MojoSetup.installs)

-- !!! FIXME: is MojoSetup.installs just nil? Should we lose saw_an_installer?
local saw_an_installer = false
for installkey,install in pairs(MojoSetup.installs) do
Expand Down

0 comments on commit 322d3a8

Please sign in to comment.