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
56using std::nothrow;
57using 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).
62static 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).
66static 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.
70static 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.
74static 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.
78static const bigtime_t kIconAnimateInterval = 50000 * B_LARGE_ICON; // 0.05 s
79
80// message what fields (must not clobber the registrar's message namespace)
81enum {
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
90enum {
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
101enum {
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
112static bool
113inverse_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*/
122static inline
123void
124throw_error(status_t error)
125{
126	throw error;
127}
128
129
130class ShutdownProcess::TimeoutEvent : public MessageEvent {
131public:
132	TimeoutEvent(BHandler* target)
133		: MessageEvent(0, target, MSG_PHASE_TIMED_OUT)
134	{
135		SetAutoDelete(false);
136
137		fMessage.AddInt32("phase", INVALID_PHASE);
138		fMessage.AddInt32("team", -1);
139	}
140
141	void SetPhase(int32 phase)
142	{
143		fMessage.ReplaceInt32("phase", phase);
144	}
145
146	void SetTeam(team_id team)
147	{
148		fMessage.ReplaceInt32("team", team);
149	}
150
151	static int32 GetMessagePhase(BMessage* message)
152	{
153		int32 phase;
154		if (message->FindInt32("phase", &phase) != B_OK)
155			phase = INVALID_PHASE;
156
157		return phase;
158	}
159
160	static int32 GetMessageTeam(BMessage* message)
161	{
162		team_id team;
163		if (message->FindInt32("team", &team) != B_OK)
164			team = -1;
165
166		return team;
167	}
168};
169
170
171class ShutdownProcess::InternalEvent
172	: public DoublyLinkedListLinkImpl<InternalEvent> {
173public:
174	InternalEvent(uint32 type, team_id team, int32 phase)
175		:
176		fType(type),
177		fTeam(team),
178		fPhase(phase)
179	{
180	}
181
182	uint32 Type() const			{ return fType; }
183	team_id Team() const		{ return fTeam; }
184	int32 Phase() const			{ return fPhase; }
185
186private:
187	uint32	fType;
188	int32	fTeam;
189	int32	fPhase;
190};
191
192
193struct ShutdownProcess::InternalEventList : DoublyLinkedList<InternalEvent> {
194};
195
196
197class ShutdownProcess::QuitRequestReplyHandler : public BHandler {
198public:
199	QuitRequestReplyHandler(ShutdownProcess* shutdownProcess)
200		: BHandler("shutdown quit reply handler"),
201		fShutdownProcess(shutdownProcess)
202	{
203	}
204
205	virtual void MessageReceived(BMessage* message)
206	{
207		switch (message->what) {
208			case B_REPLY:
209			{
210				bool result;
211				thread_id thread;
212				if (message->FindBool("result", &result) == B_OK
213					&& message->FindInt32("thread", &thread) == B_OK) {
214					if (!result)
215						fShutdownProcess->_NegativeQuitRequestReply(thread);
216				}
217
218				break;
219			}
220
221			default:
222				BHandler::MessageReceived(message);
223				break;
224		}
225	}
226
227private:
228	ShutdownProcess	*fShutdownProcess;
229};
230
231
232class ShutdownProcess::ShutdownWindow : public BAlert {
233public:
234	ShutdownWindow()
235		:
236		BAlert(),
237		fKillAppMessage(NULL),
238		fCurrentApp(-1),
239
240		fCurrentIconBitmap(NULL),
241		fNormalIconBitmap(NULL),
242		fTintedIconBitmap(NULL),
243		fAnimationActive(false),
244		fAnimationWorker(-1),
245		fCurrentAnimationRow(-1),
246		fAnimationLightenPhase(true)
247	{
248		SetTitle(B_TRANSLATE("Shutdown status"));
249		SetWorkspaces(B_ALL_WORKSPACES);
250		SetLook(B_TITLED_WINDOW_LOOK);
251		SetFlags(Flags() | B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE);
252
253		SetButtonWidth(B_WIDTH_AS_USUAL);
254		SetType(B_EMPTY_ALERT);
255	}
256
257	~ShutdownWindow()
258	{
259		atomic_set(&fAnimationActive, false);
260		wait_for_thread(fAnimationWorker, NULL);
261
262		delete fNormalIconBitmap;
263		delete fTintedIconBitmap;
264
265		for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) {
266			delete info;
267		}
268	}
269
270	virtual bool QuitRequested()
271	{
272		return false;
273	}
274
275	status_t Init(BMessenger target)
276	{
277		// kill app button
278		AddButton(B_TRANSLATE("Kill application"));
279		fKillAppButton = ButtonAt(CountButtons() - 1);
280
281		BMessage* message = new BMessage(MSG_KILL_APPLICATION);
282		if (!message)
283			return B_NO_MEMORY;
284		message->AddInt32("team", -1);
285		fKillAppMessage = message;
286		fKillAppButton->SetMessage(message);
287		fKillAppButton->SetTarget(target);
288
289		// cancel shutdown button
290		AddButton(B_TRANSLATE("Cancel shutdown"));
291		fCancelShutdownButton = ButtonAt(CountButtons() - 1);
292
293		message = new BMessage(MSG_CANCEL_SHUTDOWN);
294		if (!message)
295			return B_NO_MEMORY;
296		fCancelShutdownButton->SetMessage(message);
297		fCancelShutdownButton->SetTarget(target);
298
299		// reboot system button
300		AddButton(B_TRANSLATE("Restart system"));
301		fRebootSystemButton = ButtonAt(CountButtons() - 1);
302		fRebootSystemButton->Hide();
303
304		message = new BMessage(MSG_REBOOT_SYSTEM);
305		if (!message)
306			return B_NO_MEMORY;
307		fRebootSystemButton->SetMessage(message);
308		fRebootSystemButton->SetTarget(target);
309
310		// aborted OK button
311		AddButton(B_TRANSLATE("OK"));
312		fAbortedOKButton = ButtonAt(CountButtons() - 1);
313		fAbortedOKButton->Hide();
314
315		message = new BMessage(MSG_CANCEL_SHUTDOWN);
316		if (!message)
317			return B_NO_MEMORY;
318		fAbortedOKButton->SetMessage(message);
319		fAbortedOKButton->SetTarget(target);
320
321		return B_OK;
322	}
323
324	status_t AddApp(team_id team, BBitmap* appIcon)
325	{
326		AppInfo* info = new(nothrow) AppInfo;
327		if (!info) {
328			delete appIcon;
329			return B_NO_MEMORY;
330		}
331
332		info->team = team;
333		info->appIcon = appIcon;
334
335		if (!fAppInfos.AddItem(info)) {
336			delete info;
337			return B_NO_MEMORY;
338		}
339
340		return B_OK;
341	}
342
343	void RemoveApp(team_id team)
344	{
345		int32 index = _AppInfoIndexOf(team);
346		if (index < 0)
347			return;
348
349		if (team == fCurrentApp)
350			SetCurrentApp(-1);
351
352		AppInfo* info = (AppInfo*)fAppInfos.RemoveItem(index);
353		delete info;
354	}
355
356	void SetCurrentApp(team_id team)
357	{
358		AppInfo* info = (team >= 0 ? _AppInfoFor(team) : NULL);
359
360		fCurrentApp = team;
361		SetAppInfo(info);
362
363		fKillAppMessage->ReplaceInt32("team", team);
364	}
365
366	void SetText(const char* text)
367	{
368		const int32 initialLength = TextView()->TextLength(),
369			initialLines = TextView()->CountLines();
370
371		BAlert::SetText(text);
372
373		if (TextView()->CountLines() > initialLines
374				|| TextView()->CountLines() > (initialLength * 2))
375			ResizeToPreferred();
376	}
377
378	void SetCancelShutdownButtonEnabled(bool enable)
379	{
380		fCancelShutdownButton->SetEnabled(enable);
381	}
382
383	void SetKillAppButtonEnabled(bool enable)
384	{
385		if (enable != fKillAppButton->IsEnabled()) {
386			fKillAppButton->SetEnabled(enable);
387
388			if (enable)
389				fKillAppButton->Show();
390			else
391				fKillAppButton->Hide();
392			ResizeToPreferred();
393		}
394	}
395
396	void SetWaitAnimationEnabled(bool enable)
397	{
398		IconWaitAnimationEnabled(enable);
399	}
400
401	void SetWaitForShutdown()
402	{
403		fKillAppButton->Hide();
404		fCancelShutdownButton->Hide();
405		fRebootSystemButton->MakeDefault(true);
406		fRebootSystemButton->Show();
407
408		SetTitle(B_TRANSLATE("System is shut down"));
409		SetText(B_TRANSLATE("It's now safe to turn off the computer."));
410	}
411
412	void SetWaitForAbortedOK()
413	{
414		fKillAppButton->Hide();
415		fCancelShutdownButton->Hide();
416		fAbortedOKButton->MakeDefault(true);
417		fAbortedOKButton->Show();
418		ResizeToPreferred();
419
420		SetTitle(B_TRANSLATE("Shutdown aborted"));
421	}
422
423private:
424	struct AppInfo {
425		team_id		team;
426		BBitmap*	appIcon;
427
428		~AppInfo()
429		{
430			delete appIcon;
431		}
432	};
433
434	int32 _AppInfoIndexOf(team_id team)
435	{
436		if (team < 0)
437			return -1;
438
439		for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) {
440			if (info->team == team)
441				return i;
442		}
443
444		return -1;
445	}
446
447	AppInfo* _AppInfoFor(team_id team)
448	{
449		int32 index = _AppInfoIndexOf(team);
450		return (index >= 0 ? (AppInfo*)fAppInfos.ItemAt(index) : NULL);
451	}
452
453private:
454	void SetAppInfo(AppInfo* info)
455	{
456		IconWaitAnimationEnabled(false);
457
458		BAutolock lock(this);
459		if (!lock.IsLocked())
460			return;
461
462		delete fNormalIconBitmap;
463		fNormalIconBitmap = NULL;
464
465		delete fTintedIconBitmap;
466		fTintedIconBitmap = NULL;
467
468		// We do not delete the present fCurrentIconBitmap as the BAlert owns it.
469		fCurrentIconBitmap = NULL;
470
471		if (info != NULL && info->appIcon != NULL
472			&& info->appIcon->IsValid()) {
473			fCurrentIconBitmap = new BBitmap(info->appIcon->Bounds(), B_RGBA32);
474
475			if (fCurrentIconBitmap == NULL
476				|| fCurrentIconBitmap->ImportBits(info->appIcon) != B_OK) {
477				delete fCurrentIconBitmap;
478				fCurrentIconBitmap = NULL;
479			} else
480				SetIcon(fCurrentIconBitmap);
481		} else
482			SetIcon(NULL);
483	}
484
485	void IconWaitAnimationEnabled(bool enable)
486	{
487		if (atomic_get(&fAnimationActive) == enable)
488			return;
489
490		BAutolock lock(this);
491		if (!lock.IsLocked())
492			return;
493
494		if (enable) {
495			if (fCurrentIconBitmap == NULL
496				|| !fCurrentIconBitmap->IsValid())
497				return;
498
499			if (fNormalIconBitmap == NULL
500				|| !fNormalIconBitmap->IsValid()) {
501				delete fNormalIconBitmap;
502				fNormalIconBitmap = NULL;
503
504				fNormalIconBitmap = new BBitmap(fCurrentIconBitmap->Bounds(),
505					B_BITMAP_NO_SERVER_LINK, B_RGBA32);
506
507				if (fNormalIconBitmap == NULL
508					|| fNormalIconBitmap->ImportBits(fCurrentIconBitmap)
509						!= B_OK) {
510					delete fNormalIconBitmap;
511					fNormalIconBitmap = NULL;
512
513					return;
514				}
515			}
516
517			if (fTintedIconBitmap == NULL
518				|| !fTintedIconBitmap->IsValid()) {
519				delete fTintedIconBitmap;
520				fTintedIconBitmap = NULL;
521
522				fTintedIconBitmap = new BBitmap(fNormalIconBitmap->Bounds(),
523					B_BITMAP_NO_SERVER_LINK, B_RGBA32);
524
525				if (fTintedIconBitmap == NULL
526					|| fTintedIconBitmap->ImportBits(fNormalIconBitmap)
527						!= B_OK) {
528					delete fTintedIconBitmap;
529					fTintedIconBitmap = NULL;
530
531					return;
532				}
533
534				int32 width =
535					fTintedIconBitmap->Bounds().IntegerWidth() + 1;
536				int32 height =
537					fTintedIconBitmap->Bounds().IntegerHeight() + 1;
538				int32 rowLength = fTintedIconBitmap->BytesPerRow();
539
540				uint8* iconBits = (uint8*)fTintedIconBitmap->Bits();
541
542				for (int32 y = 0; y < height; y++) {
543					for (int32 x = 0; x < width; x++) {
544						int32 offset = (y * rowLength) + (x * 4);
545
546						rgb_color pixelColor = make_color(iconBits[offset],
547							iconBits[offset + 1], iconBits[offset + 2],
548							iconBits[offset + 3]);
549
550						pixelColor = tint_color(pixelColor,
551							B_DARKEN_2_TINT);
552
553						iconBits[offset] = pixelColor.red;
554						iconBits[offset + 1] = pixelColor.green;
555						iconBits[offset + 2] = pixelColor.blue;
556						iconBits[offset + 3] = pixelColor.alpha;
557					}
558				}
559			}
560
561			fAnimationWorker = spawn_thread(&_AnimateWaitIconWorker,
562				"thumb twiddling", B_DISPLAY_PRIORITY, this);
563
564			if (fAnimationWorker < B_NO_ERROR)
565				return;
566
567			atomic_set(&fAnimationActive, true);
568			if (resume_thread(fAnimationWorker) != B_OK)
569				atomic_set(&fAnimationActive, false);
570		} else {
571			atomic_set(&fAnimationActive, false);
572			wait_for_thread(fAnimationWorker, NULL);
573
574			fCurrentAnimationRow = -1;
575			fAnimationLightenPhase = true;
576
577			if (fCurrentIconBitmap != NULL && fNormalIconBitmap != NULL)
578				fCurrentIconBitmap->ImportBits(fNormalIconBitmap);
579		}
580	}
581
582private:
583	status_t _AnimateWaitIcon()
584	{
585		int32 lastHeight = 1;
586		while (atomic_get(&fAnimationActive)) {
587			if (LockWithTimeout(kIconAnimateInterval / lastHeight) != B_OK)
588				continue;
589
590			lastHeight = fCurrentIconBitmap->Bounds().IntegerHeight();
591			if (fCurrentAnimationRow < 0) {
592				fCurrentAnimationRow = lastHeight;
593				fAnimationLightenPhase = !fAnimationLightenPhase;
594			}
595
596			BBitmap* sourceBitmap = fAnimationLightenPhase ?
597				fNormalIconBitmap : fTintedIconBitmap;
598
599			fCurrentIconBitmap->ImportBits(sourceBitmap,
600				BPoint(0, fCurrentAnimationRow),
601				BPoint(0, fCurrentAnimationRow),
602				BSize(sourceBitmap->Bounds().IntegerWidth() - 1, 0));
603
604			fCurrentAnimationRow--;
605
606			ChildAt(0)->Invalidate();
607
608			Unlock();
609			snooze(kIconAnimateInterval / lastHeight);
610		}
611
612		return B_OK;
613	}
614
615	static status_t _AnimateWaitIconWorker(void* cookie)
616	{
617		ShutdownWindow* ourView = (ShutdownWindow*)cookie;
618		return ourView->_AnimateWaitIcon();
619	}
620
621private:
622	BList				fAppInfos;
623	BButton*			fKillAppButton;
624	BButton*			fCancelShutdownButton;
625	BButton*			fRebootSystemButton;
626	BButton*			fAbortedOKButton;
627	BMessage*			fKillAppMessage;
628	team_id				fCurrentApp;
629
630private:
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
644ShutdownProcess::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
667ShutdownProcess::~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
721status_t
722ShutdownProcess::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
780void
781ShutdownProcess::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
931void
932ShutdownProcess::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
945void
946ShutdownProcess::_SendReply(status_t error)
947{
948	if (!fRequestReplySent) {
949		SendReply(fRequest, error);
950		fRequestReplySent = true;
951	}
952}
953
954
955void
956ShutdownProcess::_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
970void
971ShutdownProcess::_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
988void
989ShutdownProcess::_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
1004void
1005ShutdownProcess::_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
1033void
1034ShutdownProcess::_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
1086void
1087ShutdownProcess::_RemoveShutdownWindowApp(team_id team)
1088{
1089	if (fHasGUI) {
1090		BAutolock _(fWindow);
1091
1092		fWindow->RemoveApp(team);
1093	}
1094}
1095
1096
1097void
1098ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team)
1099{
1100	if (fHasGUI) {
1101		BAutolock _(fWindow);
1102
1103		fWindow->SetCurrentApp(team);
1104	}
1105}
1106
1107
1108void
1109ShutdownProcess::_SetShutdownWindowText(const char* text)
1110{
1111	if (fHasGUI) {
1112		BAutolock _(fWindow);
1113
1114		fWindow->SetText(text);
1115	}
1116}
1117
1118
1119void
1120ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled)
1121{
1122	if (fHasGUI) {
1123		BAutolock _(fWindow);
1124
1125		fWindow->SetCancelShutdownButtonEnabled(enabled);
1126	}
1127}
1128
1129
1130void
1131ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled)
1132{
1133	if (fHasGUI) {
1134		BAutolock _(fWindow);
1135
1136		fWindow->SetKillAppButtonEnabled(enabled);
1137	}
1138}
1139
1140
1141void
1142ShutdownProcess::_SetShutdownWindowWaitAnimationEnabled(bool enabled)
1143{
1144	if (fHasGUI) {
1145		BAutolock _(fWindow);
1146
1147		fWindow->SetWaitAnimationEnabled(enabled);
1148	}
1149}
1150
1151
1152void
1153ShutdownProcess::_SetShutdownWindowWaitForShutdown()
1154{
1155	if (fHasGUI) {
1156		BAutolock _(fWindow);
1157
1158		fWindow->SetWaitForShutdown();
1159	}
1160}
1161
1162
1163void
1164ShutdownProcess::_SetShutdownWindowWaitForAbortedOK()
1165{
1166	if (fHasGUI) {
1167		BAutolock _(fWindow);
1168
1169		fWindow->SetWaitForAbortedOK();
1170	}
1171}
1172
1173
1174void
1175ShutdownProcess::_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
1186void
1187ShutdownProcess::_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
1196status_t
1197ShutdownProcess::_ShutDown()
1198{
1199	PRINT("Invoking _kern_shutdown(%d)\n", fReboot);
1200	RETURN_ERROR(_kern_shutdown(fReboot));
1201}
1202
1203
1204status_t
1205ShutdownProcess::_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
1223status_t
1224ShutdownProcess::_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
1273status_t
1274ShutdownProcess::_WorkerEntry(void* data)
1275{
1276	return ((ShutdownProcess*)data)->_Worker();
1277}
1278
1279
1280status_t
1281ShutdownProcess::_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
1302void
1303ShutdownProcess::_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
1437bool
1438ShutdownProcess::_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
1485void
1486ShutdownProcess::_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
1601void
1602ShutdownProcess::_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
1635void
1636ShutdownProcess::_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
1669void
1670ShutdownProcess::_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
1713void
1714ShutdownProcess::_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
1751void
1752ShutdownProcess::_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
1839void
1840ShutdownProcess::_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*/
1904void
1905ShutdownProcess::_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