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