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