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 "Volume.h" michael@0: #include "VolumeCommand.h" michael@0: #include "VolumeManager.h" michael@0: #include "VolumeManagerLog.h" michael@0: #include "nsIVolume.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace system { michael@0: michael@0: Volume::EventObserverList Volume::mEventObserverList; michael@0: michael@0: // We have a feature where volumes can be locked when mounted. This michael@0: // is used to prevent a volume from being shared with the PC while michael@0: // it is actively being used (say for storing an update image) michael@0: // michael@0: // We use WakeLocks (a poor choice of name, but it does what we want) michael@0: // from the PowerManagerService to determine when we're locked. michael@0: // In particular we'll create a wakelock called volume-NAME-GENERATION michael@0: // (where NAME is the volume name, and GENERATION is its generation michael@0: // number), and if this wakelock is locked, then we'll prevent a volume michael@0: // from being shared. michael@0: // michael@0: // Implementation Details: michael@0: // michael@0: // Since the AutoMounter can only control when something gets mounted michael@0: // and not when it gets unmounted (for example: a user pulls the SDCard) michael@0: // and because Volume and nsVolume data structures are maintained on michael@0: // separate threads, we have the potential for some race conditions. michael@0: // We eliminate the race conditions by introducing the concept of a michael@0: // generation number. Every time a volume transitions to the Mounted michael@0: // state, it gets assigned a new generation number. Whenever the state michael@0: // of a Volume changes, we send the updated state and current generation michael@0: // number to the main thread where it gets updated in the nsVolume. michael@0: // michael@0: // Since WakeLocks can only be queried from the main-thread, the michael@0: // nsVolumeService looks for WakeLock status changes, and forwards michael@0: // the results to the IOThread. michael@0: // michael@0: // If the Volume (IOThread) recieves a volume update where the generation michael@0: // number mismatches, then the update is simply ignored. michael@0: // michael@0: // When a Volume (IOThread) initially becomes mounted, we assume it to michael@0: // be locked until we get our first update from nsVolume (MainThread). michael@0: static int32_t sMountGeneration = 0; michael@0: michael@0: // We don't get media inserted/removed events at startup. So we michael@0: // assume it's present, and we'll be told that it's missing. michael@0: Volume::Volume(const nsCSubstring& aName) michael@0: : mMediaPresent(true), michael@0: mState(nsIVolume::STATE_INIT), michael@0: mName(aName), michael@0: mMountGeneration(-1), michael@0: mMountLocked(true), // Needs to agree with nsVolume::nsVolume michael@0: mSharingEnabled(false), michael@0: mCanBeShared(true), michael@0: mIsSharing(false), michael@0: mFormatRequested(false), michael@0: mMountRequested(false), michael@0: mUnmountRequested(false), michael@0: mIsFormatting(false) michael@0: { michael@0: DBG("Volume %s: created", NameStr()); michael@0: } michael@0: michael@0: void michael@0: Volume::SetIsSharing(bool aIsSharing) michael@0: { michael@0: if (aIsSharing == mIsSharing) { michael@0: return; michael@0: } michael@0: mIsSharing = aIsSharing; michael@0: LOG("Volume %s: IsSharing set to %d state %s", michael@0: NameStr(), (int)mIsSharing, StateStr(mState)); michael@0: if (mIsSharing) { michael@0: mEventObserverList.Broadcast(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Volume::SetIsFormatting(bool aIsFormatting) michael@0: { michael@0: if (aIsFormatting == mIsFormatting) { michael@0: return; michael@0: } michael@0: mIsFormatting = aIsFormatting; michael@0: LOG("Volume %s: IsFormatting set to %d state %s", michael@0: NameStr(), (int)mIsFormatting, StateStr(mState)); michael@0: if (mIsFormatting) { michael@0: mEventObserverList.Broadcast(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Volume::SetMediaPresent(bool aMediaPresent) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: // mMediaPresent is slightly redunant to the state, however michael@0: // when media is removed (while Idle), we get the following: michael@0: // 631 Volume sdcard /mnt/sdcard disk removed (179:0) michael@0: // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media) michael@0: // michael@0: // And on media insertion, we get: michael@0: // 630 Volume sdcard /mnt/sdcard disk inserted (179:0) michael@0: // 605 Volume sdcard /mnt/sdcard state changed from 0 (No-Media) to 2 (Pending) michael@0: // 605 Volume sdcard /mnt/sdcard state changed from 2 (Pending) to 1 (Idle-Unmounted) michael@0: // michael@0: // On media removal while the media is mounted: michael@0: // 632 Volume sdcard /mnt/sdcard bad removal (179:1) michael@0: // 605 Volume sdcard /mnt/sdcard state changed from 4 (Mounted) to 5 (Unmounting) michael@0: // 605 Volume sdcard /mnt/sdcard state changed from 5 (Unmounting) to 1 (Idle-Unmounted) michael@0: // 631 Volume sdcard /mnt/sdcard disk removed (179:0) michael@0: // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media) michael@0: // michael@0: // When sharing with a PC, it goes Mounted -> Idle -> Shared michael@0: // When unsharing with a PC, it goes Shared -> Idle -> Mounted michael@0: // michael@0: // The AutoMounter needs to know whether the media is present or not when michael@0: // processing the Idle state. michael@0: michael@0: if (mMediaPresent == aMediaPresent) { michael@0: return; michael@0: } michael@0: michael@0: LOG("Volume: %s media %s", NameStr(), aMediaPresent ? "inserted" : "removed"); michael@0: mMediaPresent = aMediaPresent; michael@0: mEventObserverList.Broadcast(this); michael@0: } michael@0: michael@0: void michael@0: Volume::SetSharingEnabled(bool aSharingEnabled) michael@0: { michael@0: mSharingEnabled = aSharingEnabled; michael@0: michael@0: LOG("SetSharingMode for volume %s to %d canBeShared = %d", michael@0: NameStr(), (int)mSharingEnabled, (int)mCanBeShared); michael@0: } michael@0: michael@0: void michael@0: Volume::SetFormatRequested(bool aFormatRequested) michael@0: { michael@0: mFormatRequested = aFormatRequested; michael@0: michael@0: LOG("SetFormatRequested for volume %s to %d CanBeFormatted = %d", michael@0: NameStr(), (int)mFormatRequested, (int)CanBeFormatted()); michael@0: } michael@0: michael@0: void michael@0: Volume::SetMountRequested(bool aMountRequested) michael@0: { michael@0: mMountRequested = aMountRequested; michael@0: michael@0: LOG("SetMountRequested for volume %s to %d CanBeMounted = %d", michael@0: NameStr(), (int)mMountRequested, (int)CanBeMounted()); michael@0: } michael@0: michael@0: void michael@0: Volume::SetUnmountRequested(bool aUnmountRequested) michael@0: { michael@0: mUnmountRequested = aUnmountRequested; michael@0: michael@0: LOG("SetUnmountRequested for volume %s to %d CanBeMounted = %d", michael@0: NameStr(), (int)mUnmountRequested, (int)CanBeMounted()); michael@0: } michael@0: michael@0: void michael@0: Volume::SetState(Volume::STATE aNewState) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: if (aNewState == mState) { michael@0: return; michael@0: } michael@0: if (aNewState == nsIVolume::STATE_MOUNTED) { michael@0: mMountGeneration = ++sMountGeneration; michael@0: LOG("Volume %s: changing state from %s to %s @ '%s' (%d observers) " michael@0: "mountGeneration = %d, locked = %d", michael@0: NameStr(), StateStr(mState), michael@0: StateStr(aNewState), mMountPoint.get(), mEventObserverList.Length(), michael@0: mMountGeneration, (int)mMountLocked); michael@0: } else { michael@0: LOG("Volume %s: changing state from %s to %s (%d observers)", michael@0: NameStr(), StateStr(mState), michael@0: StateStr(aNewState), mEventObserverList.Length()); michael@0: } michael@0: michael@0: switch (aNewState) { michael@0: case nsIVolume::STATE_NOMEDIA: michael@0: // Cover the startup case where we don't get insertion/removal events michael@0: mMediaPresent = false; michael@0: mIsSharing = false; michael@0: mUnmountRequested = false; michael@0: mMountRequested = false; michael@0: break; michael@0: michael@0: case nsIVolume::STATE_MOUNTED: michael@0: mMountRequested = false; michael@0: mIsFormatting = false; michael@0: mIsSharing = false; michael@0: break; michael@0: case nsIVolume::STATE_FORMATTING: michael@0: mFormatRequested = false; michael@0: mIsFormatting = true; michael@0: mIsSharing = false; michael@0: break; michael@0: michael@0: case nsIVolume::STATE_SHARED: michael@0: case nsIVolume::STATE_SHAREDMNT: michael@0: // Covers startup cases. Normally, mIsSharing would be set to true michael@0: // when we issue the command to initiate the sharing process, but michael@0: // it's conceivable that a volume could already be in a shared state michael@0: // when b2g starts. michael@0: mIsSharing = true; michael@0: break; michael@0: michael@0: case nsIVolume::STATE_IDLE: michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: mState = aNewState; michael@0: mEventObserverList.Broadcast(this); michael@0: } michael@0: michael@0: void michael@0: Volume::SetMountPoint(const nsCSubstring& aMountPoint) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: if (mMountPoint.Equals(aMountPoint)) { michael@0: return; michael@0: } michael@0: mMountPoint = aMountPoint; michael@0: DBG("Volume %s: Setting mountpoint to '%s'", NameStr(), mMountPoint.get()); michael@0: } michael@0: michael@0: void michael@0: Volume::StartMount(VolumeResponseCallback* aCallback) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: StartCommand(new VolumeActionCommand(this, "mount", "", aCallback)); michael@0: } michael@0: michael@0: void michael@0: Volume::StartUnmount(VolumeResponseCallback* aCallback) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: StartCommand(new VolumeActionCommand(this, "unmount", "force", aCallback)); michael@0: } michael@0: michael@0: void michael@0: Volume::StartFormat(VolumeResponseCallback* aCallback) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: StartCommand(new VolumeActionCommand(this, "format", "", aCallback)); michael@0: } michael@0: michael@0: void michael@0: Volume::StartShare(VolumeResponseCallback* aCallback) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: StartCommand(new VolumeActionCommand(this, "share", "ums", aCallback)); michael@0: } michael@0: michael@0: void michael@0: Volume::StartUnshare(VolumeResponseCallback* aCallback) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: StartCommand(new VolumeActionCommand(this, "unshare", "ums", aCallback)); michael@0: } michael@0: michael@0: void michael@0: Volume::StartCommand(VolumeCommand* aCommand) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: VolumeManager::PostCommand(aCommand); michael@0: } michael@0: michael@0: //static michael@0: void michael@0: Volume::RegisterObserver(Volume::EventObserver* aObserver) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: mEventObserverList.AddObserver(aObserver); michael@0: // Send an initial event to the observer (for each volume) michael@0: size_t numVolumes = VolumeManager::NumVolumes(); michael@0: for (size_t volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: RefPtr vol = VolumeManager::GetVolume(volIndex); michael@0: aObserver->Notify(vol); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: Volume::UnregisterObserver(Volume::EventObserver* aObserver) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: mEventObserverList.RemoveObserver(aObserver); michael@0: } michael@0: michael@0: //static michael@0: void michael@0: Volume::UpdateMountLock(const nsACString& aVolumeName, michael@0: const int32_t& aMountGeneration, michael@0: const bool& aMountLocked) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); michael@0: if (!vol || (vol->mMountGeneration != aMountGeneration)) { michael@0: return; michael@0: } michael@0: if (vol->mMountLocked != aMountLocked) { michael@0: vol->mMountLocked = aMountLocked; michael@0: DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked); michael@0: mEventObserverList.Broadcast(vol); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: michael@0: // The volume name will have already been parsed, and the tokenizer will point michael@0: // to the token after the volume name michael@0: switch (aResponseCode) { 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: nsDependentCSubstring mntPoint(aTokenizer.nextToken()); michael@0: SetMountPoint(mntPoint); michael@0: nsresult errCode; michael@0: nsCString state(aTokenizer.nextToken()); michael@0: if (state.EqualsLiteral("X")) { michael@0: // Special state for creating fake volumes which can't be shared. michael@0: mCanBeShared = false; michael@0: SetState(nsIVolume::STATE_MOUNTED); michael@0: } else { michael@0: SetState((STATE)state.ToInteger(&errCode)); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case ResponseCode::VolumeStateChange: { michael@0: // Format of the line looks 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 state after the string " to " michael@0: while (aTokenizer.hasMoreTokens()) { michael@0: nsAutoCString token(aTokenizer.nextToken()); michael@0: if (token.Equals("to")) { michael@0: nsresult errCode; michael@0: token = aTokenizer.nextToken(); michael@0: SetState((STATE)token.ToInteger(&errCode)); michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case ResponseCode::VolumeDiskInserted: michael@0: SetMediaPresent(true); michael@0: break; michael@0: michael@0: case ResponseCode::VolumeDiskRemoved: // fall-thru michael@0: case ResponseCode::VolumeBadRemoval: michael@0: SetMediaPresent(false); michael@0: break; michael@0: michael@0: default: michael@0: LOG("Volume: %s unrecognized reponse code (ignored)", NameStr()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: } // namespace system michael@0: } // namespace mozilla