OpenClonk
C4UpdateDlg.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2007-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2010-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // dialogs for update, and the actual update application code
17 // is only compiled WITH_AUTOMATIC_UPDATE
18 
19 #include "C4Include.h"
20 #include "gui/C4UpdateDlg.h"
21 
22 #include "c4group/C4Components.h"
23 #include "game/C4Application.h"
24 #include "gui/C4DownloadDlg.h"
25 
26 #ifdef _WIN32
27 #include <shellapi.h>
28 #else
29 #include <fcntl.h>
30 #include <sys/wait.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #endif
34 
35 int C4UpdateDlg::pid;
36 int C4UpdateDlg::c4group_output[2];
37 bool C4UpdateDlg::succeeded;
38 
39 // --------------------------------------------------
40 // C4UpdateDlg
41 
42 C4UpdateDlg::C4UpdateDlg() : C4GUI::InfoDialog(LoadResStr("IDS_TYPE_UPDATE"), 10)
43 {
44  // initial text update
45  UpdateText();
46  // assume update running
47  UpdateRunning = true;
48 }
49 
50 void C4UpdateDlg::UserClose(bool fOK)
51 {
52  // Update not done yet: can't close
53  if (UpdateRunning)
54  {
55  GetScreen()->ShowMessage(LoadResStr("IDS_MSG_UPDATEINPROGRESS"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
56  return;
57  }
58  // Okay to close
59  Dialog::UserClose(fOK);
60 #ifndef _WIN32
61  if (succeeded)
62  {
63  // ready for restart
65  Application.Quit();
66  }
67 #endif
68 }
69 
70 void C4UpdateDlg::UpdateText()
71 {
72  if (!UpdateRunning)
73  return;
74 #ifdef _WIN32
75  AddLine("Win32 is currently not using the C4UpdateDlg!");
76  UpdateRunning = false;
77 #else
78  char c4group_output_buf[513];
79  ssize_t amount_read;
80  // transfer output to log window
81  amount_read = read(c4group_output[0], c4group_output_buf, 512);
82  // error
83  if (amount_read == -1)
84  {
85  if (errno == EAGAIN)
86  return;
87  StdStrBuf Errormessage = FormatString("read error from c4group: %s", strerror(errno));
88  Log(Errormessage.getData());
89  AddLine(Errormessage.getData());
90  UpdateRunning = false;
91  succeeded = false;
92  }
93  // EOF: Update done.
94  else if (amount_read == 0)
95  {
96  // Close c4group output
97  close(c4group_output[0]);
98  // If c4group did not exit but something else caused EOF, then that's bad. But don't hang.
99  int child_status = 0;
100  if (waitpid(pid, &child_status, WNOHANG) == -1)
101  {
102  LogF("error in waitpid: %s", strerror(errno));
103  AddLineFmt("error in waitpid: %s", strerror(errno));
104  succeeded = false;
105  }
106  // check if c4group failed.
107  else if (WIFEXITED(child_status) && WEXITSTATUS(child_status))
108  {
109  LogF("c4group returned status %d", WEXITSTATUS(child_status));
110  AddLineFmt("c4group returned status %d", WEXITSTATUS(child_status));
111  succeeded = false;
112  }
113  else if (WIFSIGNALED(child_status))
114  {
115  LogF("c4group killed with signal %d", WTERMSIG(child_status));
116  AddLineFmt("c4group killed with signal %d", WTERMSIG(child_status));
117  succeeded = false;
118  }
119  else
120  {
121  Log("Done.");
122  AddLine("Done.");
123  }
124  UpdateRunning = false;
125  }
126  else
127  {
128  c4group_output_buf[amount_read] = 0;
129  // Fixme: This adds spurious newlines in the middle of the output.
130  LogF("%s", c4group_output_buf);
131  AddLineFmt("%s", c4group_output_buf);
132  }
133 #endif
134 
135  // Scroll to bottom
136  if (pTextWin)
137  {
138  pTextWin->UpdateHeight();
139  pTextWin->ScrollToBottom();
140  }
141 }
142 
143 // --------------------------------------------------
144 // static update application function
145 
146 void C4UpdateDlg::RedirectToDownloadPage()
147 {
148  OpenURL("http://www.openclonk.org/download");
149 }
150 
151 bool C4UpdateDlg::DoUpdate(const char *szUpdateURL, C4GUI::Screen *pScreen)
152 {
153  if(szUpdateURL == nullptr || strlen(szUpdateURL) == 0)
154  {
155  pScreen->ShowMessageModal(LoadResStr("IDS_MSG_NEWRELEASEAVAILABLE"), LoadResStr("IDS_TYPE_UPDATE"),C4GUI::MessageDialog::btnOK, C4GUI::Ico_Ex_Update);
156  RedirectToDownloadPage();
157  return true;
158  }
159 
160  // Determine local filename for update group
161  StdCopyStrBuf strLocalFilename(Config.AtTempPath(GetFilename(szUpdateURL)));
162  StdCopyStrBuf strRemoteURL(szUpdateURL);
163  // cut off http://
164  strRemoteURL.Replace("http://","");
165  // Download update group
166  if (!C4DownloadDlg::DownloadFile(LoadResStr("IDS_TYPE_UPDATE"), pScreen, strRemoteURL.getData(), strLocalFilename.getData(), LoadResStr("IDS_MSG_UPDATENOTAVAILABLE")))
167  {
168  // Download failed, open browser so the user can download a full package
169  RedirectToDownloadPage();
170  // return success, because error message has already been shown
171  return true;
172  }
173 
174  // Apply downloaded update
175  return ApplyUpdate(strLocalFilename.getData(), true, pScreen);
176 }
177 
178 bool C4UpdateDlg::ApplyUpdate(const char *strUpdateFile, bool fDeleteUpdate, C4GUI::Screen *pScreen)
179 {
180  // Apply update: If the update file is a .ocu, it will extract c4group and apply the update.
181  // If the update file is an installer, it will just launch that installer.
182  StdStrBuf strUpdateProgEx, strUpdateArgs;
183  bool fIsGroupUpdate = SEqualNoCase(GetExtension(strUpdateFile), C4CFN_UpdateGroupExtension+1);
184  // Is this an update executable or an update group?
185  if (fIsGroupUpdate)
186  {
187  // This is an update group (.ocu). Extract c4group and run it.
188  // Find a place to extract the update
190  // Determine name of update program
191  StdStrBuf strUpdateProg;
192  strUpdateProg.Copy(C4CFN_UpdateProgram);
193  // Windows: manually append extension because ExtractEntry() cannot properly glob and Extract() doesn't return failure values
194 #ifdef _WIN32
195  strUpdateProg += ".exe";
196 #endif
197  // Determine name of local extract of update program
198  strUpdateProgEx.Copy(Config.AtTempUpdatePath(strUpdateProg.getData()));
199 
200  // Extract update program (the update should be applied using the new version)
201  C4Group UpdateGroup;
202  if (!UpdateGroup.Open(strUpdateFile))
203  {
204  LogF("Error opening \"%s\": %s", strUpdateFile, UpdateGroup.GetError());
205  return false;
206  }
207  // Look for update program at top level
208  if (!UpdateGroup.ExtractEntry(strUpdateProg.getData(), strUpdateProgEx.getData()))
209  {
210  LogF("Error extracting \"%s\": %s", strUpdateProg.getData(), UpdateGroup.GetError());
211  return false;
212  }
213  // Extract any needed library files
215  UpdateGroup.Close();
216 #ifdef _WIN32
217  // Notice: even if the update program and update group are in the temp path, they must be executed in our working directory
218  DWORD ProcessID = GetCurrentProcessId();
219  //strUpdateArgs.Format("\"%s\" \"%s\" %s %lu", strUpdateProgEx.getData(), strUpdateFile, fDeleteUpdate ? "-yd" : "-y", (unsigned long)ProcessID);
220  strUpdateArgs.Format("\"%s\" %s %lu", strUpdateFile, fDeleteUpdate ? "-yd" : "-y", (unsigned long)ProcessID);
221 
222 #if 0 // debug code to reroute updating via batch file
223  CStdFile f; - reroute via vi
224  f.Create(Config.AtTempUpdatePath("update.bat"));
225  f.WriteString(FormatString("%s %s\npause\n", strUpdateProgEx.getData(), strUpdateArgs.getData()).getData());
226  f.Close();
227  strUpdateProgEx.Copy(Config.AtTempUpdatePath("update.bat"));
228  strUpdateArgs.Copy(strUpdateProgEx);
229 #endif
230 #endif
231  }
232  else
233  {
234  // This "update" is actually an installer. Just run it.
235  strUpdateProgEx = strUpdateFile;
236  strUpdateArgs = "";
237  // if group was downloaded to temp path, delete it from there
238  if (fDeleteUpdate) SCopy(strUpdateProgEx.getData(), Config.General.TempUpdatePath, CFG_MaxString);
239  }
240 
241  // Execute update program
242  Log(LoadResStr("IDS_PRC_LAUNCHINGUPDATE"));
243  succeeded = true;
244 
245 #ifdef _WIN32
246  // Notice: even if the update program and update group are in the temp path, they must be executed in our working directory
247  // the magic verb "runas" opens the update program in a shell requesting elevation
248  int iError = (intptr_t)ShellExecute(nullptr, L"runas", strUpdateProgEx.GetWideChar(), strUpdateArgs.GetWideChar(), Config.General.ExePath.GetWideChar(), SW_SHOW);
249  if (iError <= 32) return false;
250 
251  // must quit ourselves for update program to work
252  if (succeeded) Application.Quit();
253 #else
254  if (pipe(c4group_output) == -1)
255  {
256  Log("Error creating pipe");
257  return false;
258  }
259  switch (pid = fork())
260  {
261  // Error
262  case -1:
263  Log("Error creating update child process.");
264  return false;
265  // Child process
266  case 0:
267  // Close unused read end
268  close(c4group_output[0]);
269  // redirect stdout and stderr to the parent
270  dup2(c4group_output[1], STDOUT_FILENO);
271  dup2(c4group_output[1], STDERR_FILENO);
272  if (c4group_output[1] != STDOUT_FILENO && c4group_output[1] != STDERR_FILENO)
273  close(c4group_output[1]);
274  if (fIsGroupUpdate)
275  execl(C4CFN_UpdateProgram, C4CFN_UpdateProgram, "-v", strUpdateFile, (fDeleteUpdate ? "-yd" : "-y"), static_cast<char *>(0));
276  else
277  execl(strUpdateFile, strUpdateFile, static_cast<char *>(0));
278  printf("execl failed: %s\n", strerror(errno));
279  exit(1);
280  // Parent process
281  default:
282  // Close unused write end
283  close(c4group_output[1]);
284  // disable blocking
285  fcntl(c4group_output[0], F_SETFL, O_NONBLOCK);
286  // Open the update log dialog (this will update itself automatically from c4group_output)
287  pScreen->ShowRemoveDlg(new C4UpdateDlg());
288  break;
289  }
290 #endif
291  // done
292  return succeeded;
293 }
294 
295 bool C4UpdateDlg::IsValidUpdate(const char *szVersion)
296 {
297  StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
298  if (szVersion == nullptr || strlen(szVersion) == 0) return false;
299  return strcmp(szVersion,strVersion.getData()) != 0;
300 }
301 
302 bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
303 {
304  // Automatic update only once a day
305  if (fAutomatic)
306  if (time(nullptr) - Config.Network.LastUpdateTime < 60 * 60 * 24)
307  return false;
308  // Store the time of this update check (whether it's automatic or not or successful or not)
309  Config.Network.LastUpdateTime = time(nullptr);
310  // Get current update url and version info from server
311  StdStrBuf UpdateURL;
312  StdStrBuf VersionInfo;
313  C4GUI::Dialog *pWaitDlg = nullptr;
314  pWaitDlg = new C4GUI::MessageDialog(LoadResStr("IDS_MSG_LOOKINGFORUPDATES"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::MessageDialog::btnAbort, C4GUI::Ico_Ex_Update, C4GUI::MessageDialog::dsRegular);
315  pWaitDlg->SetDelOnClose(false);
316  pScreen->ShowDialog(pWaitDlg, false);
317 
318  C4Network2UpdateClient UpdateClient;
319  bool fSuccess = false, fAborted = false;
320  StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
321  StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s&action=version", Config.Network.UpdateServerAddress, strVersion.getData(), C4_OS);
322  if (UpdateClient.Init() && UpdateClient.SetServer(strQuery.getData()) && UpdateClient.QueryUpdateURL())
323  {
324  UpdateClient.SetNotify(&Application.InteractiveThread);
325  Application.InteractiveThread.AddProc(&UpdateClient);
326  // wait for version check to terminate
327  while (UpdateClient.isBusy())
328  {
329  // wait, check for program abort
330  if (!Application.ScheduleProcs()) { fAborted = true; break; }
331  // check for dialog close
332  if (pWaitDlg) if (!pWaitDlg->IsShown()) { fAborted = true; break; }
333  }
334  if (!fAborted)
335  {
336  fSuccess = UpdateClient.GetVersion(&VersionInfo);
337  UpdateClient.GetUpdateURL(&UpdateURL);
338  }
340  UpdateClient.SetNotify(nullptr);
341  }
342  delete pWaitDlg;
343  // User abort
344  if (fAborted)
345  {
346  return false;
347  }
348  // Error during update check
349  if (!fSuccess)
350  {
351  StdStrBuf sError; sError.Copy(LoadResStr("IDS_MSG_UPDATEFAILED"));
352  const char *szErrMsg = UpdateClient.GetError();
353  if (szErrMsg)
354  {
355  sError.Append(": ");
356  sError.Append(szErrMsg);
357  }
358  pScreen->ShowMessage(sError.getData(), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
359  return false;
360  }
361  // Applicable update available
362  if (C4UpdateDlg::IsValidUpdate(VersionInfo.getData()))
363  {
364  // Prompt user, then apply update
365  if (pScreen->ShowMessageModal(LoadResStr("IDS_MSG_ANUPDATETOVERSIONISAVAILA"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Ex_Update))
366  {
367  if (!DoUpdate(UpdateURL.getData(), pScreen))
368  pScreen->ShowMessage(LoadResStr("IDS_MSG_UPDATEFAILED"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
369  else
370  return true;
371  }
372  }
373  // No applicable update available
374  else
375  {
376  // Message (if not automatic)
377  if (!fAutomatic)
378  pScreen->ShowMessage(LoadResStr("IDS_MSG_NOUPDATEAVAILABLEFORTHISV"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
379  }
380  // Done (and no update has been done)
381  return false;
382 }
bool OpenURL(const char *szURL)
Definition: C4AppMac.mm:199
#define C4CFN_UpdateProgram
Definition: C4Components.h:54
#define C4CFN_UpdateProgramLibs
Definition: C4Components.h:55
#define C4CFN_UpdateGroupExtension
Definition: C4Components.h:53
C4Config Config
Definition: C4Config.cpp:930
@ CFG_MaxString
Definition: C4Config.h:28
C4Application Application
Definition: C4Globals.cpp:44
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
#define C4_OS
ptrdiff_t ssize_t
uint32_t DWORD
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:118
char * GetFilename(char *szPath)
Definition: StdFile.cpp:42
C4InteractiveThread InteractiveThread
Definition: C4Application.h:45
void Quit() override
StdCopyStrBuf ExePath
Definition: C4Config.h:54
char TempUpdatePath[CFG_MaxString+1]
Definition: C4Config.h:60
C4ConfigGeneral General
Definition: C4Config.h:255
C4ConfigNetwork Network
Definition: C4Config.h:259
const char * AtTempPath(const char *filename)
Definition: C4Config.cpp:600
const char * MakeTempUpdateFolder()
Definition: C4Config.cpp:911
const char * AtTempUpdatePath(const char *filename)
Definition: C4Config.cpp:922
static bool DownloadFile(const char *szDLType, C4GUI::Screen *pScreen, const char *szURL, const char *szSaveAsFilename, const char *szNotFoundMessage=nullptr)
void SetDelOnClose(bool fToVal=true)
Definition: C4Gui.h:2191
bool IsShown()
Definition: C4Gui.h:2148
void ShowDialog(Dialog *pDlg, bool fFade)
Definition: C4Gui.cpp:611
bool ShowRemoveDlg(Dialog *pDlg)
bool ShowMessageModal(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, int32_t *piConfigDontShowAgainSetting=nullptr)
bool ShowMessage(const char *szMessage, const char *szCaption, Icons icoIcon, int32_t *piConfigDontShowAgainSetting=nullptr)
bool Extract(const char *files, const char *destination=nullptr, const char *exclude=nullptr)
Definition: C4Group.cpp:1808
bool ExtractEntry(const char *filename, const char *destination=nullptr)
Definition: C4Group.cpp:1870
const char * GetError()
Definition: C4Group.cpp:650
bool Close()
Definition: C4Group.cpp:971
bool Open(const char *group_name, bool do_create=false)
Definition: C4Group.cpp:660
bool Init()
Definition: C4HTTP.cpp:42
virtual const char * GetError() const
Definition: C4HTTP.h:96
bool SetServer(const char *szServerAddress)
Definition: C4HTTP.cpp:298
void SetNotify(class C4InteractiveThread *pnNotify)
Definition: C4HTTP.h:106
bool isBusy() const
Definition: C4HTTP.h:86
void RemoveProc(StdSchedulerProc *pProc)
bool AddProc(StdSchedulerProc *pProc)
bool GetVersion(StdStrBuf *pVersion)
bool GetUpdateURL(StdStrBuf *pUpdateURL)
bool Close(StdBuf **ppMemory=nullptr)
Definition: CStdFile.cpp:151
bool Create(const char *szFileName, bool fCompressed=false, bool fExecutable=false, bool fMemory=false)
Definition: CStdFile.cpp:49
bool WriteString(const char *szStr)
Definition: CStdFile.cpp:264
bool ScheduleProcs(int iTimeout=1000/36)
const char * getData() const
Definition: StdBuf.h:442
void Copy()
Definition: StdBuf.h:467
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174
@ Ico_Ex_Update
Definition: C4Gui.h:712