ipc/dbus/DBusThread.cpp
author Thomas Zimmermann <tdz@users.sourceforge.net>
Fri, 25 Oct 2013 09:31:04 +0200
changeset 165986 6921551afb423f3f184dd940135a493d7e766957
parent 165985 83060920aaf5b4b5b59e39e7d6d663174fdcf695
child 165987 ac3b5c47d520b7cb06dcaad3c32336027ae98fdc
permissions -rw-r--r--
Bug 927459: Move helpers to DBusWatcher, r=qdot PollFdComparator, DBusEventTypes and flag conversion are only used by DBusWatcher. This patch moves them into DBusWatcher's namespace.

/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
 * Copyright 2009, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * NOTE: Due to being based on the dbus compatibility layer for
 * android's bluetooth implementation, this file is licensed under the
 * apache license instead of MPL.
 *
 */

#include "DBusThread.h"
#include "RawDBusConnection.h"
#include "DBusUtils.h"

#include <dbus/dbus.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/types.h>

#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>

#include <list>

#include "base/eintr_wrapper.h"
#include "base/message_loop.h"
#include "nsTArray.h"
#include "nsDataHashtable.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/NullPtr.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Monitor.h"
#include "mozilla/Util.h"
#include "mozilla/FileUtils.h"
#include "nsThreadUtils.h"
#include "nsIThread.h"
#include "nsXULAppAPI.h"
#include "nsServiceManagerUtils.h"
#include "nsCOMPtr.h"

#undef LOG
#if defined(MOZ_WIDGET_GONK)
#include <android/log.h>
#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "GonkDBus", args);
#else
#define BTDEBUG true
#define LOG(args...) if (BTDEBUG) printf(args);
#endif

#define DEFAULT_INITIAL_POLLFD_COUNT 8

namespace mozilla {
namespace ipc {

class DBusWatcher : public RawDBusConnection
{
public:
  DBusWatcher()
  { }

  ~DBusWatcher()
  { }

  bool Initialize();
  void CleanUp();

  void WakeUp();
  bool Stop();

  bool Poll();

  bool AddWatch(DBusWatch* aWatch);
  void RemoveWatch(DBusWatch* aWatch);

  void HandleWatchAdd();
  void HandleWatchRemove();

  // Information about the sockets we're polling. Socket counts
  // increase/decrease depending on how many add/remove watch signals
  // we're received via the control sockets.
  nsTArray<pollfd> mPollData;
  nsTArray<DBusWatch*> mWatchData;

  // Sockets for receiving dbus control information (watch
  // add/removes, loop shutdown, etc...)
  ScopedClose mControlFdR;
  ScopedClose mControlFdW;

private:
  struct PollFdComparator {
    bool Equals(const pollfd& a, const pollfd& b) const {
      return ((a.fd == b.fd) && (a.events == b.events));
    }
    bool LessThan(const pollfd& a, const pollfd&b) const {
      return false;
    }
  };

  enum DBusEventTypes {
    DBUS_EVENT_LOOP_EXIT = 1,
    DBUS_EVENT_LOOP_ADD = 2,
    DBUS_EVENT_LOOP_REMOVE = 3,
    DBUS_EVENT_LOOP_WAKEUP = 4
  };

  static unsigned int UnixEventsToDBusFlags(short events);
  static short        DBusFlagsToUnixEvents(unsigned int flags);

  static dbus_bool_t AddWatchFunction(DBusWatch* aWatch, void* aData);
  static void        RemoveWatchFunction(DBusWatch* aWatch, void* aData);
  static void        ToggleWatchFunction(DBusWatch* aWatch, void* aData);
  static void        DBusWakeupFunction(void* aData);

