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