mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 17:16:12 +00:00
495 lines
13 KiB
C++
495 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "GonkSensorsInterface.h"
|
|
#include "GonkSensorsPollInterface.h"
|
|
#include "GonkSensorsRegistryInterface.h"
|
|
#include "HalLog.h"
|
|
#include <mozilla/ipc/DaemonSocket.h>
|
|
#include <mozilla/ipc/DaemonSocketConnector.h>
|
|
#include <mozilla/ipc/ListenSocket.h>
|
|
|
|
namespace mozilla {
|
|
namespace hal {
|
|
|
|
using namespace mozilla::ipc;
|
|
|
|
//
|
|
// GonkSensorsResultHandler
|
|
//
|
|
|
|
void
|
|
GonkSensorsResultHandler::OnError(SensorsError aError)
|
|
{
|
|
HAL_ERR("Received error code %d", static_cast<int>(aError));
|
|
}
|
|
|
|
void
|
|
GonkSensorsResultHandler::Connect()
|
|
{ }
|
|
|
|
void
|
|
GonkSensorsResultHandler::Disconnect()
|
|
{ }
|
|
|
|
GonkSensorsResultHandler::~GonkSensorsResultHandler()
|
|
{ }
|
|
|
|
//
|
|
// GonkSensorsNotificationHandler
|
|
//
|
|
|
|
void
|
|
GonkSensorsNotificationHandler::BackendErrorNotification(bool aCrashed)
|
|
{
|
|
if (aCrashed) {
|
|
HAL_ERR("Sensors backend crashed");
|
|
} else {
|
|
HAL_ERR("Error in sensors backend");
|
|
}
|
|
}
|
|
|
|
GonkSensorsNotificationHandler::~GonkSensorsNotificationHandler()
|
|
{ }
|
|
|
|
//
|
|
// GonkSensorsProtocol
|
|
//
|
|
|
|
class GonkSensorsProtocol final
|
|
: public DaemonSocketIOConsumer
|
|
, public GonkSensorsRegistryModule
|
|
, public GonkSensorsPollModule
|
|
{
|
|
public:
|
|
GonkSensorsProtocol();
|
|
|
|
void SetConnection(DaemonSocket* aConnection);
|
|
|
|
already_AddRefed<DaemonSocketResultHandler> FetchResultHandler(
|
|
const DaemonSocketPDUHeader& aHeader);
|
|
|
|
// Methods for |SensorsRegistryModule| and |SensorsPollModule|
|
|
//
|
|
|
|
nsresult Send(DaemonSocketPDU* aPDU,
|
|
DaemonSocketResultHandler* aRes) override;
|
|
|
|
// Methods for |DaemonSocketIOConsumer|
|
|
//
|
|
|
|
void Handle(DaemonSocketPDU& aPDU) override;
|
|
void StoreResultHandler(const DaemonSocketPDU& aPDU) override;
|
|
|
|
private:
|
|
void HandleRegistrySvc(const DaemonSocketPDUHeader& aHeader,
|
|
DaemonSocketPDU& aPDU,
|
|
DaemonSocketResultHandler* aRes);
|
|
void HandlePollSvc(const DaemonSocketPDUHeader& aHeader,
|
|
DaemonSocketPDU& aPDU,
|
|
DaemonSocketResultHandler* aRes);
|
|
|
|
DaemonSocket* mConnection;
|
|
nsTArray<RefPtr<DaemonSocketResultHandler>> mResultHandlerQ;
|
|
};
|
|
|
|
GonkSensorsProtocol::GonkSensorsProtocol()
|
|
{ }
|
|
|
|
void
|
|
GonkSensorsProtocol::SetConnection(DaemonSocket* aConnection)
|
|
{
|
|
mConnection = aConnection;
|
|
}
|
|
|
|
already_AddRefed<DaemonSocketResultHandler>
|
|
GonkSensorsProtocol::FetchResultHandler(const DaemonSocketPDUHeader& aHeader)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
if (aHeader.mOpcode & 0x80) {
|
|
return nullptr; // Ignore notifications
|
|
}
|
|
|
|
RefPtr<DaemonSocketResultHandler> res = mResultHandlerQ.ElementAt(0);
|
|
mResultHandlerQ.RemoveElementAt(0);
|
|
|
|
return res.forget();
|
|
}
|
|
|
|
void
|
|
GonkSensorsProtocol::HandleRegistrySvc(
|
|
const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU,
|
|
DaemonSocketResultHandler* aRes)
|
|
{
|
|
GonkSensorsRegistryModule::HandleSvc(aHeader, aPDU, aRes);
|
|
}
|
|
|
|
void
|
|
GonkSensorsProtocol::HandlePollSvc(
|
|
const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU,
|
|
DaemonSocketResultHandler* aRes)
|
|
{
|
|
GonkSensorsPollModule::HandleSvc(aHeader, aPDU, aRes);
|
|
}
|
|
|
|
// |SensorsRegistryModule|, |SensorsPollModule|
|
|
|
|
nsresult
|
|
GonkSensorsProtocol::Send(DaemonSocketPDU* aPDU,
|
|
DaemonSocketResultHandler* aRes)
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
MOZ_ASSERT(aPDU);
|
|
|
|
aPDU->SetConsumer(this);
|
|
aPDU->SetResultHandler(aRes);
|
|
aPDU->UpdateHeader();
|
|
|
|
if (mConnection->GetConnectionStatus() == SOCKET_DISCONNECTED) {
|
|
HAL_ERR("Sensors socket is disconnected");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mConnection->SendSocketData(aPDU); // Forward PDU to data channel
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// |DaemonSocketIOConsumer|
|
|
|
|
void
|
|
GonkSensorsProtocol::Handle(DaemonSocketPDU& aPDU)
|
|
{
|
|
static void (GonkSensorsProtocol::* const HandleSvc[])(
|
|
const DaemonSocketPDUHeader&, DaemonSocketPDU&,
|
|
DaemonSocketResultHandler*) = {
|
|
[GonkSensorsRegistryModule::SERVICE_ID] =
|
|
&GonkSensorsProtocol::HandleRegistrySvc,
|
|
[GonkSensorsPollModule::SERVICE_ID] =
|
|
&GonkSensorsProtocol::HandlePollSvc
|
|
};
|
|
|
|
DaemonSocketPDUHeader header;
|
|
|
|
if (NS_FAILED(UnpackPDU(aPDU, header))) {
|
|
return;
|
|
}
|
|
if (!(header.mService < MOZ_ARRAY_LENGTH(HandleSvc)) ||
|
|
!HandleSvc[header.mService]) {
|
|
HAL_ERR("Sensors service %d unknown", header.mService);
|
|
return;
|
|
}
|
|
|
|
RefPtr<DaemonSocketResultHandler> res = FetchResultHandler(header);
|
|
|
|
(this->*(HandleSvc[header.mService]))(header, aPDU, res);
|
|
}
|
|
|
|
void
|
|
GonkSensorsProtocol::StoreResultHandler(const DaemonSocketPDU& aPDU)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
mResultHandlerQ.AppendElement(aPDU.GetResultHandler());
|
|
}
|
|
|
|
//
|
|
// GonkSensorsInterface
|
|
//
|
|
|
|
GonkSensorsInterface*
|
|
GonkSensorsInterface::GetInstance()
|
|
{
|
|
static GonkSensorsInterface* sGonkSensorsInterface;
|
|
|
|
if (sGonkSensorsInterface) {
|
|
return sGonkSensorsInterface;
|
|
}
|
|
|
|
sGonkSensorsInterface = new GonkSensorsInterface();
|
|
|
|
return sGonkSensorsInterface;
|
|
}
|
|
|
|
void
|
|
GonkSensorsInterface::SetNotificationHandler(
|
|
GonkSensorsNotificationHandler* aNotificationHandler)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mNotificationHandler = aNotificationHandler;
|
|
}
|
|
|
|
/*
|
|
* The connect procedure consists of several steps.
|
|
*
|
|
* (1) Start listening for the command channel's socket connection: We
|
|
* do this before anything else, so that we don't miss connection
|
|
* requests from the Sensors daemon. This step will create a listen
|
|
* socket.
|
|
*
|
|
* (2) Start the Sensors daemon: When the daemon starts up it will open
|
|
* a socket connection to Gecko and thus create the data channel.
|
|
* Gecko already opened the listen socket in step (1). Step (2) ends
|
|
* with the creation of the data channel.
|
|
*
|
|
* (3) Signal success to the caller.
|
|
*
|
|
* If any step fails, we roll-back the procedure and signal an error to the
|
|
* caller.
|
|
*/
|
|
void
|
|
GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandler,
|
|
GonkSensorsResultHandler* aRes)
|
|
{
|
|
#define BASE_SOCKET_NAME "sensorsd"
|
|
static unsigned long POSTFIX_LENGTH = 16;
|
|
|
|
// If we could not cleanup properly before and an old
|
|
// instance of the daemon is still running, we kill it
|
|
// here.
|
|
mozilla::hal::StopSystemService("sensorsd");
|
|
|
|
mNotificationHandler = aNotificationHandler;
|
|
|
|
mResultHandlerQ.AppendElement(aRes);
|
|
|
|
if (!mProtocol) {
|
|
mProtocol = MakeUnique<GonkSensorsProtocol>();
|
|
}
|
|
|
|
if (!mListenSocket) {
|
|
mListenSocket = new ListenSocket(this, LISTEN_SOCKET);
|
|
}
|
|
|
|
// Init, step 1: Listen for data channel... */
|
|
|
|
if (!mDataSocket) {
|
|
mDataSocket = new DaemonSocket(mProtocol.get(), this, DATA_SOCKET);
|
|
} else if (mDataSocket->GetConnectionStatus() == SOCKET_CONNECTED) {
|
|
// Command channel should not be open; let's close it.
|
|
mDataSocket->Close();
|
|
}
|
|
|
|
// The listen socket's name is generated with a random postfix. This
|
|
// avoids naming collisions if we still have a listen socket from a
|
|
// previously failed cleanup. It also makes it hard for malicious
|
|
// external programs to capture the socket name or connect before
|
|
// the daemon can do so. If no random postfix can be generated, we
|
|
// simply use the base name as-is.
|
|
nsresult rv = DaemonSocketConnector::CreateRandomAddressString(
|
|
NS_LITERAL_CSTRING(BASE_SOCKET_NAME), POSTFIX_LENGTH, mListenSocketName);
|
|
if (NS_FAILED(rv)) {
|
|
mListenSocketName.AssignLiteral(BASE_SOCKET_NAME);
|
|
}
|
|
|
|
rv = mListenSocket->Listen(new DaemonSocketConnector(mListenSocketName),
|
|
mDataSocket);
|
|
if (NS_FAILED(rv)) {
|
|
OnConnectError(DATA_SOCKET);
|
|
return;
|
|
}
|
|
|
|
// The protocol implementation needs a data channel for
|
|
// sending commands to the daemon. We set it here, because
|
|
// this is the earliest time when it's available.
|
|
mProtocol->SetConnection(mDataSocket);
|
|
}
|
|
|
|
/*
|
|
* Disconnecting is inverse to connecting.
|
|
*
|
|
* (1) Close data socket: We close the data channel and the daemon will
|
|
* will notice. Once we see the socket's disconnect, we continue with
|
|
* the cleanup.
|
|
*
|
|
* (2) Close listen socket: The listen socket is not active any longer
|
|
* and we simply close it.
|
|
*
|
|
* (3) Signal success to the caller.
|
|
*
|
|
* We don't have to stop the daemon explicitly. It will cleanup and quit
|
|
* after it noticed the closing of the data channel
|
|
*
|
|
* Rolling back half-completed cleanups is not possible. In the case of
|
|
* an error, we simply push forward and try to recover during the next
|
|
* initialization.
|
|
*/
|
|
void
|
|
GonkSensorsInterface::Disconnect(GonkSensorsResultHandler* aRes)
|
|
{
|
|
mNotificationHandler = nullptr;
|
|
|
|
// Cleanup, step 1: Close data channel
|
|
mDataSocket->Close();
|
|
|
|
mResultHandlerQ.AppendElement(aRes);
|
|
}
|
|
|
|
GonkSensorsRegistryInterface*
|
|
GonkSensorsInterface::GetSensorsRegistryInterface()
|
|
{
|
|
if (mRegistryInterface) {
|
|
return mRegistryInterface.get();
|
|
}
|
|
|
|
mRegistryInterface = MakeUnique<GonkSensorsRegistryInterface>(mProtocol.get());
|
|
|
|
return mRegistryInterface.get();
|
|
}
|
|
|
|
GonkSensorsPollInterface*
|
|
GonkSensorsInterface::GetSensorsPollInterface()
|
|
{
|
|
if (mPollInterface) {
|
|
return mPollInterface.get();
|
|
}
|
|
|
|
mPollInterface = MakeUnique<GonkSensorsPollInterface>(mProtocol.get());
|
|
|
|
return mPollInterface.get();
|
|
}
|
|
|
|
GonkSensorsInterface::GonkSensorsInterface()
|
|
: mNotificationHandler(nullptr)
|
|
{ }
|
|
|
|
GonkSensorsInterface::~GonkSensorsInterface()
|
|
{ }
|
|
|
|
void
|
|
GonkSensorsInterface::DispatchError(GonkSensorsResultHandler* aRes,
|
|
SensorsError aError)
|
|
{
|
|
DaemonResultRunnable1<GonkSensorsResultHandler, void,
|
|
SensorsError, SensorsError>::Dispatch(
|
|
aRes, &GonkSensorsResultHandler::OnError,
|
|
ConstantInitOp1<SensorsError>(aError));
|
|
}
|
|
|
|
void
|
|
GonkSensorsInterface::DispatchError(
|
|
GonkSensorsResultHandler* aRes, nsresult aRv)
|
|
{
|
|
SensorsError error;
|
|
|
|
if (NS_FAILED(Convert(aRv, error))) {
|
|
error = SENSORS_ERROR_FAIL;
|
|
}
|
|
DispatchError(aRes, error);
|
|
}
|
|
|
|
// |DaemonSocketConsumer|, |ListenSocketConsumer|
|
|
|
|
void
|
|
GonkSensorsInterface::OnConnectSuccess(int aIndex)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mResultHandlerQ.IsEmpty());
|
|
|
|
switch (aIndex) {
|
|
case LISTEN_SOCKET: {
|
|
// Init, step 2: Start Sensors daemon
|
|
nsCString args("-a ");
|
|
args.Append(mListenSocketName);
|
|
mozilla::hal::StartSystemService("sensorsd", args.get());
|
|
}
|
|
break;
|
|
case DATA_SOCKET:
|
|
if (!mResultHandlerQ.IsEmpty()) {
|
|
// Init, step 3: Signal success
|
|
RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0);
|
|
mResultHandlerQ.RemoveElementAt(0);
|
|
if (res) {
|
|
res->Connect();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
GonkSensorsInterface::OnConnectError(int aIndex)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mResultHandlerQ.IsEmpty());
|
|
|
|
switch (aIndex) {
|
|
case DATA_SOCKET:
|
|
// Stop daemon and close listen socket
|
|
mozilla::hal::StopSystemService("sensorsd");
|
|
mListenSocket->Close();
|
|
// fall through
|
|
case LISTEN_SOCKET:
|
|
if (!mResultHandlerQ.IsEmpty()) {
|
|
// Signal error to caller
|
|
RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0);
|
|
mResultHandlerQ.RemoveElementAt(0);
|
|
if (res) {
|
|
DispatchError(res, SENSORS_ERROR_FAIL);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Disconnects can happend
|
|
*
|
|
* (a) during startup,
|
|
* (b) during regular service, or
|
|
* (c) during shutdown.
|
|
*
|
|
* For cases (a) and (c), |mResultHandlerQ| contains an element. For
|
|
* case (b) |mResultHandlerQ| will be empty. This distinguishes a crash in
|
|
* the daemon. The following procedure to recover from crashes consists of
|
|
* several steps for case (b).
|
|
*
|
|
* (1) Close listen socket.
|
|
* (2) Wait for all sockets to be disconnected and inform caller about
|
|
* the crash.
|
|
* (3) After all resources have been cleaned up, let the caller restart
|
|
* the daemon.
|
|
*/
|
|
void
|
|
GonkSensorsInterface::OnDisconnect(int aIndex)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
switch (aIndex) {
|
|
case DATA_SOCKET:
|
|
// Cleanup, step 2 (Recovery, step 1): Close listen socket
|
|
mListenSocket->Close();
|
|
break;
|
|
case LISTEN_SOCKET:
|
|
// Cleanup, step 3: Signal success to caller
|
|
if (!mResultHandlerQ.IsEmpty()) {
|
|
RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0);
|
|
mResultHandlerQ.RemoveElementAt(0);
|
|
if (res) {
|
|
res->Disconnect();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* For recovery make sure all sockets disconnected, in order to avoid
|
|
* the remaining disconnects interfere with the restart procedure.
|
|
*/
|
|
if (mNotificationHandler && mResultHandlerQ.IsEmpty()) {
|
|
if (mListenSocket->GetConnectionStatus() == SOCKET_DISCONNECTED &&
|
|
mDataSocket->GetConnectionStatus() == SOCKET_DISCONNECTED) {
|
|
// Recovery, step 2: Notify the caller to prepare the restart procedure.
|
|
mNotificationHandler->BackendErrorNotification(true);
|
|
mNotificationHandler = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace hal
|
|
} // namespace mozilla
|