  bool SetUp();
};

bool
DBusWatcher::Initialize()
{
  if (!SetUp()) {
    CleanUp();
    return false;
  }

  return true;
}

void
DBusWatcher::CleanUp()
{
  MOZ_ASSERT(!NS_IsMainThread());

  dbus_connection_set_wakeup_main_function(mConnection, nullptr,
                                           nullptr, nullptr);
  dbus_bool_t success = dbus_connection_set_watch_functions(mConnection,
                                                            nullptr, nullptr,
                                                            nullptr, nullptr,
                                                            nullptr);
  if (success != TRUE) {
    NS_WARNING("dbus_connection_set_watch_functions failed");
  }

#ifdef DEBUG
  LOG("Removing DBus Sockets\n");
#endif
  if (mControlFdW.get()) {
    mControlFdW.dispose();
  }
  if (mControlFdR.get()) {
    mControlFdR.dispose();
  }
  mPollData.Clear();

  // DBusWatch pointers are maintained by DBus, so we won't leak by
  // clearing.
  mWatchData.Clear();
}

void
DBusWatcher::WakeUp()
{
  static const char control = DBUS_EVENT_LOOP_WAKEUP;

  struct pollfd fds = {
    mControlFdW.get(),
    POLLOUT,
    0
  };

  int nfds = TEMP_FAILURE_RETRY(poll(&fds, 1, 0));
  NS_ENSURE_TRUE_VOID(nfds == 1);
  NS_ENSURE_TRUE_VOID(fds.revents == POLLOUT);

  ssize_t res = TEMP_FAILURE_RETRY(
    write(mControlFdW.get(), &control, sizeof(control)));
  if (res < 0) {
    NS_WARNING("Cannot write wakeup bit to DBus controller!");
  }
}

bool
DBusWatcher::Stop()
{
  static const char data = DBUS_EVENT_LOOP_EXIT;

  ssize_t res =
    TEMP_FAILURE_RETRY(write(mControlFdW.get(), &data, sizeof(data)));
  NS_ENSURE_TRUE(res == 1, false);

  return true;
}

bool
DBusWatcher::Poll()
{
  int res = TEMP_FAILURE_RETRY(poll(mPollData.Elements(),
                                    mPollData.Length(), -1));
  NS_ENSURE_TRUE(res > 0, false);

  bool continueThread = true;

  nsTArray<pollfd>::size_type i = 0;

  while (i < mPollData.Length()) {
    if (mPollData[i].revents == POLLIN) {
      if (mPollData[i].fd == mControlFdR.get()) {
        char data;
        res = TEMP_FAILURE_RETRY(read(mControlFdR.get(), &data, sizeof(data)));
        NS_ENSURE_TRUE(res > 0, NS_OK);

        switch (data) {
          case DBUS_EVENT_LOOP_EXIT:
            continueThread = false;
            break;
          case DBUS_EVENT_LOOP_ADD:
            HandleWatchAdd();
            break;
          case DBUS_EVENT_LOOP_REMOVE:
            HandleWatchRemove();
            // don't increment i, or we'll skip one element
            continue;
          case DBUS_EVENT_LOOP_WAKEUP:
            NS_ProcessPendingEvents(NS_GetCurrentThread(),
                                    PR_INTERVAL_NO_TIMEOUT);
            break;
          default:
#if DEBUG
            nsCString warning("unknown command ");
            warning.AppendInt(data);
            NS_WARNING(warning.get());
#endif
            break;
        }
      } else {
        short events = mPollData[i].revents;
        unsigned int flags = UnixEventsToDBusFlags(events);
        dbus_watch_handle(mWatchData[i], flags);
        mPollData[i].revents = 0;
        // Break at this point since we don't know if the operation
        // was destructive
        break;
      }

      DBusDispatchStatus dbusDispatchStatus;
      do {
        dbusDispatchStatus = dbus_connection_dispatch(GetConnection());
      } while (dbusDispatchStatus == DBUS_DISPATCH_DATA_REMAINS);
    }

    ++i;
  }

  return continueThread;
}

bool
DBusWatcher::AddWatch(DBusWatch* aWatch)
{
  static const char control = DBUS_EVENT_LOOP_ADD;

  if (dbus_watch_get_enabled(aWatch) == FALSE) {
    return true;
  }

  // note that we can't just send the watch and inspect it later
  // because we may get a removeWatch call before this data is reacted
  // to by our eventloop and remove this watch..  reading the add first
  // and then inspecting the recently deceased watch would be bad.
  ssize_t res =
    TEMP_FAILURE_RETRY(write(mControlFdW.get(),&control, sizeof(control)));
  if (res < 0) {
    LOG("Cannot write DBus add watch control data to socket!\n");
    return false;
  }

  int fd = dbus_watch_get_unix_fd(aWatch);
  res = TEMP_FAILURE_RETRY(write(mControlFdW.get(), &fd, sizeof(fd)));
  if (res < 0) {
    LOG("Cannot write DBus add watch descriptor data to socket!\n");
    return false;
  }

  unsigned int flags = dbus_watch_get_flags(aWatch);
  res = TEMP_FAILURE_RETRY(write(mControlFdW.get(), &flags, sizeof(flags)));
  if (res < 0) {
    LOG("Cannot write DBus add watch flag data to socket!\n");
    return false;
  }

  res = TEMP_FAILURE_RETRY(write(mControlFdW.get(), &aWatch, sizeof(aWatch)));
  if (res < 0) {
    LOG("Cannot write DBus add watch struct data to socket!\n");
    return false;
  }

  return true;
}

void
DBusWatcher::RemoveWatch(DBusWatch* aWatch)
{
  static const char control = DBUS_EVENT_LOOP_REMOVE;

  ssize_t res =
    TEMP_FAILURE_RETRY(write(mControlFdW.get(), &control, sizeof(control)));
  if (res < 0) {
    LOG("Cannot write DBus remove watch control data to socket!\n");
    return;
  }

  int fd = dbus_watch_get_unix_fd(aWatch);
  res = TEMP_FAILURE_RETRY(write(mControlFdW.get(), &fd, sizeof(fd)));
  if (res < 0) {
    LOG("Cannot write DBus remove watch descriptor data to socket!\n");
    return;
  }

  unsigned int flags = dbus_watch_get_flags(aWatch);
  res = TEMP_FAILURE_RETRY(write(mControlFdW.get(), &flags, sizeof(flags)));
  if (res < 0) {
    LOG("Cannot write DBus remove watch flag data to socket!\n");
    return;
  }
}

void
DBusWatcher::HandleWatchAdd()
{
  int fd;
  ssize_t res = TEMP_FAILURE_RETRY(read(mControlFdR.get(), &fd, sizeof(fd)));
  if (res < 0) {
    LOG("Cannot read DBus watch add descriptor data from socket!\n");
    return;
  }

  unsigned int flags;
  res = TEMP_FAILURE_RETRY(read(mControlFdR.get(), &flags, sizeof(flags)));
  if (res < 0) {
    LOG("Cannot read DBus watch add flag data from socket!\n");
    return;
  }

  DBusWatch* watch;
  res = TEMP_FAILURE_RETRY(read(mControlFdR.get(), &watch, sizeof(watch)));
  if (res < 0) {
    LOG("Cannot read DBus watch add watch data from socket!\n");
    return;
  }

  struct pollfd p = {
    fd, // .fd
    DBusFlagsToUnixEvents(flags), // .events
    0 // .revents
  };
  if (mPollData.Contains(p, PollFdComparator())) {
    return;
  }
  mPollData.AppendElement(p);
  mWatchData.AppendElement(watch);
}

void
DBusWatcher::HandleWatchRemove()
{
  int fd;
  ssize_t res = TEMP_FAILURE_RETRY(read(mControlFdR.get(), &fd, sizeof(fd)));
  if (res < 0) {
    LOG("Cannot read DBus watch remove descriptor data from socket!\n");
    return;
  }

  unsigned int flags;
  res = TEMP_FAILURE_RETRY(read(mControlFdR.get(), &flags, sizeof(flags)));
  if (res < 0) {
    LOG("Cannot read DBus watch remove flag data from socket!\n");
    return;
  }

  struct pollfd p = {
    fd, // .fd
    DBusFlagsToUnixEvents(flags), // .events
    0 // .revents
  };
  int index = mPollData.IndexOf(p, 0, PollFdComparator());
  // There are times where removes can be requested for watches that
  // haven't been added (for example, whenever gecko comes up after
  // adapters have already been enabled), so check to make sure we're
  // using the watch in the first place
  if (index < 0) {
    LOG("DBus requested watch removal of non-existant socket, ignoring...");
    return;
  }
  mPollData.RemoveElementAt(index);

  // DBusWatch pointers are maintained by DBus, so we won't leak by
  // removing.
  mWatchData.RemoveElementAt(index);
}

// Flag conversion

unsigned int
DBusWatcher::UnixEventsToDBusFlags(short events)
{
  return (events & DBUS_WATCH_READABLE ? POLLIN : 0) |
         (events & DBUS_WATCH_WRITABLE ? POLLOUT : 0) |
         (events & DBUS_WATCH_ERROR ? POLLERR : 0) |
         (events & DBUS_WATCH_HANGUP ? POLLHUP : 0);
}

short
DBusWatcher::DBusFlagsToUnixEvents(unsigned int flags)
{
  return (flags & POLLIN ? DBUS_WATCH_READABLE : 0) |
         (flags & POLLOUT ? DBUS_WATCH_WRITABLE : 0) |
         (flags & POLLERR ? DBUS_WATCH_ERROR : 0) |
         (flags & POLLHUP ? DBUS_WATCH_HANGUP : 0);
}

// DBus utility functions, used as function pointers in DBus setup

dbus_bool_t
DBusWatcher::AddWatchFunction(DBusWatch* aWatch, void* aData)
{
  MOZ_ASSERT(aData);
  DBusWatcher* dbusWatcher = static_cast<DBusWatcher*>(aData);
  return dbusWatcher->AddWatch(aWatch);
}

void
DBusWatcher::RemoveWatchFunction(DBusWatch* aWatch, void* aData)
{
  MOZ_ASSERT(aData);
  DBusWatcher* dbusWatcher = static_cast<DBusWatcher*>(aData);
  dbusWatcher->RemoveWatch(aWatch);
}

void
DBusWatcher::ToggleWatchFunction(DBusWatch* aWatch, void* aData)
{
  MOZ_ASSERT(aData);
  DBusWatcher* dbusWatcher = static_cast<DBusWatcher*>(aData);

  if (dbus_watch_get_enabled(aWatch)) {
    dbusWatcher->AddWatch(aWatch);
  } else {
    dbusWatcher->RemoveWatch(aWatch);
  }
}

void
DBusWatcher::DBusWakeupFunction(void* aData)
{
  MOZ_ASSERT(aData);
  DBusWatcher* dbusWatcher = static_cast<DBusWatcher*>(aData);
  dbusWatcher->WakeUp();
}

bool
DBusWatcher::SetUp()
{
  MOZ_ASSERT(!NS_IsMainThread());

  // If we already have a connection, exit
  if (mConnection) {
    return false;
  }

  // socketpair opens two sockets for the process to communicate on.
  // This is how android's implementation of the dbus event loop
  // communicates with itself in relation to IPC signals. These
  // sockets are contained sequentially in the same struct in the
  // android code, but we break them out into class members here.
  // Therefore we read into a local array and then copy.

  int sockets[2];
  if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets) < 0) {
    return false;
  }

