* Moved TRoster out of the BPrivate namespace. It does no longer appear in

any public header.
* Replaced a good deal of the MessageDeliverer's DeliverMessage() versions
  by more general ones using the new interface MessagingTargetSet to represent
  a set of targets. This simplifies the usage in cases where the caller doesn't
  already have the targets in a supported representation.
* Implemented a first approximation of the shutdown process. There is no
  GUI yet. Only superficially tested under R5.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@13417 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold 2005-07-03 17:17:40 +00:00
parent 925d069d23
commit 553ea30124
20 changed files with 1683 additions and 232 deletions

View File

@ -46,10 +46,6 @@
class BHandler;
class BLooper;
namespace BPrivate {
class TRoster;
};
// BMessenger class ------------------------------------------------------------
class BMessenger {
public:

View File

@ -56,8 +56,7 @@ AppInfoList::AppInfoList()
AppInfoList::~AppInfoList()
{
// delete all infos
for (int32 i = 0; RosterAppInfo *info = InfoAt(i); i++)
delete info;
MakeEmpty(true);
}
// AddInfo
@ -90,8 +89,13 @@ AppInfoList::RemoveInfo(RosterAppInfo *info)
/*! \brief Removes all RosterAppInfos from the list.
*/
void
AppInfoList::MakeEmpty()
AppInfoList::MakeEmpty(bool deleteInfos)
{
if (deleteInfos) {
for (int32 i = 0; RosterAppInfo *info = InfoAt(i); i++)
delete info;
}
fInfos.MakeEmpty();
}

View File

@ -45,13 +45,14 @@ public:
bool AddInfo(RosterAppInfo *info);
bool RemoveInfo(RosterAppInfo *info);
void MakeEmpty();
void MakeEmpty(bool deleteInfos = false);
RosterAppInfo *InfoFor(const char *signature) const;
RosterAppInfo *InfoFor(team_id team) const;
RosterAppInfo *InfoFor(const entry_ref *ref) const;
RosterAppInfo *InfoForToken(uint32 token) const;
bool IsEmpty() const { return (CountInfos() == 0); };
int32 CountInfos() const;
Iterator It();

View File

@ -0,0 +1,74 @@
/*
* Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*/
#include <Application.h>
#include <TokenSpace.h>
#include "AppInfoListMessagingTargetSet.h"
#include "RosterAppInfo.h"
// constructor
AppInfoListMessagingTargetSet::AppInfoListMessagingTargetSet(
AppInfoList &list, bool skipRegistrar)
: fList(list),
fIterator(list.It()),
fSkipRegistrar(skipRegistrar)
{
_SkipFilteredOutInfos();
}
// destructor
AppInfoListMessagingTargetSet::~AppInfoListMessagingTargetSet()
{
}
// HasNext
bool
AppInfoListMessagingTargetSet::HasNext() const
{
return fIterator.IsValid();
}
// Next
bool
AppInfoListMessagingTargetSet::Next(port_id &port, int32 &token)
{
if (!fIterator.IsValid())
return false;
port = (*fIterator)->port;
token = B_PREFERRED_TOKEN;
++fIterator;
_SkipFilteredOutInfos();
return true;
}
// Rewind
void
AppInfoListMessagingTargetSet::Rewind()
{
fIterator = fList.It();
}
// Filter
bool
AppInfoListMessagingTargetSet::Filter(const RosterAppInfo *info)
{
if (!fSkipRegistrar)
return true;
return (!fSkipRegistrar || info->team != be_app->Team());
}
// _SkipFilteredOutInfos
void
AppInfoListMessagingTargetSet::_SkipFilteredOutInfos()
{
while (fIterator.IsValid() && !Filter(*fIterator))
++fIterator;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*
* Implements the MessagingTargetSet interface for AppInfoLists, so that
* no other representation (array/list) is needed to feed them into the
* MessageDeliverer.
*/
#ifndef APP_INFO_LIST_MESSAGING_TARGET_SET_H
#define APP_INFO_LIST_MESSAGING_TARGET_SET_H
#include "AppInfoList.h"
#include "MessageDeliverer.h"
class RosterAppInfo;
class AppInfoListMessagingTargetSet : public MessagingTargetSet {
public:
AppInfoListMessagingTargetSet(AppInfoList &list,
bool skipRegistrar = true);
virtual ~AppInfoListMessagingTargetSet();
virtual bool HasNext() const;
virtual bool Next(port_id &port, int32 &token);
virtual void Rewind();
virtual bool Filter(const RosterAppInfo *info);
private:
void _SkipFilteredOutInfos();
AppInfoList &fList;
AppInfoList::Iterator fIterator;
bool fSkipRegistrar;
};
#endif // APP_INFO_LIST_MESSAGING_TARGET_SET_H

View File

@ -9,6 +9,7 @@ AddResources obos_registrar : registrar.rdef ;
local registrar_sources =
AppInfoList.cpp
AppInfoListMessagingTargetSet.cpp
Clipboard.cpp
ClipboardHandler.cpp
Event.cpp
@ -27,6 +28,7 @@ local registrar_sources =
Registrar.cpp
RosterAppInfo.cpp
RosterSettingsCharStream.cpp
ShutdownProcess.cpp
TRoster.cpp
Watcher.cpp
WatchingService.cpp

View File

@ -31,6 +31,123 @@ static const bigtime_t kRetryDelay = 100000; // 100 ms
static const int32 kMaxMessagesPerPort = 10000;
static const int32 kMaxDataPerPort = 50 * 1024 * 1024; // 50 MB
// MessagingTargetSet
// destructor
MessagingTargetSet::~MessagingTargetSet()
{
}
// #pragma mark -
// DefaultMessagingTargetSet
// constructor
DefaultMessagingTargetSet::DefaultMessagingTargetSet(
const messaging_target *targets, int32 targetCount)
: MessagingTargetSet(),
fTargets(targets),
fTargetCount(targetCount),
fNextIndex(0)
{
}
// destructor
DefaultMessagingTargetSet::~DefaultMessagingTargetSet()
{
}
// HasNext
bool
DefaultMessagingTargetSet::HasNext() const
{
return (fNextIndex < fTargetCount);
}
// Next
bool
DefaultMessagingTargetSet::Next(port_id &port, int32 &token)
{
if (fNextIndex >= fTargetCount)
return false;
port = fTargets[fNextIndex].port;
token = fTargets[fNextIndex].token;
fNextIndex++;
return true;
}
// Rewind
void
DefaultMessagingTargetSet::Rewind()
{
fNextIndex = 0;
}
// #pragma mark -
// SingleMessagingTargetSet
// constructor
SingleMessagingTargetSet::SingleMessagingTargetSet(BMessenger target)
: MessagingTargetSet(),
fAtBeginning(true)
{
BMessenger::Private messengerPrivate(target);
fPort = messengerPrivate.Port();
fToken = (messengerPrivate.IsPreferredTarget()
? B_PREFERRED_TOKEN : messengerPrivate.Token());
}
// constructor
SingleMessagingTargetSet::SingleMessagingTargetSet(port_id port, int32 token)
: MessagingTargetSet(),
fPort(port),
fToken(token),
fAtBeginning(true)
{
}
// destructor
SingleMessagingTargetSet::~SingleMessagingTargetSet()
{
}
// HasNext
bool
SingleMessagingTargetSet::HasNext() const
{
return fAtBeginning;
}
// Next
bool
SingleMessagingTargetSet::Next(port_id &port, int32 &token)
{
if (!fAtBeginning)
return false;
port = fPort;
token = fToken;
fAtBeginning = false;
return true;
}
// Rewind
void
SingleMessagingTargetSet::Rewind()
{
fAtBeginning = true;
}
// #pragma mark -
// Message
/*! \brief Encapsulates a message to be delivered.
@ -457,109 +574,8 @@ status_t
MessageDeliverer::DeliverMessage(BMessage *message, BMessenger target,
bigtime_t timeout)
{
BMessenger::Private messengerPrivate(target);
return DeliverMessage(message, messengerPrivate.Port(),
messengerPrivate.IsPreferredTarget()
? B_PREFERRED_TOKEN : messengerPrivate.Token(),
timeout);
}
// DeliverMessage
/*! \brief Delivers a message to the supplied target.
The method tries to send the message right now (if there are not already
messages pending for the target port). If that fails due to a full target
port, the message is queued for later delivery.
\param message The message to be delivered.
\param port The port the message shall be sent to.
\param token The token identifying the target BHandler.
\param timeout If given, the message will be dropped, when it couldn't be
delivered after this amount of microseconds.
\return
- \c B_OK, if sending the message succeeded or if the target port was
full and the message has been queued,
- another error code otherwise.
*/
status_t
MessageDeliverer::DeliverMessage(BMessage *message, port_id port, int32 token,
bigtime_t timeout)
{
if (!message)
return B_BAD_VALUE;
// Set the token now, so that the header contains room for it.
// It will be set when sending the message anyway, but if it is not set
// before flattening, the header will not contain room for it, and it
// will not possible to send the message flattened later.
BMessage::Private(message).SetTarget(token, (token < 0));
// flatten the message
BMallocIO mallocIO;
status_t error = message->Flatten(&mallocIO);
if (error != B_OK)
return error;
return DeliverMessage(mallocIO.Buffer(), mallocIO.BufferLength(), port,
token, timeout);
}
// DeliverMessage
/*! \brief Delivers a flattened message to the supplied target.
The method tries to send the message right now (if there are not already
messages pending for the target port). If that fails due to a full target
port, the message is queued for later delivery.
\param message The flattened message to be delivered. This may be a
flattened BMessage or KMessage.
\param messageSize The size of the flattened message buffer.
\param target A BMessenger identifying the delivery target.
\param timeout If given, the message will be dropped, when it couldn't be
delivered after this amount of microseconds.
\return
- \c B_OK, if sending the message succeeded or if the target port was
full and the message has been queued,
- another error code otherwise.
*/
status_t
MessageDeliverer::DeliverMessage(const void *message, int32 messageSize,
BMessenger target, bigtime_t timeout)
{
BMessenger::Private messengerPrivate(target);
return DeliverMessage(message, messageSize, messengerPrivate.Port(),
messengerPrivate.IsPreferredTarget()
? B_PREFERRED_TOKEN : messengerPrivate.Token(),
timeout);
}
// DeliverMessage
/*! \brief Delivers a flattened message to the supplied target.
The method tries to send the message right now (if there are not already
messages pending for the target port). If that fails due to a full target
port, the message is queued for later delivery.
\param message The flattened message to be delivered. This may be a
flattened BMessage or KMessage.
\param messageSize The size of the flattened message buffer.
\param port The port the message shall be sent to.
\param token The token identifying the target BHandler.
\param timeout If given, the message will be dropped, when it couldn't be
delivered after this amount of microseconds.
\return
- \c B_OK, if sending the message succeeded or if the target port was
full and the message has been queued,
- another error code otherwise.
*/
status_t
MessageDeliverer::DeliverMessage(const void *message, int32 messageSize,
port_id port, int32 token, bigtime_t timeout)
{
messaging_target target;
target.port = port;
target.token = token;
return DeliverMessage(message, messageSize, &target, 1, timeout);
SingleMessagingTargetSet set(target);
return DeliverMessage(message, set, timeout);
}
// DeliverMessage
@ -570,8 +586,7 @@ MessageDeliverer::DeliverMessage(const void *message, int32 messageSize,
fails due to a full target port, the message is queued for later delivery.
\param message The message to be delivered.
\param targets An array of BMessengers identifying the delivery targets.
\param targetCount The number of delivery targets.
\param targets MessagingTargetSet providing the the delivery targets.
\param timeout If given, the message will be dropped, when it couldn't be
delivered after this amount of microseconds.
\return
@ -580,29 +595,13 @@ MessageDeliverer::DeliverMessage(const void *message, int32 messageSize,
- another error code otherwise.
*/
status_t
MessageDeliverer::DeliverMessage(BMessage *message, const BMessenger *targets,
int32 targetCount, bigtime_t timeout)
MessageDeliverer::DeliverMessage(BMessage *message, MessagingTargetSet &targets,
bigtime_t timeout)
{
if (!message || targetCount < 0 || !targets)
if (!message)
return B_BAD_VALUE;
// convert the reply targets
messaging_target *messagingTargets
= new(nothrow) messaging_target[targetCount];
if (!messagingTargets)
return B_NO_MEMORY;
ArrayDeleter<messaging_target> _(messagingTargets);
for (int i = 0; i < targetCount; i++) {
BMessenger messenger(targets[i]);
BMessenger::Private messengerPrivate(messenger);
messaging_target &target = messagingTargets[i];
target.port = messengerPrivate.Port();
target.token = messengerPrivate.IsPreferredTarget()
? B_PREFERRED_TOKEN : messengerPrivate.Token();
}
// Set a dummy token now, so that the header contains room for it.
// Set the token now, so that the header contains room for it.
// It will be set when sending the message anyway, but if it is not set
// before flattening, the header will not contain room for it, and it
// will not possible to send the message flattened later.
@ -614,8 +613,8 @@ MessageDeliverer::DeliverMessage(BMessage *message, const BMessenger *targets,
if (error != B_OK)
return error;
return DeliverMessage(mallocIO.Buffer(), mallocIO.BufferLength(),
messagingTargets, targetCount, timeout);
return DeliverMessage(mallocIO.Buffer(), mallocIO.BufferLength(), targets,
timeout);
}
// DeliverMessage
@ -628,9 +627,7 @@ MessageDeliverer::DeliverMessage(BMessage *message, const BMessenger *targets,
\param message The flattened message to be delivered. This may be a
flattened BMessage or KMessage.
\param messageSize The size of the flattened message buffer.
\param targets An array of messaging_targets identifying the delivery
targets.
\param targetCount The number of delivery targets.
\param targets MessagingTargetSet providing the the delivery targets.
\param timeout If given, the message will be dropped, when it couldn't be
delivered after this amount of microseconds.
\return
@ -640,7 +637,7 @@ MessageDeliverer::DeliverMessage(BMessage *message, const BMessenger *targets,
*/
status_t
MessageDeliverer::DeliverMessage(const void *messageData, int32 messageSize,
const messaging_target *targets, int32 targetCount, bigtime_t timeout)
MessagingTargetSet &targets, bigtime_t timeout)
{
if (!messageData || messageSize <= 0)
return B_BAD_VALUE;
@ -661,16 +658,19 @@ MessageDeliverer::DeliverMessage(const void *messageData, int32 messageSize,
// add the message to the respective target ports
BAutolock locker(fLock);
for (int32 i = 0; i < targetCount; i++) {
for (int32 targetIndex = 0; targets.HasNext(); targetIndex++) {
port_id portID;
int32 token;
targets.Next(portID, token);
// get the target port
TargetPort *port = _GetTargetPort(targets[i].port, true);
TargetPort *port = _GetTargetPort(portID, true);
if (!port)
return B_NO_MEMORY;
// try sending the message, if there are no queued messages yet
if (port->IsEmpty()) {
status_t error = _SendMessage(message, targets[i].port,
targets[i].token);
status_t error = _SendMessage(message, portID, token);
// if the message was delivered OK, we're done with the target
if (error == B_OK) {
_PutTargetPort(port);
@ -680,14 +680,14 @@ MessageDeliverer::DeliverMessage(const void *messageData, int32 messageSize,
// if the port is not full, but an error occurred, we skip this target
if (error != B_WOULD_BLOCK) {
_PutTargetPort(port);
if (targetCount == 1)
if (targetIndex == 0 && !targets.HasNext())
return error;
continue;
}
}
// add the message
status_t error = port->PushMessage(message, targets[i].token);
status_t error = port->PushMessage(message, token);
_PutTargetPort(port);
if (error != B_OK)
return error;

View File

@ -11,6 +11,51 @@
struct messaging_target;
// MessagingTargetSet
class MessagingTargetSet {
public:
virtual ~MessagingTargetSet();
virtual bool HasNext() const = 0;
virtual bool Next(port_id &port, int32 &token) = 0;
virtual void Rewind() = 0;
};
// DefaultMessagingTargetSet
class DefaultMessagingTargetSet : public MessagingTargetSet {
public:
DefaultMessagingTargetSet(const messaging_target *targets,
int32 targetCount);
virtual ~DefaultMessagingTargetSet();
virtual bool HasNext() const;
virtual bool Next(port_id &port, int32 &token);
virtual void Rewind();
private:
const messaging_target *fTargets;
int32 fTargetCount;
int32 fNextIndex;
};
// SingleMessagingTargetSet
class SingleMessagingTargetSet : public MessagingTargetSet {
public:
SingleMessagingTargetSet(BMessenger target);
SingleMessagingTargetSet(port_id port, int32 token);
virtual ~SingleMessagingTargetSet();
virtual bool HasNext() const;
virtual bool Next(port_id &port, int32 &token);
virtual void Rewind();
private:
port_id fPort;
int32 fToken;
bool fAtBeginning;
};
// MessageDeliverer
class MessageDeliverer {
private:
MessageDeliverer();
@ -25,19 +70,10 @@ public:
status_t DeliverMessage(BMessage *message, BMessenger target,
bigtime_t timeout = B_INFINITE_TIMEOUT);
status_t DeliverMessage(BMessage *message, port_id port, int32 token,
status_t DeliverMessage(BMessage *message, MessagingTargetSet &targets,
bigtime_t timeout = B_INFINITE_TIMEOUT);
status_t DeliverMessage(const void *message, int32 messageSize,
BMessenger target, bigtime_t timeout = B_INFINITE_TIMEOUT);
status_t DeliverMessage(const void *message, int32 messageSize,
port_id port, int32 token, bigtime_t timeout = B_INFINITE_TIMEOUT);
status_t DeliverMessage(BMessage *message, const BMessenger *targets,
int32 targetCount,
bigtime_t timeout = B_INFINITE_TIMEOUT);
status_t DeliverMessage(const void *message, int32 messageSize,
const messaging_target *targets, int32 targetCount,
bigtime_t timeout = B_INFINITE_TIMEOUT);
MessagingTargetSet &targets, bigtime_t timeout = B_INFINITE_TIMEOUT);
private:
class Message;

View File

@ -46,7 +46,7 @@ public:
virtual bool Do(EventQueue *queue);
private:
protected:
BMessage fMessage;
BMessenger fMessenger;
BHandler *fHandler;

View File

@ -186,8 +186,10 @@ class MessagingService::DefaultSendCommandHandler
+ sizeof(messaging_command_send_message)
+ sizeof(messaging_target) * sendData->target_count;
DefaultMessagingTargetSet set(sendData->targets,
sendData->target_count);
MessageDeliverer::Default()->DeliverMessage(messageData,
sendData->message_size, sendData->targets, sendData->target_count);
sendData->message_size, set);
}
};

View File

@ -36,9 +36,7 @@
#include <stdio.h>
#include <string>
namespace BPrivate {
class TRoster;
}
class TRoster;
struct entry_ref;
@ -56,7 +54,7 @@ public:
static const int32 kQualifyingAppFlags = 0;
private:
friend class BPrivate::TRoster;
friend class TRoster;
// For loading from disk
static status_t GetRefForApp(const char *appSig, entry_ref *result);

View File

@ -37,9 +37,7 @@
#include <stdio.h>
#include <string>
namespace BPrivate {
class TRoster;
}
class TRoster;
struct recent_entry {
recent_entry(const entry_ref *ref, const char *appSig, uint32 index);
@ -62,7 +60,7 @@ public:
status_t Print();
status_t Save(FILE* file, const char *description, const char *tag);
private:
friend class BPrivate::TRoster;
friend class TRoster;
static status_t GetTypeForRef(const entry_ref *ref, char *result);

View File

@ -19,6 +19,7 @@
#include "MessagingService.h"
#include "MIMEManager.h"
#include "Registrar.h"
#include "ShutdownProcess.h"
#include "TRoster.h"
/*!
@ -98,6 +99,7 @@ Registrar::MessageReceived(BMessage *message)
message->SendReply(&reply);
break;
}
case B_REG_GET_CLIPBOARD_MESSENGER:
{
PRINT(("B_REG_GET_CLIPBOARD_MESSENGER\n"));
@ -107,6 +109,14 @@ Registrar::MessageReceived(BMessage *message)
message->SendReply(&reply);
break;
}
case B_REG_SHUT_DOWN:
{
PRINT(("B_REG_SHUT_DOWN\n"));
_HandleShutDown(message);
}
// roster requests
case B_REG_ADD_APP:
fRoster->HandleAddApplication(message);
@ -180,6 +190,7 @@ Registrar::MessageReceived(BMessage *message)
case B_REG_SAVE_RECENT_LISTS:
fRoster->HandleSaveRecentLists(message);
break;
// message runner requests
case B_REG_REGISTER_MESSAGE_RUNNER:
fMessageRunnerManager->HandleRegisterRunner(message);
@ -193,12 +204,22 @@ Registrar::MessageReceived(BMessage *message)
case B_REG_GET_MESSAGE_RUNNER_INFO:
fMessageRunnerManager->HandleGetRunnerInfo(message);
break;
// internal messages
case B_REG_ROSTER_SANITY_EVENT:
fRoster->CheckSanity();
fSanityEvent->SetTime(system_time() + kRosterSanityEventInterval);
fEventQueue->AddEvent(fSanityEvent);
break;
case B_REG_SHUTDOWN_FINISHED:
if (fShutdownProcess) {
if (fShutdownProcess->Lock()) {
fShutdownProcess->Quit();
fShutdownProcess = NULL;
}
}
break;
default:
BApplication::MessageReceived(message);
break;
@ -291,6 +312,40 @@ Registrar::App()
return dynamic_cast<Registrar*>(be_app);
}
// _HandleShutDown
/*! \brief Handle a shut down request message.
\param request The request to be handled.
*/
void
Registrar::_HandleShutDown(BMessage *request)
{
status_t error = B_OK;
// check, whether we're already shutting down
if (fShutdownProcess)
error = B_SHUTTING_DOWN;
bool needsReply = true;
if (error == B_OK) {
// create a ShutdownProcess
fShutdownProcess = new(nothrow) ShutdownProcess(fRoster, fEventQueue);
if (fShutdownProcess) {
error = fShutdownProcess->Init(request);
if (error == B_OK) {
DetachCurrentMessage();
fShutdownProcess->Run();
needsReply = false;
} else {
delete fShutdownProcess;
fShutdownProcess = NULL;
}
} else
error = B_NO_MEMORY;
}
if (needsReply)
ShutdownProcess::SendReply(request, error);
}
// main
/*! \brief Creates and runs the registrar application.

View File

@ -34,10 +34,9 @@ class EventQueue;
class MessageEvent;
class MessageRunnerManager;
class MIMEManager;
class ShutdownProcess;
namespace BPrivate {
class TRoster;
};
class TRoster;
class Registrar : public BServer {
public:
@ -53,12 +52,15 @@ public:
static Registrar *App();
private:
BPrivate::TRoster *fRoster;
void _HandleShutDown(BMessage *message);
TRoster *fRoster;
ClipboardHandler *fClipboardHandler;
MIMEManager *fMIMEManager;
EventQueue *fEventQueue;
MessageRunnerManager *fMessageRunnerManager;
MessageEvent *fSanityEvent;
ShutdownProcess *fShutdownProcess;
};
#endif // REGISTRAR_H

View File

@ -24,6 +24,8 @@
// Description: An extended app_info.
//------------------------------------------------------------------------------
#include <new>
#include <string.h>
#include "RosterAppInfo.h"
@ -54,3 +56,14 @@ RosterAppInfo::Init(thread_id thread, team_id team, port_id port, uint32 flags,
this->signature[0] = '\0';
}
// Clone
RosterAppInfo *
RosterAppInfo::Clone() const
{
RosterAppInfo *clone = new(nothrow) RosterAppInfo;
if (!clone)
return NULL;
clone->Init(thread, team, port, flags, &ref, signature);
return clone;
}

View File

@ -46,6 +46,8 @@ struct RosterAppInfo : app_info {
RosterAppInfo();
void Init(thread_id thread, team_id team, port_id port, uint32 flags,
const entry_ref *ref, const char *signature);
RosterAppInfo *Clone() const;
};
#endif // ROSTER_APP_INFO_H

View File

@ -0,0 +1,922 @@
/*
* Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*/
// TODO: While debugging only. Remove when implementation is done.
#ifdef DEBUG
#undef DEBUG
#endif
#define DEBUG 1
#include <new>
#include <signal.h>
#include <unistd.h>
#include <Autolock.h>
#include <Message.h>
#include <MessagePrivate.h>
#include <RegistrarDefs.h>
#include <Roster.h> // for B_REQUEST_QUIT
#include <TokenSpace.h>
#include <util/DoublyLinkedList.h>
#include "AppInfoListMessagingTargetSet.h"
#include "Debug.h"
#include "EventQueue.h"
#include "MessageDeliverer.h"
#include "MessageEvent.h"
#include "Registrar.h"
#include "RosterAppInfo.h"
#include "ShutdownProcess.h"
#include "TRoster.h"
using namespace BPrivate;
// The time span user applications have after the quit message has been
// delivered (more precisely: has been handed over to the MessageDeliverer).
static const bigtime_t USER_APPS_QUIT_TIMEOUT = 5000000; // 5 s
// The time span system applications have after the quit message has been
// delivered (more precisely: has been handed over to the MessageDeliverer).
static const bigtime_t SYSTEM_APPS_QUIT_TIMEOUT = 3000000; // 3 s
// The time span non-app processes have after the HUP signal has been send
// to them before they get a KILL signal.
static const bigtime_t NON_APPS_QUIT_TIMEOUT = 500000; // 0.5 s
// message what fields
enum {
MSG_PHASE_TIMED_OUT = 'phto',
MSG_DONE = 'done',
};
// internal events
enum {
NO_EVENT,
ABORT_EVENT,
TIMEOUT_EVENT,
USER_APP_QUIT_EVENT,
SYSTEM_APP_QUIT_EVENT,
};
// phases
enum {
INVALID_PHASE = -1,
USER_APP_TERMINATION_PHASE = 0,
SYSTEM_APP_TERMINATION_PHASE = 1,
OTHER_PROCESSES_TERMINATION_PHASE = 2,
DONE_PHASE = 3,
ABORTED_PHASE = 4,
};
// TimeoutEvent
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;
}
};
// InternalEvent
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;
};
// InternalEventList
struct ShutdownProcess::InternalEventList : DoublyLinkedList<InternalEvent> {
};
// QuitRequestReplyHandler
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;
};
// #pragma mark -
// constructor
ShutdownProcess::ShutdownProcess(TRoster *roster, EventQueue *eventQueue)
: BLooper("shutdown process"),
EventMaskWatcher(BMessenger(this), B_REQUEST_QUIT),
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)
{
}
// destructor
ShutdownProcess::~ShutdownProcess()
{
// 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;
}
// 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(fRequest, fShutdownError);
delete fRequest;
}
// Init
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);
// tell TRoster not to accept new applications anymore
fRoster->SetShuttingDown(true);
// start watching application quits
status_t error = fRoster->AddWatcher(this);
if (error != B_OK) {
fRoster->SetShuttingDown(false);
RETURN_ERROR(error);
}
// get a list of all applications to shut down and sort them
error = fRoster->GetShutdownApps(fUserApps, fSystemApps, fVitalSystemApps);
if (error != B_OK) {
fRoster->RemoveWatcher(this);
fRoster->SetShuttingDown(false);
RETURN_ERROR(error);
}
// TODO: sort the system apps by descending registration time
// display alert
// TODO:...
// start the worker thread
fWorker = spawn_thread(_WorkerEntry, "shutdown worker", B_NORMAL_PRIORITY,
this);
if (fWorker < 0) {
fRoster->RemoveWatcher(this);
fRoster->SetShuttingDown(false);
RETURN_ERROR(fWorker);
}
// everything went fine: now we own the request
fRequest = request;
resume_thread(fWorker);
PRINT(("ShutdownProcess::Init() done\n"));
return B_OK;
}
// MessageReceived
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: %ld\n",
team));
// remove the app info from the respective list
_LockAppLists();
uint32 event;
RosterAppInfo *info = fUserApps.InfoFor(team);
if (info) {
fUserApps.RemoveInfo(info);
event = USER_APP_QUIT_EVENT;
} else if ((info = fSystemApps.InfoFor(team))) {
fSystemApps.RemoveInfo(info);
event = SYSTEM_APP_QUIT_EVENT;
} else // not found
return;
int32 phase = fCurrentPhase;
_UnlockAppLists();
// post the event
_PushEvent(event, team, phase);
delete info;
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);;
BAutolock _(fWorkerLock);
if (phase == INVALID_PHASE || phase != fCurrentPhase)
return;
// post the event
_PushEvent(TIMEOUT_EVENT, team, phase);
break;
}
case MSG_DONE:
{
// notify the registrar that we're done
be_app->PostMessage(B_REG_SHUTDOWN_FINISHED, be_app);
break;
}
default:
BLooper::MessageReceived(message);
break;
}
}
// SendReply
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);
}
}
// _SetPhase
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);
}
// _ScheduleTimeoutEvent
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);
}
// _LockAppLists
bool
ShutdownProcess::_LockAppLists()
{
return fWorkerLock.Lock();
}
// _UnlockAppLists
void
ShutdownProcess::_UnlockAppLists()
{
fWorkerLock.Unlock();
}
// _NegativeQuitRequestReply
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);
}
// _PrepareShutdownMessage
void
ShutdownProcess::_PrepareShutdownMessage(BMessage &message) const
{
message.what = B_QUIT_REQUESTED;
message.AddBool("_shutdown_", true);
BMessage::Private(message).SetReply(BMessenger(fQuitRequestReplyHandler));
}
// _ShutDown
status_t
ShutdownProcess::_ShutDown()
{
// get the reboot flag
bool reboot;
if (fRequest->FindBool("reboot", &reboot) != B_OK)
reboot = false;
#ifdef __HAIKU__
return _kern_shutdown(reboot);
#else
// we can't do anything on R5
return B_ERROR;
#endif
}
// _PushEvent
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;
}
// _GetNextEvent
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;
}
return B_OK;
}
// _WorkerEntry
status_t
ShutdownProcess::_WorkerEntry(void *data)
{
return ((ShutdownProcess*)data)->_Worker();
}
// _Worker
status_t
ShutdownProcess::_Worker()
{
try {
_WorkerDoShutdown();
fShutdownError = B_OK;
} catch (status_t error) {
PRINT(("ShutdownProcess::_Worker(): caught exception: %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;
}
// _WorkerDoShutdown
void
ShutdownProcess::_WorkerDoShutdown()
{
PRINT(("ShutdownProcess::_WorkerDoShutdown()\n"));
// TODO: Set alert text to "Tidying things up a bit".
PRINT((" sync()...\n"));
sync();
// phase 1: terminate the user apps
_SetPhase(USER_APP_TERMINATION_PHASE);
_QuitUserApps();
_ScheduleTimeoutEvent(USER_APPS_QUIT_TIMEOUT);
_WaitForUserApps();
_KillUserApps();
// phase 2: terminate the system apps
_SetPhase(SYSTEM_APP_TERMINATION_PHASE);
_QuitSystemApps();
// phase 3: terminate the other processes
_SetPhase(OTHER_PROCESSES_TERMINATION_PHASE);
_QuitNonApps();
// we're through: do the shutdown
_SetPhase(DONE_PHASE);
_ShutDown();
PRINT((" _kern_shutdown() failed\n"));
// shutdown failed: This can happen for power off mode -- reboot will
// always work. We close the alert, and display a new one with
// a "Reboot System" button.
if (fHasGUI) {
// TODO:...
} else {
// there's no GUI: we enter the kernel debugger instead
// TODO:...
}
}
// _QuitUserApps
void
ShutdownProcess::_QuitUserApps()
{
PRINT(("ShutdownProcess::_QuitUserApps()\n"));
// TODO: Set alert text to "Asking user applications to quit"
// prepare the shutdown message
BMessage message;
_PrepareShutdownMessage(message);
// send shutdown messages to user apps
_LockAppLists();
AppInfoListMessagingTargetSet targetSet(fUserApps);
if (targetSet.HasNext()) {
PRINT((" sending shutdown message to %ld apps\n",
fUserApps.CountInfos()));
status_t error = MessageDeliverer::Default()->DeliverMessage(
&message, targetSet);
if (error != B_OK) {
WARNING(("ShutdownProcess::_Worker(): Failed to deliver "
"shutdown message to all applications: %s\n",
strerror(error)));
}
}
_UnlockAppLists();
PRINT(("ShutdownProcess::_QuitUserApps() done\n"));
}
// _WaitForUserApps
void
ShutdownProcess::_WaitForUserApps()
{
PRINT(("ShutdownProcess::_WaitForUserApps()\n"));
// wait for user apps
bool moreApps = true;
while (moreApps) {
_LockAppLists();
moreApps = !fUserApps.IsEmpty();
_UnlockAppLists();
if (moreApps) {
uint32 event;
team_id team;
int32 phase;
status_t error = _GetNextEvent(event, team, phase, true);
if (error != B_OK)
throw error;
if (event == ABORT_EVENT)
throw B_SHUTDOWN_CANCELLED;
if (event == TIMEOUT_EVENT)
return;
}
}
PRINT(("ShutdownProcess::_WaitForUserApps() done\n"));
}
// _KillUserApps
void
ShutdownProcess::_KillUserApps()
{
PRINT(("ShutdownProcess::_KillUserApps()\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;
if (event == ABORT_EVENT)
throw B_SHUTDOWN_CANCELLED;
} while (event != NO_EVENT);
// get the first team to kill
_LockAppLists();
team_id team = -1;
AppInfoList &list = fUserApps;
if (!list.IsEmpty()) {
RosterAppInfo *info = *list.It();
team = info->team;
}
_UnlockAppLists();
if (team < 0) {
PRINT(("ShutdownProcess::_KillUserApps() done\n"));
return;
}
// TODO: Set alert text...
// TODO: check whether the app blocks on a modal alert
if (false) {
// ...
} else {
// it does not: kill it
PRINT((" killing team %ld\n", team));
kill_team(team);
// remove the app (the roster will note eventually and send us
// a notification, but we want to be sure)
_LockAppLists();
if (RosterAppInfo *info = list.InfoFor(team)) {
list.RemoveInfo(info);
delete info;
}
_UnlockAppLists();
}
}
}
// _QuitSystemApps
void
ShutdownProcess::_QuitSystemApps()
{
PRINT(("ShutdownProcess::_QuitSystemApps()\n"));
// TODO: Disable the abort button.
// 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;
if (event == ABORT_EVENT)
throw B_SHUTDOWN_CANCELLED;
} while (event != NO_EVENT);
// prepare the shutdown message
BMessage message;
_PrepareShutdownMessage(message);
// now iterate through the list of system 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;
} while (event != NO_EVENT);
// get the first app to quit
_LockAppLists();
AppInfoList &list = fSystemApps;
team_id team = -1;
port_id port = -1;
if (!list.IsEmpty()) {
RosterAppInfo *info = *list.It();
team = info->team;
port = info->port;
}
_UnlockAppLists();
if (team < 0) {
PRINT(("ShutdownProcess::_QuitSystemApps() done\n"));
return;
}
// TODO: Set alert text...
// send the shutdown message to the app
PRINT((" sending team %ld (port: %ld) a shutdown message\n", team,
port));
SingleMessagingTargetSet target(port, B_PREFERRED_TOKEN);
MessageDeliverer::Default()->DeliverMessage(&message, target);
// schedule a timeout event
_ScheduleTimeoutEvent(SYSTEM_APPS_QUIT_TIMEOUT, team);
// wait for the app to die or for the timeout to occur
do {
team_id eventTeam;
int32 phase;
status_t error = _GetNextEvent(event, eventTeam, phase, true);
if (error != B_OK)
throw error;
if (event == TIMEOUT_EVENT && eventTeam == team)
break;
// 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 (event == ABORT_EVENT && eventTeam == team)
break;
BAutolock _(fWorkerLock);
if (!list.InfoFor(team))
break;
} while (event != NO_EVENT);
// TODO: Set alert text...
// TODO: check whether the app blocks on a modal alert
if (false) {
// ...
} else {
// it does not: kill it
PRINT((" killing team %ld\n", team));
kill_team(team);
// remove the app (the roster will note eventually and send us
// a notification, but we want to be sure)
_LockAppLists();
if (RosterAppInfo *info = list.InfoFor(team)) {
list.RemoveInfo(info);
delete info;
}
_UnlockAppLists();
}
}
}
// _QuitNonApps
void
ShutdownProcess::_QuitNonApps()
{
PRINT(("ShutdownProcess::_QuitNonApps()\n"));
// TODO: Set alert text...
// iterate through the remaining teams and send them the HUP signal
int32 cookie = 0;
team_info teamInfo;
while (get_next_team_info(&cookie, &teamInfo) == B_OK) {
if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) {
PRINT((" sending team %ld HUP signal\n", teamInfo.team));
#ifdef __HAIKU__
// Note: team ID == team main thread ID under Haiku
send_signal(teamInfo.team, SIGHUP);
#else
// We don't want to do this when testing under R5, since it
// would kill all teams besides our app server and registrar.
#endif
}
}
// 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(NON_APPS_QUIT_TIMEOUT);
// iterate through the remaining teams and kill them
cookie = 0;
while (get_next_team_info(&cookie, &teamInfo) == B_OK) {
if (fVitalSystemApps.find(teamInfo.team) == fVitalSystemApps.end()) {
PRINT((" killing team %ld\n", teamInfo.team));
#ifdef __HAIKU__
kill_team(teamInfo.team);
#else
// We don't want to do this when testing under R5, since it
// would kill all teams besides our app server and registrar.
#endif
}
}
PRINT(("ShutdownProcess::_QuitNonApps() done\n"));
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*
* Manages the shutdown process.
*/
#ifndef SHUTDOWN_PROCESS_H
#define SHUTDOWN_PROCESS_H
#include <hash_set>
#include <Looper.h>
#include "AppInfoList.h"
#include "EventMaskWatcher.h"
class EventQueue;
class TRoster;
// Note: EventMaskWatcher is inherited public due to a gcc bug/C++ feature:
// Once cast into a Watcher dynamic_cast<>()ing it back into an
// EventMaskWatcher fails otherwise.
class ShutdownProcess : public BLooper, public EventMaskWatcher {
public:
ShutdownProcess(TRoster *roster, EventQueue *eventQueue);
~ShutdownProcess();
status_t Init(BMessage *request);
virtual void MessageReceived(BMessage *message);
static void SendReply(BMessage *request, status_t error);
private:
void _NegativeQuitRequestReply(thread_id thread);
void _PrepareShutdownMessage(BMessage &message) const;
status_t _ShutDown();
status_t _PushEvent(uint32 eventType, team_id team, int32 phase);
status_t _GetNextEvent(uint32 &eventType, team_id &team, int32 &phase,
bool block);
void _SetPhase(int32 phase);
void _ScheduleTimeoutEvent(bigtime_t timeout, team_id team = -1);
bool _LockAppLists();
void _UnlockAppLists();
static status_t _WorkerEntry(void *data);
status_t _Worker();
void _WorkerDoShutdown();
void _QuitUserApps();
void _WaitForUserApps();
void _KillUserApps();
void _QuitSystemApps();
void _QuitNonApps();
private:
class TimeoutEvent;
class InternalEvent;
struct InternalEventList;
class QuitRequestReplyHandler;
friend class QuitRequestReplyHandler;
BLocker fWorkerLock; // protects fields shared by looper
// and worker
BMessage *fRequest;
TRoster *fRoster;
EventQueue *fEventQueue;
hash_set<team_id> fVitalSystemApps;
AppInfoList fSystemApps;
AppInfoList fUserApps;
TimeoutEvent *fTimeoutEvent;
InternalEventList *fInternalEvents;
sem_id fInternalEventSemaphore;
QuitRequestReplyHandler *fQuitRequestReplyHandler;
thread_id fWorker;
int32 fCurrentPhase;
status_t fShutdownError;
bool fHasGUI;
};
#endif // SHUTDOWN_PROCESS_H

