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