mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
20b739aeda
MozReview-Commit-ID: BcAboKGMr3a --HG-- extra : rebase_source : 6c5050527d8dffbce4a5e867bd48254a1041e657
592 lines
16 KiB
C++
592 lines
16 KiB
C++
/* 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
|