Display a more helpful message to the user if an update cannot be installed because the application was run from a read-only file system.

On Mac, this can happen if the user runs the app directly from a disk image.  On all platforms,
this can happen if the app is run from any kind of read-only media or network share.

 * Intercept IO errors during installation and try to find a more friendly alternative
   to the OS error string.

 * Catch any IO exceptions throw whilst reverting a partial update and log details.
This commit is contained in:
Robert Knight 2011-09-15 14:45:53 +01:00
parent 972811de23
commit 1bad51e3dc
4 changed files with 96 additions and 7 deletions

View File

@ -24,28 +24,57 @@
#endif
FileUtils::IOException::IOException(const std::string& error)
: m_errno(0)
{
init(errno,error);
}
FileUtils::IOException::IOException(int errorCode, const std::string& error)
{
init(errorCode,error);
}
void FileUtils::IOException::init(int errorCode, const std::string& error)
{
m_error = error;
#ifdef PLATFORM_UNIX
m_errno = errno;
m_errorCode = errorCode;
if (m_errno > 0)
if (m_errorCode > 0)
{
m_error += " details: " + std::string(strerror(m_errno));
m_error += " details: " + std::string(strerror(m_errorCode));
}
#endif
#ifdef PLATFORM_WINDOWS
m_errorCode = 0;
m_error += " GetLastError returned: " + intToStr(GetLastError());
#endif
LOG(Info,"created IOException with errno " + intToStr(m_errorCode) + " rofs " + intToStr(EROFS));
}
FileUtils::IOException::~IOException() throw ()
{
}
FileUtils::IOException::Type FileUtils::IOException::type() const
{
#ifdef PLATFORM_UNIX
switch (m_errorCode)
{
case 0:
return NoError;
case EROFS:
return ReadOnlyFileSystem;
default:
return Unknown;
}
#else
return Unknown;
#endif
}
bool FileUtils::fileExists(const char* path) throw (IOException)
{
#ifdef PLATFORM_UNIX

View File

@ -21,17 +21,32 @@ class FileUtils
{
public:
IOException(const std::string& error);
IOException(int errno, const std::string& error);
virtual ~IOException() throw ();
enum Type
{
NoError,
/** Unknown error type. Call what() to get the description
* provided by the OS.
*/
Unknown,
ReadOnlyFileSystem
};
virtual const char* what() const throw ()
{
return m_error.c_str();
}
Type type() const;
private:
void init(int errorCode, const std::string& error);
std::string m_error;
int m_errno;
int m_errorCode;
};
/** Remove a file. Throws an exception if the file

View File

@ -71,6 +71,29 @@ void UpdateInstaller::reportError(const std::string& error)
}
}
std::string UpdateInstaller::friendlyErrorForError(const FileUtils::IOException& exception) const
{
std::string friendlyError;
switch (exception.type())
{
case FileUtils::IOException::ReadOnlyFileSystem:
#ifdef PLATFORM_MAC
friendlyError = AppInfo::appName() + " was started from a read-only location. "
"Copy it to the Applications folder on your Mac and run "
"it from there.";
#else
friendlyError = AppInfo::appName() + " was started from a read-only location. "
"Re-install it to a location that can be updated and run it from there.";
#endif
break;
default:
break;
}
return friendlyError;
}
void UpdateInstaller::run() throw ()
{
if (!m_script || !m_script->isValid())
@ -144,7 +167,13 @@ void UpdateInstaller::run() throw ()
{
LOG(Info,"Starting update installation");
// the detailed error string returned by the OS
std::string error;
// the message to present to the user. This may be the same
// as 'error' or may be different if a more helpful suggestion
// can be made for a particular problem
std::string friendlyError;
try
{
LOG(Info,"Installing new and updated files");
@ -161,6 +190,7 @@ void UpdateInstaller::run() throw ()
catch (const FileUtils::IOException& exception)
{
error = exception.what();
friendlyError = friendlyErrorForError(exception);
}
catch (const std::string& genericError)
{
@ -170,10 +200,23 @@ void UpdateInstaller::run() throw ()
if (!error.empty())
{
LOG(Error,std::string("Error installing update ") + error);
revert();
try
{
revert();
}
catch (const FileUtils::IOException& exception)
{
LOG(Error,"Error reverting partial update " + std::string(exception.what()));
}
if (m_observer)
{
m_observer->updateError(error);
if (friendlyError.empty())
{
friendlyError = error;
}
m_observer->updateError(friendlyError);
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "Platform.h"
#include "FileUtils.h"
#include "UpdateScript.h"
#include <list>
@ -52,6 +53,7 @@ class UpdateInstaller
void postInstallUpdate();
std::list<std::string> updaterArgs() const;
std::string friendlyErrorForError(const FileUtils::IOException& ex) const;
Mode m_mode;
std::string m_installDir;