diff --git a/headers/os/app/Messenger.h b/headers/os/app/Messenger.h index 38b25c3641..97d88a03cb 100644 --- a/headers/os/app/Messenger.h +++ b/headers/os/app/Messenger.h @@ -46,10 +46,6 @@ class BHandler; class BLooper; -namespace BPrivate { - class TRoster; -}; - // BMessenger class ------------------------------------------------------------ class BMessenger { public: diff --git a/src/servers/registrar/AppInfoList.cpp b/src/servers/registrar/AppInfoList.cpp index f3ca45a752..ef0e5d18f7 100644 --- a/src/servers/registrar/AppInfoList.cpp +++ b/src/servers/registrar/AppInfoList.cpp @@ -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(); } diff --git a/src/servers/registrar/AppInfoList.h b/src/servers/registrar/AppInfoList.h index ec001dfd23..95801bc167 100644 --- a/src/servers/registrar/AppInfoList.h +++ b/src/servers/registrar/AppInfoList.h @@ -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(); diff --git a/src/servers/registrar/AppInfoListMessagingTargetSet.cpp b/src/servers/registrar/AppInfoListMessagingTargetSet.cpp new file mode 100644 index 0000000000..c872b2d31c --- /dev/null +++ b/src/servers/registrar/AppInfoListMessagingTargetSet.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net. + * Distributed under the terms of the MIT License. + */ + +#include +#include + +#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; +} + diff --git a/src/servers/registrar/AppInfoListMessagingTargetSet.h b/src/servers/registrar/AppInfoListMessagingTargetSet.h new file mode 100644 index 0000000000..da56dc3d26 --- /dev/null +++ b/src/servers/registrar/AppInfoListMessagingTargetSet.h @@ -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 diff --git a/src/servers/registrar/Jamfile b/src/servers/registrar/Jamfile index 64cb620390..9484a616c7 100644 --- a/src/servers/registrar/Jamfile +++ b/src/servers/registrar/Jamfile @@ -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 diff --git a/src/servers/registrar/MessageDeliverer.cpp b/src/servers/registrar/MessageDeliverer.cpp index 10141266f1..8978cb6d5c 100644 --- a/src/servers/registrar/MessageDeliverer.cpp +++ b/src/servers/registrar/MessageDeliverer.cpp @@ -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 _(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; diff --git a/src/servers/registrar/MessageDeliverer.h b/src/servers/registrar/MessageDeliverer.h index db49c1df16..f76d0389ee 100644 --- a/src/servers/registrar/MessageDeliverer.h +++ b/src/servers/registrar/MessageDeliverer.h @@ -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; diff --git a/src/servers/registrar/MessageEvent.h b/src/servers/registrar/MessageEvent.h index 4fbaccb980..49f310a9ac 100644 --- a/src/servers/registrar/MessageEvent.h +++ b/src/servers/registrar/MessageEvent.h @@ -46,7 +46,7 @@ public: virtual bool Do(EventQueue *queue); -private: +protected: BMessage fMessage; BMessenger fMessenger; BHandler *fHandler; diff --git a/src/servers/registrar/MessagingService.cpp b/src/servers/registrar/MessagingService.cpp index 256dffbbe7..4bda6cf210 100644 --- a/src/servers/registrar/MessagingService.cpp +++ b/src/servers/registrar/MessagingService.cpp @@ -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); } }; diff --git a/src/servers/registrar/RecentApps.h b/src/servers/registrar/RecentApps.h index 0fddc432c7..b1e3df9dec 100644 --- a/src/servers/registrar/RecentApps.h +++ b/src/servers/registrar/RecentApps.h @@ -36,9 +36,7 @@ #include #include -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); diff --git a/src/servers/registrar/RecentEntries.h b/src/servers/registrar/RecentEntries.h index b30d34f187..34f5a21825 100644 --- a/src/servers/registrar/RecentEntries.h +++ b/src/servers/registrar/RecentEntries.h @@ -37,9 +37,7 @@ #include #include -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); diff --git a/src/servers/registrar/Registrar.cpp b/src/servers/registrar/Registrar.cpp index aabbfe0881..9776a9a8fe 100644 --- a/src/servers/registrar/Registrar.cpp +++ b/src/servers/registrar/Registrar.cpp @@ -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(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. diff --git a/src/servers/registrar/Registrar.h b/src/servers/registrar/Registrar.h index 0f2cf1f429..f4a5b97c6c 100644 --- a/src/servers/registrar/Registrar.h +++ b/src/servers/registrar/Registrar.h @@ -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 diff --git a/src/servers/registrar/RosterAppInfo.cpp b/src/servers/registrar/RosterAppInfo.cpp index f293a1eb29..bf65a743a9 100644 --- a/src/servers/registrar/RosterAppInfo.cpp +++ b/src/servers/registrar/RosterAppInfo.cpp @@ -24,6 +24,8 @@ // Description: An extended app_info. //------------------------------------------------------------------------------ +#include + #include #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; +} diff --git a/src/servers/registrar/RosterAppInfo.h b/src/servers/registrar/RosterAppInfo.h index e81708402f..c8a536554d 100644 --- a/src/servers/registrar/RosterAppInfo.h +++ b/src/servers/registrar/RosterAppInfo.h @@ -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 diff --git a/src/servers/registrar/ShutdownProcess.cpp b/src/servers/registrar/ShutdownProcess.cpp new file mode 100644 index 0000000000..b77938b267 --- /dev/null +++ b/src/servers/registrar/ShutdownProcess.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include +#include // for B_REQUEST_QUIT + +#include +#include + +#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 { +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 { +}; + + +// 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")); +} + diff --git a/src/servers/registrar/ShutdownProcess.h b/src/servers/registrar/ShutdownProcess.h new file mode 100644 index 0000000000..8384d39058 --- /dev/null +++ b/src/servers/registrar/ShutdownProcess.h @@ -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 + +#include + +#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 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 diff --git a/src/servers/registrar/TRoster.cpp b/src/servers/registrar/TRoster.cpp index 365df2d418..89707a0675 100644 --- a/src/servers/registrar/TRoster.cpp +++ b/src/servers/registrar/TRoster.cpp @@ -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 #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#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 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 &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) { diff --git a/src/servers/registrar/TRoster.h b/src/servers/registrar/TRoster.h index 128916aeea..b56f469b7b 100644 --- a/src/servers/registrar/TRoster.h +++ b/src/servers/registrar/TRoster.h @@ -28,8 +28,10 @@ #ifndef T_ROSTER_H #define T_ROSTER_H +#include #include +#include #include #include "AppInfoList.h" @@ -48,9 +50,6 @@ struct IAPRRequest { typedef map 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 &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