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