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), 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 MSG_PHASE_TIMED_OUT: 773 { 774 // get the phase the event is intended for 775 int32 phase = TimeoutEvent::GetMessagePhase(message); 776 team_id team = TimeoutEvent::GetMessageTeam(message);; 777 PRINT("MSG_PHASE_TIMED_OUT: phase: %" B_PRId32 ", team: %" B_PRId32 778 "\n", phase, team); 779 780 BAutolock _(fWorkerLock); 781 782 if (phase == INVALID_PHASE || phase != fCurrentPhase) 783 return; 784 785 // post the event 786 _PushEvent(TIMEOUT_EVENT, team, phase); 787 788 break; 789 } 790 791 case MSG_KILL_APPLICATION: 792 { 793 team_id team; 794 if (message->FindInt32("team", &team) != B_OK) 795 break; 796 797 // post the event 798 _PushEvent(KILL_APP_EVENT, team, fCurrentPhase); 799 break; 800 } 801 802 case MSG_CANCEL_SHUTDOWN: 803 { 804 // post the event 805 _PushEvent(ABORT_EVENT, -1, fCurrentPhase); 806 break; 807 } 808 809 case MSG_REBOOT_SYSTEM: 810 { 811 // post the event 812 _PushEvent(REBOOT_SYSTEM_EVENT, -1, INVALID_PHASE); 813 break; 814 } 815 816 case MSG_DONE: 817 { 818 // notify the registrar that we're done 819 be_app->PostMessage(B_REG_SHUTDOWN_FINISHED, be_app); 820 break; 821 } 822 823 case B_REG_TEAM_DEBUGGER_ALERT: 824 { 825 bool stopShutdown; 826 if (message->FindBool("stop shutdown", &stopShutdown) == B_OK 827 && stopShutdown) { 828 // post abort event to the worker 829 _PushEvent(ABORT_EVENT, -1, fCurrentPhase); 830 break; 831 } 832 833 bool open; 834 team_id team; 835 if (message->FindInt32("team", &team) != B_OK 836 || message->FindBool("open", &open) != B_OK) 837 break; 838 839 BAutolock _(fWorkerLock); 840 if (open) { 841 PRINT("B_REG_TEAM_DEBUGGER_ALERT: insert %" B_PRId32 "\n", 842 team); 843 fDebuggedTeams.insert(team); 844 } else { 845 PRINT("B_REG_TEAM_DEBUGGER_ALERT: remove %" B_PRId32 "\n", 846 team); 847 fDebuggedTeams.erase(team); 848 _PushEvent(DEBUG_EVENT, -1, fCurrentPhase); 849 } 850 break; 851 } 852 853 default: 854 BLooper::MessageReceived(message); 855 break; 856 } 857 } 858 859 860 void 861 ShutdownProcess::SendReply(BMessage* request, status_t error) 862 { 863 if (error == B_OK) { 864 BMessage reply(B_REG_SUCCESS); 865 request->SendReply(&reply); 866 } else { 867 BMessage reply(B_REG_ERROR); 868 reply.AddInt32("error", error); 869 request->SendReply(&reply); 870 } 871 } 872 873 874 void 875 ShutdownProcess::_SendReply(status_t error) 876 { 877 if (!fRequestReplySent) { 878 SendReply(fRequest, error); 879 fRequestReplySent = true; 880 } 881 } 882 883 884 void 885 ShutdownProcess::_SetPhase(int32 phase) 886 { 887 BAutolock _(fWorkerLock); 888 889 if (phase == fCurrentPhase) 890 return; 891 892 fCurrentPhase = phase; 893 894 // remove the timeout event scheduled for the previous phase 895 fEventQueue->RemoveEvent(fTimeoutEvent); 896 } 897 898 899 void 900 ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout, team_id team) 901 { 902 BAutolock _(fWorkerLock); 903 904 // remove the timeout event 905 fEventQueue->RemoveEvent(fTimeoutEvent); 906 907 // set the event's phase, team and time 908 fTimeoutEvent->SetPhase(fCurrentPhase); 909 fTimeoutEvent->SetTeam(team); 910 fTimeoutEvent->SetTime(system_time() + timeout); 911 912 // add the event 913 fEventQueue->AddEvent(fTimeoutEvent); 914 } 915 916 917 void 918 ShutdownProcess::_SetShowShutdownWindow(bool show) 919 { 920 if (fHasGUI) { 921 BAutolock _(fWindow); 922 923 if (show == fWindow->IsHidden()) { 924 if (show) 925 fWindow->Show(); 926 else 927 fWindow->Hide(); 928 } 929 } 930 } 931 932 933 void 934 ShutdownProcess::_InitShutdownWindow() 935 { 936 // prepare the window 937 if (fHasGUI) { 938 fWindow = new(nothrow) ShutdownWindow; 939 if (fWindow != NULL) { 940 status_t error = fWindow->Init(BMessenger(this)); 941 if (error != B_OK) { 942 delete fWindow; 943 fWindow = NULL; 944 } 945 } 946 947 // add the applications 948 if (fWindow) { 949 BAutolock _(fWorkerLock); 950 _AddShutdownWindowApps(fUserApps); 951 _AddShutdownWindowApps(fSystemApps); 952 } else { 953 WARNING("ShutdownProcess::Init(): Failed to create or init " 954 "shutdown window."); 955 956 fHasGUI = false; 957 } 958 } 959 } 960 961 962 void 963 ShutdownProcess::_AddShutdownWindowApps(AppInfoList& infos) 964 { 965 if (!fHasGUI) 966 return; 967 968 for (AppInfoList::Iterator it = infos.It(); it.IsValid(); ++it) { 969 RosterAppInfo* info = *it; 970 971 // init an app file info 972 BFile file; 973 status_t error = file.SetTo(&info->ref, B_READ_ONLY); 974 if (error != B_OK) { 975 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 976 "open file for app %s: %s\n", info->signature, 977 strerror(error)); 978 continue; 979 } 980 981 BAppFileInfo appFileInfo; 982 error = appFileInfo.SetTo(&file); 983 if (error != B_OK) { 984 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 985 "init app file info for app %s: %s\n", info->signature, 986 strerror(error)); 987 } 988 989 // get the application icons 990 #ifdef __HAIKU__ 991 color_space format = B_RGBA32; 992 #else 993 color_space format = B_CMAP8; 994 #endif 995 996 // mini icon 997 BBitmap* miniIcon = new(nothrow) BBitmap(BRect(0, 0, 15, 15), format); 998 if (miniIcon != NULL) { 999 error = miniIcon->InitCheck(); 1000 if (error == B_OK) 1001 error = appFileInfo.GetTrackerIcon(miniIcon, B_MINI_ICON); 1002 if (error != B_OK) { 1003 delete miniIcon; 1004 miniIcon = NULL; 1005 } 1006 } 1007 1008 // mini icon 1009 BBitmap* largeIcon = new(nothrow) BBitmap(BRect(0, 0, 31, 31), format); 1010 if (largeIcon != NULL) { 1011 error = largeIcon->InitCheck(); 1012 if (error == B_OK) 1013 error = appFileInfo.GetTrackerIcon(largeIcon, B_LARGE_ICON); 1014 if (error != B_OK) { 1015 delete largeIcon; 1016 largeIcon = NULL; 1017 } 1018 } 1019 1020 // add the app 1021 error = fWindow->AddApp(info->team, miniIcon, largeIcon); 1022 if (error != B_OK) { 1023 WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to " 1024 "add app to the shutdown window: %s\n", strerror(error)); 1025 } 1026 } 1027 } 1028 1029 1030 void 1031 ShutdownProcess::_RemoveShutdownWindowApp(team_id team) 1032 { 1033 if (fHasGUI) { 1034 BAutolock _(fWindow); 1035 1036 fWindow->RemoveApp(team); 1037 } 1038 } 1039 1040 1041 void 1042 ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team) 1043 { 1044 if (fHasGUI) { 1045 BAutolock _(fWindow); 1046 1047 fWindow->SetCurrentApp(team); 1048 } 1049 } 1050 1051 1052 void 1053 ShutdownProcess::_SetShutdownWindowText(const char* text) 1054 { 1055 if (fHasGUI) { 1056 BAutolock _(fWindow); 1057 1058 fWindow->SetText(text); 1059 } 1060 } 1061 1062 1063 void 1064 ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled) 1065 { 1066 if (fHasGUI) { 1067 BAutolock _(fWindow); 1068 1069 fWindow->SetCancelShutdownButtonEnabled(enabled); 1070 } 1071 } 1072 1073 1074 void 1075 ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled) 1076 { 1077 if (fHasGUI) { 1078 BAutolock _(fWindow); 1079 1080 fWindow->SetKillAppButtonEnabled(enabled); 1081 } 1082 } 1083 1084 1085 void 1086 ShutdownProcess::_SetShutdownWindowWaitForShutdown() 1087 { 1088 if (fHasGUI) { 1089 BAutolock _(fWindow); 1090 1091 fWindow->SetWaitForShutdown(); 1092 } 1093 } 1094 1095 1096 void 1097 ShutdownProcess::_SetShutdownWindowWaitForAbortedOK() 1098 { 1099 if (fHasGUI) { 1100 BAutolock _(fWindow); 1101 1102 fWindow->SetWaitForAbortedOK(); 1103 } 1104 } 1105 1106 1107 void 1108 ShutdownProcess::_NegativeQuitRequestReply(thread_id thread) 1109 { 1110 BAutolock _(fWorkerLock); 1111 1112 // Note: team ID == team main thread ID under Haiku. When testing under R5 1113 // using the team ID in case of an ABORT_EVENT won't work correctly. But 1114 // this is done only for system apps. 1115 _PushEvent(ABORT_EVENT, thread, fCurrentPhase); 1116 } 1117 1118 1119 void 1120 ShutdownProcess::_PrepareShutdownMessage(BMessage& message) const 1121 { 1122 message.what = B_QUIT_REQUESTED; 1123 message.AddBool("_shutdown_", true); 1124 1125 BMessage::Private(message).SetReply(BMessenger(fQuitRequestReplyHandler)); 1126 } 1127 1128 1129 status_t 1130 ShutdownProcess::_ShutDown() 1131 { 1132 PRINT("Invoking _kern_shutdown(%d)\n", fReboot); 1133 RETURN_ERROR(_kern_shutdown(fReboot)); 1134 } 1135 1136 1137 status_t 1138 ShutdownProcess::_PushEvent(uint32 eventType, team_id team, int32 phase) 1139 { 1140 InternalEvent* event = new(nothrow) InternalEvent(eventType, team, phase); 1141 if (!event) { 1142 ERROR("ShutdownProcess::_PushEvent(): Failed to create event!\n"); 1143 1144 return B_NO_MEMORY; 1145 } 1146 1147 BAutolock _(fWorkerLock); 1148 1149 fInternalEvents->Add(event); 1150 release_sem(fInternalEventSemaphore); 1151 1152 return B_OK; 1153 } 1154 1155 1156 status_t 1157 ShutdownProcess::_GetNextEvent(uint32& eventType, thread_id& team, int32& phase, 1158 bool block) 1159 { 1160 while (true) { 1161 // acquire the semaphore 1162 if (block) { 1163 status_t error; 1164 do { 1165 error = acquire_sem(fInternalEventSemaphore); 1166 } while (error == B_INTERRUPTED); 1167 1168 if (error != B_OK) 1169 return error; 1170 } else { 1171 status_t error = acquire_sem_etc(fInternalEventSemaphore, 1, 1172 B_RELATIVE_TIMEOUT, 0); 1173 if (error != B_OK) { 1174 eventType = NO_EVENT; 1175 return B_OK; 1176 } 1177 } 1178 1179 // get the event 1180 BAutolock _(fWorkerLock); 1181 1182 InternalEvent* event = fInternalEvents->Head(); 1183 fInternalEvents->Remove(event); 1184 1185 eventType = event->Type(); 1186 team = event->Team(); 1187 phase = event->Phase(); 1188 1189 delete event; 1190 1191 // if the event is an obsolete timeout event, we drop it right here 1192 if (eventType == TIMEOUT_EVENT && phase != fCurrentPhase) 1193 continue; 1194 1195 break; 1196 } 1197 1198 // notify the window, if an app has been removed 1199 if (eventType == APP_QUIT_EVENT) 1200 _RemoveShutdownWindowApp(team); 1201 1202 return B_OK; 1203 } 1204 1205 1206 status_t 1207 ShutdownProcess::_WorkerEntry(void* data) 1208 { 1209 return ((ShutdownProcess*)data)->_Worker(); 1210 } 1211 1212 1213 status_t 1214 ShutdownProcess::_Worker() 1215 { 1216 try { 1217 _WorkerDoShutdown(); 1218 fShutdownError = B_OK; 1219 } catch (status_t error) { 1220 PRINT("ShutdownProcess::_Worker(): error while shutting down: %s\n", 1221 strerror(error)); 1222 1223 fShutdownError = error; 1224 } 1225 1226 // this can happen only, if the shutdown process failed or was aborted: 1227 // notify the looper 1228 _SetPhase(DONE_PHASE); 1229 PostMessage(MSG_DONE); 1230 1231 return B_OK; 1232 } 1233 1234 1235 void 1236 ShutdownProcess::_WorkerDoShutdown() 1237 { 1238 PRINT("ShutdownProcess::_WorkerDoShutdown()\n"); 1239 1240 // If we are here, the shutdown process has been initiated successfully, 1241 // that is, if an asynchronous BRoster::Shutdown() was requested, we 1242 // notify the caller at this point. 1243 bool synchronous; 1244 if (fRequest->FindBool("synchronous", &synchronous) == B_OK && !synchronous) 1245 _SendReply(B_OK); 1246 1247 // ask the user to confirm the shutdown, if desired 1248 bool askUser; 1249 if (fHasGUI && fRequest->FindBool("confirm", &askUser) == B_OK && askUser) { 1250 const char* restart = B_TRANSLATE("Restart"); 1251 const char* shutdown = B_TRANSLATE("Shut down"); 1252 BString title = B_TRANSLATE("%action%?"); 1253 title.ReplaceFirst("%action%", fReboot ? restart : shutdown); 1254 const char* text = fReboot 1255 ? B_TRANSLATE("Do you really want to restart the system?") 1256 : B_TRANSLATE("Do you really want to shut down the system?"); 1257 const char* defaultText = fReboot ? restart : shutdown; 1258 const char* otherText = fReboot ? shutdown : restart; 1259 BAlert* alert = new BAlert(title.String(), text, 1260 B_TRANSLATE("Cancel"), otherText, defaultText, 1261 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1262 // We want the alert to behave more like a regular window... 1263 alert->SetFeel(B_NORMAL_WINDOW_FEEL); 1264 // ...but not quit. Minimizing the alert would prevent the user from 1265 // finding it again, since registrar does not have an entry in the 1266 // Deskbar. 1267 alert->SetFlags(alert->Flags() | B_NOT_MINIMIZABLE | B_CLOSE_ON_ESCAPE); 1268 alert->SetWorkspaces(B_ALL_WORKSPACES); 1269 int32 result = alert->Go(); 1270 1271 if (result == 1) { 1272 // Toggle shutdown method 1273 fReboot = !fReboot; 1274 } else if (result < 1) 1275 throw_error(B_SHUTDOWN_CANCELLED); 1276 } 1277 1278 // tell TRoster not to accept new applications anymore 1279 fRoster->SetShuttingDown(true); 1280 1281 fWorkerLock.Lock(); 1282 1283 // get a list of all applications to shut down and sort them 1284 status_t status = fRoster->GetShutdownApps(fUserApps, fSystemApps, 1285 fBackgroundApps, fVitalSystemApps); 1286 if (status != B_OK) { 1287 fWorkerLock.Unlock(); 1288 fRoster->RemoveWatcher(this); 1289 fRoster->SetShuttingDown(false); 1290 return; 1291 } 1292 1293 fUserApps.Sort(&inverse_compare_by_registration_time); 1294 fSystemApps.Sort(&inverse_compare_by_registration_time); 1295 1296 fWorkerLock.Unlock(); 1297 1298 // make the shutdown window ready and show it 1299 _InitShutdownWindow(); 1300 _SetShutdownWindowCurrentApp(-1); 1301 _SetShutdownWindowText(B_TRANSLATE("Tidying things up a bit.")); 1302 _SetShutdownWindowCancelButtonEnabled(true); 1303 _SetShutdownWindowKillButtonEnabled(false); 1304 _SetShowShutdownWindow(true); 1305 1306 // sync 1307 sync(); 1308 1309 // phase 1: terminate the user apps 1310 _SetPhase(USER_APP_TERMINATION_PHASE); 1311 _QuitApps(fUserApps, false); 1312 _WaitForDebuggedTeams(); 1313 1314 // phase 2: terminate the system apps 1315 _SetPhase(SYSTEM_APP_TERMINATION_PHASE); 1316 _QuitApps(fSystemApps, true); 1317 _WaitForDebuggedTeams(); 1318 1319 // phase 3: terminate the background apps 1320 _SetPhase(BACKGROUND_APP_TERMINATION_PHASE); 1321 _QuitBackgroundApps(); 1322 _WaitForDebuggedTeams(); 1323 1324 // phase 4: terminate the other processes 1325 _SetPhase(OTHER_PROCESSES_TERMINATION_PHASE); 1326 _QuitNonApps(); 1327 _ScheduleTimeoutEvent(kBackgroundAppQuitTimeout, -1); 1328 _WaitForBackgroundApps(); 1329 _KillBackgroundApps(); 1330 _WaitForDebuggedTeams(); 1331 1332 // we're through: do the shutdown 1333 _SetPhase(DONE_PHASE); 1334 if (fReboot) 1335 _SetShutdownWindowText(B_TRANSLATE("Restarting" B_UTF8_ELLIPSIS)); 1336 else 1337 _SetShutdownWindowText(B_TRANSLATE("Shutting down" B_UTF8_ELLIPSIS)); 1338 _ShutDown(); 1339 _SetShutdownWindowWaitForShutdown(); 1340 1341 PRINT(" _kern_shutdown() failed\n"); 1342 1343 // shutdown failed: This can happen for power off mode -- reboot should 1344 // always work. 1345 if (fHasGUI) { 1346 // wait for the reboot event 1347 uint32 event; 1348 do { 1349 team_id team; 1350 int32 phase; 1351 status = _GetNextEvent(event, team, phase, true); 1352 if (status != B_OK) 1353 break; 1354 } while (event != REBOOT_SYSTEM_EVENT); 1355 1356 _kern_shutdown(true); 1357 } 1358 1359 // either there's no GUI or reboot failed: we enter the kernel debugger 1360 // instead 1361 #ifdef __HAIKU__ 1362 // TODO: Introduce the syscall. 1363 // while (true) { 1364 // _kern_kernel_debugger("The system is shut down. It's now safe to turn " 1365 // "off the computer."); 1366 // } 1367 #endif 1368 } 1369 1370 1371 bool 1372 ShutdownProcess::_WaitForApp(team_id team, AppInfoList* list, bool systemApps) 1373 { 1374 uint32 event; 1375 do { 1376 team_id eventTeam; 1377 int32 phase; 1378 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1379 if (error != B_OK) 1380 throw_error(error); 1381 1382 if (event == APP_QUIT_EVENT && eventTeam == team) 1383 return true; 1384 1385 if (event == TIMEOUT_EVENT && eventTeam == team) 1386 return false; 1387 1388 if (event == ABORT_EVENT) { 1389 if (eventTeam == -1) { 1390 // The user canceled the shutdown process by pressing the 1391 // Cancel button. 1392 throw_error(B_SHUTDOWN_CANCELLED); 1393 } 1394 if (systemApps) { 1395 // If the app requests aborting the shutdown, we don't need 1396 // to wait any longer. It has processed the request and 1397 // won't quit by itself. We ignore this for system apps. 1398 if (eventTeam == team) 1399 return false; 1400 } else { 1401 // The app returned false in QuitRequested(). 1402 PRINT("ShutdownProcess::_WaitForApp(): shutdown cancelled " 1403 "by team %" B_PRId32 " (-1 => user)\n", eventTeam); 1404 1405 _DisplayAbortingApp(team); 1406 throw_error(B_SHUTDOWN_CANCELLED); 1407 } 1408 } 1409 1410 BAutolock _(fWorkerLock); 1411 if (list != NULL && !list->InfoFor(team)) 1412 return true; 1413 } while (event != NO_EVENT); 1414 1415 return false; 1416 } 1417 1418 1419 void 1420 ShutdownProcess::_QuitApps(AppInfoList& list, bool systemApps) 1421 { 1422 PRINT("ShutdownProcess::_QuitApps(%s)\n", 1423 (systemApps ? "system" : "user")); 1424 1425 if (systemApps) { 1426 _SetShutdownWindowCancelButtonEnabled(false); 1427 1428 // check one last time for abort events 1429 uint32 event; 1430 do { 1431 team_id team; 1432 int32 phase; 1433 status_t error = _GetNextEvent(event, team, phase, false); 1434 if (error != B_OK) 1435 throw_error(error); 1436 1437 if (event == ABORT_EVENT) { 1438 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by " 1439 "team %" B_PRId32 " (-1 => user)\n", team); 1440 1441 _DisplayAbortingApp(team); 1442 throw_error(B_SHUTDOWN_CANCELLED); 1443 } 1444 1445 } while (event != NO_EVENT); 1446 } 1447 1448 // prepare the shutdown message 1449 BMessage message; 1450 _PrepareShutdownMessage(message); 1451 1452 // now iterate through the list of apps 1453 while (true) { 1454 // eat events 1455 uint32 event; 1456 do { 1457 team_id team; 1458 int32 phase; 1459 status_t error = _GetNextEvent(event, team, phase, false); 1460 if (error != B_OK) 1461 throw_error(error); 1462 1463 if (!systemApps && event == ABORT_EVENT) { 1464 PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by " 1465 "team %" B_PRId32 " (-1 => user)\n", team); 1466 1467 _DisplayAbortingApp(team); 1468 throw_error(B_SHUTDOWN_CANCELLED); 1469 } 1470 1471 } while (event != NO_EVENT); 1472 1473 // get the first app to quit 1474 team_id team = -1; 1475 port_id port = -1; 1476 char appName[B_FILE_NAME_LENGTH]; 1477 { 1478 BAutolock _(fWorkerLock); 1479 while (!list.IsEmpty()) { 1480 RosterAppInfo* info = *list.It(); 1481 team = info->team; 1482 port = info->port; 1483 strcpy(appName, info->ref.name); 1484 1485 if (info->IsRunning()) 1486 break; 1487 list.RemoveInfo(info); 1488 delete info; 1489 } 1490 } 1491 1492 if (team < 0) { 1493 PRINT("ShutdownProcess::_QuitApps() done\n"); 1494 return; 1495 } 1496 1497 // set window text 1498 BString buffer = B_TRANSLATE("Asking \"%appName%\" to quit."); 1499 buffer.ReplaceFirst("%appName%", appName); 1500 _SetShutdownWindowText(buffer.String()); 1501 _SetShutdownWindowCurrentApp(team); 1502 1503 // send the shutdown message to the app 1504 PRINT(" sending team %" B_PRId32 " (port: %" B_PRId32 ") a shutdown " 1505 "message\n", team, port); 1506 SingleMessagingTargetSet target(port, B_PREFERRED_TOKEN); 1507 MessageDeliverer::Default()->DeliverMessage(&message, target); 1508 1509 // schedule a timeout event 1510 _ScheduleTimeoutEvent(kAppQuitTimeout, team); 1511 1512 // wait for the app to die or for the timeout to occur 1513 bool appGone = _WaitForApp(team, &list, systemApps); 1514 if (appGone) { 1515 // fine: the app finished in an orderly manner 1516 } else { 1517 // the app is either blocking on a model alert or blocks for another 1518 // reason 1519 if (!systemApps) 1520 _QuitBlockingApp(list, team, appName, true); 1521 else { 1522 // This is a system app: remove it from the list 1523 BAutolock _(fWorkerLock); 1524 1525 if (RosterAppInfo* info = list.InfoFor(team)) { 1526 list.RemoveInfo(info); 1527 delete info; 1528 } 1529 } 1530 } 1531 } 1532 } 1533 1534 1535 void 1536 ShutdownProcess::_QuitBackgroundApps() 1537 { 1538 PRINT("ShutdownProcess::_QuitBackgroundApps()\n"); 1539 1540 _SetShutdownWindowText( 1541 B_TRANSLATE("Asking background applications to quit.")); 1542 1543 // prepare the shutdown message 1544 BMessage message; 1545 _PrepareShutdownMessage(message); 1546 1547 // send shutdown messages to user apps 1548 BAutolock _(fWorkerLock); 1549 1550 AppInfoListMessagingTargetSet targetSet(fBackgroundApps); 1551 1552 if (targetSet.HasNext()) { 1553 PRINT(" sending shutdown message to %" B_PRId32 " apps\n", 1554 fBackgroundApps.CountInfos()); 1555 1556 status_t error = MessageDeliverer::Default()->DeliverMessage( 1557 &message, targetSet); 1558 if (error != B_OK) { 1559 WARNING("_QuitBackgroundApps::_Worker(): Failed to deliver " 1560 "shutdown message to all applications: %s\n", 1561 strerror(error)); 1562 } 1563 } 1564 1565 PRINT("ShutdownProcess::_QuitBackgroundApps() done\n"); 1566 } 1567 1568 1569 void 1570 ShutdownProcess::_WaitForBackgroundApps() 1571 { 1572 PRINT("ShutdownProcess::_WaitForBackgroundApps()\n"); 1573 1574 // wait for user apps 1575 bool moreApps = true; 1576 while (moreApps) { 1577 { 1578 BAutolock _(fWorkerLock); 1579 moreApps = !fBackgroundApps.IsEmpty(); 1580 } 1581 1582 if (moreApps) { 1583 uint32 event; 1584 team_id team; 1585 int32 phase; 1586 status_t error = _GetNextEvent(event, team, phase, true); 1587 if (error != B_OK) 1588 throw_error(error); 1589 1590 if (event == ABORT_EVENT) { 1591 // ignore: it's too late to abort the shutdown 1592 } 1593 1594 if (event == TIMEOUT_EVENT) 1595 return; 1596 } 1597 } 1598 1599 PRINT("ShutdownProcess::_WaitForBackgroundApps() done\n"); 1600 } 1601 1602 1603 void 1604 ShutdownProcess::_KillBackgroundApps() 1605 { 1606 PRINT("ShutdownProcess::_KillBackgroundApps()\n"); 1607 1608 while (true) { 1609 // eat events (we need to be responsive for an abort event) 1610 uint32 event; 1611 do { 1612 team_id team; 1613 int32 phase; 1614 status_t error = _GetNextEvent(event, team, phase, false); 1615 if (error != B_OK) 1616 throw_error(error); 1617 1618 } while (event != NO_EVENT); 1619 1620 // get the first team to kill 1621 team_id team = -1; 1622 char appName[B_FILE_NAME_LENGTH]; 1623 AppInfoList& list = fBackgroundApps; 1624 { 1625 BAutolock _(fWorkerLock); 1626 1627 if (!list.IsEmpty()) { 1628 RosterAppInfo* info = *list.It(); 1629 team = info->team; 1630 strcpy(appName, info->ref.name); 1631 } 1632 } 1633 1634 1635 if (team < 0) { 1636 PRINT("ShutdownProcess::_KillBackgroundApps() done\n"); 1637 return; 1638 } 1639 1640 // the app is either blocking on a model alert or blocks for another 1641 // reason 1642 _QuitBlockingApp(list, team, appName, false); 1643 } 1644 } 1645 1646 1647 void 1648 ShutdownProcess::_QuitNonApps() 1649 { 1650 PRINT("ShutdownProcess::_QuitNonApps()\n"); 1651 1652 _SetShutdownWindowText(B_TRANSLATE("Asking other processes to quit.")); 1653 1654 // iterate through the remaining teams and send them the TERM signal 1655 int32 cookie = 0; 1656 team_info teamInfo; 1657 while (get_next_team_info(&cookie, &teamInfo) == B_OK) { 1658 if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) { 1659 PRINT(" sending team %" B_PRId32 " TERM signal\n", teamInfo.team); 1660 1661 #ifdef __HAIKU__ 1662 // Note: team ID == team main thread ID under Haiku 1663 send_signal(teamInfo.team, SIGTERM); 1664 #else 1665 // We don't want to do this when testing under R5, since it 1666 // would kill all teams besides our app server and registrar. 1667 #endif 1668 } 1669 } 1670 1671 // give them a bit of time to terminate 1672 // TODO: Instead of just waiting we could periodically check whether the 1673 // processes are already gone to shorten the process. 1674 snooze(kNonAppQuitTimeout); 1675 1676 // iterate through the remaining teams and kill them 1677 cookie = 0; 1678 while (get_next_team_info(&cookie, &teamInfo) == B_OK) { 1679 if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) { 1680 PRINT(" killing team %" B_PRId32 "\n", teamInfo.team); 1681 1682 #ifdef __HAIKU__ 1683 kill_team(teamInfo.team); 1684 #else 1685 // We don't want to do this when testing under R5, since it 1686 // would kill all teams besides our app server and registrar. 1687 #endif 1688 } 1689 } 1690 1691 PRINT("ShutdownProcess::_QuitNonApps() done\n"); 1692 } 1693 1694 1695 void 1696 ShutdownProcess::_QuitBlockingApp(AppInfoList& list, team_id team, 1697 const char* appName, bool cancelAllowed) 1698 { 1699 bool debugged = false; 1700 bool modal = false; 1701 { 1702 BAutolock _(fWorkerLock); 1703 if (fDebuggedTeams.find(team) != fDebuggedTeams.end()) 1704 debugged = true; 1705 } 1706 if (!debugged) 1707 modal = BPrivate::is_app_showing_modal_window(team); 1708 1709 if (modal) { 1710 // app blocks on a modal window 1711 BString buffer = B_TRANSLATE("The application \"%appName%\" might be " 1712 "blocked on a modal panel."); 1713 buffer.ReplaceFirst("%appName%", appName); 1714 _SetShutdownWindowText(buffer.String()); 1715 _SetShutdownWindowCurrentApp(team); 1716 _SetShutdownWindowKillButtonEnabled(true); 1717 } 1718 1719 if (modal || debugged) { 1720 // wait for something to happen 1721 bool appGone = false; 1722 while (true) { 1723 uint32 event; 1724 team_id eventTeam; 1725 int32 phase; 1726 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1727 if (error != B_OK) 1728 throw_error(error); 1729 1730 if ((event == APP_QUIT_EVENT) && eventTeam == team) { 1731 appGone = true; 1732 break; 1733 } 1734 1735 if (event == KILL_APP_EVENT && eventTeam == team) 1736 break; 1737 1738 if (event == ABORT_EVENT) { 1739 if (cancelAllowed || debugged) { 1740 PRINT("ShutdownProcess::_QuitBlockingApp(): shutdown " 1741 "cancelled by team %" B_PRId32 " (-1 => user)\n", 1742 eventTeam); 1743 1744 if (!debugged) 1745 _DisplayAbortingApp(eventTeam); 1746 throw_error(B_SHUTDOWN_CANCELLED); 1747 } 1748 1749 // If the app requests aborting the shutdown, we don't need 1750 // to wait any longer. It has processed the request and 1751 // won't quit by itself. We'll have to kill it. 1752 if (eventTeam == team) 1753 break; 1754 } 1755 } 1756 1757 _SetShutdownWindowKillButtonEnabled(false); 1758 1759 if (appGone) 1760 return; 1761 } 1762 1763 // kill the app 1764 PRINT(" killing team %" B_PRId32 "\n", team); 1765 1766 kill_team(team); 1767 1768 // remove the app (the roster will note eventually and send us 1769 // a notification, but we want to be sure) 1770 { 1771 BAutolock _(fWorkerLock); 1772 1773 if (RosterAppInfo* info = list.InfoFor(team)) { 1774 list.RemoveInfo(info); 1775 delete info; 1776 } 1777 } 1778 } 1779 1780 1781 void 1782 ShutdownProcess::_DisplayAbortingApp(team_id team) 1783 { 1784 // find the app that cancelled the shutdown 1785 char appName[B_FILE_NAME_LENGTH]; 1786 bool foundApp = false; 1787 { 1788 BAutolock _(fWorkerLock); 1789 1790 RosterAppInfo* info = fUserApps.InfoFor(team); 1791 if (!info) 1792 info = fSystemApps.InfoFor(team); 1793 if (!info) 1794 fBackgroundApps.InfoFor(team); 1795 1796 if (info) { 1797 foundApp = true; 1798 strcpy(appName, info->ref.name); 1799 } 1800 } 1801 1802 if (!foundApp) { 1803 PRINT("ShutdownProcess::_DisplayAbortingApp(): Didn't find the app " 1804 "that has cancelled the shutdown.\n"); 1805 return; 1806 } 1807 1808 // compose the text to be displayed 1809 BString buffer = B_TRANSLATE("Application \"%appName%\" has aborted the " 1810 "shutdown process."); 1811 buffer.ReplaceFirst("%appName%", appName); 1812 1813 // set up the window 1814 _SetShutdownWindowCurrentApp(team); 1815 _SetShutdownWindowText(buffer.String()); 1816 _SetShutdownWindowWaitForAbortedOK(); 1817 1818 // schedule the timeout event 1819 _SetPhase(ABORTED_PHASE); 1820 _ScheduleTimeoutEvent(kDisplayAbortingAppTimeout); 1821 1822 // wait for the timeout or the user to press the cancel button 1823 while (true) { 1824 uint32 event; 1825 team_id eventTeam; 1826 int32 phase; 1827 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1828 if (error != B_OK) 1829 break; 1830 1831 // stop waiting when the timeout occurs 1832 if (event == TIMEOUT_EVENT) 1833 break; 1834 1835 // stop waiting when the user hit the cancel button 1836 if (event == ABORT_EVENT && phase == ABORTED_PHASE && eventTeam < 0) 1837 break; 1838 } 1839 } 1840 1841 1842 /*! Waits until the debugged team list is empty, ie. when there is no one 1843 left to debug. 1844 */ 1845 void 1846 ShutdownProcess::_WaitForDebuggedTeams() 1847 { 1848 PRINT("ShutdownProcess::_WaitForDebuggedTeams()\n"); 1849 { 1850 BAutolock _(fWorkerLock); 1851 if (fDebuggedTeams.empty()) 1852 return; 1853 } 1854 1855 PRINT(" not empty!\n"); 1856 1857 // wait for something to happen 1858 while (true) { 1859 uint32 event; 1860 team_id eventTeam; 1861 int32 phase; 1862 status_t error = _GetNextEvent(event, eventTeam, phase, true); 1863 if (error != B_OK) 1864 throw_error(error); 1865 1866 if (event == ABORT_EVENT) 1867 throw_error(B_SHUTDOWN_CANCELLED); 1868 1869 BAutolock _(fWorkerLock); 1870 if (fDebuggedTeams.empty()) { 1871 PRINT(" out empty"); 1872 return; 1873 } 1874 } 1875 } 1876 1877