summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/VolumeManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/VolumeManager.cpp')
-rw-r--r--dom/system/gonk/VolumeManager.cpp591
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