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