1573: Handle FS failures while updating (fixes #1560) r=heinrich5991 a=def-

Not sure if I caught all locations

Co-authored-by: def <dennis@felsin9.de>
This commit is contained in:
bors[bot] 2019-04-07 18:50:08 +00:00
commit 52c00dcc02
4 changed files with 72 additions and 45 deletions

View file

@ -83,17 +83,25 @@ CRequest::CRequest(const char *pUrl, bool CanTimeout) :
void CRequest::Run() void CRequest::Run()
{ {
if(BeforeInit()) if(!BeforeInit())
{ {
m_State = HTTP_ERROR; m_State = HTTP_ERROR;
OnCompletion();
return; return;
} }
CURL *pHandle = curl_easy_init(); CURL *pHandle = curl_easy_init();
m_State = RunImpl(pHandle);
curl_easy_cleanup(pHandle);
OnCompletion();
}
int CRequest::RunImpl(CURL *pHandle)
{
if(!pHandle) if(!pHandle)
{ {
m_State = HTTP_ERROR; return HTTP_ERROR;
return;
} }
if(g_Config.m_DbgCurl) if(g_Config.m_DbgCurl)
@ -142,31 +150,28 @@ void CRequest::Run()
curl_easy_setopt(pHandle, CURLOPT_PROGRESSDATA, this); curl_easy_setopt(pHandle, CURLOPT_PROGRESSDATA, this);
curl_easy_setopt(pHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback); curl_easy_setopt(pHandle, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
if(AfterInit(pHandle)) if(!AfterInit(pHandle))
{ {
curl_easy_cleanup(pHandle); return HTTP_ERROR;
m_State = HTTP_ERROR;
return;
} }
dbg_msg("http", "http %s", m_aUrl); dbg_msg("http", "http %s", m_aUrl);
m_State = HTTP_RUNNING; m_State = HTTP_RUNNING;
int Ret = curl_easy_perform(pHandle); int Ret = curl_easy_perform(pHandle);
BeforeCompletion(); if(!BeforeCompletion())
{
return HTTP_ERROR;
}
if(Ret != CURLE_OK) if(Ret != CURLE_OK)
{ {
dbg_msg("http", "task failed. libcurl error: %s", aErr); dbg_msg("http", "task failed. libcurl error: %s", aErr);
m_State = (Ret == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR; return (Ret == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR;
} }
else else
{ {
dbg_msg("http", "task done %s", m_aUrl); dbg_msg("http", "task done %s", m_aUrl);
m_State = HTTP_DONE; return HTTP_DONE;
} }
curl_easy_cleanup(pHandle);
OnCompletion();
} }
size_t CRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pUser) size_t CRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pUser)
@ -265,7 +270,7 @@ CGetFile::CGetFile(IStorage *pStorage, const char *pUrl, const char *pDest, int
str_copy(m_aDest, pDest, sizeof(m_aDest)); str_copy(m_aDest, pDest, sizeof(m_aDest));
} }
int CGetFile::BeforeInit() bool CGetFile::BeforeInit()
{ {
char aPath[512]; char aPath[512];
if(m_StorageType == -2) if(m_StorageType == -2)
@ -274,15 +279,18 @@ int CGetFile::BeforeInit()
m_pStorage->GetCompletePath(m_StorageType, m_aDest, aPath, sizeof(aPath)); m_pStorage->GetCompletePath(m_StorageType, m_aDest, aPath, sizeof(aPath));
if(fs_makedir_rec_for(aPath) < 0) if(fs_makedir_rec_for(aPath) < 0)
{
dbg_msg("http", "i/o error, cannot create folder for: %s", aPath); dbg_msg("http", "i/o error, cannot create folder for: %s", aPath);
return false;
}
m_File = io_open(aPath, IOFLAG_WRITE); m_File = io_open(aPath, IOFLAG_WRITE);
if(!m_File) if(!m_File)
{ {
dbg_msg("http", "i/o error, cannot open file: %s", m_aDest); dbg_msg("http", "i/o error, cannot open file: %s", m_aDest);
return 1; return false;
} }
return 0; return true;
} }
size_t CGetFile::OnData(char *pData, size_t DataSize) size_t CGetFile::OnData(char *pData, size_t DataSize)
@ -290,9 +298,9 @@ size_t CGetFile::OnData(char *pData, size_t DataSize)
return io_write(m_File, pData, DataSize); return io_write(m_File, pData, DataSize);
} }
void CGetFile::BeforeCompletion() bool CGetFile::BeforeCompletion()
{ {
io_close(m_File); return io_close(m_File) == 0;
} }
CPostJson::CPostJson(const char *pUrl, bool CanTimeout, const char *pJson) CPostJson::CPostJson(const char *pUrl, bool CanTimeout, const char *pJson)
@ -301,7 +309,7 @@ CPostJson::CPostJson(const char *pUrl, bool CanTimeout, const char *pJson)
str_copy(m_aJson, pJson, sizeof(m_aJson)); str_copy(m_aJson, pJson, sizeof(m_aJson));
} }
int CPostJson::AfterInit(void *pCurl) bool CPostJson::AfterInit(void *pCurl)
{ {
CURL *pHandle = (CURL *)pCurl; CURL *pHandle = (CURL *)pCurl;
@ -310,5 +318,5 @@ int CPostJson::AfterInit(void *pCurl)
curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, pHeaders); curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, pHeaders);
curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_aJson); curl_easy_setopt(pHandle, CURLOPT_POSTFIELDS, m_aJson);
return 0; return true;
} }

