diff options
Diffstat (limited to 'dom/system/gonk/Volume.cpp')
-rw-r--r-- | dom/system/gonk/Volume.cpp | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/dom/system/gonk/Volume.cpp b/dom/system/gonk/Volume.cpp new file mode 100644 index 000000000..f90c7b693 --- /dev/null +++ b/dom/system/gonk/Volume.cpp @@ -0,0 +1,596 @@ +/* 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 "Volume.h" +#include "VolumeCommand.h" +#include "VolumeManager.h" +#include "VolumeManagerLog.h" +#include "nsIVolume.h" +#include "nsXULAppAPI.h" + +#include <vold/ResponseCode.h> + +namespace mozilla { +namespace system { + +#if DEBUG_VOLUME_OBSERVER +void +VolumeObserverList::Broadcast(Volume* const& aVolume) +{ + uint32_t size = mObservers.Length(); + for (uint32_t i = 0; i < size; ++i) { + LOG("VolumeObserverList::Broadcast to [%u] %p volume '%s'", + i, mObservers[i], aVolume->NameStr()); + mObservers[i]->Notify(aVolume); + } +} +#endif + +VolumeObserverList Volume::sEventObserverList; + +// We have a feature where volumes can be locked when mounted. This +// is used to prevent a volume from being shared with the PC while +// it is actively being used (say for storing an update image) +// +// We use WakeLocks (a poor choice of name, but it does what we want) +// from the PowerManagerService to determine when we're locked. +// In particular we'll create a wakelock called volume-NAME-GENERATION +// (where NAME is the volume name, and GENERATION is its generation +// number), and if this wakelock is locked, then we'll prevent a volume +// from being shared. +// +// Implementation Details: +// +// Since the AutoMounter can only control when something gets mounted +// and not when it gets unmounted (for example: a user pulls the SDCard) +// and because Volume and nsVolume data structures are maintained on +// separate threads, we have the potential for some race conditions. +// We eliminate the race conditions by introducing the concept of a +// generation number. Every time a volume transitions to the Mounted +// state, it gets assigned a new generation number. Whenever the state +// of a Volume changes, we send the updated state and current generation +// number to the main thread where it gets updated in the nsVolume. +// +// Since WakeLocks can only be queried from the main-thread, the +// nsVolumeService looks for WakeLock status changes, and forwards +// the results to the IOThread. +// +// If the Volume (IOThread) receives a volume update where the generation +// number mismatches, then the update is simply ignored. +// +// When a Volume (IOThread) initially becomes mounted, we assume it to +// be locked until we get our first update from nsVolume (MainThread). +static int32_t sMountGeneration = 0; + +static uint32_t sNextId = 1; + +// We don't get media inserted/removed events at startup. So we +// assume it's present, and we'll be told that it's missing. +Volume::Volume(const nsCSubstring& aName) + : mMediaPresent(true), + mState(nsIVolume::STATE_INIT), + mName(aName), + mMountGeneration(-1), + mMountLocked(true), // Needs to agree with nsVolume::nsVolume + mSharingEnabled(false), + mFormatRequested(false), + mMountRequested(false), + mUnmountRequested(false), + mCanBeShared(true), + mIsSharing(false), + mIsFormatting(false), + mIsUnmounting(false), + mIsRemovable(false), + mIsHotSwappable(false), + mId(sNextId++) +{ + DBG("Volume %s: created", NameStr()); +} + +void +Volume::Dump(const char* aLabel) const +{ + LOG("%s: Volume: %s (%d) is %s and %s @ %s gen %d locked %d", + aLabel, + NameStr(), + Id(), + StateStr(), + MediaPresent() ? "inserted" : "missing", + MountPoint().get(), + MountGeneration(), + (int)IsMountLocked()); + LOG("%s: Sharing %s Mounting %s Formating %s Unmounting %s", + aLabel, + CanBeShared() ? (IsSharingEnabled() ? (IsSharing() ? "en-y" : "en-n") + : "dis") + : "x", + IsMountRequested() ? "req" : "n", + IsFormatRequested() ? (IsFormatting() ? "req-y" : "req-n") + : (IsFormatting() ? "y" : "n"), + IsUnmountRequested() ? (IsUnmounting() ? "req-y" : "req-n") + : (IsUnmounting() ? "y" : "n")); +} + +void +Volume::ResolveAndSetMountPoint(const nsCSubstring& aMountPoint) +{ + nsCString mountPoint(aMountPoint); + char realPathBuf[PATH_MAX]; + + // Call realpath so that we wind up with a path which is compatible with + // functions like nsVolumeService::GetVolumeByPath. + + if (realpath(mountPoint.get(), realPathBuf) < 0) { + // The path we were handed doesn't exist. Warn about it, but use it + // anyways assuming that the user knows what they're doing. + + ERR("ResolveAndSetMountPoint: realpath on '%s' failed: %d", + mountPoint.get(), errno); + mMountPoint = mountPoint; + } else { + mMountPoint = realPathBuf; + } + DBG("Volume %s: Setting mountpoint to '%s'", NameStr(), mMountPoint.get()); +} + +void Volume::SetFakeVolume(const nsACString& aMountPoint) +{ + this->mMountLocked = false; + this->mCanBeShared = false; + ResolveAndSetMountPoint(aMountPoint); + SetState(nsIVolume::STATE_MOUNTED); +} + +void +Volume::SetIsSharing(bool aIsSharing) +{ + if (aIsSharing == mIsSharing) { + return; + } + mIsSharing = aIsSharing; + LOG("Volume %s: IsSharing set to %d state %s", + NameStr(), (int)mIsSharing, StateStr(mState)); + sEventObserverList.Broadcast(this); +} + +void +Volume::SetIsFormatting(bool aIsFormatting) +{ + if (aIsFormatting == mIsFormatting) { + return; + } + mIsFormatting = aIsFormatting; + LOG("Volume %s: IsFormatting set to %d state %s", + NameStr(), (int)mIsFormatting, StateStr(mState)); + if (mIsFormatting) { + sEventObserverList.Broadcast(this); + } +} + +void +Volume::SetIsUnmounting(bool aIsUnmounting) +{ + if (aIsUnmounting == mIsUnmounting) { + return; + } + mIsUnmounting = aIsUnmounting; + LOG("Volume %s: IsUnmounting set to %d state %s", + NameStr(), (int)mIsUnmounting, StateStr(mState)); + sEventObserverList.Broadcast(this); +} + +void +Volume::SetIsRemovable(bool aIsRemovable) +{ + if (aIsRemovable == mIsRemovable) { + return; + } + mIsRemovable = aIsRemovable; + if (!mIsRemovable) { + mIsHotSwappable = false; + } + LOG("Volume %s: IsRemovable set to %d state %s", + NameStr(), (int)mIsRemovable, StateStr(mState)); + sEventObserverList.Broadcast(this); +} + +void +Volume::SetIsHotSwappable(bool aIsHotSwappable) +{ + if (aIsHotSwappable == mIsHotSwappable) { + return; + } + mIsHotSwappable = aIsHotSwappable; + if (mIsHotSwappable) { + mIsRemovable = true; + } + LOG("Volume %s: IsHotSwappable set to %d state %s", + NameStr(), (int)mIsHotSwappable, StateStr(mState)); + sEventObserverList.Broadcast(this); +} + +bool +Volume::BoolConfigValue(const nsCString& aConfigValue, bool& aBoolValue) +{ + if (aConfigValue.EqualsLiteral("1") || + aConfigValue.LowerCaseEqualsLiteral("true")) { + aBoolValue = true; + return true; + } + if (aConfigValue.EqualsLiteral("0") || + aConfigValue.LowerCaseEqualsLiteral("false")) { + aBoolValue = false; + return true; + } + return false; +} + +void +Volume::SetConfig(const nsCString& aConfigName, const nsCString& aConfigValue) +{ + if (aConfigName.LowerCaseEqualsLiteral("removable")) { + bool value = false; + if (BoolConfigValue(aConfigValue, value)) { + SetIsRemovable(value); + } else { + ERR("Volume %s: invalid value '%s' for configuration '%s'", + NameStr(), aConfigValue.get(), aConfigName.get()); + } + return; + } + if (aConfigName.LowerCaseEqualsLiteral("hotswappable")) { + bool value = false; + if (BoolConfigValue(aConfigValue, value)) { + SetIsHotSwappable(value); + } else { + ERR("Volume %s: invalid value '%s' for configuration '%s'", + NameStr(), aConfigValue.get(), aConfigName.get()); + } + return; + } + ERR("Volume %s: invalid config '%s'", NameStr(), aConfigName.get()); +} + +void +Volume::SetMediaPresent(bool aMediaPresent) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + // mMediaPresent is slightly redunant to the state, however + // when media is removed (while Idle), we get the following: + // 631 Volume sdcard /mnt/sdcard disk removed (179:0) + // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media) + // + // And on media insertion, we get: + // 630 Volume sdcard /mnt/sdcard disk inserted (179:0) + // 605 Volume sdcard /mnt/sdcard state changed from 0 (No-Media) to 2 (Pending) + // 605 Volume sdcard /mnt/sdcard state changed from 2 (Pending) to 1 (Idle-Unmounted) + // + // On media removal while the media is mounted: + // 632 Volume sdcard /mnt/sdcard bad removal (179:1) + // 605 Volume sdcard /mnt/sdcard state changed from 4 (Mounted) to 5 (Unmounting) + // 605 Volume sdcard /mnt/sdcard state changed from 5 (Unmounting) to 1 (Idle-Unmounted) + // 631 Volume sdcard /mnt/sdcard disk removed (179:0) + // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media) + // + // When sharing with a PC, it goes Mounted -> Idle -> Shared + // When unsharing with a PC, it goes Shared -> Idle -> Mounted + // + // The AutoMounter needs to know whether the media is present or not when + // processing the Idle state. + + if (mMediaPresent == aMediaPresent) { + return; + } + + LOG("Volume: %s media %s", NameStr(), aMediaPresent ? "inserted" : "removed"); + mMediaPresent = aMediaPresent; + sEventObserverList.Broadcast(this); +} + +void +Volume::SetSharingEnabled(bool aSharingEnabled) +{ + mSharingEnabled = aSharingEnabled; + + LOG("SetSharingMode for volume %s to %d canBeShared = %d", + NameStr(), (int)mSharingEnabled, (int)mCanBeShared); + sEventObserverList.Broadcast(this); +} + +void +Volume::SetFormatRequested(bool aFormatRequested) +{ + mFormatRequested = aFormatRequested; + + LOG("SetFormatRequested for volume %s to %d CanBeFormatted = %d", + NameStr(), (int)mFormatRequested, (int)CanBeFormatted()); +} + +void +Volume::SetMountRequested(bool aMountRequested) +{ + mMountRequested = aMountRequested; + + LOG("SetMountRequested for volume %s to %d CanBeMounted = %d", + NameStr(), (int)mMountRequested, (int)CanBeMounted()); +} + +void +Volume::SetUnmountRequested(bool aUnmountRequested) +{ + mUnmountRequested = aUnmountRequested; + + LOG("SetUnmountRequested for volume %s to %d CanBeMounted = %d", + NameStr(), (int)mUnmountRequested, (int)CanBeMounted()); +} + +void +Volume::SetState(Volume::STATE aNewState) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + if (aNewState == mState) { + return; + } + if (aNewState == nsIVolume::STATE_MOUNTED) { + mMountGeneration = ++sMountGeneration; + LOG("Volume %s (%u): changing state from %s to %s @ '%s' (%d observers) " + "mountGeneration = %d, locked = %d", + NameStr(), mId, StateStr(mState), + StateStr(aNewState), mMountPoint.get(), sEventObserverList.Length(), + mMountGeneration, (int)mMountLocked); + } else { + LOG("Volume %s (%u): changing state from %s to %s (%d observers)", + NameStr(), mId, StateStr(mState), + StateStr(aNewState), sEventObserverList.Length()); + } + + switch (aNewState) { + case nsIVolume::STATE_NOMEDIA: + // Cover the startup case where we don't get insertion/removal events + mMediaPresent = false; + mIsSharing = false; + mUnmountRequested = false; + mMountRequested = false; + mIsUnmounting = false; + break; + + case nsIVolume::STATE_MOUNTED: + case nsIVolume::STATE_MOUNT_FAIL: + mMountRequested = false; + mIsFormatting = false; + mIsSharing = false; + mIsUnmounting = false; + break; + + case nsIVolume::STATE_FORMATTING: + mFormatRequested = false; + mIsFormatting = true; + mIsSharing = false; + mIsUnmounting = false; + break; + + case nsIVolume::STATE_SHARED: + case nsIVolume::STATE_SHAREDMNT: + // Covers startup cases. Normally, mIsSharing would be set to true + // when we issue the command to initiate the sharing process, but + // it's conceivable that a volume could already be in a shared state + // when b2g starts. + mIsSharing = true; + mIsUnmounting = false; + mIsFormatting = false; + break; + + case nsIVolume::STATE_UNMOUNTING: + mIsUnmounting = true; + mIsFormatting = false; + mIsSharing = false; + break; + + case nsIVolume::STATE_IDLE: // Fall through + case nsIVolume::STATE_CHECKMNT: // Fall through + default: + break; + } + mState = aNewState; + sEventObserverList.Broadcast(this); +} + +void +Volume::SetMountPoint(const nsCSubstring& aMountPoint) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (mMountPoint.Equals(aMountPoint)) { + return; + } + ResolveAndSetMountPoint(aMountPoint); +} + +void +Volume::StartMount(VolumeResponseCallback* aCallback) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + StartCommand(new VolumeActionCommand(this, "mount", "", aCallback)); +} + +void +Volume::StartUnmount(VolumeResponseCallback* aCallback) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + StartCommand(new VolumeActionCommand(this, "unmount", "force", aCallback)); +} + +void +Volume::StartFormat(VolumeResponseCallback* aCallback) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + StartCommand(new VolumeActionCommand(this, "format", "", aCallback)); +} + +void +Volume::StartShare(VolumeResponseCallback* aCallback) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + StartCommand(new VolumeActionCommand(this, "share", "ums", aCallback)); +} + +void +Volume::StartUnshare(VolumeResponseCallback* aCallback) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + StartCommand(new VolumeActionCommand(this, "unshare", "ums", aCallback)); +} + +void +Volume::StartCommand(VolumeCommand* aCommand) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + VolumeManager::PostCommand(aCommand); +} + +//static +void +Volume::RegisterVolumeObserver(Volume::EventObserver* aObserver, const char* aName) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + sEventObserverList.AddObserver(aObserver); + + DBG("Added Volume Observer '%s' @%p, length = %u", + aName, aObserver, sEventObserverList.Length()); + + // Send an initial event to the observer (for each volume) + size_t numVolumes = VolumeManager::NumVolumes(); + for (size_t volIndex = 0; volIndex < numVolumes; volIndex++) { + RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex); + aObserver->Notify(vol); + } +} + +//static +void +Volume::UnregisterVolumeObserver(Volume::EventObserver* aObserver, const char* aName) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + sEventObserverList.RemoveObserver(aObserver); + + DBG("Removed Volume Observer '%s' @%p, length = %u", + aName, aObserver, sEventObserverList.Length()); +} + +//static +void +Volume::UpdateMountLock(const nsACString& aVolumeName, + const int32_t& aMountGeneration, + const bool& aMountLocked) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName); + if (!vol || (vol->mMountGeneration != aMountGeneration)) { + return; + } + if (vol->mMountLocked != aMountLocked) { + vol->mMountLocked = aMountLocked; + DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked); + sEventObserverList.Broadcast(vol); + } +} + +void +Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + // The volume name will have already been parsed, and the tokenizer will point + // to the token after the volume name + switch (aResponseCode) { + case ::ResponseCode::VolumeListResult: { + // Each line will look something like: + // + // sdcard /mnt/sdcard 1 + // + nsDependentCSubstring mntPoint(aTokenizer.nextToken()); + SetMountPoint(mntPoint); + nsresult errCode; + nsCString state(aTokenizer.nextToken()); + if (state.EqualsLiteral("X")) { + // Special state for creating fake volumes which can't be shared. + mCanBeShared = false; + SetState(nsIVolume::STATE_MOUNTED); + } else { + SetState((STATE)state.ToInteger(&errCode)); + } + break; + } + + case ::ResponseCode::VolumeStateChange: { + // Format of the line looks something like: + // + // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted) + // + // So we parse out the state after the string " to " + while (aTokenizer.hasMoreTokens()) { + nsAutoCString token(aTokenizer.nextToken()); + if (token.EqualsLiteral("to")) { + nsresult errCode; + token = aTokenizer.nextToken(); + STATE newState = (STATE)(token.ToInteger(&errCode)); + if (newState == nsIVolume::STATE_MOUNTED) { + // We set the state to STATE_CHECKMNT here, and the once the + // AutoMounter detects that the volume is actually accessible + // then the AutoMounter will set the volume as STATE_MOUNTED. + SetState(nsIVolume::STATE_CHECKMNT); + } else { + if (State() == nsIVolume::STATE_CHECKING && newState == nsIVolume::STATE_IDLE) { + LOG("Mount of volume '%s' failed", NameStr()); + SetState(nsIVolume::STATE_MOUNT_FAIL); + } else { + SetState(newState); + } + } + break; + } + } + break; + } + + case ::ResponseCode::VolumeDiskInserted: + SetMediaPresent(true); + break; + + case ::ResponseCode::VolumeDiskRemoved: // fall-thru + case ::ResponseCode::VolumeBadRemoval: + SetMediaPresent(false); + break; + + default: + LOG("Volume: %s unrecognized reponse code (ignored)", NameStr()); + break; + } +} + +} // namespace system +} // namespace mozilla |