diff -r 000000000000 -r 6474c204b198 dom/system/gonk/VolumeManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/system/gonk/VolumeManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,425 @@ +/* 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 "VolumeManager.h" + +#include "Volume.h" +#include "VolumeCommand.h" +#include "VolumeManagerLog.h" +#include "VolumeServiceTest.h" + +#include "nsWhitespaceTokenizer.h" +#include "nsXULAppAPI.h" + +#include "base/message_loop.h" +#include "mozilla/Scoped.h" +#include "mozilla/StaticPtr.h" + +#include +#include +#include +#include + +namespace mozilla { +namespace system { + +static StaticRefPtr sVolumeManager; + +VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED; +VolumeManager::StateObserverList VolumeManager::mStateObserverList; + +/***************************************************************************/ + +VolumeManager::VolumeManager() + : LineWatcher('\0', kRcvBufSize), + mSocket(-1), + mCommandPending(false) +{ + DBG("VolumeManager constructor called"); +} + +VolumeManager::~VolumeManager() +{ +} + +//static +size_t +VolumeManager::NumVolumes() +{ + if (!sVolumeManager) { + return 0; + } + return sVolumeManager->mVolumeArray.Length(); +} + +//static +TemporaryRef +VolumeManager::GetVolume(size_t aIndex) +{ + MOZ_ASSERT(aIndex < NumVolumes()); + return sVolumeManager->mVolumeArray[aIndex]; +} + +//static +VolumeManager::STATE +VolumeManager::State() +{ + return mState; +} + +//static +const char * +VolumeManager::StateStr(VolumeManager::STATE aState) +{ + switch (aState) { + case UNINITIALIZED: return "Uninitialized"; + case STARTING: return "Starting"; + case VOLUMES_READY: return "Volumes Ready"; + } + return "???"; +} + + +//static +void +VolumeManager::SetState(STATE aNewState) +{ + if (mState != aNewState) { + LOG("changing state from '%s' to '%s'", + StateStr(mState), StateStr(aNewState)); + mState = aNewState; + mStateObserverList.Broadcast(StateChangedEvent()); + } +} + +//static +void +VolumeManager::RegisterStateObserver(StateObserver* aObserver) +{ + mStateObserverList.AddObserver(aObserver); +} + +//static +void VolumeManager::UnregisterStateObserver(StateObserver* aObserver) +{ + mStateObserverList.RemoveObserver(aObserver); +} + +//static +TemporaryRef +VolumeManager::FindVolumeByName(const nsCSubstring& aName) +{ + if (!sVolumeManager) { + return nullptr; + } + VolumeArray::size_type numVolumes = NumVolumes(); + VolumeArray::index_type volIndex; + for (volIndex = 0; volIndex < numVolumes; volIndex++) { + RefPtr vol = GetVolume(volIndex); + if (vol->Name().Equals(aName)) { + return vol; + } + } + return nullptr; +} + +//static +TemporaryRef +VolumeManager::FindAddVolumeByName(const nsCSubstring& aName) +{ + RefPtr vol = FindVolumeByName(aName); + if (vol) { + return vol; + } + // No volume found, create and add a new one. + vol = new Volume(aName); + sVolumeManager->mVolumeArray.AppendElement(vol); + return vol; +} + +class VolumeListCallback : public VolumeResponseCallback +{ + virtual void ResponseReceived(const VolumeCommand* aCommand) + { + switch (ResponseCode()) { + case ResponseCode::VolumeListResult: { + // Each line will look something like: + // + // sdcard /mnt/sdcard 1 + // + // So for each volume that we get back, we update any volumes that + // we have of the same name, or add new ones if they don't exist. + nsCWhitespaceTokenizer tokenizer(ResponseStr()); + nsDependentCSubstring volName(tokenizer.nextToken()); + RefPtr vol = VolumeManager::FindAddVolumeByName(volName); + vol->HandleVoldResponse(ResponseCode(), tokenizer); + break; + } + + case ResponseCode::CommandOkay: { + // We've received the list of volumes. Tell anybody who + // is listening that we're open for business. + VolumeManager::SetState(VolumeManager::VOLUMES_READY); + break; + } + } + } +}; + +bool +VolumeManager::OpenSocket() +{ + SetState(STARTING); + if ((mSocket.rwget() = socket_local_client("vold", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM)) < 0) { + ERR("Error connecting to vold: (%s) - will retry", strerror(errno)); + return false; + } + // add FD_CLOEXEC flag + int flags = fcntl(mSocket.get(), F_GETFD); + if (flags == -1) { + return false; + } + flags |= FD_CLOEXEC; + if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { + return false; + } + // set non-blocking + if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { + return false; + } + if (!MessageLoopForIO::current()-> + WatchFileDescriptor(mSocket.get(), + true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, + this)) { + return false; + } + + LOG("Connected to vold"); + PostCommand(new VolumeListCommand(new VolumeListCallback)); + return true; +} + +//static +void +VolumeManager::PostCommand(VolumeCommand* aCommand) +{ + if (!sVolumeManager) { + ERR("VolumeManager not initialized. Dropping command '%s'", aCommand->Data()); + return; + } + aCommand->SetPending(true); + + DBG("Sending command '%s'", aCommand->Data()); + // vold can only process one command at a time, so add our command + // to the end of the command queue. + sVolumeManager->mCommands.push(aCommand); + if (!sVolumeManager->mCommandPending) { + // There aren't any commands currently being processed, so go + // ahead and kick this one off. + sVolumeManager->mCommandPending = true; + sVolumeManager->WriteCommandData(); + } +} + +/*************************************************************************** +* The WriteCommandData initiates sending of a command to vold. Since +* we're running on the IOThread and not allowed to block, WriteCommandData +* will write as much data as it can, and if not all of the data can be +* written then it will setup a file descriptor watcher and +* OnFileCanWriteWithoutBlocking will call WriteCommandData to write out +* more of the command data. +*/ +void +VolumeManager::WriteCommandData() +{ + if (mCommands.size() == 0) { + return; + } + + VolumeCommand* cmd = mCommands.front(); + if (cmd->BytesRemaining() == 0) { + // All bytes have been written. We're waiting for a response. + return; + } + // There are more bytes left to write. Try to write them all. + ssize_t bytesWritten = write(mSocket.get(), cmd->Data(), cmd->BytesRemaining()); + if (bytesWritten < 0) { + ERR("Failed to write %d bytes to vold socket", cmd->BytesRemaining()); + Restart(); + return; + } + DBG("Wrote %ld bytes (of %d)", bytesWritten, cmd->BytesRemaining()); + cmd->ConsumeBytes(bytesWritten); + if (cmd->BytesRemaining() == 0) { + return; + } + // We were unable to write all of the command bytes. Setup a watcher + // so we'll get called again when we can write without blocking. + if (!MessageLoopForIO::current()-> + WatchFileDescriptor(mSocket.get(), + false, // one-shot + MessageLoopForIO::WATCH_WRITE, + &mWriteWatcher, + this)) { + ERR("Failed to setup write watcher for vold socket"); + Restart(); + } +} + +void +VolumeManager::OnLineRead(int aFd, nsDependentCSubstring& aMessage) +{ + MOZ_ASSERT(aFd == mSocket.get()); + char* endPtr; + int responseCode = strtol(aMessage.Data(), &endPtr, 10); + if (*endPtr == ' ') { + endPtr++; + } + + // Now fish out the rest of the line after the response code + nsDependentCString responseLine(endPtr, aMessage.Length() - (endPtr - aMessage.Data())); + DBG("Rcvd: %d '%s'", responseCode, responseLine.Data()); + + if (responseCode >= ResponseCode::UnsolicitedInformational) { + // These are unsolicited broadcasts. We intercept these and process + // them ourselves + HandleBroadcast(responseCode, responseLine); + } else { + // Everything else is considered to be part of the command response. + if (mCommands.size() > 0) { + VolumeCommand* cmd = mCommands.front(); + cmd->HandleResponse(responseCode, responseLine); + if (responseCode >= ResponseCode::CommandOkay) { + // That's a terminating response. We can remove the command. + mCommands.pop(); + mCommandPending = false; + // Start the next command, if there is one. + WriteCommandData(); + } + } else { + ERR("Response with no command"); + } + } +} + +void +VolumeManager::OnFileCanWriteWithoutBlocking(int aFd) +{ + MOZ_ASSERT(aFd == mSocket.get()); + WriteCommandData(); +} + +void +VolumeManager::HandleBroadcast(int aResponseCode, nsCString& aResponseLine) +{ + // Format of the line is something like: + // + // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted) + // + // So we parse out the volume name and the state after the string " to " + nsCWhitespaceTokenizer tokenizer(aResponseLine); + tokenizer.nextToken(); // The word "Volume" + nsDependentCSubstring volName(tokenizer.nextToken()); + + RefPtr vol = FindVolumeByName(volName); + if (!vol) { + return; + } + vol->HandleVoldResponse(aResponseCode, tokenizer); +} + +void +VolumeManager::Restart() +{ + mReadWatcher.StopWatchingFileDescriptor(); + mWriteWatcher.StopWatchingFileDescriptor(); + + while (!mCommands.empty()) { + mCommands.pop(); + } + mCommandPending = false; + mSocket.dispose(); + Start(); +} + +//static +void +VolumeManager::Start() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (!sVolumeManager) { + return; + } + SetState(STARTING); + if (!sVolumeManager->OpenSocket()) { + // Socket open failed, try again in a second. + MessageLoopForIO::current()-> + PostDelayedTask(FROM_HERE, + NewRunnableFunction(VolumeManager::Start), + 1000); + } +} + +void +VolumeManager::OnError() +{ + Restart(); +} + +/***************************************************************************/ + +static void +InitVolumeManagerIOThread() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + MOZ_ASSERT(!sVolumeManager); + + sVolumeManager = new VolumeManager(); + VolumeManager::Start(); + + InitVolumeServiceTestIOThread(); +} + +static void +ShutdownVolumeManagerIOThread() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + sVolumeManager = nullptr; +} + +/************************************************************************** +* +* Public API +* +* Since the VolumeManager runs in IO Thread context, we need to switch +* to IOThread context before we can do anything. +* +**************************************************************************/ + +void +InitVolumeManager() +{ + XRE_GetIOMessageLoop()->PostTask( + FROM_HERE, + NewRunnableFunction(InitVolumeManagerIOThread)); +} + +void +ShutdownVolumeManager() +{ + ShutdownVolumeServiceTest(); + + XRE_GetIOMessageLoop()->PostTask( + FROM_HERE, + NewRunnableFunction(ShutdownVolumeManagerIOThread)); +} + +} // system +} // mozilla