xref: /haiku/src/servers/registrar/ShutdownProcess.cpp (revision 2710b4f5d4251c5cf88c82b0114ea99b0ef46d22)
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