root/src/servers/registrar/ShutdownProcess.cpp
/*
 * Copyright 2005-2008, Ingo Weinhold, bonefish@users.sf.net.
 * Copyright 2006-2009, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2006-2008, Stephan Aßmus.
 * Copyright 2006, Ryan Leavengood.
 * Copyright 2021, Jacob Secunda.
 *
 * Distributed under the terms of the MIT License.
 */

#include "ShutdownProcess.h"

#include <new>
#include <string.h>

#include <signal.h>
#include <unistd.h>

#include <Alert.h>
#include <AppFileInfo.h>
#include <AppMisc.h>
#include <Autolock.h>
#include <Bitmap.h>
#include <Button.h>
#include <ControlLook.h>
#include <Catalog.h>
#include <File.h>
#include <Message.h>
#include <MessagePrivate.h>
#include <RegistrarDefs.h>
#include <Roster.h>             // for B_REQUEST_QUIT
#include <Screen.h>
#include <String.h>
#include <TextView.h>
#include <Window.h>

#include <TokenSpace.h>
#include <util/DoublyLinkedList.h>

#include <syscalls.h>

#include "AppInfoListMessagingTargetSet.h"
#include "Debug.h"
#include "EventQueue.h"
#include "MessageDeliverer.h"
#include "MessageEvent.h"
#include "Registrar.h"
#include "RosterAppInfo.h"
#include "TRoster.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ShutdownProcess"


using std::nothrow;
using namespace BPrivate;

// The time span a non-background application has after the quit message has
// been delivered (more precisely: has been handed over to the
// MessageDeliverer).
static const bigtime_t kAppQuitTimeout = 3000000; // 3 s

// The time span a background application has after the quit message has been
// delivered (more precisely: has been handed over to the MessageDeliverer).
static const bigtime_t kBackgroundAppQuitTimeout = 3000000; // 3 s

// The time span non-app processes have after the TERM signal has been send
// to them before they get a KILL signal.
static const bigtime_t kNonAppQuitTimeout = 500000; // 0.5 s

// The time span the app that has aborted the shutdown shall be displayed in
// the shutdown window before closing it automatically.
static const bigtime_t kDisplayAbortingAppTimeout = 3000000; // 3 s

// The speed of the animation played when an application is blocked on a modal
// panel.
static const bigtime_t kIconAnimateInterval = 50000 * B_LARGE_ICON; // 0.05 s

// message what fields (must not clobber the registrar's message namespace)
enum {
        MSG_PHASE_TIMED_OUT             = 'phto',
        MSG_DONE                                = 'done',
        MSG_KILL_APPLICATION    = 'kill',
        MSG_CANCEL_SHUTDOWN             = 'cncl',
        MSG_REBOOT_SYSTEM               = 'lbot',
};

// internal events
enum {
        NO_EVENT,
        ABORT_EVENT,
        TIMEOUT_EVENT,
        APP_QUIT_EVENT,
        KILL_APP_EVENT,
        REBOOT_SYSTEM_EVENT,
        DEBUG_EVENT
};

// phases
enum {
        INVALID_PHASE                                           = -1,
        USER_APP_TERMINATION_PHASE                      = 0,
        SYSTEM_APP_TERMINATION_PHASE            = 1,
        BACKGROUND_APP_TERMINATION_PHASE        = 2,
        OTHER_PROCESSES_TERMINATION_PHASE       = 3,
        ABORTED_PHASE                                           = 4,
        DONE_PHASE                                                      = 5,
};


static bool
inverse_compare_by_registration_time(const RosterAppInfo* info1,
        const RosterAppInfo* info2)
{
        return (info2->registration_time < info1->registration_time);
}


/*!     \brief Used to avoid type matching problems when throwing a constant.
*/
static inline
void
throw_error(status_t error)
{
        throw error;
}


class ShutdownProcess::TimeoutEvent : public MessageEvent {
public:
        TimeoutEvent(BHandler* target)
                : MessageEvent(0, target, MSG_PHASE_TIMED_OUT)
        {
                SetAutoDelete(false);

                fMessage.AddInt32("phase", INVALID_PHASE);
                fMessage.AddInt32("team", -1);
        }

        void SetPhase(int32 phase)
        {
                fMessage.ReplaceInt32("phase", phase);
        }

        void SetTeam(team_id team)
        {
                fMessage.ReplaceInt32("team", team);
        }

        static int32 GetMessagePhase(BMessage* message)
        {
                int32 phase;
                if (message->FindInt32("phase", &phase) != B_OK)
                        phase = INVALID_PHASE;

                return phase;
        }

        static int32 GetMessageTeam(BMessage* message)
        {
                team_id team;
                if (message->FindInt32("team", &team) != B_OK)
                        team = -1;

                return team;
        }
};


class ShutdownProcess::InternalEvent
        : public DoublyLinkedListLinkImpl<InternalEvent> {
public:
        InternalEvent(uint32 type, team_id team, int32 phase)
                :
                fType(type),
                fTeam(team),
                fPhase(phase)
        {
        }

        uint32 Type() const                     { return fType; }
        team_id Team() const            { return fTeam; }
        int32 Phase() const                     { return fPhase; }

private:
        uint32  fType;
        int32   fTeam;
        int32   fPhase;
};


struct ShutdownProcess::InternalEventList : DoublyLinkedList<InternalEvent> {
};


class ShutdownProcess::QuitRequestReplyHandler : public BHandler {
public:
        QuitRequestReplyHandler(ShutdownProcess* shutdownProcess)
                : BHandler("shutdown quit reply handler"),
                fShutdownProcess(shutdownProcess)
        {
        }

        virtual void MessageReceived(BMessage* message)
        {
                switch (message->what) {
                        case B_REPLY:
                        {
                                bool result;
                                thread_id thread;
                                if (message->FindBool("result", &result) == B_OK
                                        && message->FindInt32("thread", &thread) == B_OK) {
                                        if (!result)
                                                fShutdownProcess->_NegativeQuitRequestReply(thread);
                                }

                                break;
                        }

                        default:
                                BHandler::MessageReceived(message);
                                break;
                }
        }

private:
        ShutdownProcess *fShutdownProcess;
};


class ShutdownProcess::ShutdownWindow : public BAlert {
public:
        ShutdownWindow()
                :
                BAlert(),
                fKillAppMessage(NULL),
                fCurrentApp(-1),

                fCurrentIconBitmap(NULL),
                fNormalIconBitmap(NULL),
                fTintedIconBitmap(NULL),
                fAnimationActive(false),
                fAnimationWorker(-1),
                fCurrentAnimationRow(-1),
                fAnimationLightenPhase(true)
        {
                SetTitle(B_TRANSLATE("Shutdown status"));
                SetWorkspaces(B_ALL_WORKSPACES);
                SetLook(B_TITLED_WINDOW_LOOK);
                SetFlags(Flags() | B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE);

                SetButtonWidth(B_WIDTH_AS_USUAL);
                SetType(B_EMPTY_ALERT);
        }

