1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/system/gonk/Volume.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,408 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "Volume.h" 1.9 +#include "VolumeCommand.h" 1.10 +#include "VolumeManager.h" 1.11 +#include "VolumeManagerLog.h" 1.12 +#include "nsIVolume.h" 1.13 +#include "nsXULAppAPI.h" 1.14 + 1.15 +#include <vold/ResponseCode.h> 1.16 + 1.17 +namespace mozilla { 1.18 +namespace system { 1.19 + 1.20 +Volume::EventObserverList Volume::mEventObserverList; 1.21 + 1.22 +// We have a feature where volumes can be locked when mounted. This 1.23 +// is used to prevent a volume from being shared with the PC while 1.24 +// it is actively being used (say for storing an update image) 1.25 +// 1.26 +// We use WakeLocks (a poor choice of name, but it does what we want) 1.27 +// from the PowerManagerService to determine when we're locked. 1.28 +// In particular we'll create a wakelock called volume-NAME-GENERATION 1.29 +// (where NAME is the volume name, and GENERATION is its generation 1.30 +// number), and if this wakelock is locked, then we'll prevent a volume 1.31 +// from being shared. 1.32 +// 1.33 +// Implementation Details: 1.34 +// 1.35 +// Since the AutoMounter can only control when something gets mounted 1.36 +// and not when it gets unmounted (for example: a user pulls the SDCard) 1.37 +// and because Volume and nsVolume data structures are maintained on 1.38 +// separate threads, we have the potential for some race conditions. 1.39 +// We eliminate the race conditions by introducing the concept of a 1.40 +// generation number. Every time a volume transitions to the Mounted 1.41 +// state, it gets assigned a new generation number. Whenever the state 1.42 +// of a Volume changes, we send the updated state and current generation 1.43 +// number to the main thread where it gets updated in the nsVolume. 1.44 +// 1.45 +// Since WakeLocks can only be queried from the main-thread, the 1.46 +// nsVolumeService looks for WakeLock status changes, and forwards 1.47 +// the results to the IOThread. 1.48 +// 1.49 +// If the Volume (IOThread) recieves a volume update where the generation 1.50 +// number mismatches, then the update is simply ignored. 1.51 +// 1.52 +// When a Volume (IOThread) initially becomes mounted, we assume it to 1.53 +// be locked until we get our first update from nsVolume (MainThread). 1.54 +static int32_t sMountGeneration = 0; 1.55 + 1.56 +// We don't get media inserted/removed events at startup. So we 1.57 +// assume it's present, and we'll be told that it's missing. 1.58 +Volume::Volume(const nsCSubstring& aName) 1.59 + : mMediaPresent(true), 1.60 + mState(nsIVolume::STATE_INIT), 1.61 + mName(aName), 1.62 + mMountGeneration(-1), 1.63 + mMountLocked(true), // Needs to agree with nsVolume::nsVolume 1.64 + mSharingEnabled(false), 1.65 + mCanBeShared(true), 1.66 + mIsSharing(false), 1.67 + mFormatRequested(false), 1.68 + mMountRequested(false), 1.69 + mUnmountRequested(false), 1.70 + mIsFormatting(false) 1.71 +{ 1.72 + DBG("Volume %s: created", NameStr()); 1.73 +} 1.74 + 1.75 +void 1.76 +Volume::SetIsSharing(bool aIsSharing) 1.77 +{ 1.78 + if (aIsSharing == mIsSharing) { 1.79 + return; 1.80 + } 1.81 + mIsSharing = aIsSharing; 1.82 + LOG("Volume %s: IsSharing set to %d state %s", 1.83 + NameStr(), (int)mIsSharing, StateStr(mState)); 1.84 + if (mIsSharing) { 1.85 + mEventObserverList.Broadcast(this); 1.86 + } 1.87 +} 1.88 + 1.89 +void 1.90 +Volume::SetIsFormatting(bool aIsFormatting) 1.91 +{ 1.92 + if (aIsFormatting == mIsFormatting) { 1.93 + return; 1.94 + } 1.95 + mIsFormatting = aIsFormatting; 1.96 + LOG("Volume %s: IsFormatting set to %d state %s", 1.97 + NameStr(), (int)mIsFormatting, StateStr(mState)); 1.98 + if (mIsFormatting) { 1.99 + mEventObserverList.Broadcast(this); 1.100 + } 1.101 +} 1.102 + 1.103 +void 1.104 +Volume::SetMediaPresent(bool aMediaPresent) 1.105 +{ 1.106 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.107 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.108 + 1.109 + // mMediaPresent is slightly redunant to the state, however 1.110 + // when media is removed (while Idle), we get the following: 1.111 + // 631 Volume sdcard /mnt/sdcard disk removed (179:0) 1.112 + // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media) 1.113 + // 1.114 + // And on media insertion, we get: 1.115 + // 630 Volume sdcard /mnt/sdcard disk inserted (179:0) 1.116 + // 605 Volume sdcard /mnt/sdcard state changed from 0 (No-Media) to 2 (Pending) 1.117 + // 605 Volume sdcard /mnt/sdcard state changed from 2 (Pending) to 1 (Idle-Unmounted) 1.118 + // 1.119 + // On media removal while the media is mounted: 1.120 + // 632 Volume sdcard /mnt/sdcard bad removal (179:1) 1.121 + // 605 Volume sdcard /mnt/sdcard state changed from 4 (Mounted) to 5 (Unmounting) 1.122 + // 605 Volume sdcard /mnt/sdcard state changed from 5 (Unmounting) to 1 (Idle-Unmounted) 1.123 + // 631 Volume sdcard /mnt/sdcard disk removed (179:0) 1.124 + // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media) 1.125 + // 1.126 + // When sharing with a PC, it goes Mounted -> Idle -> Shared 1.127 + // When unsharing with a PC, it goes Shared -> Idle -> Mounted 1.128 + // 1.129 + // The AutoMounter needs to know whether the media is present or not when 1.130 + // processing the Idle state. 1.131 + 1.132 + if (mMediaPresent == aMediaPresent) { 1.133 + return; 1.134 + } 1.135 + 1.136 + LOG("Volume: %s media %s", NameStr(), aMediaPresent ? "inserted" : "removed"); 1.137 + mMediaPresent = aMediaPresent; 1.138 + mEventObserverList.Broadcast(this); 1.139 +} 1.140 + 1.141 +void 1.142 +Volume::SetSharingEnabled(bool aSharingEnabled) 1.143 +{ 1.144 + mSharingEnabled = aSharingEnabled; 1.145 + 1.146 + LOG("SetSharingMode for volume %s to %d canBeShared = %d", 1.147 + NameStr(), (int)mSharingEnabled, (int)mCanBeShared); 1.148 +} 1.149 + 1.150 +void 1.151 +Volume::SetFormatRequested(bool aFormatRequested) 1.152 +{ 1.153 + mFormatRequested = aFormatRequested; 1.154 + 1.155 + LOG("SetFormatRequested for volume %s to %d CanBeFormatted = %d", 1.156 + NameStr(), (int)mFormatRequested, (int)CanBeFormatted()); 1.157 +} 1.158 + 1.159 +void 1.160 +Volume::SetMountRequested(bool aMountRequested) 1.161 +{ 1.162 + mMountRequested = aMountRequested; 1.163 + 1.164 + LOG("SetMountRequested for volume %s to %d CanBeMounted = %d", 1.165 + NameStr(), (int)mMountRequested, (int)CanBeMounted()); 1.166 +} 1.167 + 1.168 +void 1.169 +Volume::SetUnmountRequested(bool aUnmountRequested) 1.170 +{ 1.171 + mUnmountRequested = aUnmountRequested; 1.172 + 1.173 + LOG("SetUnmountRequested for volume %s to %d CanBeMounted = %d", 1.174 + NameStr(), (int)mUnmountRequested, (int)CanBeMounted()); 1.175 +} 1.176 + 1.177 +void 1.178 +Volume::SetState(Volume::STATE aNewState) 1.179 +{ 1.180 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.181 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.182 + if (aNewState == mState) { 1.183 + return; 1.184 + } 1.185 + if (aNewState == nsIVolume::STATE_MOUNTED) { 1.186 + mMountGeneration = ++sMountGeneration; 1.187 + LOG("Volume %s: changing state from %s to %s @ '%s' (%d observers) " 1.188 + "mountGeneration = %d, locked = %d", 1.189 + NameStr(), StateStr(mState), 1.190 + StateStr(aNewState), mMountPoint.get(), mEventObserverList.Length(), 1.191 + mMountGeneration, (int)mMountLocked); 1.192 + } else { 1.193 + LOG("Volume %s: changing state from %s to %s (%d observers)", 1.194 + NameStr(), StateStr(mState), 1.195 + StateStr(aNewState), mEventObserverList.Length()); 1.196 + } 1.197 + 1.198 + switch (aNewState) { 1.199 + case nsIVolume::STATE_NOMEDIA: 1.200 + // Cover the startup case where we don't get insertion/removal events 1.201 + mMediaPresent = false; 1.202 + mIsSharing = false; 1.203 + mUnmountRequested = false; 1.204 + mMountRequested = false; 1.205 + break; 1.206 + 1.207 + case nsIVolume::STATE_MOUNTED: 1.208 + mMountRequested = false; 1.209 + mIsFormatting = false; 1.210 + mIsSharing = false; 1.211 + break; 1.212 + case nsIVolume::STATE_FORMATTING: 1.213 + mFormatRequested = false; 1.214 + mIsFormatting = true; 1.215 + mIsSharing = false; 1.216 + break; 1.217 + 1.218 + case nsIVolume::STATE_SHARED: 1.219 + case nsIVolume::STATE_SHAREDMNT: 1.220 + // Covers startup cases. Normally, mIsSharing would be set to true 1.221 + // when we issue the command to initiate the sharing process, but 1.222 + // it's conceivable that a volume could already be in a shared state 1.223 + // when b2g starts. 1.224 + mIsSharing = true; 1.225 + break; 1.226 + 1.227 + case nsIVolume::STATE_IDLE: 1.228 + break; 1.229 + default: 1.230 + break; 1.231 + } 1.232 + mState = aNewState; 1.233 + mEventObserverList.Broadcast(this); 1.234 +} 1.235 + 1.236 +void 1.237 +Volume::SetMountPoint(const nsCSubstring& aMountPoint) 1.238 +{ 1.239 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.240 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.241 + 1.242 + if (mMountPoint.Equals(aMountPoint)) { 1.243 + return; 1.244 + } 1.245 + mMountPoint = aMountPoint; 1.246 + DBG("Volume %s: Setting mountpoint to '%s'", NameStr(), mMountPoint.get()); 1.247 +} 1.248 + 1.249 +void 1.250 +Volume::StartMount(VolumeResponseCallback* aCallback) 1.251 +{ 1.252 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.253 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.254 + 1.255 + StartCommand(new VolumeActionCommand(this, "mount", "", aCallback)); 1.256 +} 1.257 + 1.258 +void 1.259 +Volume::StartUnmount(VolumeResponseCallback* aCallback) 1.260 +{ 1.261 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.262 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.263 + 1.264 + StartCommand(new VolumeActionCommand(this, "unmount", "force", aCallback)); 1.265 +} 1.266 + 1.267 +void 1.268 +Volume::StartFormat(VolumeResponseCallback* aCallback) 1.269 +{ 1.270 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.271 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.272 + 1.273 + StartCommand(new VolumeActionCommand(this, "format", "", aCallback)); 1.274 +} 1.275 + 1.276 +void 1.277 +Volume::StartShare(VolumeResponseCallback* aCallback) 1.278 +{ 1.279 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.280 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.281 + 1.282 + StartCommand(new VolumeActionCommand(this, "share", "ums", aCallback)); 1.283 +} 1.284 + 1.285 +void 1.286 +Volume::StartUnshare(VolumeResponseCallback* aCallback) 1.287 +{ 1.288 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.289 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.290 + 1.291 + StartCommand(new VolumeActionCommand(this, "unshare", "ums", aCallback)); 1.292 +} 1.293 + 1.294 +void 1.295 +Volume::StartCommand(VolumeCommand* aCommand) 1.296 +{ 1.297 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.298 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.299 + 1.300 + VolumeManager::PostCommand(aCommand); 1.301 +} 1.302 + 1.303 +//static 1.304 +void 1.305 +Volume::RegisterObserver(Volume::EventObserver* aObserver) 1.306 +{ 1.307 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.308 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.309 + 1.310 + mEventObserverList.AddObserver(aObserver); 1.311 + // Send an initial event to the observer (for each volume) 1.312 + size_t numVolumes = VolumeManager::NumVolumes(); 1.313 + for (size_t volIndex = 0; volIndex < numVolumes; volIndex++) { 1.314 + RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex); 1.315 + aObserver->Notify(vol); 1.316 + } 1.317 +} 1.318 + 1.319 +//static 1.320 +void 1.321 +Volume::UnregisterObserver(Volume::EventObserver* aObserver) 1.322 +{ 1.323 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.324 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.325 + 1.326 + mEventObserverList.RemoveObserver(aObserver); 1.327 +} 1.328 + 1.329 +//static 1.330 +void 1.331 +Volume::UpdateMountLock(const nsACString& aVolumeName, 1.332 + const int32_t& aMountGeneration, 1.333 + const bool& aMountLocked) 1.334 +{ 1.335 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.336 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.337 + 1.338 + RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName); 1.339 + if (!vol || (vol->mMountGeneration != aMountGeneration)) { 1.340 + return; 1.341 + } 1.342 + if (vol->mMountLocked != aMountLocked) { 1.343 + vol->mMountLocked = aMountLocked; 1.344 + DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked); 1.345 + mEventObserverList.Broadcast(vol); 1.346 + } 1.347 +} 1.348 + 1.349 +void 1.350 +Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer) 1.351 +{ 1.352 + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); 1.353 + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); 1.354 + 1.355 + // The volume name will have already been parsed, and the tokenizer will point 1.356 + // to the token after the volume name 1.357 + switch (aResponseCode) { 1.358 + case ResponseCode::VolumeListResult: { 1.359 + // Each line will look something like: 1.360 + // 1.361 + // sdcard /mnt/sdcard 1 1.362 + // 1.363 + nsDependentCSubstring mntPoint(aTokenizer.nextToken()); 1.364 + SetMountPoint(mntPoint); 1.365 + nsresult errCode; 1.366 + nsCString state(aTokenizer.nextToken()); 1.367 + if (state.EqualsLiteral("X")) { 1.368 + // Special state for creating fake volumes which can't be shared. 1.369 + mCanBeShared = false; 1.370 + SetState(nsIVolume::STATE_MOUNTED); 1.371 + } else { 1.372 + SetState((STATE)state.ToInteger(&errCode)); 1.373 + } 1.374 + break; 1.375 + } 1.376 + 1.377 + case ResponseCode::VolumeStateChange: { 1.378 + // Format of the line looks something like: 1.379 + // 1.380 + // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted) 1.381 + // 1.382 + // So we parse out the state after the string " to " 1.383 + while (aTokenizer.hasMoreTokens()) { 1.384 + nsAutoCString token(aTokenizer.nextToken()); 1.385 + if (token.Equals("to")) { 1.386 + nsresult errCode; 1.387 + token = aTokenizer.nextToken(); 1.388 + SetState((STATE)token.ToInteger(&errCode)); 1.389 + break; 1.390 + } 1.391 + } 1.392 + break; 1.393 + } 1.394 + 1.395 + case ResponseCode::VolumeDiskInserted: 1.396 + SetMediaPresent(true); 1.397 + break; 1.398 + 1.399 + case ResponseCode::VolumeDiskRemoved: // fall-thru 1.400 + case ResponseCode::VolumeBadRemoval: 1.401 + SetMediaPresent(false); 1.402 + break; 1.403 + 1.404 + default: 1.405 + LOG("Volume: %s unrecognized reponse code (ignored)", NameStr()); 1.406 + break; 1.407 + } 1.408 +} 1.409 + 1.410 +} // namespace system 1.411 +} // namespace mozilla