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