        ~ShutdownWindow()
        {
                atomic_set(&fAnimationActive, false);
                wait_for_thread(fAnimationWorker, NULL);

                delete fNormalIconBitmap;
                delete fTintedIconBitmap;

                for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) {
                        delete info;
                }
        }

        virtual bool QuitRequested()
        {
                return false;
        }

        status_t Init(BMessenger target)
        {
                // kill app button
                AddButton(B_TRANSLATE("Kill application"));
                fKillAppButton = ButtonAt(CountButtons() - 1);

                BMessage* message = new BMessage(MSG_KILL_APPLICATION);
                if (!message)
                        return B_NO_MEMORY;
                message->AddInt32("team", -1);
                fKillAppMessage = message;
                fKillAppButton->SetMessage(message);
                fKillAppButton->SetTarget(target);

                // cancel shutdown button
                AddButton(B_TRANSLATE("Cancel shutdown"));
                fCancelShutdownButton = ButtonAt(CountButtons() - 1);

                message = new BMessage(MSG_CANCEL_SHUTDOWN);
                if (!message)
                        return B_NO_MEMORY;
                fCancelShutdownButton->SetMessage(message);
                fCancelShutdownButton->SetTarget(target);

                // reboot system button
                AddButton(B_TRANSLATE("Restart system"));
                fRebootSystemButton = ButtonAt(CountButtons() - 1);
                fRebootSystemButton->Hide();

                message = new BMessage(MSG_REBOOT_SYSTEM);
                if (!message)
                        return B_NO_MEMORY;
                fRebootSystemButton->SetMessage(message);
                fRebootSystemButton->SetTarget(target);

                // aborted OK button
                AddButton(B_TRANSLATE("OK"));
                fAbortedOKButton = ButtonAt(CountButtons() - 1);
                fAbortedOKButton->Hide();

                message = new BMessage(MSG_CANCEL_SHUTDOWN);
                if (!message)
                        return B_NO_MEMORY;
                fAbortedOKButton->SetMessage(message);
                fAbortedOKButton->SetTarget(target);

                return B_OK;
        }

        status_t AddApp(team_id team, BBitmap* appIcon)
        {
                AppInfo* info = new(nothrow) AppInfo;
                if (!info) {
                        delete appIcon;
                        return B_NO_MEMORY;
                }

                info->team = team;
                info->appIcon = appIcon;

                if (!fAppInfos.AddItem(info)) {
                        delete info;
                        return B_NO_MEMORY;
                }

                return B_OK;
        }

        void RemoveApp(team_id team)
        {
                int32 index = _AppInfoIndexOf(team);
                if (index < 0)
                        return;

                if (team == fCurrentApp)
                        SetCurrentApp(-1);

                AppInfo* info = (AppInfo*)fAppInfos.RemoveItem(index);
                delete info;
        }

        void SetCurrentApp(team_id team)
        {
                AppInfo* info = (team >= 0 ? _AppInfoFor(team) : NULL);

                fCurrentApp = team;
                SetAppInfo(info);

                fKillAppMessage->ReplaceInt32("team", team);
        }

        void SetText(const char* text)
        {
                const int32 initialLength = TextView()->TextLength(),
                        initialLines = TextView()->CountLines();

                BAlert::SetText(text);

                if (TextView()->CountLines() > initialLines
                                || TextView()->CountLines() > (initialLength * 2))
                        ResizeToPreferred();
        }

        void SetCancelShutdownButtonEnabled(bool enable)
        {
                fCancelShutdownButton->SetEnabled(enable);
        }

        void SetKillAppButtonEnabled(bool enable)
        {
                if (enable != fKillAppButton->IsEnabled()) {
                        fKillAppButton->SetEnabled(enable);

                        if (enable)
                                fKillAppButton->Show();
                        else
                                fKillAppButton->Hide();
                        ResizeToPreferred();
                }
        }

        void SetWaitAnimationEnabled(bool enable)
        {
                IconWaitAnimationEnabled(enable);
        }

        void SetWaitForShutdown()
        {
                fKillAppButton->Hide();
                fCancelShutdownButton->Hide();
                fRebootSystemButton->MakeDefault(true);
                fRebootSystemButton->Show();

                SetTitle(B_TRANSLATE("System is shut down"));
                SetText(B_TRANSLATE("It's now safe to turn off the computer."));
        }

        void SetWaitForAbortedOK()
        {
                fKillAppButton->Hide();
                fCancelShutdownButton->Hide();
                fAbortedOKButton->MakeDefault(true);
                fAbortedOKButton->Show();
                ResizeToPreferred();

                SetTitle(B_TRANSLATE("Shutdown aborted"));
        }

private:
        struct AppInfo {
                team_id         team;
                BBitmap*        appIcon;

                ~AppInfo()
                {
                        delete appIcon;
                }
        };

        int32 _AppInfoIndexOf(team_id team)
        {
                if (team < 0)
                        return -1;

                for (int32 i = 0; AppInfo* info = (AppInfo*)fAppInfos.ItemAt(i); i++) {
                        if (info->team == team)
                                return i;
                }

                return -1;
        }

        AppInfo* _AppInfoFor(team_id team)
        {
                int32 index = _AppInfoIndexOf(team);
                return (index >= 0 ? (AppInfo*)fAppInfos.ItemAt(index) : NULL);
        }

