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