  mControlFdR.rwget() = sockets[0];
  mControlFdW.rwget() = sockets[1];

  pollfd* p = mPollData.AppendElement();

  p->fd = mControlFdR.get();
  p->events = POLLIN;
  p->revents = 0;

  // Due to the fact that mPollData and mWatchData have to match, we
  // push a null to the front of mWatchData since it has the control
  // fd in the first slot of mPollData.

  mWatchData.AppendElement(static_cast<DBusWatch*>(nullptr));

  // If we can't establish a connection to dbus, nothing else will work
  nsresult rv = EstablishDBusConnection();
  if (NS_FAILED(rv)) {
    NS_WARNING("Cannot create DBus Connection for DBus Thread!");
    return false;
  }

  dbus_bool_t success =
    dbus_connection_set_watch_functions(mConnection, AddWatchFunction,
                                        RemoveWatchFunction,
                                        ToggleWatchFunction, this, nullptr);
  NS_ENSURE_TRUE(success == TRUE, false);

  dbus_connection_set_wakeup_main_function(mConnection, DBusWakeupFunction,
                                           this, nullptr);
  return true;
}

// Main task for polling the DBus system

class DBusPollTask : public nsRunnable
{
public:
  DBusPollTask(DBusWatcher* aDBusWatcher)
  : mDBusWatcher(aDBusWatcher)
  { }