private:
        void SetAppInfo(AppInfo* info)
        {
                IconWaitAnimationEnabled(false);

                BAutolock lock(this);
                if (!lock.IsLocked())
                        return;

                delete fNormalIconBitmap;
                fNormalIconBitmap = NULL;

                delete fTintedIconBitmap;
                fTintedIconBitmap = NULL;

                // We do not delete the present fCurrentIconBitmap as the BAlert owns it.
                fCurrentIconBitmap = NULL;

                if (info != NULL && info->appIcon != NULL
                        && info->appIcon->IsValid()) {
                        fCurrentIconBitmap = new BBitmap(info->appIcon->Bounds(), B_RGBA32);

                        if (fCurrentIconBitmap == NULL
                                || fCurrentIconBitmap->ImportBits(info->appIcon) != B_OK) {
                                delete fCurrentIconBitmap;
                                fCurrentIconBitmap = NULL;
                        } else
                                SetIcon(fCurrentIconBitmap);
                } else
                        SetIcon(NULL);
        }

        void IconWaitAnimationEnabled(bool enable)
        {
                if (atomic_get(&fAnimationActive) == enable)
                        return;

                BAutolock lock(this);
                if (!lock.IsLocked())
                        return;

                if (enable) {
                        if (fCurrentIconBitmap == NULL
                                || !fCurrentIconBitmap->IsValid())
                                return;

                        if (fNormalIconBitmap == NULL
                                || !fNormalIconBitmap->IsValid()) {
                                delete fNormalIconBitmap;
                                fNormalIconBitmap = NULL;

                                fNormalIconBitmap = new BBitmap(fCurrentIconBitmap->Bounds(),
                                        B_BITMAP_NO_SERVER_LINK, B_RGBA32);

                                if (fNormalIconBitmap == NULL
                                        || fNormalIconBitmap->ImportBits(fCurrentIconBitmap)
                                                != B_OK) {
                                        delete fNormalIconBitmap;
                                        fNormalIconBitmap = NULL;

                                        return;
                                }
                        }

                        if (fTintedIconBitmap == NULL
                                || !fTintedIconBitmap->IsValid()) {
                                delete fTintedIconBitmap;
                                fTintedIconBitmap = NULL;

                                fTintedIconBitmap = new BBitmap(fNormalIconBitmap->Bounds(),
                                        B_BITMAP_NO_SERVER_LINK, B_RGBA32);

                                if (fTintedIconBitmap == NULL
                                        || fTintedIconBitmap->ImportBits(fNormalIconBitmap)
                                                != B_OK) {
                                        delete fTintedIconBitmap;
                                        fTintedIconBitmap = NULL;

                                        return;
                                }

                                int32 width =
                                        fTintedIconBitmap->Bounds().IntegerWidth() + 1;
                                int32 height =
                                        fTintedIconBitmap->Bounds().IntegerHeight() + 1;
                                int32 rowLength = fTintedIconBitmap->BytesPerRow();

                                uint8* iconBits = (uint8*)fTintedIconBitmap->Bits();

                                for (int32 y = 0; y < height; y++) {
                                        for (int32 x = 0; x < width; x++) {
                                                int32 offset = (y * rowLength) + (x * 4);

                                                rgb_color pixelColor = make_color(iconBits[offset],
                                                        iconBits[offset + 1], iconBits[offset + 2],
                                                        iconBits[offset + 3]);

                                                pixelColor = tint_color(pixelColor,
                                                        B_DARKEN_2_TINT);

                                                iconBits[offset] = pixelColor.red;
                                                iconBits[offset + 1] = pixelColor.green;
                                                iconBits[offset + 2] = pixelColor.blue;
                                                iconBits[offset + 3] = pixelColor.alpha;
                                        }
                                }
                        }

                        fAnimationWorker = spawn_thread(&_AnimateWaitIconWorker,
                                "thumb twiddling", B_DISPLAY_PRIORITY, this);

                        if (fAnimationWorker < B_NO_ERROR)
                                return;

                        atomic_set(&fAnimationActive, true);
                        if (resume_thread(fAnimationWorker) != B_OK)
                                atomic_set(&fAnimationActive, false);
                } else {
                        atomic_set(&fAnimationActive, false);
                        wait_for_thread(fAnimationWorker, NULL);

                        fCurrentAnimationRow = -1;
                        fAnimationLightenPhase = true;

                        if (fCurrentIconBitmap != NULL && fNormalIconBitmap != NULL)
                                fCurrentIconBitmap->ImportBits(fNormalIconBitmap);
                }
        }

private:
        status_t _AnimateWaitIcon()
        {
                int32 lastHeight = 1;
                while (atomic_get(&fAnimationActive)) {
                        if (LockWithTimeout(kIconAnimateInterval / lastHeight) != B_OK)
                                continue;

                        lastHeight = fCurrentIconBitmap->Bounds().IntegerHeight();
                        if (fCurrentAnimationRow < 0) {
                                fCurrentAnimationRow = lastHeight;
                                fAnimationLightenPhase = !fAnimationLightenPhase;
                        }

                        BBitmap* sourceBitmap = fAnimationLightenPhase ?
                                fNormalIconBitmap : fTintedIconBitmap;

                        fCurrentIconBitmap->ImportBits(sourceBitmap,
                                BPoint(0, fCurrentAnimationRow),
                                BPoint(0, fCurrentAnimationRow),
                                BSize(sourceBitmap->Bounds().IntegerWidth() - 1, 0));

                        fCurrentAnimationRow--;

                        ChildAt(0)->Invalidate();

                        Unlock();
                        snooze(kIconAnimateInterval / lastHeight);
                }

                return B_OK;
        }

        static status_t _AnimateWaitIconWorker(void* cookie)
        {
                ShutdownWindow* ourView = (ShutdownWindow*)cookie;
                return ourView->_AnimateWaitIcon();
        }

private:
        BList                           fAppInfos;
        BButton*                        fKillAppButton;
        BButton*                        fCancelShutdownButton;
        BButton*                        fRebootSystemButton;
        BButton*                        fAbortedOKButton;
        BMessage*                       fKillAppMessage;
        team_id                         fCurrentApp;

private:
        BBitmap*                fCurrentIconBitmap;
        BBitmap*                fNormalIconBitmap;
        BBitmap*                fTintedIconBitmap;
        int32                   fAnimationActive;
        thread_id               fAnimationWorker;
        int32                   fCurrentAnimationRow;
        bool                    fAnimationLightenPhase;
};


// #pragma mark -


ShutdownProcess::ShutdownProcess(TRoster* roster, EventQueue* eventQueue)
        :
        BLooper("shutdown process"),
        EventMaskWatcher(BMessenger(this), B_REQUEST_QUIT | B_REQUEST_LAUNCHED),
        fWorkerLock("worker lock"),
        fRequest(NULL),
        fRoster(roster),
        fEventQueue(eventQueue),
        fTimeoutEvent(NULL),
        fInternalEvents(NULL),
        fInternalEventSemaphore(-1),
        fQuitRequestReplyHandler(NULL),
        fWorker(-1),
        fCurrentPhase(INVALID_PHASE),
        fShutdownError(B_ERROR),
        fHasGUI(false),
        fReboot(false),
        fRequestReplySent(false),
        fWindow(NULL)
{
}


ShutdownProcess::~ShutdownProcess()
{
        // terminate the GUI
        if (fHasGUI && fWindow && fWindow->Lock())
                fWindow->Quit();

        // remove and delete the quit request reply handler
        if (fQuitRequestReplyHandler) {
                BAutolock _(this);
                RemoveHandler(fQuitRequestReplyHandler);
                delete fQuitRequestReplyHandler;
        }

        // remove and delete the timeout event
        if (fTimeoutEvent) {
                fEventQueue->RemoveEvent(fTimeoutEvent);

                delete fTimeoutEvent;
        }

        // remove the application quit watcher
        fRoster->RemoveWatcher(this);

        // If an error occurred (e.g. the shutdown process was cancelled), the
        // roster should accept applications again.
        if (fShutdownError != B_OK)
                fRoster->SetShuttingDown(false);

        // delete the internal event semaphore
        if (fInternalEventSemaphore >= 0)
                delete_sem(fInternalEventSemaphore);

        // wait for the worker thread to terminate
        if (fWorker >= 0) {
                int32 result;
                wait_for_thread(fWorker, &result);
        }

        // delete all internal events and the queue
        if (fInternalEvents) {
                while (InternalEvent* event = fInternalEvents->First()) {
                        fInternalEvents->Remove(event);
                        delete event;
                }

                delete fInternalEvents;
        }

        // send a reply to the request and delete it
        _SendReply(fShutdownError);
        delete fRequest;
}


