gecko/hal/gonk/GonkSensorsInterface.cpp
Thomas Zimmermann 3fee8bcdf4 Bug 1194721: Add interface for Gonk sensors daemon, r=gsvelto
This patch adds the interface for the Gonk sensors daemon. There
are only two major interfaces: connect and disconnect. The sensors
daemon is started before connecting and stopped after a disconnect.

Connections are race-free: we first set up a listen socket and let
Gecko listen for connections. Then the daemon gets started with the
socket's address as parameter. When the daemon connects to the
socket, Gecko will continue the startup and signal success to the
caller.
2016-02-04 12:35:13 +01:00

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 = new GonkSensorsProtocol();
}
if (!mListenSocket) {
mListenSocket = new ListenSocket(this, LISTEN_SOCKET);
}
// Init, step 1: Listen for data channel... */
if (!mDataSocket) {
mDataSocket = new DaemonSocket(mProtocol, 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;
}
mRegistryInterface = new GonkSensorsRegistryInterface(mProtocol);
return mRegistryInterface;
}
GonkSensorsPollInterface*
GonkSensorsInterface::GetSensorsPollInterface()
{
if (mPollInterface) {
return mPollInterface;
}
mPollInterface = new GonkSensorsPollInterface(mProtocol);
return mPollInterface;
}
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