View file

@ -6,6 +6,7 @@
#include <engine/kernel.h> #include <engine/kernel.h>
typedef struct _json_value json_value; typedef struct _json_value json_value;
typedef void CURL;
enum enum
{ {
@ -21,12 +22,12 @@ class CRequest : public IJob
// Abort the request with an error if `BeforeInit()` or `AfterInit()` // Abort the request with an error if `BeforeInit()` or `AfterInit()`
// returns something nonzero. Also abort the request if `OnData()` // returns something nonzero. Also abort the request if `OnData()`
// returns something other than `DataSize`. // returns something other than `DataSize`.
virtual int BeforeInit() { return 0; } virtual bool BeforeInit() { return true; }
virtual int AfterInit(void *pCurl) { return 0; } virtual bool AfterInit(void *pCurl) { return true; }
virtual size_t OnData(char *pData, size_t DataSize) = 0; virtual size_t OnData(char *pData, size_t DataSize) = 0;
virtual void OnProgress() { } virtual void OnProgress() { }
virtual void BeforeCompletion() { } virtual bool BeforeCompletion() { return true; }
virtual void OnCompletion() { } virtual void OnCompletion() { }
char m_aUrl[256]; char m_aUrl[256];
@ -43,6 +44,7 @@ class CRequest : public IJob
static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser); static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser);
void Run(); void Run();
int RunImpl(CURL *pHandle);
public: public:
CRequest(const char *pUrl, bool CanTimeout); CRequest(const char *pUrl, bool CanTimeout);
@ -75,8 +77,8 @@ public:
class CGetFile : public CRequest class CGetFile : public CRequest
{ {
virtual size_t OnData(char *pData, size_t DataSize); virtual size_t OnData(char *pData, size_t DataSize);
virtual int BeforeInit(); virtual bool BeforeInit();
virtual void BeforeCompletion(); virtual bool BeforeCompletion();
IStorage *m_pStorage; IStorage *m_pStorage;
@ -94,7 +96,7 @@ public:
class CPostJson : public CRequest class CPostJson : public CRequest
{ {
virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; } virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; }
virtual int AfterInit(void *pCurl); virtual bool AfterInit(void *pCurl);
char m_aJson[1024]; char m_aJson[1024];

View file

@ -135,23 +135,26 @@ void CUpdater::FetchFile(const char *pFile, const char *pDestPath)
m_pEngine->AddJob(std::make_shared<CUpdaterFetchTask>(this, pFile, pDestPath)); m_pEngine->AddJob(std::make_shared<CUpdaterFetchTask>(this, pFile, pDestPath));
} }
void CUpdater::MoveFile(const char *pFile) bool CUpdater::MoveFile(const char *pFile)
{ {
char aBuf[256]; char aBuf[256];
size_t len = str_length(pFile); size_t len = str_length(pFile);
bool Success = true;
if(!str_comp_nocase(pFile + len - 4, ".dll") || !str_comp_nocase(pFile + len - 4, ".ttf")) if(!str_comp_nocase(pFile + len - 4, ".dll") || !str_comp_nocase(pFile + len - 4, ".ttf"))
{ {
str_format(aBuf, sizeof(aBuf), "%s.old", pFile); str_format(aBuf, sizeof(aBuf), "%s.old", pFile);
m_pStorage->RenameBinaryFile(pFile, aBuf); Success &= m_pStorage->RenameBinaryFile(pFile, aBuf);
str_format(aBuf, sizeof(aBuf), "update/%s", pFile); str_format(aBuf, sizeof(aBuf), "update/%s", pFile);
m_pStorage->RenameBinaryFile(aBuf, pFile); Success &= m_pStorage->RenameBinaryFile(aBuf, pFile);
} }
else else
{ {
str_format(aBuf, sizeof(aBuf), "update/%s", pFile); str_format(aBuf, sizeof(aBuf), "update/%s", pFile);
m_pStorage->RenameBinaryFile(aBuf, pFile); Success &= m_pStorage->RenameBinaryFile(aBuf, pFile);
} }
return Success;
} }
void CUpdater::Update() void CUpdater::Update()
@ -174,43 +177,53 @@ void CUpdater::AddFileJob(const char *pFile, bool Job)
m_FileJobs[string(pFile)] = Job; m_FileJobs[string(pFile)] = Job;
} }
void CUpdater::ReplaceClient() bool CUpdater::ReplaceClient()
{ {
dbg_msg("updater", "replacing " PLAT_CLIENT_EXEC); dbg_msg("updater", "replacing " PLAT_CLIENT_EXEC);
bool Success = true;
// Replace running executable by renaming twice... // Replace running executable by renaming twice...
if(!m_IsWinXP) if(!m_IsWinXP)
{ {
m_pStorage->RemoveBinaryFile("DDNet.old"); m_pStorage->RemoveBinaryFile("DDNet.old");
m_pStorage->RenameBinaryFile(PLAT_CLIENT_EXEC, "DDNet.old"); Success &= m_pStorage->RenameBinaryFile(PLAT_CLIENT_EXEC, "DDNet.old");
m_pStorage->RenameBinaryFile("update/DDNet.tmp", PLAT_CLIENT_EXEC); Success &= m_pStorage->RenameBinaryFile("update/DDNet.tmp", PLAT_CLIENT_EXEC);
} }
#if !defined(CONF_FAMILY_WINDOWS) #if !defined(CONF_FAMILY_WINDOWS)
char aPath[512]; char aPath[512];
m_pStorage->GetBinaryPath(PLAT_CLIENT_EXEC, aPath, sizeof aPath); m_pStorage->GetBinaryPath(PLAT_CLIENT_EXEC, aPath, sizeof aPath);
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath); str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath);
if (system(aBuf)) if(system(aBuf))
{
dbg_msg("updater", "ERROR: failed to set client executable bit"); dbg_msg("updater", "ERROR: failed to set client executable bit");
Success &= false;
}
#endif #endif
return Success;
} }
void CUpdater::ReplaceServer() bool CUpdater::ReplaceServer()
{ {
dbg_msg("updater", "replacing " PLAT_SERVER_EXEC); dbg_msg("updater", "replacing " PLAT_SERVER_EXEC);
bool Success = true;
//Replace running executable by renaming twice... //Replace running executable by renaming twice...
m_pStorage->RemoveBinaryFile("DDNet-Server.old"); m_pStorage->RemoveBinaryFile("DDNet-Server.old");
m_pStorage->RenameBinaryFile(PLAT_SERVER_EXEC, "DDNet-Server.old"); Success &= m_pStorage->RenameBinaryFile(PLAT_SERVER_EXEC, "DDNet-Server.old");
m_pStorage->RenameBinaryFile("update/DDNet-Server.tmp", PLAT_SERVER_EXEC); Success &= m_pStorage->RenameBinaryFile("update/DDNet-Server.tmp", PLAT_SERVER_EXEC);
#if !defined(CONF_FAMILY_WINDOWS) #if !defined(CONF_FAMILY_WINDOWS)
char aPath[512]; char aPath[512];
m_pStorage->GetBinaryPath(PLAT_SERVER_EXEC, aPath, sizeof aPath); m_pStorage->GetBinaryPath(PLAT_SERVER_EXEC, aPath, sizeof aPath);
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath); str_format(aBuf, sizeof aBuf, "chmod +x %s", aPath);
if (system(aBuf)) if (system(aBuf))
{
dbg_msg("updater", "ERROR: failed to set server executable bit"); dbg_msg("updater", "ERROR: failed to set server executable bit");
Success &= false;
}
#endif #endif
return Success;
} }
void CUpdater::ParseUpdate() void CUpdater::ParseUpdate()
@ -323,15 +336,19 @@ void CUpdater::PerformUpdate()
void CUpdater::CommitUpdate() void CUpdater::CommitUpdate()
{ {
bool Success = true;
for(map<std::string, bool>::iterator it = m_FileJobs.begin(); it != m_FileJobs.end(); ++it) for(map<std::string, bool>::iterator it = m_FileJobs.begin(); it != m_FileJobs.end(); ++it)
if(it->second) if(it->second)
MoveFile(it->first.c_str()); Success &= MoveFile(it->first.c_str());
if(m_ClientUpdate) if(m_ClientUpdate)
ReplaceClient(); Success &= ReplaceClient();
if(m_ServerUpdate) if(m_ServerUpdate)
ReplaceServer(); Success &= ReplaceServer();
if(m_pClient->State() == IClient::STATE_ONLINE || m_pClient->EditorHasUnsavedData()) if(!Success)
m_State = FAIL;
else if(m_pClient->State() == IClient::STATE_ONLINE || m_pClient->EditorHasUnsavedData())
m_State = NEED_RESTART; m_State = NEED_RESTART;
else else
{ {

View file

@ -56,14 +56,14 @@ class CUpdater : public IUpdater
void AddFileJob(const char *pFile, bool Job); void AddFileJob(const char *pFile, bool Job);
void FetchFile(const char *pFile, const char *pDestPath = 0); void FetchFile(const char *pFile, const char *pDestPath = 0);
void MoveFile(const char *pFile); bool MoveFile(const char *pFile);
void ParseUpdate(); void ParseUpdate();
void PerformUpdate(); void PerformUpdate();
void CommitUpdate(); void CommitUpdate();
void ReplaceClient(); bool ReplaceClient();
void ReplaceServer(); bool ReplaceServer();
void SetCurrentState(int NewState); void SetCurrentState(int NewState);