status_t
ShutdownProcess::Init(BMessage* request)
{
        PRINT("ShutdownProcess::Init()\n");

        // create and add the quit request reply handler
        fQuitRequestReplyHandler = new(nothrow) QuitRequestReplyHandler(this);
        if (!fQuitRequestReplyHandler)
                RETURN_ERROR(B_NO_MEMORY);
        AddHandler(fQuitRequestReplyHandler);

        // create the timeout event
        fTimeoutEvent = new(nothrow) TimeoutEvent(this);
        if (!fTimeoutEvent)
                RETURN_ERROR(B_NO_MEMORY);

        // create the event list
        fInternalEvents = new(nothrow) InternalEventList;
        if (!fInternalEvents)
                RETURN_ERROR(B_NO_MEMORY);

        // create the event sempahore
        fInternalEventSemaphore = create_sem(0, "shutdown events");
        if (fInternalEventSemaphore < 0)
                RETURN_ERROR(fInternalEventSemaphore);

        // init the app server connection
        fHasGUI = Registrar::App()->InitGUIContext() == B_OK;

        // start watching application quits
        status_t error = fRoster->AddWatcher(this);
        if (error != B_OK) {
                fRoster->SetShuttingDown(false);
                RETURN_ERROR(error);
        }

        // start the worker thread
        fWorker = spawn_thread(_WorkerEntry, "shutdown worker",
                B_NORMAL_PRIORITY + 1, this);
        if (fWorker < 0) {
                fRoster->RemoveWatcher(this);
                fRoster->SetShuttingDown(false);
                RETURN_ERROR(fWorker);
        }

        // everything went fine: now we own the request
        fRequest = request;

        if (fRequest->FindBool("reboot", &fReboot) != B_OK)
                fReboot = false;

        resume_thread(fWorker);

        PRINT("ShutdownProcess::Init() done\n");

        return B_OK;
}


void
ShutdownProcess::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_SOME_APP_QUIT:
                {
                        // get the team
                        team_id team;
                        if (message->FindInt32("be:team", &team) != B_OK) {
                                // should not happen
                                return;
                        }

                        PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_QUIT: %"
                                B_PRId32 "\n", team);

                        // remove the app info from the respective list
                        int32 phase;
                        RosterAppInfo* info;
                        {
                                BAutolock _(fWorkerLock);

                                info = fUserApps.InfoFor(team);
                                if (info)
                                        fUserApps.RemoveInfo(info);
                                else if ((info = fSystemApps.InfoFor(team)))
                                        fSystemApps.RemoveInfo(info);
                                else if ((info = fBackgroundApps.InfoFor(team)))
                                        fBackgroundApps.RemoveInfo(info);
                                else    // not found
                                        return;

                                phase = fCurrentPhase;
                        }

                        // post the event
                        _PushEvent(APP_QUIT_EVENT, team, phase);

                        delete info;

                        break;
                }

                case B_SOME_APP_LAUNCHED:
                {
                        // get the team
                        team_id team;
                        if (message->FindInt32("be:team", &team) != B_OK) {
                                // should not happen
                                return;
                        }

                        PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_LAUNCHED: %"
                                B_PRId32 "\n", team);

                        // add the user app info to the respective list
                        {
                                BAutolock _(fWorkerLock);
                                fRoster->AddAppInfo(fUserApps, team);
                        }
                        break;
                }

                case MSG_PHASE_TIMED_OUT:
                {
                        // get the phase the event is intended for
                        int32 phase = TimeoutEvent::GetMessagePhase(message);
                        team_id team = TimeoutEvent::GetMessageTeam(message);;
                        PRINT("MSG_PHASE_TIMED_OUT: phase: %" B_PRId32 ", team: %" B_PRId32
                                "\n", phase, team);

                        BAutolock _(fWorkerLock);

                        if (phase == INVALID_PHASE || phase != fCurrentPhase)
                                return;

                        // post the event
                        _PushEvent(TIMEOUT_EVENT, team, phase);

                        break;
                }

                case MSG_KILL_APPLICATION:
                {
                        team_id team;
                        if (message->FindInt32("team", &team) != B_OK)
                                break;

                        // post the event
                        _PushEvent(KILL_APP_EVENT, team, fCurrentPhase);
                        break;
                }

                case MSG_CANCEL_SHUTDOWN:
                {
                        // post the event
                        _PushEvent(ABORT_EVENT, -1, fCurrentPhase);
                        break;
                }

                case MSG_REBOOT_SYSTEM:
                {
                        // post the event
                        _PushEvent(REBOOT_SYSTEM_EVENT, -1, INVALID_PHASE);
                        break;
                }

                case MSG_DONE:
                {
                        // notify the registrar that we're done
                        be_app->PostMessage(B_REG_SHUTDOWN_FINISHED, be_app);
                        break;
                }

                case B_REG_TEAM_DEBUGGER_ALERT:
                {
                        bool stopShutdown;
                        if (message->FindBool("stop shutdown", &stopShutdown) == B_OK
                                && stopShutdown) {
                                // post abort event to the worker
                                _PushEvent(ABORT_EVENT, -1, fCurrentPhase);
                                break;
                        }

                        bool open;
                        team_id team;
                        if (message->FindInt32("team", &team) != B_OK
                                || message->FindBool("open", &open) != B_OK)
                                break;

                        BAutolock _(fWorkerLock);
                        if (open) {
                                PRINT("B_REG_TEAM_DEBUGGER_ALERT: insert %" B_PRId32 "\n",
                                        team);
                                fDebuggedTeams.Add(team);
                        } else {
                                PRINT("B_REG_TEAM_DEBUGGER_ALERT: remove %" B_PRId32 "\n",
                                        team);
                                fDebuggedTeams.Remove(team);
                                _PushEvent(DEBUG_EVENT, -1, fCurrentPhase);
                        }
                        break;
                }

                default:
                        BLooper::MessageReceived(message);
                        break;
        }
}


void
ShutdownProcess::SendReply(BMessage* request, status_t error)
{
        if (error == B_OK) {
                BMessage reply(B_REG_SUCCESS);
                request->SendReply(&reply);
        } else {
                BMessage reply(B_REG_ERROR);
                reply.AddInt32("error", error);
                request->SendReply(&reply);
        }
}


void
ShutdownProcess::_SendReply(status_t error)
{
        if (!fRequestReplySent) {
                SendReply(fRequest, error);
                fRequestReplySent = true;
        }
}