  NS_IMETHOD Run()
  {
    MOZ_ASSERT(!NS_IsMainThread());

    bool continueThread;

    do {
      continueThread = mDBusWatcher->Poll();
    } while (continueThread);

    mDBusWatcher->CleanUp();

    return NS_OK;
  }

private:
  nsRefPtr<DBusWatcher> mDBusWatcher;
};

static StaticRefPtr<DBusWatcher> gDBusWatcher;
static StaticRefPtr<nsIThread>   gDBusServiceThread;

// Startup/Shutdown utility functions

bool
StartDBus()
{
  MOZ_ASSERT(!NS_IsMainThread());
  NS_ENSURE_TRUE(!gDBusWatcher, true);

  nsRefPtr<DBusWatcher> dbusWatcher(new DBusWatcher());

  bool eventLoopStarted = dbusWatcher->Initialize();
  NS_ENSURE_TRUE(eventLoopStarted, false);

  nsresult rv;

  if (!gDBusServiceThread) {
    nsIThread* dbusServiceThread;
    rv = NS_NewNamedThread("DBus Thread", &dbusServiceThread);
    NS_ENSURE_SUCCESS(rv, false);
    gDBusServiceThread = dbusServiceThread;
  }

#ifdef DEBUG
  LOG("DBus Thread Starting\n");
#endif

  nsRefPtr<nsIRunnable> pollTask(new DBusPollTask(dbusWatcher));
  NS_ENSURE_TRUE(pollTask, false);

  rv = gDBusServiceThread->Dispatch(pollTask, NS_DISPATCH_NORMAL);
  NS_ENSURE_SUCCESS(rv, false);

  gDBusWatcher = dbusWatcher;

  return true;
}

