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
inverse_compare_by_registration_time(const RosterAppInfo * info1,const RosterAppInfo * info2)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
throw_error(status_t error)124 throw_error(status_t error)
125 {
126 throw error;
127 }
128
129
130 class ShutdownProcess::TimeoutEvent : public MessageEvent {
131 public:
TimeoutEvent(BHandler * target)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
SetPhase(int32 phase)141 void SetPhase(int32 phase)
142 {
143 fMessage.ReplaceInt32("phase", phase);
144 }
145
SetTeam(team_id team)146 void SetTeam(team_id team)
147 {
148 fMessage.ReplaceInt32("team", team);
149 }
150
GetMessagePhase(BMessage * message)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
GetMessageTeam(BMessage * message)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:
InternalEvent(uint32 type,team_id team,int32 phase)174 InternalEvent(uint32 type, team_id team, int32 phase)
175 :
176 fType(type),
177 fTeam(team),
178 fPhase(phase)
179 {
180 }
181
Type() const182 uint32 Type() const { return fType; }
Team() const183 team_id Team() const { return fTeam; }
Phase() const184 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:
QuitRequestReplyHandler(ShutdownProcess * shutdownProcess)199 QuitRequestReplyHandler(ShutdownProcess* shutdownProcess)
200 : BHandler("shutdown quit reply handler"),
201 fShutdownProcess(shutdownProcess)
202 {
203 }
204
MessageReceived(BMessage * message)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:
ShutdownWindow()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
~ShutdownWindow()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
QuitRequested()270 virtual bool QuitRequested()
271 {
272 return false;
273 }
274
Init(BMessenger target)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
AddApp(team_id team,BBitmap * appIcon)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
RemoveApp(team_id team)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
SetCurrentApp(team_id team)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
SetText(const char * text)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
SetCancelShutdownButtonEnabled(bool enable)378 void SetCancelShutdownButtonEnabled(bool enable)
379 {
380 fCancelShutdownButton->SetEnabled(enable);
381 }
382
SetKillAppButtonEnabled(bool enable)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
SetWaitAnimationEnabled(bool enable)396 void SetWaitAnimationEnabled(bool enable)
397 {
398 IconWaitAnimationEnabled(enable);
399 }
400
SetWaitForShutdown()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
SetWaitForAbortedOK()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
~AppInfoShutdownProcess::ShutdownWindow::AppInfo428 ~AppInfo()
429 {
430 delete appIcon;
431 }
432 };
433
_AppInfoIndexOf(team_id team)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
_AppInfoFor(team_id team)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:
SetAppInfo(AppInfo * info)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
IconWaitAnimationEnabled(bool enable)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:
_AnimateWaitIcon()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
_AnimateWaitIconWorker(void * cookie)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
ShutdownProcess(TRoster * roster,EventQueue * eventQueue)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
~ShutdownProcess()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
Init(BMessage * request)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
MessageReceived(BMessage * message)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
SendReply(BMessage * request,status_t error)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
_SendReply(status_t error)946 ShutdownProcess::_SendReply(status_t error)
947 {
948 if (!fRequestReplySent) {
949 SendReply(fRequest, error);
950 fRequestReplySent = true;
951 }
952 }
953
954
955 void
_SetPhase(int32 phase)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
_ScheduleTimeoutEvent(bigtime_t timeout,team_id team)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
_SetShowShutdownWindow(bool show)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
_InitShutdownWindow()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
_AddShutdownWindowApps(AppInfoList & infos)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
_RemoveShutdownWindowApp(team_id team)1087 ShutdownProcess::_RemoveShutdownWindowApp(team_id team)
1088 {
1089 if (fHasGUI) {
1090 BAutolock _(fWindow);
1091
1092 fWindow->RemoveApp(team);
1093 }
1094 }
1095
1096
1097 void
_SetShutdownWindowCurrentApp(team_id team)1098 ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team)
1099 {
1100 if (fHasGUI) {
1101 BAutolock _(fWindow);
1102
1103 fWindow->SetCurrentApp(team);
1104 }
1105 }
1106
1107
1108 void
_SetShutdownWindowText(const char * text)1109 ShutdownProcess::_SetShutdownWindowText(const char* text)
1110 {
1111 if (fHasGUI) {
1112 BAutolock _(fWindow);
1113
1114 fWindow->SetText(text);
1115 }
1116 }
1117
1118
1119 void
_SetShutdownWindowCancelButtonEnabled(bool enabled)1120 ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled)
1121 {
1122 if (fHasGUI) {
1123 BAutolock _(fWindow);
1124
1125 fWindow->SetCancelShutdownButtonEnabled(enabled);
1126 }
1127 }
1128
1129
1130 void
_SetShutdownWindowKillButtonEnabled(bool enabled)1131 ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled)
1132 {
1133 if (fHasGUI) {
1134 BAutolock _(fWindow);
1135
1136 fWindow->SetKillAppButtonEnabled(enabled);
1137 }
1138 }
1139
1140
1141 void
_SetShutdownWindowWaitAnimationEnabled(bool enabled)1142 ShutdownProcess::_SetShutdownWindowWaitAnimationEnabled(bool enabled)
1143 {
1144 if (fHasGUI) {
1145 BAutolock _(fWindow);
1146
1147 fWindow->SetWaitAnimationEnabled(enabled);
1148 }
1149 }
1150
1151
1152 void
_SetShutdownWindowWaitForShutdown()1153 ShutdownProcess::_SetShutdownWindowWaitForShutdown()
1154 {
1155 if (fHasGUI) {
1156 BAutolock _(fWindow);
1157
1158 fWindow->SetWaitForShutdown();
1159 }
1160 }
1161
1162
1163 void
_SetShutdownWindowWaitForAbortedOK()1164 ShutdownProcess::_SetShutdownWindowWaitForAbortedOK()
1165 {
1166 if (fHasGUI) {
1167 BAutolock _(fWindow);
1168
1169 fWindow->SetWaitForAbortedOK();
1170 }
1171 }
1172
1173
1174 void
_NegativeQuitRequestReply(thread_id thread)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
_PrepareShutdownMessage(BMessage & message) const1187 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
_ShutDown()1197 ShutdownProcess::_ShutDown()
1198 {
1199 PRINT("Invoking _kern_shutdown(%d)\n", fReboot);
1200 RETURN_ERROR(_kern_shutdown(fReboot));
1201 }
1202
1203
1204 status_t
_PushEvent(uint32 eventType,team_id team,int32 phase)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
_GetNextEvent(uint32 & eventType,thread_id & team,int32 & phase,bool block)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
_WorkerEntry(void * data)1274 ShutdownProcess::_WorkerEntry(void* data)
1275 {
1276 return ((ShutdownProcess*)data)->_Worker();
1277 }
1278
1279
1280 status_t
_Worker()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
_WorkerDoShutdown()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
_WaitForApp(team_id team,AppInfoList * list,bool systemApps)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
_QuitApps(AppInfoList & list,bool systemApps)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
_QuitBackgroundApps()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
_WaitForBackgroundApps()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
_KillBackgroundApps()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
_QuitNonApps()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
_QuitBlockingApp(AppInfoList & list,team_id team,const char * appName,bool cancelAllowed)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
_DisplayAbortingApp(team_id team)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
_WaitForDebuggedTeams()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