void
ShutdownProcess::_SetPhase(int32 phase)
{
        BAutolock _(fWorkerLock);

        if (phase == fCurrentPhase)
                return;

        fCurrentPhase = phase;

        // remove the timeout event scheduled for the previous phase
        fEventQueue->RemoveEvent(fTimeoutEvent);
}


void
ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout, team_id team)
{
        BAutolock _(fWorkerLock);

        // remove the timeout event
        fEventQueue->RemoveEvent(fTimeoutEvent);

        // set the event's phase, team and time
        fTimeoutEvent->SetPhase(fCurrentPhase);
        fTimeoutEvent->SetTeam(team);
        fTimeoutEvent->SetTime(system_time() + timeout);

        // add the event
        fEventQueue->AddEvent(fTimeoutEvent);
}


void
ShutdownProcess::_SetShowShutdownWindow(bool show)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                if (show == fWindow->IsHidden()) {
                        if (show)
                                fWindow->Go(NULL);
                        else
                                fWindow->Hide();
                }
        }
}


void
ShutdownProcess::_InitShutdownWindow()
{
        // prepare the window
        if (fHasGUI) {
                fWindow = new(nothrow) ShutdownWindow;
                if (fWindow != NULL) {
                        status_t error = fWindow->Init(BMessenger(this));
                        if (error != B_OK) {
                                delete fWindow;
                                fWindow = NULL;
                        }
                }

                // add the applications
                if (fWindow) {
                        BAutolock _(fWorkerLock);
                        _AddShutdownWindowApps(fUserApps);
                        _AddShutdownWindowApps(fSystemApps);
                } else {
                        WARNING("ShutdownProcess::Init(): Failed to create or init "
                                "shutdown window.");

                        fHasGUI = false;
                }
        }
}


void
ShutdownProcess::_AddShutdownWindowApps(AppInfoList& infos)
{
        if (!fHasGUI)
                return;

        for (AppInfoList::Iterator it = infos.It(); it.IsValid(); ++it) {
                RosterAppInfo* info = *it;

                // init an app file info
                BFile file;
                status_t error = file.SetTo(&info->ref, B_READ_ONLY);
                if (error != B_OK) {
                        WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to "
                                "open file for app %s: %s\n", info->signature,
                                strerror(error));
                        continue;
                }

                BAppFileInfo appFileInfo;
                error = appFileInfo.SetTo(&file);
                if (error != B_OK) {
                        WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to "
                                "init app file info for app %s: %s\n", info->signature,
                                strerror(error));
                }

                // get the application icon
                BBitmap* appIcon = new(nothrow) BBitmap(
                        BRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_LARGE_ICON)),
                        B_BITMAP_NO_SERVER_LINK, B_RGBA32);
                if (appIcon != NULL) {
                        error = appIcon->InitCheck();
                        if (error == B_OK) {
                                error = appFileInfo.GetTrackerIcon(appIcon,
                                        (icon_size)(appIcon->Bounds().IntegerWidth() + 1));
                        }
                        if (error != B_OK) {
                                delete appIcon;
                                appIcon = NULL;
                        }
                }

                // add the app
                error = fWindow->AddApp(info->team, appIcon);
                if (error != B_OK) {
                        WARNING("ShutdownProcess::_AddShutdownWindowApps(): Failed to "
                                "add app to the shutdown window: %s\n", strerror(error));
                }
        }
}


void
ShutdownProcess::_RemoveShutdownWindowApp(team_id team)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->RemoveApp(team);
        }
}


void
ShutdownProcess::_SetShutdownWindowCurrentApp(team_id team)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetCurrentApp(team);
        }
}


void
ShutdownProcess::_SetShutdownWindowText(const char* text)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetText(text);
        }
}


void
ShutdownProcess::_SetShutdownWindowCancelButtonEnabled(bool enabled)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetCancelShutdownButtonEnabled(enabled);
        }
}


void
ShutdownProcess::_SetShutdownWindowKillButtonEnabled(bool enabled)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetKillAppButtonEnabled(enabled);
        }
}


void
ShutdownProcess::_SetShutdownWindowWaitAnimationEnabled(bool enabled)
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetWaitAnimationEnabled(enabled);
        }
}


void
ShutdownProcess::_SetShutdownWindowWaitForShutdown()
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetWaitForShutdown();
        }
}


void
ShutdownProcess::_SetShutdownWindowWaitForAbortedOK()
{
        if (fHasGUI) {
                BAutolock _(fWindow);

                fWindow->SetWaitForAbortedOK();
        }
}


void
ShutdownProcess::_NegativeQuitRequestReply(thread_id thread)
{
        BAutolock _(fWorkerLock);

        // Note: team ID == team main thread ID under Haiku. When testing under R5
        // using the team ID in case of an ABORT_EVENT won't work correctly. But
        // this is done only for system apps.
        _PushEvent(ABORT_EVENT, thread, fCurrentPhase);
}


void
ShutdownProcess::_PrepareShutdownMessage(BMessage& message) const
{
        message.what = B_QUIT_REQUESTED;
        message.AddBool("_shutdown_", true);

        BMessage::Private(message).SetReply(BMessenger(fQuitRequestReplyHandler));
}


status_t
ShutdownProcess::_ShutDown()
{
        PRINT("Invoking _kern_shutdown(%d)\n", fReboot);
        RETURN_ERROR(_kern_shutdown(fReboot));
}


status_t
ShutdownProcess::_PushEvent(uint32 eventType, team_id team, int32 phase)
{
        InternalEvent* event = new(nothrow) InternalEvent(eventType, team, phase);
        if (!event) {
                ERROR("ShutdownProcess::_PushEvent(): Failed to create event!\n");

                return B_NO_MEMORY;
        }

        BAutolock _(fWorkerLock);

        fInternalEvents->Add(event);
        release_sem(fInternalEventSemaphore);

        return B_OK;
}


status_t
ShutdownProcess::_GetNextEvent(uint32& eventType, thread_id& team, int32& phase,
        bool block)
{
        while (true) {
                // acquire the semaphore
                if (block) {
                        status_t error;
                        do {
                                error = acquire_sem(fInternalEventSemaphore);
                        } while (error == B_INTERRUPTED);

                        if (error != B_OK)
                                return error;
                } else {
                        status_t error = acquire_sem_etc(fInternalEventSemaphore, 1,
                                B_RELATIVE_TIMEOUT, 0);
                        if (error != B_OK) {
                                eventType = NO_EVENT;
                                return B_OK;
                        }
                }

                // get the event
                BAutolock _(fWorkerLock);

                InternalEvent* event = fInternalEvents->Head();
                fInternalEvents->Remove(event);

                eventType = event->Type();
                team = event->Team();
                phase = event->Phase();

                delete event;

                // if the event is an obsolete timeout event, we drop it right here
                if (eventType == TIMEOUT_EVENT && phase != fCurrentPhase)
                        continue;

                break;
        }

        // notify the window, if an app has been removed
        if (eventType == APP_QUIT_EVENT)
                _RemoveShutdownWindowApp(team);

        return B_OK;
}