View File

@ -1,45 +1,29 @@
//------------------------------------------------------------------------------
// Copyright (c) 2001-2002, OpenBeOS
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// File Name: TRoster.cpp
// Author: Ingo Weinhold (bonefish@users.sf.net)
// Description: TRoster is the incarnation of The Roster. It manages the
// running applications.
//------------------------------------------------------------------------------
/*
* Copyright 2001-2005, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*
* TRoster is the incarnation of The Roster. It manages the running
* applications.
*/
#include <new>
#include <Application.h>
#include <AppMisc.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <File.h>
#include <FindDirectory.h>
#include <MessagePrivate.h>
#include <MessengerPrivate.h>
#include <Path.h>
#include <ServerProtocol.h>
#include <storage_support.h>
#include <errno.h>
#include <stdio.h>
#include "AppInfoListMessagingTargetSet.h"
#include "Debug.h"
#include "EventMaskWatcher.h"
#include "MessageDeliverer.h"
@ -90,6 +74,12 @@ static bool larger_index(const recent_entry *entry1, const recent_entry *entry2)
//! The maximal period of time an app may be early pre-registered (60 s).
const bigtime_t kMaximalEarlyPreRegistrationPeriod = 60000000LL;
//! Applications living in this tree are considered "vital system apps".
static const char *const kVitalSystemAppPathPrefix
= "/boot/beos/system/servers";
//! Applications living in this tree are considered "system apps".
static const char *const kSystemAppPathPrefix = "/boot/beos/system";
// get_default_roster_settings_file
/*! \brief Returns the path to the default roster settings.
@ -119,7 +109,8 @@ get_default_roster_settings_file(BPath &path)
The object is completely initialized and ready to handle requests.
*/
TRoster::TRoster()
: fRegisteredApps(),
: fLock("roster"),
fRegisteredApps(),
fEarlyPreRegisteredApps(),
fIAPRRequests(),
fActiveApp(NULL),
@ -127,7 +118,8 @@ TRoster::TRoster()
fRecentApps(),
fRecentDocuments(),
fRecentFolders(),
fLastToken(0)
fLastToken(0),
fShuttingDown(false)
{
_LoadRosterSettings();
}
@ -148,6 +140,8 @@ TRoster::HandleAddApplication(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
const char *signature;
@ -173,9 +167,14 @@ TRoster::HandleAddApplication(BMessage *request)
fullReg = false;
PRINT(("team: %ld, signature: %s\n", team, signature));
PRINT(("full registration: %d\n", fullReg));
if (fShuttingDown)
error = B_SHUTTING_DOWN;
// check the parameters
team_id otherTeam = -1;
uint32 launchFlags = flags & B_LAUNCH_MASK;
// entry_ref
if (error == B_OK) {
// the entry_ref must be valid
@ -194,6 +193,7 @@ PRINT(("ref: %ld, %lld, %s\n", ref.device, ref.directory, ref.name));
} else
SET_ERROR(error, B_ENTRY_NOT_FOUND);
}
// signature
if (error == B_OK && signature) {
// check exclusive launchers
@ -205,6 +205,7 @@ PRINT(("ref: %ld, %lld, %s\n", ref.device, ref.directory, ref.name));
otherTeam = info->team;
}
}
// If no team ID is given, full registration isn't possible.
if (error == B_OK) {
if (team < 0) {
@ -213,6 +214,7 @@ PRINT(("ref: %ld, %lld, %s\n", ref.device, ref.directory, ref.name));
} else if (fRegisteredApps.InfoFor(team))
SET_ERROR(error, B_REG_ALREADY_REGISTERED);
}
// Add the application info.
uint32 token = 0;
if (error == B_OK) {
@ -245,6 +247,7 @@ PRINT(("added to early pre-regs, token: %lu\n", token));
if (error != B_OK && info)
delete info;
}
// reply to the request
if (error == B_OK) {
// add to recent apps if successful
@ -279,6 +282,8 @@ TRoster::HandleCompleteRegistration(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -290,13 +295,19 @@ TRoster::HandleCompleteRegistration(BMessage *request)
thread = -1;
if (request->FindInt32("port", &port) != B_OK)
port = -1;
if (fShuttingDown)
error = B_SHUTTING_DOWN;
// check the parameters
// port
if (error == B_OK && port < 0)
SET_ERROR(error, B_BAD_VALUE);
// thread
if (error == B_OK && thread < 0)
SET_ERROR(error, B_BAD_VALUE);
// team
if (error == B_OK) {
if (team >= 0) {
@ -311,6 +322,7 @@ TRoster::HandleCompleteRegistration(BMessage *request)
} else
SET_ERROR(error, B_BAD_VALUE);
}
// reply to the request
if (error == B_OK) {
BMessage reply(B_REG_SUCCESS);
@ -333,6 +345,8 @@ TRoster::HandleIsAppPreRegistered(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
entry_ref ref;
@ -387,6 +401,8 @@ TRoster::HandleRemovePreRegApp(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
uint32 token;
@ -423,6 +439,8 @@ TRoster::HandleRemoveApp(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -459,6 +477,8 @@ TRoster::HandleSetThreadAndTeam(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -535,6 +555,8 @@ TRoster::HandleSetSignature(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -572,6 +594,8 @@ TRoster::HandleGetAppInfo(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -639,6 +663,8 @@ TRoster::HandleGetAppList(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
const char *signature;
@ -673,6 +699,8 @@ TRoster::HandleActivateApp(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -707,6 +735,8 @@ TRoster::HandleBroadcast(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
team_id team;
@ -721,15 +751,6 @@ TRoster::HandleBroadcast(BMessage *request)
error = B_BAD_VALUE;
}
// allocate an error for the message targets
BMessenger *targets = NULL;
if (error == B_OK) {
targets = new(nothrow) BMessenger[fRegisteredApps.CountInfos()];
if (!targets)
error = B_NO_MEMORY;
}
ArrayDeleter<BMessenger> targetsDeleter(targets);
// reply to the request -- do this first, don't let the inquirer wait
if (error == B_OK) {
BMessage reply(B_REG_SUCCESS);
@ -741,28 +762,33 @@ TRoster::HandleBroadcast(BMessage *request)
}
// broadcast the message
team_id registrarTeam = BPrivate::current_team();
if (error == B_OK) {
// get the list of targets
int32 targetCount = 0;
for (AppInfoList::Iterator it = fRegisteredApps.It();
it.IsValid();
++it) {
// don't send the message to the requesting team or the registrar
if ((*it)->team != team && (*it)->team != registrarTeam) {
BMessenger::Private messengerPrivate(targets[targetCount]);
messengerPrivate.SetTo((*it)->team, (*it)->port, 0, true);
targetCount++;
}
}
// the target set (excludes the registrar and the requesting team)
class BroadcastMessagingTargetSet
: public AppInfoListMessagingTargetSet {
public:
BroadcastMessagingTargetSet(AppInfoList &list, team_id team)
: AppInfoListMessagingTargetSet(list, true),
fTeam(team)
{
}
if (targetCount > 0) {
virtual bool Filter(const RosterAppInfo *info)
{
return AppInfoListMessagingTargetSet::Filter(info)
&& (info->team != fTeam);
}
private:
team_id fTeam;
} targetSet(fRegisteredApps, team);
if (targetSet.HasNext()) {
// set the reply target
BMessage::Private(message).SetReply(replyTarget);
// send the messages
MessageDeliverer::Default()->DeliverMessage(&message, targets,
targetCount);
MessageDeliverer::Default()->DeliverMessage(&message, targetSet);
}
}
@ -778,6 +804,8 @@ TRoster::HandleStartWatching(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
BMessenger target;
@ -819,6 +847,8 @@ TRoster::HandleStopWatching(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
status_t error = B_OK;
// get the parameters
BMessenger target;
@ -850,7 +880,11 @@ void
TRoster::HandleGetRecentDocuments(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
_HandleGetRecentEntries(request);
FUNCTION_END();
}
@ -862,7 +896,11 @@ void
TRoster::HandleGetRecentFolders(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
_HandleGetRecentEntries(request);
FUNCTION_END();
}
@ -875,6 +913,8 @@ TRoster::HandleGetRecentApps(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
if (!request) {
D(PRINT(("WARNING: TRoster::HandleGetRecentApps(NULL) called\n")));
return;
@ -901,6 +941,8 @@ TRoster::HandleAddToRecentDocuments(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
if (!request) {
D(PRINT(("WARNING: TRoster::HandleAddToRecentDocuments(NULL) called\n")));
return;
@ -930,6 +972,8 @@ TRoster::HandleAddToRecentFolders(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
if (!request) {
D(PRINT(("WARNING: TRoster::HandleAddToRecentFolders(NULL) called\n")));
return;
@ -959,6 +1003,8 @@ TRoster::HandleAddToRecentApps(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
if (!request) {
D(PRINT(("WARNING: TRoster::HandleAddToRecentApps(NULL) called\n")));
return;
@ -981,6 +1027,8 @@ TRoster::HandleLoadRecentLists(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
if (!request) {
D(PRINT(("WARNING: TRoster::HandleLoadRecentLists(NULL) called\n")));
return;
@ -1003,6 +1051,8 @@ TRoster::HandleSaveRecentLists(BMessage *request)
{
FUNCTION_START();
BAutolock _(fLock);
if (!request) {
D(PRINT(("WARNING: TRoster::HandleSaveRecentLists(NULL) called\n")));
return;
@ -1026,6 +1076,8 @@ TRoster::HandleSaveRecentLists(BMessage *request)
void
TRoster::ClearRecentDocuments()
{
BAutolock _(fLock);
fRecentDocuments.Clear();
}
@ -1035,6 +1087,8 @@ TRoster::ClearRecentDocuments()
void
TRoster::ClearRecentFolders()
{
BAutolock _(fLock);
fRecentFolders.Clear();
}
@ -1044,6 +1098,8 @@ TRoster::ClearRecentFolders()
void
TRoster::ClearRecentApps()
{
BAutolock _(fLock);
fRecentApps.Clear();
}
@ -1062,14 +1118,21 @@ status_t
TRoster::Init()
{
status_t error = B_OK;
// check lock initialization
if (fLock.Sem() < 0)
return fLock.Sem();
// create the info
RosterAppInfo *info = new(nothrow) RosterAppInfo;
if (!info)
error = B_NO_MEMORY;
// get the app's ref
entry_ref ref;
if (error == B_OK)
error = get_app_ref(&ref);
// init and add the info
if (error == B_OK) {
info->Init(be_app->Thread(), be_app->Team(),
@ -1079,9 +1142,11 @@ TRoster::Init()
info->registration_time = system_time();
error = AddApp(info);
}
// cleanup on error
if (error != B_OK && info)
delete info;
return error;
}
@ -1093,6 +1158,8 @@ TRoster::Init()
status_t
TRoster::AddApp(RosterAppInfo *info)
{
BAutolock _(fLock);
status_t error = (info ? B_OK : B_BAD_VALUE);
if (info) {
if (fRegisteredApps.AddInfo(info))
@ -1112,6 +1179,8 @@ TRoster::AddApp(RosterAppInfo *info)
void
TRoster::RemoveApp(RosterAppInfo *info)
{
BAutolock _(fLock);
if (info) {
if (fRegisteredApps.RemoveInfo(info)) {
info->state = APP_STATE_UNREGISTERED;
@ -1132,6 +1201,8 @@ TRoster::RemoveApp(RosterAppInfo *info)
void
TRoster::ActivateApp(RosterAppInfo *info)
{
BAutolock _(fLock);
if (info != fActiveApp) {
// deactivate the currently active app
RosterAppInfo *oldActiveApp = fActiveApp;
@ -1154,6 +1225,8 @@ TRoster::ActivateApp(RosterAppInfo *info)
void
TRoster::CheckSanity()
{
BAutolock _(fLock);
// not early (pre-)registered applications
AppInfoList obsoleteApps;
for (AppInfoList::Iterator it = fRegisteredApps.It(); it.IsValid(); ++it) {
@ -1182,6 +1255,121 @@ TRoster::CheckSanity()
}
}
// SetShuttingDown
/*! \brief Tells the roster whether a shutdown process is in progess at the
moment.
After this method is called with \a shuttingDown == \c true, no more
applications can be created.
\param shuttingDown \c true, to indicate the start of the shutdown process,
\c false to signalling its end.
*/
void
TRoster::SetShuttingDown(bool shuttingDown)
{
BAutolock _(fLock);
fShuttingDown = shuttingDown;
}
// GetShutdownApps
/*! \brief Returns lists of applications to be asked to quit on shutdown.
\param userApps List of RosterAppInfos identifying the user applications.
Those will be ask to quit first.
\param systemApps List of RosterAppInfos identifying the system applications
(like Tracker and Deskbar), which will be asked to quit after the
user applications are gone.
\param vitalSystemApps A set of team_ids identifying teams that must not
be terminated (app server and registrar).
\return \c B_OK, if everything went fine, another error code otherwise.
*/
status_t
TRoster::GetShutdownApps(AppInfoList &userApps, AppInfoList &systemApps,
hash_set<team_id> &vitalSystemApps)
{
BAutolock _(fLock);
status_t error = B_OK;
// add ourself to the vital system apps and try to find the app server
if (error == B_OK) {
// ourself
vitalSystemApps.insert(be_app->Team());
// the app server
port_id appServerPort = find_port(SERVER_PORT_NAME);
port_info portInfo;
if (appServerPort >= 0
&& get_port_info(appServerPort, &portInfo) == B_OK) {
vitalSystemApps.insert(portInfo.team);
}
}
for (AppInfoList::Iterator it(fRegisteredApps.It());
RosterAppInfo *info = *it;
++it) {
if (vitalSystemApps.find(info->team) != vitalSystemApps.end()) {
// must be us or the app server
} else if (_IsVitalSystemApp(info)) {
vitalSystemApps.insert(info->team);
} else {
RosterAppInfo *clonedInfo = info->Clone();
if (clonedInfo) {
if (_IsSystemApp(info)) {
if (!systemApps.AddInfo(clonedInfo))
error = B_NO_MEMORY;
} else {
if (!userApps.AddInfo(clonedInfo))
error = B_NO_MEMORY;
}
if (error != B_OK)
delete clonedInfo;
} else
error = B_NO_MEMORY;
}
if (error != B_OK)
break;
}
// clean up on error
if (error != B_OK) {
userApps.MakeEmpty(true);
systemApps.MakeEmpty(true);
}
return error;
}
// AddWatcher
status_t
TRoster::AddWatcher(Watcher *watcher)
{
BAutolock _(fLock);
if (!watcher)
return B_BAD_VALUE;
if (!fWatchingService.AddWatcher(watcher))
return B_NO_MEMORY;
return B_OK;
}
// RemoveWatcher
void
TRoster::RemoveWatcher(Watcher *watcher)
{
BAutolock _(fLock);
if (watcher)
fWatchingService.RemoveWatcher(watcher, false);
}
// _AppAdded
/*! \brief Hook method invoked, when an application has been added.
@ -1452,6 +1640,36 @@ TRoster::_HandleGetRecentEntries(BMessage *request)
FUNCTION_END();
}
// _IsVitalSystemApp
bool
TRoster::_IsVitalSystemApp(RosterAppInfo *info) const
{
BPath path;
status_t error = path.SetTo(&info->ref);
if (error != B_OK)
return false;
int len = strlen(path.Path());
int prefixLen = strlen(kVitalSystemAppPathPrefix);
return (len > prefixLen
&& strncmp(path.Path(), kVitalSystemAppPathPrefix, prefixLen) == 0);
}
// _IsSystemApp
bool
TRoster::_IsSystemApp(RosterAppInfo *info) const
{
BPath path;
status_t error = path.SetTo(&info->ref);
if (error != B_OK)
return false;
int len = strlen(path.Path());
int prefixLen = strlen(kSystemAppPathPrefix);
return (len > prefixLen
&& strncmp(path.Path(), kSystemAppPathPrefix, prefixLen) == 0);
}
status_t
TRoster::_LoadRosterSettings(const char *path)
{

View File

@ -28,8 +28,10 @@
#ifndef T_ROSTER_H
#define T_ROSTER_H
#include <hash_set>
#include <map>
#include <Locker.h>
#include <SupportDefs.h>
#include "AppInfoList.h"
@ -48,9 +50,6 @@ struct IAPRRequest {
typedef map<team_id, IAPRRequest> IAPRRequestMap;
// For strategic reasons, as TRoster appears in the BMessenger header.
namespace BPrivate {
class TRoster {
public:
TRoster();
@ -90,6 +89,13 @@ public:
void CheckSanity();
void SetShuttingDown(bool shuttingDown);
status_t GetShutdownApps(AppInfoList &userApps, AppInfoList &systemApps,
hash_set<team_id> &vitalSystemApps);
status_t AddWatcher(Watcher *watcher);
void RemoveWatcher(Watcher *watcher);
private:
// hook functions
void _AppAdded(RosterAppInfo *info);
@ -106,12 +112,16 @@ private:
void _ReplyToIAPRRequest(BMessage *request, const RosterAppInfo *info);
void _HandleGetRecentEntries(BMessage *request);
bool _IsVitalSystemApp(RosterAppInfo *info) const;
bool _IsSystemApp(RosterAppInfo *info) const;
status_t _LoadRosterSettings(const char *path = NULL);
status_t _SaveRosterSettings(const char *path = NULL);
static const char *kDefaultRosterSettingsFile;
private:
BLocker fLock;
AppInfoList fRegisteredApps;
AppInfoList fEarlyPreRegisteredApps;
IAPRRequestMap fIAPRRequests;
@ -121,12 +131,7 @@ private:
RecentEntries fRecentDocuments;
RecentEntries fRecentFolders;
uint32 fLastToken;
bool fShuttingDown;
};
}; // namespace BPrivate
// Only the registrar code uses this header. No need to hide TRoster in the
// namespace.
using BPrivate::TRoster;
#endif // T_ROSTER_H