1 /* 2 * Copyright 2005-2008, Ingo Weinhold, bonefish@users.sf.net. 3 * Copyright 2006-2009, Axel Dörfler, axeld@pinc-software.de. 4 * Copyright 2006-2008, Stephan Aßmus. 5 * Copyright 2006, Ryan Leavengood. 6 * 7 * Distributed under the terms of the MIT License. 8 */ 9 10 #include "ShutdownProcess.h" 11 12 #include <new> 13 #include <string.h> 14 15 #include <signal.h> 16 #include <unistd.h> 17 18 #include <Alert.h> 19 #include <AppFileInfo.h> 20 #include <AppMisc.h> 21 #include <Autolock.h> 22 #include <Bitmap.h> 23 #include <Button.h> 24 #include <Catalog.h> 25 #include <File.h> 26 #include <Message.h> 27 #include <MessagePrivate.h> 28 #include <RegistrarDefs.h> 29 #include <Roster.h> // for B_REQUEST_QUIT 30 #include <Screen.h> 31 #include <String.h> 32 #include <TextView.h> 33 #include <View.h> 34 #include <Window.h> 35 36 #include <TokenSpace.h> 37 #include <util/DoublyLinkedList.h> 38 39 #include <syscalls.h> 40 41 #include "AppInfoListMessagingTargetSet.h" 42 #include "Debug.h" 43 #include "EventQueue.h" 44 #include "MessageDeliverer.h" 45 #include "MessageEvent.h" 46 #include "Registrar.h" 47 #include "RosterAppInfo.h" 48 #include "TRoster.h" 49 50 51 #undef B_TRANSLATION_CONTEXT 52 #define B_TRANSLATION_CONTEXT "ShutdownProcess" 53 54 55 using std::nothrow; 56 using namespace BPrivate; 57 58 // The time span a non-background application has after the quit message has 59 // been delivered (more precisely: has been handed over to the 60 // MessageDeliverer). 61 static const bigtime_t kAppQuitTimeout = 3000000; // 3 s 62 63 // The time span a background application has after the quit message has been 64 // delivered (more precisely: has been handed over to the MessageDeliverer). 65 static const bigtime_t kBackgroundAppQuitTimeout = 3000000; // 3 s 66 67 // The time span non-app processes have after the TERM signal has been send 68 // to them before they get a KILL signal. 69 static const bigtime_t kNonAppQuitTimeout = 500000; // 0.5 s 70 71 // The time span the app that has aborted the shutdown shall be displayed in 72 // the shutdown window before closing it automatically. 73 static const bigtime_t kDisplayAbortingAppTimeout = 3000000; // 3 s 74 75 static const int kStripeWidth = 30; 76 static const int kIconVSpacing = 6; 77 static const int kIconSize = 32; 78 79 // message what fields (must not clobber the registrar's message namespace) 80 enum { 81 MSG_PHASE_TIMED_OUT = 'phto', 82 MSG_DONE = 'done', 83 MSG_KILL_APPLICATION = 'kill', 84 MSG_CANCEL_SHUTDOWN = 'cncl', 85 MSG_REBOOT_SYSTEM = 'lbot', 86 }; 87 88 // internal events 89 enum { 90 NO_EVENT, 91 ABORT_EVENT, 92 TIMEOUT_EVENT, 93 APP_QUIT_EVENT, 94 KILL_APP_EVENT, 95 REBOOT_SYSTEM_EVENT, 96 DEBUG_EVENT 97 }; 98 99 // phases 100 enum { 101 INVALID_PHASE = -1, 102 USER_APP_TERMINATION_PHASE = 0, 103 SYSTEM_APP_TERMINATION_PHASE = 1, 104 BACKGROUND_APP_TERMINATION_PHASE = 2, 105 OTHER_PROCESSES_TERMINATION_PHASE = 3, 106 ABORTED_PHASE = 4, 107 DONE_PHASE = 5, 108 }; 109 110 111 static bool 112 inverse_compare_by_registration_time(const RosterAppInfo* info1, 113 const RosterAppInfo* info2) 114 { 115 return (info2->registration_time < info1->registration_time); 116 } 117 118 119 /*! \brief Used to avoid type matching problems when throwing a constant. 120 */ 121 static inline 122 void 123 throw_error(status_t error) 124 { 125 throw error; 126 } 127 128 129 class ShutdownProcess::TimeoutEvent : public MessageEvent { 130 public: 131 TimeoutEvent(BHandler* target) 132 : MessageEvent(0, target, MSG_PHASE_TIMED_OUT) 133 { 134 SetAutoDelete(false); 135 136 fMessage.AddInt32("phase", INVALID_PHASE); 137 fMessage.AddInt32("team", -1); 138 } 139 140 void SetPhase(int32 phase) 141 { 142 fMessage.ReplaceInt32("phase", phase); 143 } 144 145 void SetTeam(team_id team) 146 { 147 fMessage.ReplaceInt32("team", team); 148 } 149 150 static int32 GetMessagePhase(BMessage* message) 151 { 152 int32 phase; 153 if (message->FindInt32("phase", &phase) != B_OK) 154 phase = INVALID_PHASE; 155 156 return phase; 157 } 158 159 static int32 GetMessageTeam(BMessage* message) 160 { 161 team_id team; 162 if (message->FindInt32("team", &team) != B_OK) 163 team = -1; 164 165 return team; 166 } 167 }; 168 169 170 class ShutdownProcess::InternalEvent 171 : public DoublyLinkedListLinkImpl<InternalEvent> { 172 public: 173 InternalEvent(uint32 type, team_id team, int32 phase) 174 : 175 fType(type), 176 fTeam(team), 177 fPhase(phase) 178 { 179 } 180 181 uint32 Type() const { return fType; } 182 team_id Team() const { return fTeam; } 183 int32 Phase() const { return fPhase; } 184 185 private: 186 uint32 fType; 187 int32 fTeam; 188 int32 fPhase; 189 }; 190 191 192 struct ShutdownProcess::InternalEventList : DoublyLinkedList<InternalEvent> { 193 }; 194 195 196 class ShutdownProcess::QuitRequestReplyHandler : public BHandler { 197 public: 198 QuitRequestReplyHandler(ShutdownProcess* shutdownProcess) 199 : BHandler("shutdown quit reply handler"), 200 fShutdownProcess(shutdownProcess) 201 { 202 } 203 204 virtual void MessageReceived(BMessage* message) 205 { 206 switch (message->what) { 207 case B_REPLY: 208 { 209 bool result; 210 thread_id thread; 211 if (message->FindBool("result", &result) == B_OK 212 && message->FindInt32("thread", &thread) == B_OK) { 213 if (!result) 214 fShutdownProcess->_NegativeQuitRequestReply(thread); 215 } 216 217 break; 218 } 219 220 default: 221 BHandler::MessageReceived(message); 222 break; 223 } 224 } 225 226 private: 227 ShutdownProcess *fShutdownProcess; 228 }; 229 230 231 class ShutdownProcess::ShutdownWindow : public BWindow { 232 public: 233 ShutdownWindow() 234 : BWindow(BRect(0, 0, 200, 100), B_TRANSLATE("Shutdown status"), 235 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 236 B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE | B_NOT_MINIMIZABLE 237 | B_NOT_ZOOMABLE | B_NOT_CLOSABLE, B_ALL_WORKSPACES), 238 fKillAppMessage(NULL), 239 fCurrentApp(-1) 240 { 241 } 242 243 ~ShutdownWindow() 244 { 245 for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) { 246 delete info; 247 } 248 } 249 250 virtual bool QuitRequested() 251 { 252 return false; 253 } 254 255 status_t Init(BMessenger target) 256 { 257 // create the views 258 259 // root view 260 fRootView = new(nothrow) TAlertView(BRect(0, 0, 10, 10), "app icons", 261 B_FOLLOW_NONE, 0); 262 if (!fRootView) 263 return B_NO_MEMORY; 264 fRootView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 265 AddChild(fRootView); 266 267 // text view 268 fTextView = new(nothrow) BTextView(BRect(0, 0, 10, 10), "text", 269 BRect(0, 0, 10, 10), B_FOLLOW_NONE); 270 if (!fTextView) 271 return B_NO_MEMORY; 272 fTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 273 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 274 fTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor); 275 fTextView->MakeEditable(false); 276 fTextView->MakeSelectable(false); 277 fTextView->SetWordWrap(false); 278 fRootView->AddChild(fTextView); 279 280 // kill app button 281 fKillAppButton = new(nothrow) BButton(BRect(0, 0, 10, 10), "kill app", 282 B_TRANSLATE("Kill application"), NULL, B_FOLLOW_NONE); 283 if (!fKillAppButton) 284 return B_NO_MEMORY; 285 fRootView->AddChild(fKillAppButton); 286 287 BMessage* message = new BMessage(MSG_KILL_APPLICATION); 288 if (!message) 289 return B_NO_MEMORY; 290 message->AddInt32("team", -1); 291 fKillAppMessage = message; 292 fKillAppButton->SetMessage(message); 293 fKillAppButton->SetTarget(target); 294 295 // cancel shutdown button 296 fCancelShutdownButton = new(nothrow) BButton(BRect(0, 0, 10, 10), 297 "cancel shutdown", B_TRANSLATE("Cancel shutdown"), NULL, 298 B_FOLLOW_NONE); 299 if (!fCancelShutdownButton) 300 return B_NO_MEMORY; 301 fRootView->AddChild(fCancelShutdownButton); 302 303 message = new BMessage(MSG_CANCEL_SHUTDOWN); 304 if (!message) 305 return B_NO_MEMORY; 306 fCancelShutdownButton->SetMessage(message); 307 fCancelShutdownButton->SetTarget(target); 308 309 // reboot system button 310 fRebootSystemButton = new(nothrow) BButton(BRect(0, 0, 10, 10), 311 "reboot", B_TRANSLATE("Restart system"), NULL, B_FOLLOW_NONE); 312 if (!fRebootSystemButton) 313 return B_NO_MEMORY; 314 fRebootSystemButton->Hide(); 315 fRootView->AddChild(fRebootSystemButton); 316 317 message = new BMessage(MSG_REBOOT_SYSTEM); 318 if (!message) 319 return B_NO_MEMORY; 320 fRebootSystemButton->SetMessage(message); 321 fRebootSystemButton->SetTarget(target); 322 323 // aborted OK button 324 fAbortedOKButton = new(nothrow) BButton(BRect(0, 0, 10, 10), 325 "ok", B_TRANSLATE("OK"), NULL, B_FOLLOW_NONE); 326 if (!fAbortedOKButton) 327 return B_NO_MEMORY; 328 fAbortedOKButton->Hide(); 329 fRootView->AddChild(fAbortedOKButton); 330 331 message = new BMessage(MSG_CANCEL_SHUTDOWN); 332 if (!message) 333 return B_NO_MEMORY; 334 fAbortedOKButton->SetMessage(message); 335 fAbortedOKButton->SetTarget(target); 336 337 // compute the sizes 338 static const int kHSpacing = 10; 339 static const int kVSpacing = 10; 340 static const int kInnerHSpacing = 5; 341 static const int kInnerVSpacing = 8; 342 343 // buttons 344 fKillAppButton->ResizeToPreferred(); 345 fCancelShutdownButton->ResizeToPreferred(); 346 fRebootSystemButton->MakeDefault(true); 347 fRebootSystemButton->ResizeToPreferred(); 348 fAbortedOKButton->MakeDefault(true); 349 fAbortedOKButton->ResizeToPreferred(); 350 351 BRect rect(fKillAppButton->Frame()); 352 int buttonWidth = rect.IntegerWidth() + 1; 353 int buttonHeight = rect.IntegerHeight() + 1; 354 355 rect = fCancelShutdownButton->Frame(); 356 if (rect.IntegerWidth() >= buttonWidth) 357 buttonWidth = rect.IntegerWidth() + 1; 358 359 int defaultButtonHeight 360 = fRebootSystemButton->Frame().IntegerHeight() + 1; 361 362 // text view 363 fTextView->SetText("two\nlines"); 364 int textHeight = (int)fTextView->TextHeight(0, 1) + 1; 365 366 int rightPartX = kStripeWidth + kIconSize / 2 + 1; 367 int textX = rightPartX + kInnerHSpacing; 368 int textY = kVSpacing; 369 int buttonsY = textY + textHeight + kInnerVSpacing; 370 int nonDefaultButtonsY = buttonsY 371 + (defaultButtonHeight - buttonHeight) / 2; 372 int rightPartWidth = 2 * buttonWidth + kInnerHSpacing; 373 int width = rightPartX + rightPartWidth + kHSpacing; 374 int height = buttonsY + defaultButtonHeight + kVSpacing; 375 376 // now layout the views 377 378 // text view 379 fTextView->MoveTo(textX, textY); 380 fTextView->ResizeTo(rightPartWidth + rightPartX - textX - 1, 381 textHeight - 1); 382 fTextView->SetTextRect(fTextView->Bounds()); 383 384 fTextView->SetWordWrap(true); 385 386 // buttons 387 fKillAppButton->MoveTo(rightPartX, nonDefaultButtonsY); 388 fKillAppButton->ResizeTo(buttonWidth - 1, buttonHeight - 1); 389 390 fCancelShutdownButton->MoveTo( 391 rightPartX + buttonWidth + kInnerVSpacing - 1, 392 nonDefaultButtonsY); 393 fCancelShutdownButton->ResizeTo(buttonWidth - 1, buttonHeight - 1); 394 395 fRebootSystemButton->MoveTo( 396 (width - fRebootSystemButton->Frame().IntegerWidth()) / 2, 397 buttonsY); 398 399 fAbortedOKButton->MoveTo( 400 (width - fAbortedOKButton->Frame().IntegerWidth()) / 2, 401 buttonsY); 402 403 // set the root view and window size 404 fRootView->ResizeTo(width - 1, height - 1); 405 ResizeTo(width - 1, height - 1); 406 407 // move the window to the same position as BAlerts 408 BScreen screen(this); 409 BRect screenFrame = screen.Frame(); 410 411 MoveTo(screenFrame.left + (screenFrame.Width() - width) / 2.0, 412 screenFrame.top + screenFrame.Height() / 4.0 - ceilf(height / 3.0)); 413 414 return B_OK; 415 } 416 417 status_t AddApp(team_id team, BBitmap* miniIcon, BBitmap* largeIcon) 418 { 419 AppInfo* info = new(nothrow) AppInfo; 420 if (!info) { 421 delete miniIcon; 422 delete largeIcon; 423 return B_NO_MEMORY; 424 } 425 426 info->team = team; 427 info->miniIcon = miniIcon; 428 info->largeIcon = largeIcon; 429 430 if (!fAppInfos.AddItem(info)) { 431 delete info; 432 return B_NO_MEMORY; 433 } 434 435 return B_OK; 436 } 437 438 void RemoveApp(team_id team) 439 { 440 int32 index = _AppInfoIndexOf(team); 441 if (index < 0) 442 return; 443 444 if (team == fCurrentApp) 445 SetCurrentApp(-1); 446 447 AppInfo* info = (AppInfo*)fAppInfos.RemoveItem(index); 448 delete info; 449 } 450 451 void SetCurrentApp(team_id team) 452 { 453 AppInfo* info = (team >= 0 ? _AppInfoFor(team) : NULL); 454 455 fCurrentApp = team; 456 fRootView->SetAppInfo(info); 457 458 fKillAppMessage->ReplaceInt32("team", team); 459 } 460 461 void SetText(const char* text) 462 { 463 fTextView->SetText(text); 464 } 465 466 void SetCancelShutdownButtonEnabled(bool enable) 467 { 468 fCancelShutdownButton->SetEnabled(enable); 469 } 470 471 void SetKillAppButtonEnabled(bool enable) 472 { 473 if (enable != fKillAppButton->IsEnabled()) { 474 fKillAppButton->SetEnabled(enable); 475 476 if (enable) 477 fKillAppButton->Show(); 478 else 479 fKillAppButton->Hide(); 480 } 481 } 482 483 void SetWaitForShutdown() 484 { 485 fKillAppButton->Hide(); 486 fCancelShutdownButton->Hide(); 487 fRebootSystemButton->MakeDefault(true); 488 fRebootSystemButton->Show(); 489 490 SetTitle(B_TRANSLATE("System is shut down")); 491 fTextView->SetText( 492 B_TRANSLATE("It's now safe to turn off the computer.")); 493 } 494 495 void SetWaitForAbortedOK() 496 { 497 fKillAppButton->Hide(); 498 fCancelShutdownButton->Hide(); 499 fAbortedOKButton->MakeDefault(true); 500 fAbortedOKButton->Show(); 501 // TODO: Temporary work-around for a Haiku bug. 502 fAbortedOKButton->Invalidate(); 503 504 SetTitle(B_TRANSLATE("Shutdown aborted")); 505 } 506 507 private: 508 struct AppInfo { 509 team_id team; 510 BBitmap *miniIcon; 511 BBitmap *largeIcon; 512 513 ~AppInfo() 514 { 515 delete miniIcon; 516 delete largeIcon; 517 } 518 }; 519 520 int32 _AppInfoIndexOf(team_id team) 521 { 522 if (team < 0) 523 return -1; 524 525 for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) { 526 if (info->team == team) 527 return i; 528 } 529 530 return -1; 531 } 532 533 AppInfo* _AppInfoFor(team_id team) 534 { 535 int32 index = _AppInfoIndexOf(team); 536 return (index >= 0 ? (AppInfo*)fAppInfos.ItemAt(index) : NULL); 537 } 538 539 class TAlertView : public BView { 540 public: 541 TAlertView(BRect frame, const char* name, uint32 resizeMask, 542 uint32 flags) 543 : BView(frame, name, resizeMask, flags | B_WILL_DRAW), 544 fAppInfo(NULL) 545 { 546 } 547 548 virtual void Draw(BRect updateRect) 549 { 550 BRect stripeRect = Bounds(); 551 stripeRect.right = kStripeWidth; 552 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); 553 FillRect(stripeRect); 554 555 if (fAppInfo && fAppInfo->largeIcon) { 556 if (fAppInfo->largeIcon->ColorSpace() == B_RGBA32) { 557 SetDrawingMode(B_OP_ALPHA); 558 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 559 } else 560 SetDrawingMode(B_OP_OVER); 561 562 DrawBitmapAsync(fAppInfo->largeIcon, 563 BPoint(kStripeWidth - kIconSize / 2, kIconVSpacing)); 564 } 565 } 566 567 void SetAppInfo(AppInfo* info) 568 { 569 fAppInfo = info; 570 Invalidate(); 571 } 572 573 private: 574 const AppInfo *fAppInfo; 575 }; 576 577 private: 578 BList fAppInfos; 579 TAlertView* fRootView; 580 BTextView* fTextView; 581 BButton* fKillAppButton; 582 BButton* fCancelShutdownButton; 583 BButton* fRebootSystemButton; 584 BButton* fAbortedOKButton; 585 BMessage* fKillAppMessage; 586 team_id fCurrentApp; 587 }; 588 589 590 // #pragma mark - 591 592 593 ShutdownProcess::ShutdownProcess(TRoster* roster, EventQueue* eventQueue) 594 : 595 BLooper("shutdown process"), 596 EventMaskWatcher(BMessenger(this), B_REQUEST_QUIT | B_REQUEST_LAUNCHED), 597 fWorkerLock("worker lock"), 598 fRequest(NULL), 599 fRoster(roster), 600 fEventQueue(eventQueue), 601 fTimeoutEvent(NULL), 602 fInternalEvents(NULL), 603 fInternalEventSemaphore(-1), 604 fQuitRequestReplyHandler(NULL), 605 fWorker(-1), 606 fCurrentPhase(INVALID_PHASE), 607 fShutdownError(B_ERROR), 608 fHasGUI(false), 609 fReboot(false), 610 fRequestReplySent(false), 611 fWindow(NULL) 612 { 613 } 614 615 616 ShutdownProcess::~ShutdownProcess() 617 { 618 // terminate the GUI 619 if (fHasGUI && fWindow && fWindow->Lock()) 620 fWindow->Quit(); 621 622 // remove and delete the quit request reply handler 623 if (fQuitRequestReplyHandler) { 624 BAutolock _(this); 625 RemoveHandler(fQuitRequestReplyHandler); 626 delete fQuitRequestReplyHandler; 627 } 628 629 // remove and delete the timeout event 630 if (fTimeoutEvent) { 631 fEventQueue->RemoveEvent(fTimeoutEvent); 632 633 delete fTimeoutEvent; 634 } 635 636 // remove the application quit watcher 637 fRoster->RemoveWatcher(this); 638 639 // If an error occurred (e.g. the shutdown process was cancelled), the 640 // roster should accept applications again. 641 if (fShutdownError != B_OK) 642 fRoster->SetShuttingDown(false); 643 644 // delete the internal event semaphore 645 if (fInternalEventSemaphore >= 0) 646 delete_sem(fInternalEventSemaphore); 647 648 // wait for the worker thread to terminate 649 if (fWorker >= 0) { 650 int32 result; 651 wait_for_thread(fWorker, &result); 652 } 653 654 // delete all internal events and the queue 655 if (fInternalEvents) { 656 while (InternalEvent* event = fInternalEvents->First()) { 657 fInternalEvents->Remove(event); 658 delete event; 659 } 660 661 delete fInternalEvents; 662 } 663 664 // send a reply to the request and delete it 665 _SendReply(fShutdownError); 666 delete fRequest; 667 } 668 669 670 status_t 671 ShutdownProcess::Init(BMessage* request) 672 { 673 PRINT("ShutdownProcess::Init()\n"); 674 675 // create and add the quit request reply handler 676 fQuitRequestReplyHandler = new(nothrow) QuitRequestReplyHandler(this); 677 if (!fQuitRequestReplyHandler) 678 RETURN_ERROR(B_NO_MEMORY); 679 AddHandler(fQuitRequestReplyHandler); 680 681 // create the timeout event 682 fTimeoutEvent = new(nothrow) TimeoutEvent(this); 683 if (!fTimeoutEvent) 684 RETURN_ERROR(B_NO_MEMORY); 685 686 // create the event list 687 fInternalEvents = new(nothrow) InternalEventList; 688 if (!fInternalEvents) 689 RETURN_ERROR(B_NO_MEMORY); 690 691 // create the event sempahore 692 fInternalEventSemaphore = create_sem(0, "shutdown events"); 693 if (fInternalEventSemaphore < 0) 694 RETURN_ERROR(fInternalEventSemaphore); 695 696 // init the app server connection 697 fHasGUI = Registrar::App()->InitGUIContext() == B_OK; 698 699 // start watching application quits 700 status_t error = fRoster->AddWatcher(this); 701 if (error != B_OK) { 702 fRoster->SetShuttingDown(false); 703 RETURN_ERROR(error); 704 } 705 706 // start the worker thread 707 fWorker = spawn_thread(_WorkerEntry, "shutdown worker", 708 B_NORMAL_PRIORITY + 1, this); 709 if (fWorker < 0) { 710 fRoster->RemoveWatcher(this); 711 fRoster->SetShuttingDown(false); 712 RETURN_ERROR(fWorker); 713 } 714 715 // everything went fine: now we own the request 716 fRequest = request; 717 718 if (fRequest->FindBool("reboot", &fReboot) != B_OK) 719 fReboot = false; 720 721 resume_thread(fWorker); 722 723 PRINT("ShutdownProcess::Init() done\n"); 724 725 return B_OK; 726 } 727 728 729 void 730 ShutdownProcess::MessageReceived(BMessage* message) 731 { 732 switch (message->what) { 733 case B_SOME_APP_QUIT: 734 { 735 // get the team 736 team_id team; 737 if (message->FindInt32("be:team", &team) != B_OK) { 738 // should not happen 739 return; 740 } 741 742 PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_QUIT: %" 743 B_PRId32 "\n", team); 744 745 // remove the app info from the respective list 746 int32 phase; 747 RosterAppInfo* info; 748 { 749 BAutolock _(fWorkerLock); 750 751 info = fUserApps.InfoFor(team); 752 if (info) 753 fUserApps.RemoveInfo(info); 754 else if ((info = fSystemApps.InfoFor(team))) 755 fSystemApps.RemoveInfo(info); 756 else if ((info = fBackgroundApps.InfoFor(team))) 757 fBackgroundApps.RemoveInfo(info); 758 else // not found 759 return; 760 761 phase = fCurrentPhase; 762 } 763 764 // post the event 765 _PushEvent(APP_QUIT_EVENT, team, phase); 766 767 delete info; 768 769 break; 770 } 771 772 case B_SOME_APP_LAUNCHED: 773 { 774 // get the team 775 team_id team; 776 if (message->FindInt32("be:team", &team) != B_OK) { 777 // should not happen 778 return; 779 } 780 781 PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_LAUNCHED: %" 782 B_PRId32 "\n", team); 783 784 // add the user app info to the respective list 785 { 786 BAutolock _(fWorkerLock); 787 fRoster->AddAppInfo(fUserApps, team); 788 } 789 break; 790 } 791 792 case MSG_PHASE_TIMED_OUT: 793 { 794 // get the phase the event is intended for 795 int32 phase = TimeoutEvent::GetMessagePhase(message); 796 team_id team = TimeoutEvent::GetMessageTeam(message);; 797 PRINT("MSG_PHASE_TIMED_OUT: phase: %" B_PRId32 ", team: %" B_PRId32 798 "\n", phase, team); 799 800 BAutolock _(fWorkerLock); 801 802 if (phase == INVALID_PHASE || phase != fCurrentPhase) 803 return; 804 805 // post the event 806 _PushEvent(TIMEOUT_EVENT, team, phase); 807 808 break; 809 } 810 811 case MSG_KILL_APPLICATION: 812 { 813 team_id team; 814 if (message->FindInt32("team", &team) != B_OK) 815 break; 816 817 // post the event 818 _PushEvent(KILL_APP_EVENT, team, fCurrentPhase); 819 break; 820 } 821 822 case MSG_CANCEL_SHUTDOWN: 823 { 824 // post the event 825 _PushEvent(ABORT_EVENT, -1, fCurrentPhase); 826 break; 827 } 828 829 case MSG_REBOOT_SYSTEM: 830 { 831 // post the event 832 _PushEvent(REBOOT_SYSTEM_EVENT, -1, INVALID_PHASE); 833 break; 834 } 835 836 case MSG_DONE: 837 { 838 // notify the registrar that we're done 839 be_app->PostMessage(B_REG_SHUTDOWN_FINISHED, be_app); 840 break; 841 } 842 843 case B_REG_TEAM_DEBUGGER_ALERT: 844 { 845 bool stopShutdown; 846 if (message->FindBool("stop shutdown", &stopShutdown) == B_OK 847 && stopShutdown) { 848 // post abort event to the worker 849 _PushEvent(ABORT_EVENT, -1, fCurrentPhase); 850 break; 851 } 852 853 bool open; 854 team_id team; 855 if (message->FindInt32("team", &team) != B_OK 856 || message->FindBool("open", &open) != B_OK) 857 break; 858 859 BAutolock _(fWorkerLock); 860 if (open) { 861 PRINT("B_REG_TEAM_DEBUGGER_ALERT: insert %" B_PRId32 "\n", 862 team); 863 fDebuggedTeams.insert(team); 864 } else { 865 PRINT("B_REG_TEAM_DEBUGGER_ALERT: remove %" B_PRId32 "\n", 866 team); 867 fDebuggedTeams.erase(team); 868 _PushEvent(DEBUG_EVENT, -1, fCurrentPhase); 869 } 870 break; 871 } 872 873 default: 874 BLooper::MessageReceived(message); 875 break; 876 } 877 } 878 879 880 void 881 ShutdownProcess::SendReply(BMessage* request, status_t error) 882 { 883 if (error == B_OK) { 884 BMessage reply(B_REG_SUCCESS); 885 request->SendReply(&reply); 886 } else { 887 BMessage reply(B_REG_ERROR); 888 reply.AddInt32("error", error); 889 request->SendReply(&reply); 890 } 891 } 892 893 894 void 895 ShutdownProcess::_SendReply(status_t error) 896 { 897 if (!fRequestReplySent) { 898 SendReply(fRequest, error); 899 fRequestReplySent = true; 900 } 901 } 902 903 904 void 905 ShutdownProcess::_SetPhase(int32 phase) 906 { 907 BAutolock _(fWorkerLock); 908 909 if (phase == fCurrentPhase) 910 return; 911 912 fCurrentPhase = phase; 913 914 // remove the timeout event scheduled for the previous phase 915 fEventQueue->RemoveEvent(fTimeoutEvent); 916 } 917 918 919 void 920 ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout, team_id team) 921 { 922 BAutolock _(fWorkerLock); 923 924 // remove the timeout event 925 fEventQueue->RemoveEvent(fTimeoutEvent); 926 927 // set the event's phase, team and time 928 fTimeoutEvent->SetPhase(fCurrentPhase); 929 fTimeoutEvent->SetTeam(team); 930 fTimeoutEvent->SetTime(system_time() + timeout); 931 932 // add the event 933 fEventQueue->AddEvent(fTimeoutEvent); 934 } 935 936 937 void 938 ShutdownProcess::_SetShowShutdownWindow(bool show) 939 { 940 if (fHasGUI) { 941 BAutolock _(fWindow); 942 943 if (show == fWindow->IsHidden()) { 944 if (show) 945 fWindow->Show(); 946 else 947 fWindow->Hide(); 948 } 949 } 950 } 951 952 953 void 954 ShutdownProcess::_InitShutdownWindow() 955 { 956 // prepare the window 957 if (fHasGUI) { 958 fWindow = new(nothrow) ShutdownWindow; 959 if (fWindow != NULL) { 960 status_t error = fWindow->Init(BMessenger(this)); 961 if (error != B_OK) { 962 delete fWindow; 963 fWindow = NULL; 964 } 965 } 966 967 // add the applications 968 if (fWindow) { 969 BAutolock _(fWorkerLock); 970 _AddShutdownWindowApps(fUserApps); 971 _AddShutdownWindowApps(fSystemApps); 972 } else { 973 WARNING("ShutdownProcess::Init(): Failed to create or init " 974 "shutdown window."); 975 976 fHasGUI = false; 977 } 978 } 979 } 980 981 982 void 983 ShutdownProcess::_AddShutdownWindowApps(AppInfoList& infos) 984 { 985 if (!fHasGUI) 986 return; 987 988 for (AppInfoList::Iterator it = infos.It(); it.IsValid(); ++it) { 989 RosterAppInfo* info = *it; 990 991 // init an app file info 992 BFile file; 993 status_t error = file.SetTo(&info->ref, B_READ_ONLY); 994 if (error != B_OK) { 995 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 996 "open file for app %s: %s\n", info->signature, 997 strerror(error)); 998 continue; 999 } 1000 1001 BAppFileInfo appFileInfo; 1002 error = appFileInfo.SetTo(&file); 1003 if (error != B_OK) { 1004 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 1005 "init app file info for app %s: %s\n", info->signature, 1006 strerror(error)); 1007 } 1008 1009 // get the application icons 1010 #ifdef __HAIKU__ 1011 color_space format = B_RGBA32; 1012 #else 1013 color_space format = B_CMAP8; 1014 #endif 1015 1016 // mini icon 1017 BBitmap* miniIcon = new(nothrow) BBitmap(BRect(0, 0, 15, 15), format); 1018 if (miniIcon != NULL) { 1019 error = miniIcon->InitCheck(); 1020 if (error == B_OK) 1021 error = appFileInfo.GetTrackerIcon(miniIcon, B_MINI_ICON); 1022 if (error != B_OK) { 1023 delete miniIcon; 1024 miniIcon = NULL; 1025 } 1026 } 1027 1028 // mini icon 1029 BBitmap* largeIcon = new(nothrow) BBitmap(BRect(0, 0, 31, 31), format); 1030 if (largeIcon != NULL) { 1031 error = largeIcon->InitCheck(); 1032 if (error == B_OK) 1033 error = appFileInfo.GetTrackerIcon(largeIcon, B_LARGE_ICON); 1034 if (error != B_OK) { 1035 delete largeIcon; 1036 largeIcon = NULL; 1037 } 1038 } 1039 1040 // add the app 1041 error = fWindow->AddApp(info->team, miniIcon, largeIcon); 1042 if (error != B_OK) { 1043 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 1044 "add app to the shutdown window: %s\n", strerror(error)); 1045 } 1046 } 1047 } 1048 1049 1050 void 1051 ShutdownProcess::_RemoveShutdownWindowApp(team_id team) 1052 { 1053 if (fHasGUI) { 1054 BAutolock _(fWindow); 1055 1056 fWindow->RemoveApp(team); 1057 } 1058 } 1059 1060 1061 void 1062 ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team) 1063 { 1064 if (fHasGUI) { 1065 BAutolock _(fWindow); 1066 1067 fWindow->SetCurrentApp(team); 1068 } 1069 } 1070 1071 1072 void 1073 ShutdownProcess::_SetShutdownWindowText(const char* text) 1074 { 1075 if (fHasGUI) { 1076 BAutolock _(fWindow); 1077 1078 fWindow->SetText(text); 1079 } 1080 } 1081 1082 1083 void 1084 ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled) 1085 { 1086 if (fHasGUI) { 1087 BAutolock _(fWindow); 1088 1089 fWindow->SetCancelShutdownButtonEnabled(enabled); 1090 } 1091 } 1092 1093 1094 void 1095 ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled) 1096 { 1097 if (fHasGUI) { 1098 BAutolock _(fWindow); 1099 1100 fWindow->SetKillAppButtonEnabled(enabled); 1101 } 1102 } 1103 1104 1105 void 1106 ShutdownProcess::_SetShutdownWindowWaitForShutdown() 1107 { 1108 if (fHasGUI) { 1109 BAutolock _(fWindow); 1110 1111 fWindow->SetWaitForShutdown(); 1112 } 1113 } 1114 1115 1116 void 1117 ShutdownProcess::_SetShutdownWindowWaitForAbortedOK() 1118 { 1119 if (fHasGUI) { 1120 BAutolock _(fWindow); 1121 1122 fWindow->SetWaitForAbortedOK(); 1123 } 1124 } 1125 1126 1127 void 1128 ShutdownProcess::_NegativeQuitRequestReply(thread_id thread) 1129 { 1130 BAutolock _(fWorkerLock); 1131 1132 // Note: team ID == team main thread ID under Haiku. When testing under R5 1133 // using the team ID in case of an ABORT_EVENT won't work correctly. But 1134 // this is done only for system apps. 1135 _PushEvent(ABORT_EVENT, thread, fCurrentPhase); 1136 } 1137 1138 1139 void 1140 ShutdownProcess::_PrepareShutdownMessage(BMessage& message) const 1141 { 1142 message.what = B_QUIT_REQUESTED; 1143 message.AddBool("_shutdown_", true); 1144 1145 BMessage::Private(message).SetReply(BMessenger(fQuitRequestReplyHandler)); 1146 } 1147 1148 1149 status_t 1150 ShutdownProcess::_ShutDown() 1151 { 1152 PRINT("Invoking _kern_shutdown(%d)\n", fReboot); 1153 RETURN_ERROR(_kern_shutdown(fReboot)); 1154 } 1155 1156 1157 status_t 1158 ShutdownProcess::_PushEvent(uint32 eventType, team_id team, int32 phase) 1159 { 1160 InternalEvent* event = new(nothrow) InternalEvent(eventType, team, phase); 1161 if (!event) { 1162 ERROR("ShutdownProcess::_PushEvent(): Failed to create event!\n"); 1163 1164 return B_NO_MEMORY; 1165 } 1166 1167 BAutolock _(fWorkerLock); 1168 1169 fInternalEvents->Add(event); 1170 release_sem(fInternalEventSemaphore); 1171 1172 return B_OK; 1173 } 1174 1175 1176 status_t 1177 ShutdownProcess::_GetNextEvent(uint32& eventType, thread_id& team, int32& phase, 1178 bool block) 1179 { 1180 while (true) { 1181 // acquire the semaphore 1182 if (block) { 1183 status_t error; 1184 do { 1185 error = acquire_sem(fInternalEventSemaphore); 1186 } while (error == B_INTERRUPTED); 1187 1188 if (error != B_OK) 1189 return error; 1190 } else { 1191 status_t error = acquire_sem_etc(fInternalEventSemaphore, 1, 1192 B_RELATIVE_TIMEOUT, 0); 1193 if (error != B_OK) { 1194 eventType = NO_EVENT; 1195 return B_OK; 1196 } 1197 } 1198 1199 // get the event 1200 BAutolock _(fWorkerLock); 1201 1202 InternalEvent* event = fInternalEvents->Head(); 1203 fInternalEvents->Remove(event); 1204 1205 eventType = event->Type(); 1206 team = event->Team(); 1207 phase = event->Phase(); 1208 1209 delete event; 1210 1211 // if the event is an obsolete timeout event, we drop it right here 1212 if (eventType == TIMEOUT_EVENT && phase != fCurrentPhase) 1213 continue; 1214 1215 break; 1216 } 1217 1218 // notify the window, if an app has been removed 1219 if (eventType == APP_QUIT_EVENT) 1220 _RemoveShutdownWindowApp(team); 1221 1222 return B_OK; 1223 } 1224 1225 1226 status_t 1227 ShutdownProcess::_WorkerEntry(void* data) 1228 { 1229 return ((ShutdownProcess*)data)->_Worker(); 1230 } 1231 1232 1233 status_t 1234 ShutdownProcess::_Worker() 1235 { 1236 try { 1237 _WorkerDoShutdown(); 1238 fShutdownError = B_OK; 1239 } catch (status_t error) { 1240 PRINT("ShutdownProcess::_Worker(): error while shutting down: %s\n", 1241 strerror(error)); 1242 1243 fShutdownError = error; 1244 } 1245 1246 // this can happen only, if the shutdown process failed or was aborted: 1247 // notify the looper 1248 _SetPhase(DONE_PHASE); 1249 PostMessage(MSG_DONE); 1250 1251 return B_OK; 1252 } 1253 1254 1255 void 1256 ShutdownProcess::_WorkerDoShutdown() 1257 { 1258 PRINT("ShutdownProcess::_WorkerDoShutdown()\n"); 1259 1260 // If we are here, the shutdown process has been initiated successfully, 1261 // that is, if an asynchronous BRoster::Shutdown() was requested, we 1262 // notify the caller at this point. 1263 bool synchronous; 1264 if (fRequest->FindBool("synchronous", &synchronous) == B_OK && !synchronous) 1265 _SendReply(B_OK); 1266 1267 // ask the user to confirm the shutdown, if desired 1268 bool askUser; 1269 if (fHasGUI && fRequest->FindBool("confirm", &askUser) == B_OK && askUser) { 1270 const char* restart = B_TRANSLATE("Restart"); 1271 const char* shutdown = B_TRANSLATE("Shut down"); 1272 BString title = B_TRANSLATE("%action%?"); 1273 title.ReplaceFirst("%action%", fReboot ? restart : shutdown); 1274 const char* text = fReboot 1275 ? B_TRANSLATE("Do you really want to restart the system?") 1276 : B_TRANSLATE("Do you really want to shut down the system?"); 1277 const char* defaultText = fReboot ? restart : shutdown; 1278 const char* otherText = fReboot ? shutdown : restart; 1279 BAlert* alert = new BAlert(title.String(), text, 1280 B_TRANSLATE("Cancel"), otherText, defaultText, 1281 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1282 // We want the alert to behave more like a regular window... 1283 alert->SetFeel(B_NORMAL_WINDOW_FEEL); 1284 // ...but not quit. Minimizing the alert would prevent the user from 1285 // finding it again, since registrar does not have an entry in the 1286 // Deskbar. 1287 alert->SetFlags(alert->Flags() | B_NOT_MINIMIZABLE | B_CLOSE_ON_ESCAPE); 1288 alert->SetWorkspaces(B_ALL_WORKSPACES); 1289 int32 result = alert->Go(); 1290 1291 if (result == 1) { 1292 // Toggle shutdown method 1293 fReboot = !fReboot; 1294 } else if (result < 1) 1295 throw_error(B_SHUTDOWN_CANCELLED); 1296 } 1297 1298 fWorkerLock.Lock(); 1299 // get a list of all applications to shut down and sort them 1300 status_t status = fRoster->GetShutdownApps(fUserApps, fSystemApps, 1301 fBackgroundApps, fVitalSystemApps); 1302 if (status != B_OK) { 1303 fWorkerLock.Unlock(); 1304 fRoster->RemoveWatcher(this); 1305 return; 1306 } 1307 1308 fUserApps.Sort(&inverse_compare_by_registration_time); 1309 fSystemApps.Sort(&inverse_compare_by_registration_time); 1310 1311 fWorkerLock.Unlock(); 1312 1313 // make the shutdown window ready and show it 1314 _InitShutdownWindow(); 1315 _SetShutdownWindowCurrentApp(-1); 1316 _SetShutdownWindowText(B_TRANSLATE("Tidying things up a bit.")); 1317 _SetShutdownWindowCancelButtonEnabled(true); 1318 _SetShutdownWindowKillButtonEnabled(false); 1319 _SetShowShutdownWindow(true); 1320 1321 // sync 1322 sync(); 1323 1324 // phase 1: terminate the user apps 1325 _SetPhase(USER_APP_TERMINATION_PHASE); 1326 1327 // since, new apps can still be launched, loop until all are gone 1328 if (!fUserApps.IsEmpty()) { 1329 _QuitApps(fUserApps, false); 1330 _WaitForDebuggedTeams(); 1331 } 1332 1333 // tell TRoster not to accept new applications anymore 1334 fRoster->SetShuttingDown(true); 1335 1336 // phase 2: terminate the system apps 1337 _SetPhase(SYSTEM_APP_TERMINATION_PHASE); 1338 _QuitApps(fSystemApps, true); 1339 _WaitForDebuggedTeams(); 1340 1341 // phase 3: terminate the background apps 1342 _SetPhase(BACKGROUND_APP_TERMINATION_PHASE); 1343 _QuitBackgroundApps(); 1344 _WaitForDebuggedTeams(); 1345 1346 // phase 4: terminate the other processes 1347 _SetPhase(OTHER_PROCESSES_TERMINATION_PHASE); 1348 _QuitNonApps(); 1349 _ScheduleTimeoutEvent(kBackgroundAppQuitTimeout, -1); 1350 _WaitForBackgroundApps(); 1351 _KillBackgroundApps(); 1352 _WaitForDebuggedTeams(); 1353 1354 // we're through: do the shutdown 1355 _SetPhase(DONE_PHASE); 1356 if (fReboot) 1357 _SetShutdownWindowText(B_TRANSLATE("Restarting" B_UTF8_ELLIPSIS)); 1358 else 1359 _SetShutdownWindowText(B_TRANSLATE("Shutting down" B_UTF8_ELLIPSIS)); 1360 _ShutDown(); 1361 _SetShutdownWindowWaitForShutdown(); 1362 1363 PRINT(" _kern_shutdown() failed\n"); 1364 1365 // shutdown failed: This can happen for power off mode -- reboot should 1366 // always work. 1367 if (fHasGUI) { 1368 // wait for the reboot event 1369 uint32 event; 1370 do { 1371 team_id team; 1372 int32 phase; 1373 status = _GetNextEvent(event, team, phase, true); 1374 if (status != B_OK) 1375 break; 1376 } while (event != REBOOT_SYSTEM_EVENT); 1377 1378 _kern_shutdown(true); 1379 } 1380 1381 // either there's no GUI or reboot failed: we enter the kernel debugger 1382 // instead 1383 #ifdef __HAIKU__ 1384 // TODO: Introduce the syscall. 1385 // while (true) { 1386 // _kern_kernel_debugger("The system is shut down. It's now safe to turn " 1387 // "off the computer."); 1388 // } 1389 #endif 1390 } 1391 1392 1393 bool 1394 ShutdownProcess::_WaitForApp(team_id team, AppInfoList* list, bool systemApps) 1395 { 1396 uint32 event; 1397 do { 1398 team_id eventTeam; 1399 int32 phase; 1400 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1401 if (error != B_OK) 1402 throw_error(error); 1403 1404 if (event == APP_QUIT_EVENT && eventTeam == team) 1405 return true; 1406 1407 if (event == TIMEOUT_EVENT && eventTeam == team) 1408 return false; 1409 1410 if (event == ABORT_EVENT) { 1411 if (eventTeam == -1) { 1412 // The user canceled the shutdown process by pressing the 1413 // Cancel button. 1414 throw_error(B_SHUTDOWN_CANCELLED); 1415 } 1416 if (systemApps) { 1417 // If the app requests aborting the shutdown, we don't need 1418 // to wait any longer. It has processed the request and 1419 // won't quit by itself. We ignore this for system apps. 1420 if (eventTeam == team) 1421 return false; 1422 } else { 1423 // The app returned false in QuitRequested(). 1424 PRINT("ShutdownProcess::_WaitForApp(): shutdown cancelled " 1425 "by team %" B_PRId32 " (-1 => user)\n", eventTeam); 1426 1427 _DisplayAbortingApp(team); 1428 throw_error(B_SHUTDOWN_CANCELLED); 1429 } 1430 } 1431 1432 BAutolock _(fWorkerLock); 1433 if (list != NULL && !list->InfoFor(team)) 1434 return true; 1435 } while (event != NO_EVENT); 1436 1437 return false; 1438 } 1439 1440 1441 void 1442 ShutdownProcess::_QuitApps(AppInfoList& list, bool systemApps) 1443 { 1444 PRINT("ShutdownProcess::_QuitApps(%s)\n", 1445 (systemApps ? "system" : "user")); 1446 1447 if (systemApps) { 1448 _SetShutdownWindowCancelButtonEnabled(false); 1449 1450 // check one last time for abort events 1451 uint32 event; 1452 do { 1453 team_id team; 1454 int32 phase; 1455 status_t error = _GetNextEvent(event, team, phase, false); 1456 if (error != B_OK) 1457 throw_error(error); 1458 1459 if (event == ABORT_EVENT) { 1460 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by " 1461 "team %" B_PRId32 " (-1 => user)\n", team); 1462 1463 _DisplayAbortingApp(team); 1464 throw_error(B_SHUTDOWN_CANCELLED); 1465 } 1466 1467 } while (event != NO_EVENT); 1468 } 1469 1470 // prepare the shutdown message 1471 BMessage message; 1472 _PrepareShutdownMessage(message); 1473 1474 // now iterate through the list of apps 1475 while (true) { 1476 // eat events 1477 uint32 event; 1478 do { 1479 team_id team; 1480 int32 phase; 1481 status_t error = _GetNextEvent(event, team, phase, false); 1482 if (error != B_OK) 1483 throw_error(error); 1484 1485 if (!systemApps && event == ABORT_EVENT) { 1486 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by " 1487 "team %" B_PRId32 " (-1 => user)\n", team); 1488 1489 _DisplayAbortingApp(team); 1490 throw_error(B_SHUTDOWN_CANCELLED); 1491 } 1492 1493 } while (event != NO_EVENT); 1494 1495 // get the first app to quit 1496 team_id team = -1; 1497 port_id port = -1; 1498 char appName[B_FILE_NAME_LENGTH]; 1499 { 1500 BAutolock _(fWorkerLock); 1501 while (!list.IsEmpty()) { 1502 RosterAppInfo* info = *list.It(); 1503 team = info->team; 1504 port = info->port; 1505 strcpy(appName, info->ref.name); 1506 1507 if (info->IsRunning()) 1508 break; 1509 list.RemoveInfo(info); 1510 delete info; 1511 } 1512 } 1513 1514 if (team < 0) { 1515 PRINT("ShutdownProcess::_QuitApps() done\n"); 1516 return; 1517 } 1518 1519 // set window text 1520 BString buffer = B_TRANSLATE("Asking \"%appName%\" to quit."); 1521 buffer.ReplaceFirst("%appName%", appName); 1522 _SetShutdownWindowText(buffer.String()); 1523 _SetShutdownWindowCurrentApp(team); 1524 1525 // send the shutdown message to the app 1526 PRINT(" sending team %" B_PRId32 " (port: %" B_PRId32 ") a shutdown " 1527 "message\n", team, port); 1528 SingleMessagingTargetSet target(port, B_PREFERRED_TOKEN); 1529 MessageDeliverer::Default()->DeliverMessage(&message, target); 1530 1531 // schedule a timeout event 1532 _ScheduleTimeoutEvent(kAppQuitTimeout, team); 1533 1534 // wait for the app to die or for the timeout to occur 1535 bool appGone = _WaitForApp(team, &list, systemApps); 1536 if (appGone) { 1537 // fine: the app finished in an orderly manner 1538 } else { 1539 // the app is either blocking on a model alert or blocks for another 1540 // reason 1541 if (!systemApps) 1542 _QuitBlockingApp(list, team, appName, true); 1543 else { 1544 // This is a system app: remove it from the list 1545 BAutolock _(fWorkerLock); 1546 1547 if (RosterAppInfo* info = list.InfoFor(team)) { 1548 list.RemoveInfo(info); 1549 delete info; 1550 } 1551 } 1552 } 1553 } 1554 } 1555 1556 1557 void 1558 ShutdownProcess::_QuitBackgroundApps() 1559 { 1560 PRINT("ShutdownProcess::_QuitBackgroundApps()\n"); 1561 1562 _SetShutdownWindowText( 1563 B_TRANSLATE("Asking background applications to quit.")); 1564 1565 // prepare the shutdown message 1566 BMessage message; 1567 _PrepareShutdownMessage(message); 1568 1569 // send shutdown messages to user apps 1570 BAutolock _(fWorkerLock); 1571 1572 AppInfoListMessagingTargetSet targetSet(fBackgroundApps); 1573 1574 if (targetSet.HasNext()) { 1575 PRINT(" sending shutdown message to %" B_PRId32 " apps\n", 1576 fBackgroundApps.CountInfos()); 1577 1578 status_t error = MessageDeliverer::Default()->DeliverMessage( 1579 &message, targetSet); 1580 if (error != B_OK) { 1581 WARNING("_QuitBackgroundApps::_Worker(): Failed to deliver " 1582 "shutdown message to all applications: %s\n", 1583 strerror(error)); 1584 } 1585 } 1586 1587 PRINT("ShutdownProcess::_QuitBackgroundApps() done\n"); 1588 } 1589 1590 1591 void 1592 ShutdownProcess::_WaitForBackgroundApps() 1593 { 1594 PRINT("ShutdownProcess::_WaitForBackgroundApps()\n"); 1595 1596 // wait for user apps 1597 bool moreApps = true; 1598 while (moreApps) { 1599 { 1600 BAutolock _(fWorkerLock); 1601 moreApps = !fBackgroundApps.IsEmpty(); 1602 } 1603 1604 if (moreApps) { 1605 uint32 event; 1606 team_id team; 1607 int32 phase; 1608 status_t error = _GetNextEvent(event, team, phase, true); 1609 if (error != B_OK) 1610 throw_error(error); 1611 1612 if (event == ABORT_EVENT) { 1613 // ignore: it's too late to abort the shutdown 1614 } 1615 1616 if (event == TIMEOUT_EVENT) 1617 return; 1618 } 1619 } 1620 1621 PRINT("ShutdownProcess::_WaitForBackgroundApps() done\n"); 1622 } 1623 1624 1625 void 1626 ShutdownProcess::_KillBackgroundApps() 1627 { 1628 PRINT("ShutdownProcess::_KillBackgroundApps()\n"); 1629 1630 while (true) { 1631 // eat events (we need to be responsive for an abort event) 1632 uint32 event; 1633 do { 1634 team_id team; 1635 int32 phase; 1636 status_t error = _GetNextEvent(event, team, phase, false); 1637 if (error != B_OK) 1638 throw_error(error); 1639 1640 } while (event != NO_EVENT); 1641 1642 // get the first team to kill 1643 team_id team = -1; 1644 char appName[B_FILE_NAME_LENGTH]; 1645 AppInfoList& list = fBackgroundApps; 1646 { 1647 BAutolock _(fWorkerLock); 1648 1649 if (!list.IsEmpty()) { 1650 RosterAppInfo* info = *list.It(); 1651 team = info->team; 1652 strcpy(appName, info->ref.name); 1653 } 1654 } 1655 1656 1657 if (team < 0) { 1658 PRINT("ShutdownProcess::_KillBackgroundApps() done\n"); 1659 return; 1660 } 1661 1662 // the app is either blocking on a model alert or blocks for another 1663 // reason 1664 _QuitBlockingApp(list, team, appName, false); 1665 } 1666 } 1667 1668 1669 void 1670 ShutdownProcess::_QuitNonApps() 1671 { 1672 PRINT("ShutdownProcess::_QuitNonApps()\n"); 1673 1674 _SetShutdownWindowText(B_TRANSLATE("Asking other processes to quit.")); 1675 1676 // iterate through the remaining teams and send them the TERM signal 1677 int32 cookie = 0; 1678 team_info teamInfo; 1679 while (get_next_team_info(&cookie, &teamInfo) == B_OK) { 1680 if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) { 1681 PRINT(" sending team %" B_PRId32 " TERM signal\n", teamInfo.team); 1682 1683 #ifdef __HAIKU__ 1684 // Note: team ID == team main thread ID under Haiku 1685 send_signal(teamInfo.team, SIGTERM); 1686 #else 1687 // We don't want to do this when testing under R5, since it 1688 // would kill all teams besides our app server and registrar. 1689 #endif 1690 } 1691 } 1692 1693 // give them a bit of time to terminate 1694 // TODO: Instead of just waiting we could periodically check whether the 1695 // processes are already gone to shorten the process. 1696 snooze(kNonAppQuitTimeout); 1697 1698 // iterate through the remaining teams and kill them 1699 cookie = 0; 1700 while (get_next_team_info(&cookie, &teamInfo) == B_OK) { 1701 if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) { 1702 PRINT(" killing team %" B_PRId32 "\n", teamInfo.team); 1703 1704 #ifdef __HAIKU__ 1705 kill_team(teamInfo.team); 1706 #else 1707 // We don't want to do this when testing under R5, since it 1708 // would kill all teams besides our app server and registrar. 1709 #endif 1710 } 1711 } 1712 1713 PRINT("ShutdownProcess::_QuitNonApps() done\n"); 1714 } 1715 1716 1717 void 1718 ShutdownProcess::_QuitBlockingApp(AppInfoList& list, team_id team, 1719 const char* appName, bool cancelAllowed) 1720 { 1721 bool debugged = false; 1722 bool modal = false; 1723 { 1724 BAutolock _(fWorkerLock); 1725 if (fDebuggedTeams.find(team) != fDebuggedTeams.end()) 1726 debugged = true; 1727 } 1728 if (!debugged) 1729 modal = BPrivate::is_app_showing_modal_window(team); 1730 1731 if (modal) { 1732 // app blocks on a modal window 1733 BString buffer = B_TRANSLATE("The application \"%appName%\" might be " 1734 "blocked on a modal panel."); 1735 buffer.ReplaceFirst("%appName%", appName); 1736 _SetShutdownWindowText(buffer.String()); 1737 _SetShutdownWindowCurrentApp(team); 1738 _SetShutdownWindowKillButtonEnabled(true); 1739 } 1740 1741 if (modal || debugged) { 1742 // wait for something to happen 1743 bool appGone = false; 1744 while (true) { 1745 uint32 event; 1746 team_id eventTeam; 1747 int32 phase; 1748 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1749 if (error != B_OK) 1750 throw_error(error); 1751 1752 if ((event == APP_QUIT_EVENT) && eventTeam == team) { 1753 appGone = true; 1754 break; 1755 } 1756 1757 if (event == KILL_APP_EVENT && eventTeam == team) 1758 break; 1759 1760 if (event == ABORT_EVENT) { 1761 if (cancelAllowed || debugged) { 1762 PRINT("ShutdownProcess::_QuitBlockingApp(): shutdown " 1763 "cancelled by team %" B_PRId32 " (-1 => user)\n", 1764 eventTeam); 1765 1766 if (!debugged) 1767 _DisplayAbortingApp(eventTeam); 1768 throw_error(B_SHUTDOWN_CANCELLED); 1769 } 1770 1771 // If the app requests aborting the shutdown, we don't need 1772 // to wait any longer. It has processed the request and 1773 // won't quit by itself. We'll have to kill it. 1774 if (eventTeam == team) 1775 break; 1776 } 1777 } 1778 1779 _SetShutdownWindowKillButtonEnabled(false); 1780 1781 if (appGone) 1782 return; 1783 } 1784 1785 // kill the app 1786 PRINT(" killing team %" B_PRId32 "\n", team); 1787 1788 kill_team(team); 1789 1790 // remove the app (the roster will note eventually and send us 1791 // a notification, but we want to be sure) 1792 { 1793 BAutolock _(fWorkerLock); 1794 1795 if (RosterAppInfo* info = list.InfoFor(team)) { 1796 list.RemoveInfo(info); 1797 delete info; 1798 } 1799 } 1800 } 1801 1802 1803 void 1804 ShutdownProcess::_DisplayAbortingApp(team_id team) 1805 { 1806 // find the app that cancelled the shutdown 1807 char appName[B_FILE_NAME_LENGTH]; 1808 bool foundApp = false; 1809 { 1810 BAutolock _(fWorkerLock); 1811 1812 RosterAppInfo* info = fUserApps.InfoFor(team); 1813 if (!info) 1814 info = fSystemApps.InfoFor(team); 1815 if (!info) 1816 fBackgroundApps.InfoFor(team); 1817 1818 if (info) { 1819 foundApp = true; 1820 strcpy(appName, info->ref.name); 1821 } 1822 } 1823 1824 if (!foundApp) { 1825 PRINT("ShutdownProcess::_DisplayAbortingApp(): Didn't find the app " 1826 "that has cancelled the shutdown.\n"); 1827 return; 1828 } 1829 1830 // compose the text to be displayed 1831 BString buffer = B_TRANSLATE("Application \"%appName%\" has aborted the " 1832 "shutdown process."); 1833 buffer.ReplaceFirst("%appName%", appName); 1834 1835 // set up the window 1836 _SetShutdownWindowCurrentApp(team); 1837 _SetShutdownWindowText(buffer.String()); 1838 _SetShutdownWindowWaitForAbortedOK(); 1839 1840 // schedule the timeout event 1841 _SetPhase(ABORTED_PHASE); 1842 _ScheduleTimeoutEvent(kDisplayAbortingAppTimeout); 1843 1844 // wait for the timeout or the user to press the cancel button 1845 while (true) { 1846 uint32 event; 1847 team_id eventTeam; 1848 int32 phase; 1849 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1850 if (error != B_OK) 1851 break; 1852 1853 // stop waiting when the timeout occurs 1854 if (event == TIMEOUT_EVENT) 1855 break; 1856 1857 // stop waiting when the user hit the cancel button 1858 if (event == ABORT_EVENT && phase == ABORTED_PHASE && eventTeam < 0) 1859 break; 1860 } 1861 } 1862 1863 1864 /*! Waits until the debugged team list is empty, ie. when there is no one 1865 left to debug. 1866 */ 1867 void 1868 ShutdownProcess::_WaitForDebuggedTeams() 1869 { 1870 PRINT("ShutdownProcess::_WaitForDebuggedTeams()\n"); 1871 { 1872 BAutolock _(fWorkerLock); 1873 if (fDebuggedTeams.empty()) 1874 return; 1875 } 1876 1877 PRINT(" not empty!\n"); 1878 1879 // wait for something to happen 1880 while (true) { 1881 uint32 event; 1882 team_id eventTeam; 1883 int32 phase; 1884 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1885 if (error != B_OK) 1886 throw_error(error); 1887 1888 if (event == ABORT_EVENT) 1889 throw_error(B_SHUTDOWN_CANCELLED); 1890 1891 BAutolock _(fWorkerLock); 1892 if (fDebuggedTeams.empty()) { 1893 PRINT(" out empty"); 1894 return; 1895 } 1896 } 1897 } 1898 1899