status_t
ShutdownProcess::_WorkerEntry(void* data)
{
        return ((ShutdownProcess*)data)->_Worker();
}


status_t
ShutdownProcess::_Worker()
{
        try {
                _WorkerDoShutdown();
                fShutdownError = B_OK;
        } catch (status_t error) {
                PRINT("ShutdownProcess::_Worker(): error while shutting down: %s\n",
                        strerror(error));

                fShutdownError = error;
        }

        // this can happen only, if the shutdown process failed or was aborted:
        // notify the looper
        _SetPhase(DONE_PHASE);
        PostMessage(MSG_DONE);

        return B_OK;
}


void
ShutdownProcess::_WorkerDoShutdown()
{
        PRINT("ShutdownProcess::_WorkerDoShutdown()\n");

        // If we are here, the shutdown process has been initiated successfully,
        // that is, if an asynchronous BRoster::Shutdown() was requested, we
        // notify the caller at this point.
        bool synchronous;
        if (fRequest->FindBool("synchronous", &synchronous) == B_OK && !synchronous)
                _SendReply(B_OK);

        // ask the user to confirm the shutdown, if desired
        bool askUser;
        if (fHasGUI && fRequest->FindBool("confirm", &askUser) == B_OK && askUser) {
                const char* restart = B_TRANSLATE("Restart");
                const char* shutdown = B_TRANSLATE("Shut down");
                BString title = B_TRANSLATE("%action%?");
                title.ReplaceFirst("%action%", fReboot ? restart : shutdown);
                const char* text = fReboot
                        ? B_TRANSLATE("Do you really want to restart the system?")
                        : B_TRANSLATE("Do you really want to shut down the system?");
                const char* defaultText = fReboot ? restart : shutdown;
                const char* otherText = fReboot ? shutdown : restart;
                BAlert* alert = new BAlert(title.String(), text,
                        B_TRANSLATE("Cancel"), otherText, defaultText,
                        B_WIDTH_AS_USUAL, B_WARNING_ALERT);
                // We want the alert to behave more like a regular window...
                alert->SetFeel(B_NORMAL_WINDOW_FEEL);
                // ...but not quit. Minimizing the alert would prevent the user from
                // finding it again, since registrar does not have an entry in the
                // Deskbar.
                alert->SetFlags(alert->Flags() | B_NOT_MINIMIZABLE | B_CLOSE_ON_ESCAPE);
                alert->SetWorkspaces(B_ALL_WORKSPACES);
                int32 result = alert->Go();

                if (result == 1) {
                        // Toggle shutdown method
                        fReboot = !fReboot;
                } else if (result < 1)
                        throw_error(B_SHUTDOWN_CANCELLED);
        }

        fWorkerLock.Lock();
        // get a list of all applications to shut down and sort them
        status_t status = fRoster->GetShutdownApps(fUserApps, fSystemApps,
                fBackgroundApps, fVitalSystemApps);
        if (status  != B_OK) {
                fWorkerLock.Unlock();
                fRoster->RemoveWatcher(this);
                return;
        }

        fUserApps.Sort(&inverse_compare_by_registration_time);
        fSystemApps.Sort(&inverse_compare_by_registration_time);

        fWorkerLock.Unlock();

        // make the shutdown window ready and show it
        _InitShutdownWindow();
        _SetShutdownWindowCurrentApp(-1);
        _SetShutdownWindowText(B_TRANSLATE("Tidying things up a bit."));
        _SetShutdownWindowCancelButtonEnabled(true);
        _SetShutdownWindowKillButtonEnabled(false);
        _SetShowShutdownWindow(true);

        // sync
        sync();

        // phase 1: terminate the user apps
        _SetPhase(USER_APP_TERMINATION_PHASE);

        // since, new apps can still be launched, loop until all are gone
        if (!fUserApps.IsEmpty()) {
                _QuitApps(fUserApps, false);
                _WaitForDebuggedTeams();
        }

        // tell TRoster not to accept new applications anymore
        fRoster->SetShuttingDown(true);

        // phase 2: terminate the system apps
        _SetPhase(SYSTEM_APP_TERMINATION_PHASE);
        _QuitApps(fSystemApps, true);
        _WaitForDebuggedTeams();

        // phase 3: terminate the background apps
        _SetPhase(BACKGROUND_APP_TERMINATION_PHASE);
        _QuitBackgroundApps();
        _WaitForDebuggedTeams();

        // phase 4: terminate the other processes
        _SetPhase(OTHER_PROCESSES_TERMINATION_PHASE);
        _QuitNonApps();
        _ScheduleTimeoutEvent(kBackgroundAppQuitTimeout, -1);
        _WaitForBackgroundApps();
        _KillBackgroundApps();
        _WaitForDebuggedTeams();

        // we're through: do the shutdown
        _SetPhase(DONE_PHASE);
        if (fReboot)
                _SetShutdownWindowText(B_TRANSLATE("Restarting" B_UTF8_ELLIPSIS));
        else
                _SetShutdownWindowText(B_TRANSLATE("Shutting down" B_UTF8_ELLIPSIS));
        _ShutDown();
        _SetShutdownWindowWaitForShutdown();

        PRINT("  _kern_shutdown() failed\n");

        // shutdown failed: This can happen for power off mode -- reboot should
        // always work.
        if (fHasGUI) {
                // wait for the reboot event
                uint32 event;
                do {
                        team_id team;
                        int32 phase;
                        status = _GetNextEvent(event, team, phase, true);
                        if (status != B_OK)
                                break;
                } while (event != REBOOT_SYSTEM_EVENT);

                _kern_shutdown(true);
        }

        // either there's no GUI or reboot failed: we enter the kernel debugger
        // instead
        while (true) {
                _kern_kernel_debugger("The system is shut down. It's now safe to turn "
                        "off the computer.");
        }
}


bool
ShutdownProcess::_WaitForApp(team_id team, AppInfoList* list, bool systemApps)
{
        uint32 event;
        do {
                team_id eventTeam;
                int32 phase;
                status_t error = _GetNextEvent(event, eventTeam, phase, true);
                if (error != B_OK)
                        throw_error(error);

                if (event == APP_QUIT_EVENT && eventTeam == team)
                        return true;

                if (event == TIMEOUT_EVENT && eventTeam == team)
                        return false;

                if (event == ABORT_EVENT) {
                        if (eventTeam == -1) {
                                // The user canceled the shutdown process by pressing the
                                // Cancel button.
                                throw_error(B_SHUTDOWN_CANCELLED);
                        }
                        if (systemApps) {
                                // If the app requests aborting the shutdown, we don't need
                                // to wait any longer. It has processed the request and
                                // won't quit by itself. We ignore this for system apps.
                                if (eventTeam == team)
                                        return false;
                        } else {
                                // The app returned false in QuitRequested().
                                PRINT("ShutdownProcess::_WaitForApp(): shutdown cancelled "
                                        "by team %" B_PRId32 " (-1 => user)\n", eventTeam);

                                _DisplayAbortingApp(team);
                                throw_error(B_SHUTDOWN_CANCELLED);
                        }
                }

                BAutolock _(fWorkerLock);
                if (list != NULL && !list->InfoFor(team))
                        return true;
        } while (event != NO_EVENT);

        return false;
}


