dom/system/gonk/VolumeManager.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/system/gonk/VolumeManager.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,425 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +#include "VolumeManager.h"
     1.9 +
    1.10 +#include "Volume.h"
    1.11 +#include "VolumeCommand.h"
    1.12 +#include "VolumeManagerLog.h"
    1.13 +#include "VolumeServiceTest.h"
    1.14 +
    1.15 +#include "nsWhitespaceTokenizer.h"
    1.16 +#include "nsXULAppAPI.h"
    1.17 +
    1.18 +#include "base/message_loop.h"
    1.19 +#include "mozilla/Scoped.h"
    1.20 +#include "mozilla/StaticPtr.h"
    1.21 +
    1.22 +#include <android/log.h>
    1.23 +#include <cutils/sockets.h>
    1.24 +#include <fcntl.h>
    1.25 +#include <sys/socket.h>
    1.26 +
    1.27 +namespace mozilla {
    1.28 +namespace system {
    1.29 +
    1.30 +static StaticRefPtr<VolumeManager> sVolumeManager;
    1.31 +
    1.32 +VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED;
    1.33 +VolumeManager::StateObserverList VolumeManager::mStateObserverList;
    1.34 +
    1.35 +/***************************************************************************/
    1.36 +
    1.37 +VolumeManager::VolumeManager()
    1.38 +  : LineWatcher('\0', kRcvBufSize),
    1.39 +    mSocket(-1),
    1.40 +    mCommandPending(false)
    1.41 +{
    1.42 +  DBG("VolumeManager constructor called");
    1.43 +}
    1.44 +
    1.45 +VolumeManager::~VolumeManager()
    1.46 +{
    1.47 +}
    1.48 +
    1.49 +//static
    1.50 +size_t
    1.51 +VolumeManager::NumVolumes()
    1.52 +{
    1.53 +  if (!sVolumeManager) {
    1.54 +    return 0;
    1.55 +  }
    1.56 +  return sVolumeManager->mVolumeArray.Length();
    1.57 +}
    1.58 +
    1.59 +//static
    1.60 +TemporaryRef<Volume>
    1.61 +VolumeManager::GetVolume(size_t aIndex)
    1.62 +{
    1.63 +  MOZ_ASSERT(aIndex < NumVolumes());
    1.64 +  return sVolumeManager->mVolumeArray[aIndex];
    1.65 +}
    1.66 +
    1.67 +//static
    1.68 +VolumeManager::STATE
    1.69 +VolumeManager::State()
    1.70 +{
    1.71 +  return mState;
    1.72 +}
    1.73 +
    1.74 +//static
    1.75 +const char *
    1.76 +VolumeManager::StateStr(VolumeManager::STATE aState)
    1.77 +{
    1.78 +  switch (aState) {
    1.79 +    case UNINITIALIZED: return "Uninitialized";
    1.80 +    case STARTING:      return "Starting";
    1.81 +    case VOLUMES_READY: return "Volumes Ready";
    1.82 +  }
    1.83 +  return "???";
    1.84 +}
    1.85 +
    1.86 +
    1.87 +//static
    1.88 +void
    1.89 +VolumeManager::SetState(STATE aNewState)
    1.90 +{
    1.91 +  if (mState != aNewState) {
    1.92 +    LOG("changing state from '%s' to '%s'",
    1.93 +        StateStr(mState), StateStr(aNewState));
    1.94 +    mState = aNewState;
    1.95 +    mStateObserverList.Broadcast(StateChangedEvent());
    1.96 +  }
    1.97 +}
    1.98 +
    1.99 +//static
   1.100 +void
   1.101 +VolumeManager::RegisterStateObserver(StateObserver* aObserver)
   1.102 +{
   1.103 +  mStateObserverList.AddObserver(aObserver);
   1.104 +}
   1.105 +
   1.106 +//static
   1.107 +void VolumeManager::UnregisterStateObserver(StateObserver* aObserver)
   1.108 +{
   1.109 +  mStateObserverList.RemoveObserver(aObserver);
   1.110 +}
   1.111 +
   1.112 +//static
   1.113 +TemporaryRef<Volume>
   1.114 +VolumeManager::FindVolumeByName(const nsCSubstring& aName)
   1.115 +{
   1.116 +  if (!sVolumeManager) {
   1.117 +    return nullptr;
   1.118 +  }
   1.119 +  VolumeArray::size_type  numVolumes = NumVolumes();
   1.120 +  VolumeArray::index_type volIndex;
   1.121 +  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
   1.122 +    RefPtr<Volume> vol = GetVolume(volIndex);
   1.123 +    if (vol->Name().Equals(aName)) {
   1.124 +      return vol;
   1.125 +    }
   1.126 +  }
   1.127 +  return nullptr;
   1.128 +}
   1.129 +
   1.130 +//static
   1.131 +TemporaryRef<Volume>
   1.132 +VolumeManager::FindAddVolumeByName(const nsCSubstring& aName)
   1.133 +{
   1.134 +  RefPtr<Volume> vol = FindVolumeByName(aName);
   1.135 +  if (vol) {
   1.136 +    return vol;
   1.137 +  }
   1.138 +  // No volume found, create and add a new one.
   1.139 +  vol = new Volume(aName);
   1.140 +  sVolumeManager->mVolumeArray.AppendElement(vol);
   1.141 +  return vol;
   1.142 +}
   1.143 +
   1.144 +class VolumeListCallback : public VolumeResponseCallback
   1.145 +{
   1.146 +  virtual void ResponseReceived(const VolumeCommand* aCommand)
   1.147 +  {
   1.148 +    switch (ResponseCode()) {
   1.149 +      case ResponseCode::VolumeListResult: {
   1.150 +        // Each line will look something like:
   1.151 +        //
   1.152 +        //  sdcard /mnt/sdcard 1
   1.153 +        //
   1.154 +        // So for each volume that we get back, we update any volumes that
   1.155 +        // we have of the same name, or add new ones if they don't exist.
   1.156 +        nsCWhitespaceTokenizer tokenizer(ResponseStr());
   1.157 +        nsDependentCSubstring volName(tokenizer.nextToken());
   1.158 +        RefPtr<Volume> vol = VolumeManager::FindAddVolumeByName(volName);
   1.159 +        vol->HandleVoldResponse(ResponseCode(), tokenizer);
   1.160 +        break;
   1.161 +      }
   1.162 +
   1.163 +      case ResponseCode::CommandOkay: {
   1.164 +        // We've received the list of volumes. Tell anybody who
   1.165 +        // is listening that we're open for business.
   1.166 +        VolumeManager::SetState(VolumeManager::VOLUMES_READY);
   1.167 +        break;
   1.168 +      }
   1.169 +    }
   1.170 +  }
   1.171 +};
   1.172 +
   1.173 +bool
   1.174 +VolumeManager::OpenSocket()
   1.175 +{
   1.176 +  SetState(STARTING);
   1.177 +  if ((mSocket.rwget() = socket_local_client("vold",
   1.178 +                                             ANDROID_SOCKET_NAMESPACE_RESERVED,
   1.179 +                                             SOCK_STREAM)) < 0) {
   1.180 +      ERR("Error connecting to vold: (%s) - will retry", strerror(errno));
   1.181 +      return false;
   1.182 +  }
   1.183 +  // add FD_CLOEXEC flag
   1.184 +  int flags = fcntl(mSocket.get(), F_GETFD);
   1.185 +  if (flags == -1) {
   1.186 +      return false;
   1.187 +  }
   1.188 +  flags |= FD_CLOEXEC;
   1.189 +  if (fcntl(mSocket.get(), F_SETFD, flags) == -1) {
   1.190 +    return false;
   1.191 +  }
   1.192 +  // set non-blocking
   1.193 +  if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) {
   1.194 +    return false;
   1.195 +  }
   1.196 +  if (!MessageLoopForIO::current()->
   1.197 +      WatchFileDescriptor(mSocket.get(),
   1.198 +                          true,
   1.199 +                          MessageLoopForIO::WATCH_READ,
   1.200 +                          &mReadWatcher,
   1.201 +                          this)) {
   1.202 +      return false;
   1.203 +  }
   1.204 +
   1.205 +  LOG("Connected to vold");
   1.206 +  PostCommand(new VolumeListCommand(new VolumeListCallback));
   1.207 +  return true;
   1.208 +}
   1.209 +
   1.210 +//static
   1.211 +void
   1.212 +VolumeManager::PostCommand(VolumeCommand* aCommand)
   1.213 +{
   1.214 +  if (!sVolumeManager) {
   1.215 +    ERR("VolumeManager not initialized. Dropping command '%s'", aCommand->Data());
   1.216 +    return;
   1.217 +  }
   1.218 +  aCommand->SetPending(true);
   1.219 +
   1.220 +  DBG("Sending command '%s'", aCommand->Data());
   1.221 +  // vold can only process one command at a time, so add our command
   1.222 +  // to the end of the command queue.
   1.223 +  sVolumeManager->mCommands.push(aCommand);
   1.224 +  if (!sVolumeManager->mCommandPending) {
   1.225 +    // There aren't any commands currently being processed, so go
   1.226 +    // ahead and kick this one off.
   1.227 +    sVolumeManager->mCommandPending = true;
   1.228 +    sVolumeManager->WriteCommandData();
   1.229 +  }
   1.230 +}
   1.231 +
   1.232 +/***************************************************************************
   1.233 +* The WriteCommandData initiates sending of a command to vold. Since
   1.234 +* we're running on the IOThread and not allowed to block, WriteCommandData
   1.235 +* will write as much data as it can, and if not all of the data can be
   1.236 +* written then it will setup a file descriptor watcher and
   1.237 +* OnFileCanWriteWithoutBlocking will call WriteCommandData to write out
   1.238 +* more of the command data.
   1.239 +*/
   1.240 +void
   1.241 +VolumeManager::WriteCommandData()
   1.242 +{
   1.243 +  if (mCommands.size() == 0) {
   1.244 +    return;
   1.245 +  }
   1.246 +
   1.247 +  VolumeCommand* cmd = mCommands.front();
   1.248 +  if (cmd->BytesRemaining() == 0) {
   1.249 +    // All bytes have been written. We're waiting for a response.
   1.250 +    return;
   1.251 +  }
   1.252 +  // There are more bytes left to write. Try to write them all.
   1.253 +  ssize_t bytesWritten = write(mSocket.get(), cmd->Data(), cmd->BytesRemaining());
   1.254 +  if (bytesWritten < 0) {
   1.255 +    ERR("Failed to write %d bytes to vold socket", cmd->BytesRemaining());
   1.256 +    Restart();
   1.257 +    return;
   1.258 +  }
   1.259 +  DBG("Wrote %ld bytes (of %d)", bytesWritten, cmd->BytesRemaining());
   1.260 +  cmd->ConsumeBytes(bytesWritten);
   1.261 +  if (cmd->BytesRemaining() == 0) {
   1.262 +    return;
   1.263 +  }
   1.264 +  // We were unable to write all of the command bytes. Setup a watcher
   1.265 +  // so we'll get called again when we can write without blocking.
   1.266 +  if (!MessageLoopForIO::current()->
   1.267 +      WatchFileDescriptor(mSocket.get(),
   1.268 +                          false, // one-shot
   1.269 +                          MessageLoopForIO::WATCH_WRITE,
   1.270 +                          &mWriteWatcher,
   1.271 +                          this)) {
   1.272 +    ERR("Failed to setup write watcher for vold socket");
   1.273 +    Restart();
   1.274 +  }
   1.275 +}
   1.276 +
   1.277 +void
   1.278 +VolumeManager::OnLineRead(int aFd, nsDependentCSubstring& aMessage)
   1.279 +{
   1.280 +  MOZ_ASSERT(aFd == mSocket.get());
   1.281 +  char* endPtr;
   1.282 +  int responseCode = strtol(aMessage.Data(), &endPtr, 10);
   1.283 +  if (*endPtr == ' ') {
   1.284 +    endPtr++;
   1.285 +  }
   1.286 +
   1.287 +  // Now fish out the rest of the line after the response code
   1.288 +  nsDependentCString  responseLine(endPtr, aMessage.Length() - (endPtr - aMessage.Data()));
   1.289 +  DBG("Rcvd: %d '%s'", responseCode, responseLine.Data());
   1.290 +
   1.291 +  if (responseCode >= ResponseCode::UnsolicitedInformational) {
   1.292 +    // These are unsolicited broadcasts. We intercept these and process
   1.293 +    // them ourselves
   1.294 +    HandleBroadcast(responseCode, responseLine);
   1.295 +  } else {
   1.296 +    // Everything else is considered to be part of the command response.
   1.297 +    if (mCommands.size() > 0) {
   1.298 +      VolumeCommand* cmd = mCommands.front();
   1.299 +      cmd->HandleResponse(responseCode, responseLine);
   1.300 +      if (responseCode >= ResponseCode::CommandOkay) {
   1.301 +        // That's a terminating response. We can remove the command.
   1.302 +        mCommands.pop();
   1.303 +        mCommandPending = false;
   1.304 +        // Start the next command, if there is one.
   1.305 +        WriteCommandData();
   1.306 +      }
   1.307 +    } else {
   1.308 +      ERR("Response with no command");
   1.309 +    }
   1.310 +  }
   1.311 +}
   1.312 +
   1.313 +void
   1.314 +VolumeManager::OnFileCanWriteWithoutBlocking(int aFd)
   1.315 +{
   1.316 +  MOZ_ASSERT(aFd == mSocket.get());
   1.317 +  WriteCommandData();
   1.318 +}
   1.319 +
   1.320 +void
   1.321 +VolumeManager::HandleBroadcast(int aResponseCode, nsCString& aResponseLine)
   1.322 +{
   1.323 +  // Format of the line is something like:
   1.324 +  //
   1.325 +  //  Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
   1.326 +  //
   1.327 +  // So we parse out the volume name and the state after the string " to "
   1.328 +  nsCWhitespaceTokenizer  tokenizer(aResponseLine);
   1.329 +  tokenizer.nextToken();  // The word "Volume"
   1.330 +  nsDependentCSubstring volName(tokenizer.nextToken());
   1.331 +
   1.332 +  RefPtr<Volume> vol = FindVolumeByName(volName);
   1.333 +  if (!vol) {
   1.334 +    return;
   1.335 +  }
   1.336 +  vol->HandleVoldResponse(aResponseCode, tokenizer);
   1.337 +}
   1.338 +
   1.339 +void
   1.340 +VolumeManager::Restart()
   1.341 +{
   1.342 +  mReadWatcher.StopWatchingFileDescriptor();
   1.343 +  mWriteWatcher.StopWatchingFileDescriptor();
   1.344 +
   1.345 +  while (!mCommands.empty()) {
   1.346 +    mCommands.pop();
   1.347 +  }
   1.348 +  mCommandPending = false;
   1.349 +  mSocket.dispose();
   1.350 +  Start();
   1.351 +}
   1.352 +
   1.353 +//static
   1.354 +void
   1.355 +VolumeManager::Start()
   1.356 +{
   1.357 +  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   1.358 +
   1.359 +  if (!sVolumeManager) {
   1.360 +    return;
   1.361 +  }
   1.362 +  SetState(STARTING);
   1.363 +  if (!sVolumeManager->OpenSocket()) {
   1.364 +    // Socket open failed, try again in a second.
   1.365 +    MessageLoopForIO::current()->
   1.366 +      PostDelayedTask(FROM_HERE,
   1.367 +                      NewRunnableFunction(VolumeManager::Start),
   1.368 +                      1000);
   1.369 +  }
   1.370 +}
   1.371 +
   1.372 +void
   1.373 +VolumeManager::OnError()
   1.374 +{
   1.375 +  Restart();
   1.376 +}
   1.377 +
   1.378 +/***************************************************************************/
   1.379 +
   1.380 +static void
   1.381 +InitVolumeManagerIOThread()
   1.382 +{
   1.383 +  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   1.384 +  MOZ_ASSERT(!sVolumeManager);
   1.385 +
   1.386 +  sVolumeManager = new VolumeManager();
   1.387 +  VolumeManager::Start();
   1.388 +
   1.389 +  InitVolumeServiceTestIOThread();
   1.390 +}
   1.391 +
   1.392 +static void
   1.393 +ShutdownVolumeManagerIOThread()
   1.394 +{
   1.395 +  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   1.396 +
   1.397 +  sVolumeManager = nullptr;
   1.398 +}
   1.399 +
   1.400 +/**************************************************************************
   1.401 +*
   1.402 +*   Public API
   1.403 +*
   1.404 +*   Since the VolumeManager runs in IO Thread context, we need to switch
   1.405 +*   to IOThread context before we can do anything.
   1.406 +*
   1.407 +**************************************************************************/
   1.408 +
   1.409 +void
   1.410 +InitVolumeManager()
   1.411 +{
   1.412 +  XRE_GetIOMessageLoop()->PostTask(
   1.413 +      FROM_HERE,
   1.414 +      NewRunnableFunction(InitVolumeManagerIOThread));
   1.415 +}
   1.416 +
   1.417 +void
   1.418 +ShutdownVolumeManager()
   1.419 +{
   1.420 +  ShutdownVolumeServiceTest();
   1.421 +
   1.422 +  XRE_GetIOMessageLoop()->PostTask(
   1.423 +      FROM_HERE,
   1.424 +      NewRunnableFunction(ShutdownVolumeManagerIOThread));
   1.425 +}
   1.426 +
   1.427 +} // system
   1.428 +} // mozilla

mercurial