diff options
Diffstat (limited to 'dom/system/gonk/VolumeManager.cpp')
-rw-r--r-- | dom/system/gonk/VolumeManager.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/dom/system/gonk/VolumeManager.cpp b/dom/system/gonk/VolumeManager.cpp new file mode 100644 index 000000000..ddfa7af09 --- /dev/null +++ b/dom/system/gonk/VolumeManager.cpp @@ -0,0 +1,591 @@ +/* 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 "base/task.h" +#include "mozilla/Scoped.h" +#include "mozilla/StaticPtr.h" + +#include <android/log.h> +#include <cutils/sockets.h> +#include <fcntl.h> +#include <sys/socket.h> + +namespace mozilla { +namespace system { + +static StaticRefPtr<VolumeManager> 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 +void +VolumeManager::Dump(const char* aLabel) +{ + if (!sVolumeManager) { + LOG("%s: sVolumeManager == null", aLabel); + return; + } + + VolumeArray::size_type numVolumes = NumVolumes(); + VolumeArray::index_type volIndex; + for (volIndex = 0; volIndex < numVolumes; volIndex++) { + RefPtr<Volume> vol = GetVolume(volIndex); + vol->Dump(aLabel); + } +} + +//static +size_t +VolumeManager::NumVolumes() +{ + if (!sVolumeManager) { + return 0; + } + return sVolumeManager->mVolumeArray.Length(); +} + +//static +already_AddRefed<Volume> +VolumeManager::GetVolume(size_t aIndex) +{ + MOZ_ASSERT(aIndex < NumVolumes()); + RefPtr<Volume> vol = sVolumeManager->mVolumeArray[aIndex]; + return vol.forget(); +} + +//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 +already_AddRefed<Volume> +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<Volume> vol = GetVolume(volIndex); + if (vol->Name().Equals(aName)) { + return vol.forget(); + } + } + return nullptr; +} + +//static +already_AddRefed<Volume> +VolumeManager::FindAddVolumeByName(const nsCSubstring& aName) +{ + RefPtr<Volume> vol = FindVolumeByName(aName); + if (vol) { + return vol.forget(); + } + // No volume found, create and add a new one. + vol = new Volume(aName); + sVolumeManager->mVolumeArray.AppendElement(vol); + return vol.forget(); +} + +//static +bool +VolumeManager::RemoveVolumeByName(const nsCSubstring& aName) +{ + if (!sVolumeManager) { + return false; + } + VolumeArray::size_type numVolumes = NumVolumes(); + VolumeArray::index_type volIndex; + for (volIndex = 0; volIndex < numVolumes; volIndex++) { + RefPtr<Volume> vol = GetVolume(volIndex); + if (vol->Name().Equals(aName)) { + sVolumeManager->mVolumeArray.RemoveElementAt(volIndex); + return true; + } + } + // No volume found. Return false to indicate this. + return false; +} + + +//static +void VolumeManager::InitConfig() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + // This function uses /system/etc/volume.cfg to add additional volumes + // to the Volume Manager. + // + // This is useful on devices like the Nexus 4, which have no physical sd card + // or dedicated partition. + // + // The format of the volume.cfg file is as follows: + // create volume-name mount-point + // configure volume-name preference preference-value + // Blank lines and lines starting with the hash character "#" will be ignored. + + ScopedCloseFile fp; + int n = 0; + char line[255]; + const char *filename = "/system/etc/volume.cfg"; + if (!(fp = fopen(filename, "r"))) { + LOG("Unable to open volume configuration file '%s' - ignoring", filename); + return; + } + while(fgets(line, sizeof(line), fp)) { + n++; + + if (line[0] == '#') + continue; + + nsCString commandline(line); + nsCWhitespaceTokenizer tokenizer(commandline); + if (!tokenizer.hasMoreTokens()) { + // Blank line - ignore + continue; + } + + nsCString command(tokenizer.nextToken()); + if (command.EqualsLiteral("create")) { + if (!tokenizer.hasMoreTokens()) { + ERR("No vol_name in %s line %d", filename, n); + continue; + } + nsCString volName(tokenizer.nextToken()); + if (!tokenizer.hasMoreTokens()) { + ERR("No mount point for volume '%s'. %s line %d", + volName.get(), filename, n); + continue; + } + nsCString mountPoint(tokenizer.nextToken()); + RefPtr<Volume> vol = FindAddVolumeByName(volName); + vol->SetFakeVolume(mountPoint); + continue; + } + if (command.EqualsLiteral("configure")) { + if (!tokenizer.hasMoreTokens()) { + ERR("No vol_name in %s line %d", filename, n); + continue; + } + nsCString volName(tokenizer.nextToken()); + if (!tokenizer.hasMoreTokens()) { + ERR("No configuration name specified for volume '%s'. %s line %d", + volName.get(), filename, n); + continue; + } + nsCString configName(tokenizer.nextToken()); + if (!tokenizer.hasMoreTokens()) { + ERR("No value for configuration name '%s'. %s line %d", + configName.get(), filename, n); + continue; + } + nsCString configValue(tokenizer.nextToken()); + RefPtr<Volume> vol = FindVolumeByName(volName); + if (vol) { + vol->SetConfig(configName, configValue); + } else { + ERR("Invalid volume name '%s'.", volName.get()); + } + continue; + } + if (command.EqualsLiteral("ignore")) { + // This command is useful to remove volumes which are being tracked by + // vold, but for which we have no interest. + if (!tokenizer.hasMoreTokens()) { + ERR("No vol_name in %s line %d", filename, n); + continue; + } + nsCString volName(tokenizer.nextToken()); + RemoveVolumeByName(volName); + continue; + } + ERR("Unrecognized command: '%s'", command.get()); + } +} + +void +VolumeManager::DefaultConfig() +{ + + VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); + if (numVolumes == 0) { + return; + } + if (numVolumes == 1) { + // This is to cover early shipping phones like the Buri, + // which had no internal storage, and only external sdcard. + // + // Phones line the nexus-4 which only have an internal + // storage area will need to have a volume.cfg file with + // removable set to false. + RefPtr<Volume> vol = VolumeManager::GetVolume(0); + vol->SetIsRemovable(true); + vol->SetIsHotSwappable(true); + return; + } + VolumeManager::VolumeArray::index_type volIndex; + for (volIndex = 0; volIndex < numVolumes; volIndex++) { + RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex); + if (!vol->Name().EqualsLiteral("sdcard")) { + vol->SetIsRemovable(true); + vol->SetIsHotSwappable(true); + } + } +} + +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<Volume> vol = VolumeManager::FindAddVolumeByName(volName); + vol->HandleVoldResponse(ResponseCode(), tokenizer); + break; + } + + case ::ResponseCode::CommandOkay: { + // We've received the list of volumes. Now read the Volume.cfg + // file to perform customizations, and then tell everybody + // that we're ready for business. + VolumeManager::DefaultConfig(); + VolumeManager::InitConfig(); + VolumeManager::Dump("READY"); + 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 %d 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<Volume> 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(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( + NewRunnableFunction(InitVolumeManagerIOThread)); +} + +void +ShutdownVolumeManager() +{ + ShutdownVolumeServiceTest(); + + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(ShutdownVolumeManagerIOThread)); +} + +} // system +} // mozilla |