1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2008 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC. If not, see <http://www.gnu.org/licenses/>.
17
18 #if defined(__GNUG__) && !defined(__APPLE__)
19 #pragma implementation "BOINCClientManager.h"
20 #endif
21
22 #include "stdwx.h"
23
24 #include "diagnostics.h"
25 #include "miofile.h"
26 #include "str_replace.h"
27 #include "util.h"
28
29 #include "LogBOINC.h"
30 #include "BOINCGUIApp.h"
31 #include "SkinManager.h"
32 #include "MainDocument.h"
33 #include "BOINCBaseFrame.h"
34 #include "AdvancedFrame.h"
35 #include "BOINCClientManager.h"
36 #include "error_numbers.h"
37 #include "procinfo.h"
38 #include "filesys.h"
39 #include "daemonmgt.h"
40 #include "Events.h"
41 #include "version.h"
42
43 // Alert user if Client crashes 3 times in 30 minutes
44 #define CLIENT_3_CRASH_MAX_TIME 30
45
46 #ifdef __WXMAC__
47 #include "mac_util.h"
48 enum {
49 NewStyleDaemon = 1,
50 OldStyleDaemon
51 };
52
53 #elif defined(__WXMSW__)
54
55 #include "win_util.h"
56 #include "diagnostics_win.h"
57
58 extern int diagnostics_get_process_information(PVOID* ppBuffer, PULONG pcbBuffer);
59
60 #else
61 #include <sys/wait.h>
62 #endif
63
CBOINCClientManager()64 CBOINCClientManager::CBOINCClientManager() {
65 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::CBOINCClientManager - Function Begin"));
66
67 m_bBOINCStartedByManager = false;
68 m_lBOINCCoreProcessId = 0;
69 m_fAutoRestart1Time = 0;
70 m_fAutoRestart2Time = 0;
71
72 #ifdef __WXMSW__
73 m_hBOINCCoreProcess = NULL;
74 #endif
75
76 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::CBOINCClientManager - Function End"));
77 }
78
79
~CBOINCClientManager()80 CBOINCClientManager::~CBOINCClientManager() {
81 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::~CBOINCClientManager - Function Begin"));
82
83 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::~CBOINCClientManager - Function End"));
84 }
85
86
AutoRestart()87 bool CBOINCClientManager::AutoRestart() {
88 double timeNow, timeDiff;
89 if (IsBOINCCoreRunning()) return true;
90 #if ! (defined(__WXMAC__) || defined(__WXMSW__))
91 // Mac and Windows can restart Client as a daemon, but
92 // Linux may not know Client's location if it didn't start the Client
93 if (!m_bBOINCStartedByManager) return false;
94 #endif
95 // Alert user if Client crashes 3 times in CLIENT_3_CRASH_MAX_TIME
96 timeNow = dtime();
97 timeDiff = timeNow - m_fAutoRestart1Time;
98 if ((timeDiff) < (CLIENT_3_CRASH_MAX_TIME * 60)) {
99 int response;
100 ClientCrashDlg *dlg = new ClientCrashDlg(timeDiff);
101 if (dlg) {
102 CBOINCBaseFrame* pFrame = wxGetApp().GetFrame();
103 if (!pFrame->IsShown()) {
104 pFrame->Show();
105 }
106 response = dlg->ShowModal();
107 dlg->Destroy();
108 if (response == wxID_CANCEL) return false;
109 timeNow = 0;
110 m_fAutoRestart1Time = 0;
111 m_fAutoRestart2Time = 0;
112 }
113 }
114 m_lBOINCCoreProcessId = 0;
115 m_fAutoRestart1Time = m_fAutoRestart2Time;
116 m_fAutoRestart2Time = timeNow;
117 StartupBOINCCore();
118 return true;
119 }
120
121
IsSystemBooting()122 bool CBOINCClientManager::IsSystemBooting() {
123 bool bReturnValue = false;
124 #if defined(__WXMSW__)
125 if (GetTickCount() < (1000*60*5)) bReturnValue = true; // If system has been up for less than 5 minutes
126 #elif defined(__WXMAC__)
127 if (getTimeSinceBoot() < (120)) bReturnValue = true; // If system has been up for less than 2 minutes
128 #endif
129 return bReturnValue;
130 }
131
132
IsBOINCConfiguredAsDaemon()133 int CBOINCClientManager::IsBOINCConfiguredAsDaemon() {
134 bool bReturnValue = false;
135 #if defined(__WXMSW__)
136 if (is_daemon_installed()) bReturnValue = 1;
137 #elif defined(__WXMAC__)
138 if ( boinc_file_exists("/Library/LaunchDaemons/edu.berkeley.boinc.plist")) {
139 bReturnValue = NewStyleDaemon; // New-style daemon uses launchd
140 }
141 if (boinc_file_exists("/Library/StartupItems/boinc/boinc") ) {
142 bReturnValue = OldStyleDaemon; // Old-style daemon uses StartupItem
143 }
144 #endif
145 return bReturnValue;
146 }
147
148
IsBOINCCoreRunning()149 bool CBOINCClientManager::IsBOINCCoreRunning() {
150 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::IsBOINCCoreRunning - Function Begin"));
151 bool running = false;
152
153 PROC_MAP pm;
154 int retval;
155
156 #ifndef __WXMSW__
157 if (m_lBOINCCoreProcessId) {
158 // Prevent client from being a zombie
159 if (waitpid(m_lBOINCCoreProcessId, 0, WNOHANG) == m_lBOINCCoreProcessId) {
160 m_lBOINCCoreProcessId = 0;
161 }
162 }
163 #endif
164
165 // Look for BOINC Client in list of all running processes
166 retval = procinfo_setup(pm);
167 if (retval) return false; // Should never happen
168
169 PROC_MAP::iterator i;
170 for (i=pm.begin(); i!=pm.end(); ++i) {
171 PROCINFO& pi = i->second;
172 #ifdef __WXMSW__
173 if (!strcmp(pi.command, "boinc.exe"))
174 #else
175 if (!strcmp(pi.command, "boinc"))
176 #endif
177 {
178 running = true;
179 break;
180 }
181 if (!strcmp(pi.command, "boinc_client")) {
182 running = true;
183 break;
184 }
185 }
186
187 wxLogTrace(wxT("Function Status"), wxT("CBOINCClientManager::IsBOINCCoreRunning - Returning '%d'"), (int)running);
188 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::IsBOINCCoreRunning - Function End"));
189 return running;
190 }
191
192
StartupBOINCCore()193 bool CBOINCClientManager::StartupBOINCCore() {
194 wxLogTrace(wxT("Function Start/End"), wxT("CMainDocument::StartupBOINCCore - Function Begin"));
195
196 bool bReturnValue = false;
197 wxString strExecute = wxEmptyString;
198 wxString strDataDir = wxEmptyString;
199
200 if (IsBOINCCoreRunning()) return true;
201
202 #if defined(__WXMSW__)
203 const char* pszExecute = NULL;
204 const char* pszDataDirectory = NULL;
205
206 if (IsBOINCConfiguredAsDaemon()) {
207 start_daemon_via_daemonctrl();
208
209 m_bBOINCStartedByManager = true;
210 bReturnValue = IsBOINCCoreRunning();
211 } else {
212
213 // Append boinc.exe to the end of the strExecute string and get ready to rock
214 strExecute.Printf(
215 wxT("\"%sboinc.exe\" --redirectio --launched_by_manager %s"),
216 wxGetApp().GetRootDirectory().c_str(),
217 wxGetApp().GetArguments().c_str()
218 );
219
220 PROCESS_INFORMATION pi;
221 STARTUPINFOA si;
222 BOOL bProcessStarted;
223
224 memset(&pi, 0, sizeof(pi));
225 memset(&si, 0, sizeof(si));
226
227 si.cb = sizeof(si);
228 si.dwFlags = STARTF_USESHOWWINDOW;
229 si.wShowWindow = SW_HIDE;
230
231 pszExecute = (const char*)strExecute.mb_str();
232 if (wxGetApp().GetDataDirectory().empty()) {
233 pszDataDirectory = NULL;
234 } else {
235 strDataDir = wxGetApp().GetDataDirectory();
236 pszDataDirectory = (const char*)strDataDir.mb_str();
237 }
238
239 wxLogTrace(wxT("Function Status"), wxT("CMainDocument::StartupBOINCCore - pszExecute '%s'\n"), pszExecute);
240 wxLogTrace(wxT("Function Status"), wxT("CMainDocument::StartupBOINCCore - pszDataDirectory '%s'\n"), pszDataDirectory);
241
242 bProcessStarted = CreateProcessA(
243 NULL,
244 (LPSTR)pszExecute,
245 NULL,
246 NULL,
247 FALSE,
248 CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW,
249 NULL,
250 (LPSTR)pszDataDirectory,
251 &si,
252 &pi
253 );
254
255 if (bProcessStarted) {
256 m_lBOINCCoreProcessId = pi.dwProcessId;
257 m_hBOINCCoreProcess = pi.hProcess;
258 }
259 }
260
261 #elif defined(__WXMAC__)
262
263 char buf[1024];
264 char *argv[5];
265
266 if (IsBOINCConfiguredAsDaemon() == NewStyleDaemon) {
267 system ("launchctl load /Library/LaunchDaemons/edu.berkeley.boinc.plist");
268 system ("launchctl start edu.berkeley.boinc");
269 for (int i=0; i<100; i++) { // Wait up to 1 seccond in 10 ms increments
270 boinc_sleep(0.01);
271 if (IsBOINCCoreRunning()) {
272 bReturnValue = true;
273 break;
274 }
275 }
276 } else {
277
278 // Get the full path to core client inside this application's bundle
279 getPathToThisApp(buf, sizeof(buf));
280 #if 0 // The Mac version of wxExecute(wxString& ...) crashes if there is a space in the path
281 strExecute = wxT("\"");
282 strExecute += wxT(buf);
283 strExecute += wxT("/Contents/Resources/boinc\" --redirectio --launched_by_manager");
284 m_lBOINCCoreProcessId = ::wxExecute(strExecute);
285 #else // Use wxExecute(wxChar **argv ...) instead of wxExecute(wxString& ...)
286 strcat(buf, "/Contents/Resources/boinc");
287 argv[0] = buf;
288 argv[1] = "--redirectio";
289 argv[2] = "--launched_by_manager";
290 argv[3] = NULL;
291 #ifdef SANDBOX
292 if (!g_use_sandbox) {
293 argv[3] = "--insecure";
294 argv[4] = NULL;
295 }
296 #endif
297 // Under wxMac-2.8.0, wxExecute starts a separate thread
298 // to wait for child's termination.
299 // That wxProcessTerminationThread uses a huge amount of processor
300 // time (about 11% of one CPU on 2GHz Intel Dual-Core Mac).
301 // m_lBOINCCoreProcessId = ::wxExecute(argv);
302 run_program(
303 "/Library/Application Support/BOINC Data",
304 buf, argv[3] ? 4 : 3, argv, 0.0, m_lBOINCCoreProcessId
305 );
306 #endif
307 }
308
309 #else // Unix based systems
310 wxString savedWD = ::wxGetCwd();
311
312 wxSetWorkingDirectory(wxGetApp().GetDataDirectory());
313
314 // Append boinc.exe to the end of the strExecute string and get ready to rock
315 strExecute = wxGetApp().GetRootDirectory() + wxT("boinc --redirectio --launched_by_manager");
316 #ifdef SANDBOX
317 if (!g_use_sandbox) {
318 strExecute += wxT(" --insecure");
319 }
320 #endif
321
322 wxLogTrace(wxT("Function Status"), wxT("CMainDocument::StartupBOINCCore - szExecute '%s'\n"), strExecute.c_str());
323 wxLogTrace(wxT("Function Status"), wxT("CMainDocument::StartupBOINCCore - szDataDirectory '%s'\n"), wxGetApp().GetDataDirectory().c_str());
324
325 m_lBOINCCoreProcessId = ::wxExecute(strExecute);
326
327 wxSetWorkingDirectory(savedWD);
328 #endif
329
330 if (0 != m_lBOINCCoreProcessId) {
331 m_bBOINCStartedByManager = true;
332 bReturnValue = true;
333 // Allow time for daemon to start up so we don't keep relaunching it
334 for (int i=0; i<100; i++) { // Wait up to 1 seccond in 10 ms increments
335 boinc_sleep(0.01);
336 if (IsBOINCCoreRunning()) break;
337 }
338 }
339
340 wxLogTrace(wxT("Function Start/End"), wxT("CMainDocument::StartupBOINCCore - Function End"));
341 return bReturnValue;
342 }
343
344
345 #ifdef __WXMSW__
downcase_string(tstring & orig)346 static tstring downcase_string(tstring& orig) {
347 tstring retval = orig;
348 for (size_t i=0; i < retval.length(); i++) {
349 retval[i] = tolower(retval[i]);
350 }
351 return retval;
352 }
353
354
KillClient()355 void CBOINCClientManager::KillClient() {
356 ULONG cbBuffer = 128*1024; // 128k initial buffer
357 PVOID pBuffer = NULL;
358 PSYSTEM_PROCESSES pProcesses = NULL;
359
360 if (m_hBOINCCoreProcess != NULL) {
361 kill_program(m_hBOINCCoreProcess);
362 return;
363 }
364
365 // Get a snapshot of the process and thread information.
366 diagnostics_get_process_information(&pBuffer, &cbBuffer);
367
368 // Lets start walking the structures to find the good stuff.
369 pProcesses = (PSYSTEM_PROCESSES)pBuffer;
370 do {
371 if (pProcesses->ProcessId) {
372 tstring strProcessName = pProcesses->ProcessName.Buffer;
373 if (downcase_string(strProcessName) == tstring(_T("boinc.exe"))) {
374 TerminateProcessById(pProcesses->ProcessId);
375 break;
376 }
377 }
378
379 // Move to the next structure if one exists
380 if (!pProcesses->NextEntryDelta) {
381 break;
382 }
383 pProcesses = (PSYSTEM_PROCESSES)(((LPBYTE)pProcesses) + pProcesses->NextEntryDelta);
384 } while (pProcesses);
385
386 // Release resources
387 if (pBuffer) HeapFree(GetProcessHeap(), NULL, pBuffer);
388 }
389
390 #else
391
KillClient()392 void CBOINCClientManager::KillClient() {
393 PROC_MAP pm;
394 int retval;
395
396 if (m_lBOINCCoreProcessId) {
397 kill_program(m_lBOINCCoreProcessId);
398 return;
399 }
400
401 retval = procinfo_setup(pm);
402 if (retval) return; // Should never happen
403
404 PROC_MAP::iterator i;
405 for (i=pm.begin(); i!=pm.end(); ++i) {
406 PROCINFO& procinfo = i->second;
407 if (!strcmp(procinfo.command, "boinc")) {
408 kill_program(procinfo.id);
409 break;
410 }
411 }
412 }
413 #endif
414
415
ShutdownBOINCCore(bool ShuttingDownManager)416 void CBOINCClientManager::ShutdownBOINCCore(bool ShuttingDownManager) {
417 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::ShutdownBOINCCore - Function Begin"));
418
419 CMainDocument* pDoc = wxGetApp().GetDocument();
420 wxInt32 iCount = 0;
421 bool bClientQuit = false;
422 wxString strConnectedCompter = wxEmptyString;
423 wxString strPassword = wxEmptyString;
424 double startTime = 0;
425 wxDateTime zeroTime = wxDateTime((time_t)0);
426 wxDateTime rpcCompletionTime = zeroTime;
427 ASYNC_RPC_REQUEST request;
428 int quit_result;
429
430 wxASSERT(pDoc);
431 wxASSERT(wxDynamicCast(pDoc, CMainDocument));
432
433 #ifdef __WXMAC__
434 // Mac Manager shuts down client only if Manager started client
435 if (!m_bBOINCStartedByManager) return;
436 #endif
437
438 #ifdef __WXMSW__
439 if (IsBOINCConfiguredAsDaemon()) {
440 stop_daemon_via_daemonctrl();
441 bClientQuit = true;
442 } else
443 #endif
444 {
445 pDoc->GetConnectedComputerName(strConnectedCompter);
446 if (!pDoc->IsComputerNameLocal(strConnectedCompter)) {
447 RPC_CLIENT rpc;
448 if (!rpc.init("localhost")) {
449 pDoc->m_pNetworkConnection->GetLocalPassword(strPassword);
450 rpc.authorize((const char*)strPassword.mb_str());
451 if (IsBOINCCoreRunning()) {
452 rpc.quit();
453 for (iCount = 0; iCount <= 10; iCount++) {
454 if (!bClientQuit && !IsBOINCCoreRunning()) {
455 wxLogTrace(wxT("Function Status"), wxT("CBOINCClientManager::ShutdownBOINCCore - (localhost) Application Exit Detected"));
456 bClientQuit = true;
457 break;
458 }
459 wxLogTrace(wxT("Function Status"), wxT("CBOINCClientManager::ShutdownBOINCCore - (localhost) Application Exit NOT Detected, Sleeping..."));
460 ::wxSleep(1);
461 }
462 } else {
463 bClientQuit = true;
464 }
465 }
466 rpc.close();
467 } else {
468 if (IsBOINCCoreRunning()) {
469 if (ShuttingDownManager) {
470 // Set event filtering to allow RPC completion
471 // events but not events which start new RPCs
472 wxGetApp().SetEventFiltering(true);
473 }
474 quit_result = -1;
475 request.clear();
476 request.which_rpc = RPC_QUIT;
477 request.rpcType = RPC_TYPE_ASYNC_NO_REFRESH;
478 request.completionTime = &rpcCompletionTime;
479 request.resultPtr = &quit_result;
480 pDoc->RequestRPC(request); // Issue an asynchronous Quit RPC
481
482 // Client needs time to shut down project applications, so don't wait
483 // for it to shut down; assume it will exit OK if Quit RPC succeeds.
484 startTime = dtime();
485 while ((dtime() - startTime) < 10.0) { // Allow 10 seconds
486 boinc_sleep(0.25); // Check 4 times per second
487 wxSafeYield(NULL, true); // To allow handling RPC completion events
488 if (!bClientQuit && (rpcCompletionTime != zeroTime)) {
489 // If Quit RPC finished, check its returned value
490 if (quit_result) {
491 break; // Quit RPC returned an error
492 }
493 wxLogTrace(wxT("Function Status"), wxT("CBOINCClientManager::ShutdownBOINCCore - Application Exit Detected"));
494 bClientQuit = true;
495 break;
496 }
497 wxLogTrace(wxT("Function Status"), wxT("CBOINCClientManager::ShutdownBOINCCore - Application Exit NOT Detected, Sleeping..."));
498 }
499 } else {
500 bClientQuit = true;
501 }
502 }
503 }
504
505 if (!bClientQuit) {
506 KillClient();
507 }
508 m_lBOINCCoreProcessId = 0;
509
510 wxLogTrace(wxT("Function Start/End"), wxT("CBOINCClientManager::ShutdownBOINCCore - Function End"));
511 }
512
513
BEGIN_EVENT_TABLE(ClientCrashDlg,wxDialog)514 BEGIN_EVENT_TABLE(ClientCrashDlg, wxDialog)
515 EVT_BUTTON(wxID_HELP, ClientCrashDlg::OnHelp)
516 END_EVENT_TABLE()
517
518 IMPLEMENT_CLASS(ClientCrashDlg, wxDialog)
519
520 ClientCrashDlg::ClientCrashDlg(double timeDiff) : wxDialog( NULL, wxID_ANY, wxT(""), wxDefaultPosition ) {
521 wxString strDialogTitle = wxEmptyString;
522 wxString strDialogMessage = wxEmptyString;
523 int minutes = wxMax((int)((timeDiff + 59.) / 60.), 2);
524 CSkinAdvanced* pSkinAdvanced = wxGetApp().GetSkinManager()->GetAdvanced();
525 wxASSERT(pSkinAdvanced);
526
527 // %s is the application name
528 // i.e. 'BOINC Manager', 'GridRepublic Manager'
529 strDialogTitle.Printf(
530 _("%s - Unexpected Exit"),
531 pSkinAdvanced->GetApplicationName().c_str()
532 );
533 SetTitle(strDialogTitle.c_str());
534
535 // 1st %s is the application name
536 // i.e. 'BOINC Manager', 'GridRepublic Manager'
537 // 2st %s is the project name
538 // i.e. 'BOINC', 'GridRepublic'
539 strDialogMessage.Printf(
540 _("The %s client has exited unexpectedly 3 times within the last %d minutes.\nWould you like to restart it again?"),
541 pSkinAdvanced->GetApplicationShortName().c_str(),
542 minutes
543 );
544
545 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
546 wxBoxSizer *icon_text = new wxBoxSizer( wxHORIZONTAL );
547
548 icon_text->Add( CreateTextSizer( strDialogMessage ), 0, wxALIGN_CENTER | wxLEFT, 10 );
549 topsizer->Add( icon_text, 1, wxCENTER | wxLEFT|wxRIGHT|wxTOP, 10 );
550
551 wxStdDialogButtonSizer *sizerBtn = CreateStdDialogButtonSizer(wxYES | wxNO | wxHELP);
552 SetEscapeId(wxID_NO); // Changes return value of NO button to wxID_CANCEL
553
554 if ( sizerBtn )
555 topsizer->Add(sizerBtn, 0, wxEXPAND | wxALL, 10 );
556
557 SetAutoLayout( true );
558 SetSizer( topsizer );
559
560 topsizer->SetSizeHints( this );
561 topsizer->Fit( this );
562 wxSize size( GetSize() );
563 if (size.x < size.y*3/2)
564 {
565 size.x = size.y*3/2;
566 SetSize( size );
567 }
568
569 Centre( wxBOTH | wxCENTER_FRAME);
570 }
571
572
OnHelp(wxCommandEvent & WXUNUSED (eventUnused))573 void ClientCrashDlg::OnHelp(wxCommandEvent& WXUNUSED(eventUnused)) {
574 wxString strURL = wxGetApp().GetSkinManager()->GetAdvanced()->GetOrganizationHelpUrl();
575
576 wxString wxurl;
577 wxurl.Printf(
578 wxT("%s?target=crash_detection&version=%s&controlid=%d"),
579 strURL.c_str(),
580 wxString(BOINC_VERSION_STRING, wxConvUTF8).c_str(),
581 ID_HELPBOINC
582 );
583 wxLaunchDefaultBrowser(wxurl);
584 }
585