void
ShutdownProcess::_QuitApps(AppInfoList& list, bool systemApps)
{
        PRINT("ShutdownProcess::_QuitApps(%s)\n",
                (systemApps ? "system" : "user"));

        if (systemApps) {
                _SetShutdownWindowCancelButtonEnabled(false);

                // check one last time for abort events
                uint32 event;
                do {
                        team_id team;
                        int32 phase;
                        status_t error = _GetNextEvent(event, team, phase, false);
                        if (error != B_OK)
                                throw_error(error);

                        if (event == ABORT_EVENT) {
                                PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by "
                                        "team %" B_PRId32 " (-1 => user)\n", team);

                                _DisplayAbortingApp(team);
                                throw_error(B_SHUTDOWN_CANCELLED);
                        }

                } while (event != NO_EVENT);
        }

        // prepare the shutdown message
        BMessage message;
        _PrepareShutdownMessage(message);

        // now iterate through the list of apps
        while (true) {
                // eat events
                uint32 event;
                do {
                        team_id team;
                        int32 phase;
                        status_t error = _GetNextEvent(event, team, phase, false);
                        if (error != B_OK)
                                throw_error(error);

                        if (!systemApps && event == ABORT_EVENT) {
                                PRINT("ShutdownProcess::_QuitApps(): shutdown cancelled by "
                                        "team %" B_PRId32 " (-1 => user)\n", team);

                                _DisplayAbortingApp(team);
                                throw_error(B_SHUTDOWN_CANCELLED);
                        }

                } while (event != NO_EVENT);

                // get the first app to quit
                team_id team = -1;
                port_id port = -1;
                char appName[B_FILE_NAME_LENGTH];
                {
                        BAutolock _(fWorkerLock);
                        while (!list.IsEmpty()) {
                                RosterAppInfo* info = *list.It();
                                team = info->team;
                                port = info->port;
                                strcpy(appName, info->ref.name);

                                if (info->IsRunning())
                                        break;
                                list.RemoveInfo(info);
                                delete info;
                        }
                }

                if (team < 0) {
                        PRINT("ShutdownProcess::_QuitApps() done\n");
                        return;
                }

                // set window text
                BString buffer = B_TRANSLATE("Asking \"%appName%\" to quit.");
                buffer.ReplaceFirst("%appName%", appName);
                _SetShutdownWindowText(buffer.String());
                _SetShutdownWindowCurrentApp(team);

                // send the shutdown message to the app
                PRINT("  sending team %" B_PRId32 " (port: %" B_PRId32 ") a shutdown "
                        "message\n", team, port);
                SingleMessagingTargetSet target(port, B_PREFERRED_TOKEN);
                MessageDeliverer::Default()->DeliverMessage(&message, target);

                // schedule a timeout event
                _ScheduleTimeoutEvent(kAppQuitTimeout, team);

                // wait for the app to die or for the timeout to occur
                bool appGone = _WaitForApp(team, &list, systemApps);
                if (appGone) {
                        // fine: the app finished in an orderly manner
                } else {
                        // the app is either blocking on a model alert or blocks for another
                        // reason
                        if (!systemApps)
                                _QuitBlockingApp(list, team, appName, true);
                        else {
                                // This is a system app: remove it from the list
                                BAutolock _(fWorkerLock);

                                if (RosterAppInfo* info = list.InfoFor(team)) {
                                        list.RemoveInfo(info);
                                        delete info;
                                }
                        }
                }
        }
}


void
ShutdownProcess::_QuitBackgroundApps()
{
        PRINT("ShutdownProcess::_QuitBackgroundApps()\n");

        _SetShutdownWindowText(
                B_TRANSLATE("Asking background applications to quit."));

        // prepare the shutdown message
        BMessage message;
        _PrepareShutdownMessage(message);

        // send shutdown messages to user apps
        BAutolock _(fWorkerLock);

        AppInfoListMessagingTargetSet targetSet(fBackgroundApps);

        if (targetSet.HasNext()) {
                PRINT("  sending shutdown message to %" B_PRId32 " apps\n",
                        fBackgroundApps.CountInfos());

                status_t error = MessageDeliverer::Default()->DeliverMessage(
                        &message, targetSet);
                if (error != B_OK) {
                        WARNING("_QuitBackgroundApps::_Worker(): Failed to deliver "
                                "shutdown message to all applications: %s\n",
                                strerror(error));
                }
        }

        PRINT("ShutdownProcess::_QuitBackgroundApps() done\n");
}


void
ShutdownProcess::_WaitForBackgroundApps()
{
        PRINT("ShutdownProcess::_WaitForBackgroundApps()\n");

        // wait for user apps
        bool moreApps = true;
        while (moreApps) {
                {
                        BAutolock _(fWorkerLock);
                        moreApps = !fBackgroundApps.IsEmpty();
                }

                if (moreApps) {
                        uint32 event;
                        team_id team;
                        int32 phase;
                        status_t error = _GetNextEvent(event, team, phase, true);
                        if (error != B_OK)
                                throw_error(error);

                        if (event == ABORT_EVENT) {
                                // ignore: it's too late to abort the shutdown
                        }

                        if (event == TIMEOUT_EVENT)
                                return;
                }
        }

        PRINT("ShutdownProcess::_WaitForBackgroundApps() done\n");
}


void
ShutdownProcess::_KillBackgroundApps()
{
        PRINT("ShutdownProcess::_KillBackgroundApps()\n");

        while (true) {
                // eat events (we need to be responsive for an abort event)
                uint32 event;
                do {
                        team_id team;
                        int32 phase;
                        status_t error = _GetNextEvent(event, team, phase, false);
                        if (error != B_OK)
                                throw_error(error);

                } while (event != NO_EVENT);

                // get the first team to kill
                team_id team = -1;
                char appName[B_FILE_NAME_LENGTH];
                AppInfoList& list = fBackgroundApps;
                {
                        BAutolock _(fWorkerLock);

                        if (!list.IsEmpty()) {
                                RosterAppInfo* info = *list.It();
                                team = info->team;
                                strcpy(appName, info->ref.name);
                        }
                }


                if (team < 0) {
                        PRINT("ShutdownProcess::_KillBackgroundApps() done\n");
                        return;
                }

                // the app is either blocking on a model alert or blocks for another
                // reason
                _QuitBlockingApp(list, team, appName, false);
        }
}


