* 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>
#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;
static const bigtime_t kAppQuitTimeout = 3000000;
static const bigtime_t kBackgroundAppQuitTimeout = 3000000;
static const bigtime_t kNonAppQuitTimeout = 500000;
static const bigtime_t kDisplayAbortingAppTimeout = 3000000;
static const bigtime_t kIconAnimateInterval = 50000 * B_LARGE_ICON;
enum {
MSG_PHASE_TIMED_OUT = 'phto',
MSG_DONE = 'done',
MSG_KILL_APPLICATION = 'kill',
MSG_CANCEL_SHUTDOWN = 'cncl',
MSG_REBOOT_SYSTEM = 'lbot',
};
enum {
NO_EVENT,
ABORT_EVENT,
TIMEOUT_EVENT,
APP_QUIT_EVENT,
KILL_APP_EVENT,
REBOOT_SYSTEM_EVENT,
DEBUG_EVENT
};
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);
}
*/
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)
{
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);
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);
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);
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;
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;
};
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()
{
if (fHasGUI && fWindow && fWindow->Lock())
fWindow->Quit();
if (fQuitRequestReplyHandler) {
BAutolock _(this);
RemoveHandler(fQuitRequestReplyHandler);
delete fQuitRequestReplyHandler;
}
if (fTimeoutEvent) {
fEventQueue->RemoveEvent(fTimeoutEvent);
delete fTimeoutEvent;
}
fRoster->RemoveWatcher(this);
if (fShutdownError != B_OK)
fRoster->SetShuttingDown(false);
if (fInternalEventSemaphore >= 0)
delete_sem(fInternalEventSemaphore);
if (fWorker >= 0) {
int32 result;
wait_for_thread(fWorker, &result);
}
if (fInternalEvents) {
while (InternalEvent* event = fInternalEvents->First()) {
fInternalEvents->Remove(event);
delete event;
}
delete fInternalEvents;
}
_SendReply(fShutdownError);
delete fRequest;
}
status_t
ShutdownProcess::Init(BMessage* request)
{
PRINT("ShutdownProcess::Init()\n");
fQuitRequestReplyHandler = new(nothrow) QuitRequestReplyHandler(this);
if (!fQuitRequestReplyHandler)
RETURN_ERROR(B_NO_MEMORY);
AddHandler(fQuitRequestReplyHandler);
fTimeoutEvent = new(nothrow) TimeoutEvent(this);
if (!fTimeoutEvent)
RETURN_ERROR(B_NO_MEMORY);
fInternalEvents = new(nothrow) InternalEventList;
if (!fInternalEvents)
RETURN_ERROR(B_NO_MEMORY);
fInternalEventSemaphore = create_sem(0, "shutdown events");
if (fInternalEventSemaphore < 0)
RETURN_ERROR(fInternalEventSemaphore);
fHasGUI = Registrar::App()->InitGUIContext() == B_OK;
status_t error = fRoster->AddWatcher(this);
if (error != B_OK) {
fRoster->SetShuttingDown(false);
RETURN_ERROR(error);
}
fWorker = spawn_thread(_WorkerEntry, "shutdown worker",
B_NORMAL_PRIORITY + 1, this);
if (fWorker < 0) {
fRoster->RemoveWatcher(this);
fRoster->SetShuttingDown(false);
RETURN_ERROR(fWorker);
}
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:
{
team_id team;
if (message->FindInt32("be:team", &team) != B_OK) {
return;
}
PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_QUIT: %"
B_PRId32 "\n", team);
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
return;
phase = fCurrentPhase;
}
_PushEvent(APP_QUIT_EVENT, team, phase);
delete info;
break;
}
case B_SOME_APP_LAUNCHED:
{
team_id team;
if (message->FindInt32("be:team", &team) != B_OK) {
return;
}
PRINT("ShutdownProcess::MessageReceived(): B_SOME_APP_LAUNCHED: %"
B_PRId32 "\n", team);
{
BAutolock _(fWorkerLock);
fRoster->AddAppInfo(fUserApps, team);
}
break;
}
case MSG_PHASE_TIMED_OUT:
{
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;
_PushEvent(TIMEOUT_EVENT, team, phase);
break;
}
case MSG_KILL_APPLICATION:
{
team_id team;
if (message->FindInt32("team", &team) != B_OK)
break;
_PushEvent(KILL_APP_EVENT, team, fCurrentPhase);
break;
}
case MSG_CANCEL_SHUTDOWN:
{
_PushEvent(ABORT_EVENT, -1, fCurrentPhase);
break;
}
case MSG_REBOOT_SYSTEM:
{
_PushEvent(REBOOT_SYSTEM_EVENT, -1, INVALID_PHASE);
break;
}
case MSG_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) {
_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;
fEventQueue->RemoveEvent(fTimeoutEvent);
}
void
ShutdownProcess::_ScheduleTimeoutEvent(bigtime_t timeout, team_id team)
{
BAutolock _(fWorkerLock);
fEventQueue->RemoveEvent(fTimeoutEvent);
fTimeoutEvent->SetPhase(fCurrentPhase);
fTimeoutEvent->SetTeam(team);
fTimeoutEvent->SetTime(system_time() + timeout);
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()
{
if (fHasGUI) {
fWindow = new(nothrow) ShutdownWindow;
if (fWindow != NULL) {
status_t error = fWindow->Init(BMessenger(this));
if (error != B_OK) {
delete fWindow;
fWindow = NULL;
}
}
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;
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));
}
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;
}
}
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);
_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) {
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;
}
}
BAutolock _(fWorkerLock);
InternalEvent* event = fInternalEvents->Head();
fInternalEvents->Remove(event);
eventType = event->Type();
team = event->Team();
phase = event->Phase();
delete event;
if (eventType == TIMEOUT_EVENT && phase != fCurrentPhase)
continue;
break;
}
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;
}
_SetPhase(DONE_PHASE);
PostMessage(MSG_DONE);
return B_OK;
}
void
ShutdownProcess::_WorkerDoShutdown()
{
PRINT("ShutdownProcess::_WorkerDoShutdown()\n");
bool synchronous;
if (fRequest->FindBool("synchronous", &synchronous) == B_OK && !synchronous)
_SendReply(B_OK);
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);
alert->SetFeel(B_NORMAL_WINDOW_FEEL);
alert->SetFlags(alert->Flags() | B_NOT_MINIMIZABLE | B_CLOSE_ON_ESCAPE);
alert->SetWorkspaces(B_ALL_WORKSPACES);
int32 result = alert->Go();
if (result == 1) {
fReboot = !fReboot;
} else if (result < 1)
throw_error(B_SHUTDOWN_CANCELLED);
}
fWorkerLock.Lock();
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();
_InitShutdownWindow();
_SetShutdownWindowCurrentApp(-1);
_SetShutdownWindowText(B_TRANSLATE("Tidying things up a bit."));
_SetShutdownWindowCancelButtonEnabled(true);
_SetShutdownWindowKillButtonEnabled(false);
_SetShowShutdownWindow(true);
sync();
_SetPhase(USER_APP_TERMINATION_PHASE);
if (!fUserApps.IsEmpty()) {
_QuitApps(fUserApps, false);
_WaitForDebuggedTeams();
}
fRoster->SetShuttingDown(true);
_SetPhase(SYSTEM_APP_TERMINATION_PHASE);
_QuitApps(fSystemApps, true);
_WaitForDebuggedTeams();
_SetPhase(BACKGROUND_APP_TERMINATION_PHASE);
_QuitBackgroundApps();
_WaitForDebuggedTeams();
_SetPhase(OTHER_PROCESSES_TERMINATION_PHASE);
_QuitNonApps();
_ScheduleTimeoutEvent(kBackgroundAppQuitTimeout, -1);
_WaitForBackgroundApps();
_KillBackgroundApps();
_WaitForDebuggedTeams();
_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");
if (fHasGUI) {
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);
}
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) {
throw_error(B_SHUTDOWN_CANCELLED);
}
if (systemApps) {
if (eventTeam == team)
return false;
} else {
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);
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);
}
BMessage message;
_PrepareShutdownMessage(message);
while (true) {
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);
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;
}
BString buffer = B_TRANSLATE("Asking \"%appName%\" to quit.");
buffer.ReplaceFirst("%appName%", appName);
_SetShutdownWindowText(buffer.String());
_SetShutdownWindowCurrentApp(team);
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);
_ScheduleTimeoutEvent(kAppQuitTimeout, team);
bool appGone = _WaitForApp(team, &list, systemApps);
if (appGone) {
} else {
if (!systemApps)
_QuitBlockingApp(list, team, appName, true);
else {
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."));
BMessage message;
_PrepareShutdownMessage(message);
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");
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) {
}
if (event == TIMEOUT_EVENT)
return;
}
}
PRINT("ShutdownProcess::_WaitForBackgroundApps() done\n");
}
void
ShutdownProcess::_KillBackgroundApps()
{
PRINT("ShutdownProcess::_KillBackgroundApps()\n");
while (true) {
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);
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;
}
_QuitBlockingApp(list, team, appName, false);
}
}
void
ShutdownProcess::_QuitNonApps()
{
PRINT("ShutdownProcess::_QuitNonApps()\n");
_SetShutdownWindowText(B_TRANSLATE("Asking other processes to quit."));
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);
send_signal(teamInfo.team, SIGTERM);
}
}
snooze(kNonAppQuitTimeout);
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) {
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) {
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 (eventTeam == team)
break;
}
}
_SetShutdownWindowKillButtonEnabled(false);
_SetShutdownWindowWaitAnimationEnabled(false);
if (appGone)
return;
}
PRINT(" killing team %" B_PRId32 "\n", team);
kill_team(team);
{
BAutolock _(fWorkerLock);
if (RosterAppInfo* info = list.InfoFor(team)) {
list.RemoveInfo(info);
delete info;
}
}
}
void
ShutdownProcess::_DisplayAbortingApp(team_id team)
{
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;
}
BString buffer = B_TRANSLATE("Application \"%appName%\" has aborted the "
"shutdown process.");
buffer.ReplaceFirst("%appName%", appName);
_SetShutdownWindowWaitAnimationEnabled(false);
_SetShutdownWindowCurrentApp(team);
_SetShutdownWindowText(buffer.String());
_SetShutdownWindowWaitForAbortedOK();
_SetPhase(ABORTED_PHASE);
_ScheduleTimeoutEvent(kDisplayAbortingAppTimeout);
while (true) {
uint32 event;
team_id eventTeam;
int32 phase;
status_t error = _GetNextEvent(event, eventTeam, phase, true);
if (error != B_OK)
break;
if (event == TIMEOUT_EVENT)
break;
if (event == ABORT_EVENT && phase == ABORTED_PHASE && eventTeam < 0)
break;
}
}
left to debug.
*/
void
ShutdownProcess::_WaitForDebuggedTeams()
{
PRINT("ShutdownProcess::_WaitForDebuggedTeams()\n");
{
BAutolock _(fWorkerLock);
if (fDebuggedTeams.Size() == 0)
return;
}
PRINT(" not empty!\n");
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;
}
}
}