bool
StopDBus()
{
  MOZ_ASSERT(!NS_IsMainThread());
  NS_ENSURE_TRUE(gDBusServiceThread, true);

  nsRefPtr<DBusWatcher> dbusWatcher(gDBusWatcher);
  gDBusWatcher = nullptr;

  if (dbusWatcher && !dbusWatcher->Stop()) {
    return false;
  }

  nsRefPtr<nsIThread> dbusServiceThread(gDBusServiceThread);
  gDBusServiceThread = nullptr;

  nsRefPtr<nsIRunnable> runnable =
    NS_NewRunnableMethod(dbusServiceThread, &nsIThread::Shutdown);
  nsresult rv = NS_DispatchToMainThread(runnable);
  NS_ENSURE_SUCCESS(rv, false);

  return true;
}

nsresult
DispatchToDBusThread(nsIRunnable* event)
{
  nsRefPtr<nsIThread> dbusServiceThread(gDBusServiceThread);
  nsRefPtr<DBusWatcher> dbusWatcher(gDBusWatcher);

  NS_ENSURE_TRUE(dbusServiceThread.get() && dbusWatcher.get(),
                 NS_ERROR_NOT_INITIALIZED);

  nsresult rv = dbusServiceThread->Dispatch(event, NS_DISPATCH_NORMAL);
  NS_ENSURE_SUCCESS(rv, rv);

  dbusWatcher->WakeUp();

  return NS_OK;
}

}
}