void
ShutdownProcess::_QuitNonApps()
{
        PRINT("ShutdownProcess::_QuitNonApps()\n");

        _SetShutdownWindowText(B_TRANSLATE("Asking other processes to quit."));

        // iterate through the remaining teams and send them the TERM signal
        int32 cookie = 0;
        team_info teamInfo;
        while (get_next_team_info(&cookie, &teamInfo) == B_OK) {
                if (!fVitalSystemApps.Contains(teamInfo.team)) {
                        PRINT("  sending team %" B_PRId32 " TERM signal\n", teamInfo.team);

                        // Note: team ID == team main thread ID under Haiku
                        send_signal(teamInfo.team, SIGTERM);
                }
        }

        // give them a bit of time to terminate
        // TODO: Instead of just waiting we could periodically check whether the
        // processes are already gone to shorten the process.
        snooze(kNonAppQuitTimeout);

        // iterate through the remaining teams and kill them
        cookie = 0;
        while (get_next_team_info(&cookie, &teamInfo) == B_OK) {
                if (!fVitalSystemApps.Contains(teamInfo.team)) {
                        PRINT("  killing team %" B_PRId32 "\n", teamInfo.team);

                        kill_team(teamInfo.team);
                }
        }

        PRINT("ShutdownProcess::_QuitNonApps() done\n");
}


void
ShutdownProcess::_QuitBlockingApp(AppInfoList& list, team_id team,
        const char* appName, bool cancelAllowed)
{
        bool debugged = false;
        bool modal = false;
        {
                BAutolock _(fWorkerLock);
                if (fDebuggedTeams.Contains(team))
                        debugged = true;
        }
        if (!debugged)
                modal = BPrivate::is_app_showing_modal_window(team);

        if (modal) {
                // app blocks on a modal window
                BString buffer = B_TRANSLATE("The application \"%appName%\" might be "
                        "blocked on a modal panel.");
                buffer.ReplaceFirst("%appName%", appName);
                _SetShutdownWindowText(buffer.String());
                _SetShutdownWindowCurrentApp(team);
                _SetShutdownWindowKillButtonEnabled(true);
                _SetShutdownWindowWaitAnimationEnabled(true);
        }

        if (modal || debugged) {
                // wait for something to happen
                bool appGone = false;
                while (true) {
                        uint32 event;
                        team_id eventTeam;
                        int32 phase;
                        status_t error = _GetNextEvent(event, eventTeam, phase, true);
                        if (error != B_OK)
                                throw_error(error);

                        if ((event == APP_QUIT_EVENT) && eventTeam == team) {
                                appGone = true;
                                break;
                        }

                        if (event == KILL_APP_EVENT && eventTeam == team)
                                break;

                        if (event == ABORT_EVENT) {
                                if (cancelAllowed || debugged) {
                                        PRINT("ShutdownProcess::_QuitBlockingApp(): shutdown "
                                                "cancelled by team %" B_PRId32 " (-1 => user)\n",
                                                eventTeam);

                                        if (!debugged)
                                                _DisplayAbortingApp(eventTeam);
                                        throw_error(B_SHUTDOWN_CANCELLED);
                                }

                                // If the app requests aborting the shutdown, we don't need
                                // to wait any longer. It has processed the request and
                                // won't quit by itself. We'll have to kill it.
                                if (eventTeam == team)
                                        break;
                        }
                }

                _SetShutdownWindowKillButtonEnabled(false);
                _SetShutdownWindowWaitAnimationEnabled(false);

                if (appGone)
                        return;
        }

        // kill the app
        PRINT("  killing team %" B_PRId32 "\n", team);

        kill_team(team);

        // remove the app (the roster will note eventually and send us
        // a notification, but we want to be sure)
        {
                BAutolock _(fWorkerLock);

                if (RosterAppInfo* info = list.InfoFor(team)) {
                        list.RemoveInfo(info);
                        delete info;
                }
        }
}


void
ShutdownProcess::_DisplayAbortingApp(team_id team)
{
        // find the app that cancelled the shutdown
        char appName[B_FILE_NAME_LENGTH];
        bool foundApp = false;
        {
                BAutolock _(fWorkerLock);

                RosterAppInfo* info = fUserApps.InfoFor(team);
                if (!info)
                        info = fSystemApps.InfoFor(team);
                if (!info)
                        fBackgroundApps.InfoFor(team);

                if (info) {
                        foundApp = true;
                        strcpy(appName, info->ref.name);
                }
        }

        if (!foundApp) {
                PRINT("ShutdownProcess::_DisplayAbortingApp(): Didn't find the app "
                        "that has cancelled the shutdown.\n");
                return;
        }

        // compose the text to be displayed
        BString buffer = B_TRANSLATE("Application \"%appName%\" has aborted the "
                "shutdown process.");
        buffer.ReplaceFirst("%appName%", appName);

        // set up the window
        _SetShutdownWindowWaitAnimationEnabled(false);
        _SetShutdownWindowCurrentApp(team);
        _SetShutdownWindowText(buffer.String());
        _SetShutdownWindowWaitForAbortedOK();

        // schedule the timeout event
        _SetPhase(ABORTED_PHASE);
        _ScheduleTimeoutEvent(kDisplayAbortingAppTimeout);

        // wait for the timeout or the user to press the cancel button
        while (true) {
                uint32 event;
                team_id eventTeam;
                int32 phase;
                status_t error = _GetNextEvent(event, eventTeam, phase, true);
                if (error != B_OK)
                        break;

                // stop waiting when the timeout occurs
                if (event == TIMEOUT_EVENT)
                        break;

                // stop waiting when the user hit the cancel button
                if (event == ABORT_EVENT && phase == ABORTED_PHASE && eventTeam < 0)
                        break;
        }
}


/*!     Waits until the debugged team list is empty, ie. when there is no one
        left to debug.
*/
void
ShutdownProcess::_WaitForDebuggedTeams()
{
        PRINT("ShutdownProcess::_WaitForDebuggedTeams()\n");
        {
                BAutolock _(fWorkerLock);
                if (fDebuggedTeams.Size() == 0)
                        return;
        }

        PRINT("  not empty!\n");

        // wait for something to happen
        while (true) {
                uint32 event;
                team_id eventTeam;
                int32 phase;
                status_t error = _GetNextEvent(event, eventTeam, phase, true);
                if (error != B_OK)
                        throw_error(error);

                if (event == ABORT_EVENT)
                        throw_error(B_SHUTDOWN_CANCELLED);

                BAutolock _(fWorkerLock);
                if (fDebuggedTeams.Size() == 0) {
                        PRINT("  out empty");
                        return;
                }
        }
}