1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "Volume.h"
6 #include "VolumeCommand.h"
7 #include "VolumeManager.h"
8 #include "VolumeManagerLog.h"
9 #include "nsIVolume.h"
10 #include "nsXULAppAPI.h"
11 
12 #include <vold/ResponseCode.h>
13 
14 namespace mozilla {
15 namespace system {
16 
17 #if DEBUG_VOLUME_OBSERVER
18 void
Broadcast(Volume * const & aVolume)19 VolumeObserverList::Broadcast(Volume* const& aVolume)
20 {
21   uint32_t size = mObservers.Length();
22   for (uint32_t i = 0; i < size; ++i) {
23     LOG("VolumeObserverList::Broadcast to [%u] %p volume '%s'",
24         i, mObservers[i], aVolume->NameStr());
25     mObservers[i]->Notify(aVolume);
26   }
27 }
28 #endif
29 
30 VolumeObserverList Volume::sEventObserverList;
31 
32 // We have a feature where volumes can be locked when mounted. This
33 // is used to prevent a volume from being shared with the PC while
34 // it is actively being used (say for storing an update image)
35 //
36 // We use WakeLocks (a poor choice of name, but it does what we want)
37 // from the PowerManagerService to determine when we're locked.
38 // In particular we'll create a wakelock called volume-NAME-GENERATION
39 // (where NAME is the volume name, and GENERATION is its generation
40 // number), and if this wakelock is locked, then we'll prevent a volume
41 // from being shared.
42 //
43 // Implementation Details:
44 //
45 // Since the AutoMounter can only control when something gets mounted
46 // and not when it gets unmounted (for example: a user pulls the SDCard)
47 // and because Volume and nsVolume data structures are maintained on
48 // separate threads, we have the potential for some race conditions.
49 // We eliminate the race conditions by introducing the concept of a
50 // generation number. Every time a volume transitions to the Mounted
51 // state, it gets assigned a new generation number. Whenever the state
52 // of a Volume changes, we send the updated state and current generation
53 // number to the main thread where it gets updated in the nsVolume.
54 //
55 // Since WakeLocks can only be queried from the main-thread, the
56 // nsVolumeService looks for WakeLock status changes, and forwards
57 // the results to the IOThread.
58 //
59 // If the Volume (IOThread) receives a volume update where the generation
60 // number mismatches, then the update is simply ignored.
61 //
62 // When a Volume (IOThread) initially becomes mounted, we assume it to
63 // be locked until we get our first update from nsVolume (MainThread).
64 static int32_t sMountGeneration = 0;
65 
66 static uint32_t sNextId = 1;
67 
68 // We don't get media inserted/removed events at startup. So we
69 // assume it's present, and we'll be told that it's missing.
Volume(const nsCSubstring & aName)70 Volume::Volume(const nsCSubstring& aName)
71   : mMediaPresent(true),
72     mState(nsIVolume::STATE_INIT),
73     mName(aName),
74     mMountGeneration(-1),
75     mMountLocked(true),  // Needs to agree with nsVolume::nsVolume
76     mSharingEnabled(false),
77     mFormatRequested(false),
78     mMountRequested(false),
79     mUnmountRequested(false),
80     mCanBeShared(true),
81     mIsSharing(false),
82     mIsFormatting(false),
83     mIsUnmounting(false),
84     mIsRemovable(false),
85     mIsHotSwappable(false),
86     mId(sNextId++)
87 {
88   DBG("Volume %s: created", NameStr());
89 }
90 
91 void
Dump(const char * aLabel) const92 Volume::Dump(const char* aLabel) const
93 {
94   LOG("%s: Volume: %s (%d) is %s and %s @ %s gen %d locked %d",
95       aLabel,
96       NameStr(),
97       Id(),
98       StateStr(),
99       MediaPresent() ? "inserted" : "missing",
100       MountPoint().get(),
101       MountGeneration(),
102       (int)IsMountLocked());
103   LOG("%s:   Sharing %s Mounting %s Formating %s Unmounting %s",
104       aLabel,
105       CanBeShared() ? (IsSharingEnabled() ? (IsSharing() ? "en-y" : "en-n")
106                                           : "dis")
107                     : "x",
108       IsMountRequested() ? "req" : "n",
109       IsFormatRequested() ? (IsFormatting() ? "req-y" : "req-n")
110                           : (IsFormatting() ? "y" : "n"),
111       IsUnmountRequested() ? (IsUnmounting() ? "req-y" : "req-n")
112                            : (IsUnmounting() ? "y" : "n"));
113 }
114 
115 void
ResolveAndSetMountPoint(const nsCSubstring & aMountPoint)116 Volume::ResolveAndSetMountPoint(const nsCSubstring& aMountPoint)
117 {
118   nsCString mountPoint(aMountPoint);
119   char realPathBuf[PATH_MAX];
120 
121   // Call realpath so that we wind up with a path which is compatible with
122   // functions like nsVolumeService::GetVolumeByPath.
123 
124   if (realpath(mountPoint.get(), realPathBuf) < 0) {
125     // The path we were handed doesn't exist. Warn about it, but use it
126     // anyways assuming that the user knows what they're doing.
127 
128     ERR("ResolveAndSetMountPoint: realpath on '%s' failed: %d",
129         mountPoint.get(), errno);
130     mMountPoint = mountPoint;
131   } else {
132     mMountPoint = realPathBuf;
133   }
134   DBG("Volume %s: Setting mountpoint to '%s'", NameStr(), mMountPoint.get());
135 }
136 
SetFakeVolume(const nsACString & aMountPoint)137 void Volume::SetFakeVolume(const nsACString& aMountPoint)
138 {
139   this->mMountLocked = false;
140   this->mCanBeShared = false;
141   ResolveAndSetMountPoint(aMountPoint);
142   SetState(nsIVolume::STATE_MOUNTED);
143 }
144 
145 void
SetIsSharing(bool aIsSharing)146 Volume::SetIsSharing(bool aIsSharing)
147 {
148   if (aIsSharing == mIsSharing) {
149     return;
150   }
151   mIsSharing = aIsSharing;
152   LOG("Volume %s: IsSharing set to %d state %s",
153       NameStr(), (int)mIsSharing, StateStr(mState));
154   sEventObserverList.Broadcast(this);
155 }
156 
157 void
SetIsFormatting(bool aIsFormatting)158 Volume::SetIsFormatting(bool aIsFormatting)
159 {
160   if (aIsFormatting == mIsFormatting) {
161     return;
162   }
163   mIsFormatting = aIsFormatting;
164   LOG("Volume %s: IsFormatting set to %d state %s",
165       NameStr(), (int)mIsFormatting, StateStr(mState));
166   if (mIsFormatting) {
167     sEventObserverList.Broadcast(this);
168   }
169 }
170 
171 void
SetIsUnmounting(bool aIsUnmounting)172 Volume::SetIsUnmounting(bool aIsUnmounting)
173 {
174   if (aIsUnmounting == mIsUnmounting) {
175     return;
176   }
177   mIsUnmounting = aIsUnmounting;
178   LOG("Volume %s: IsUnmounting set to %d state %s",
179       NameStr(), (int)mIsUnmounting, StateStr(mState));
180   sEventObserverList.Broadcast(this);
181 }
182 
183 void
SetIsRemovable(bool aIsRemovable)184 Volume::SetIsRemovable(bool aIsRemovable)
185 {
186   if (aIsRemovable == mIsRemovable) {
187     return;
188   }
189   mIsRemovable = aIsRemovable;
190   if (!mIsRemovable) {
191     mIsHotSwappable = false;
192   }
193   LOG("Volume %s: IsRemovable set to %d state %s",
194       NameStr(), (int)mIsRemovable, StateStr(mState));
195   sEventObserverList.Broadcast(this);
196 }
197 
198 void
SetIsHotSwappable(bool aIsHotSwappable)199 Volume::SetIsHotSwappable(bool aIsHotSwappable)
200 {
201   if (aIsHotSwappable == mIsHotSwappable) {
202     return;
203   }
204   mIsHotSwappable = aIsHotSwappable;
205   if (mIsHotSwappable) {
206     mIsRemovable = true;
207   }
208   LOG("Volume %s: IsHotSwappable set to %d state %s",
209       NameStr(), (int)mIsHotSwappable, StateStr(mState));
210   sEventObserverList.Broadcast(this);
211 }
212 
213 bool
BoolConfigValue(const nsCString & aConfigValue,bool & aBoolValue)214 Volume::BoolConfigValue(const nsCString& aConfigValue, bool& aBoolValue)
215 {
216   if (aConfigValue.EqualsLiteral("1") ||
217       aConfigValue.LowerCaseEqualsLiteral("true")) {
218     aBoolValue = true;
219     return true;
220   }
221   if (aConfigValue.EqualsLiteral("0") ||
222       aConfigValue.LowerCaseEqualsLiteral("false")) {
223     aBoolValue = false;
224     return true;
225   }
226   return false;
227 }
228 
229 void
SetConfig(const nsCString & aConfigName,const nsCString & aConfigValue)230 Volume::SetConfig(const nsCString& aConfigName, const nsCString& aConfigValue)
231 {
232   if (aConfigName.LowerCaseEqualsLiteral("removable")) {
233     bool value = false;
234     if (BoolConfigValue(aConfigValue, value)) {
235       SetIsRemovable(value);
236     } else {
237       ERR("Volume %s: invalid value '%s' for configuration '%s'",
238           NameStr(), aConfigValue.get(), aConfigName.get());
239     }
240     return;
241   }
242   if (aConfigName.LowerCaseEqualsLiteral("hotswappable")) {
243     bool value = false;
244     if (BoolConfigValue(aConfigValue, value)) {
245       SetIsHotSwappable(value);
246     } else {
247       ERR("Volume %s: invalid value '%s' for configuration '%s'",
248           NameStr(), aConfigValue.get(), aConfigName.get());
249     }
250     return;
251   }
252   ERR("Volume %s: invalid config '%s'", NameStr(), aConfigName.get());
253 }
254 
255 void
SetMediaPresent(bool aMediaPresent)256 Volume::SetMediaPresent(bool aMediaPresent)
257 {
258   MOZ_ASSERT(XRE_IsParentProcess());
259   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
260 
261   // mMediaPresent is slightly redunant to the state, however
262   // when media is removed (while Idle), we get the following:
263   //    631 Volume sdcard /mnt/sdcard disk removed (179:0)
264   //    605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media)
265   //
266   // And on media insertion, we get:
267   //    630 Volume sdcard /mnt/sdcard disk inserted (179:0)
268   //    605 Volume sdcard /mnt/sdcard state changed from 0 (No-Media) to 2 (Pending)
269   //    605 Volume sdcard /mnt/sdcard state changed from 2 (Pending) to 1 (Idle-Unmounted)
270   //
271   // On media removal while the media is mounted:
272   //    632 Volume sdcard /mnt/sdcard bad removal (179:1)
273   //    605 Volume sdcard /mnt/sdcard state changed from 4 (Mounted) to 5 (Unmounting)
274   //    605 Volume sdcard /mnt/sdcard state changed from 5 (Unmounting) to 1 (Idle-Unmounted)
275   //    631 Volume sdcard /mnt/sdcard disk removed (179:0)
276   //    605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media)
277   //
278   // When sharing with a PC, it goes Mounted -> Idle -> Shared
279   // When unsharing with a PC, it goes Shared -> Idle -> Mounted
280   //
281   // The AutoMounter needs to know whether the media is present or not when
282   // processing the Idle state.
283 
284   if (mMediaPresent == aMediaPresent) {
285     return;
286   }
287 
288   LOG("Volume: %s media %s", NameStr(), aMediaPresent ? "inserted" : "removed");
289   mMediaPresent = aMediaPresent;
290   sEventObserverList.Broadcast(this);
291 }
292 
293 void
SetSharingEnabled(bool aSharingEnabled)294 Volume::SetSharingEnabled(bool aSharingEnabled)
295 {
296   mSharingEnabled = aSharingEnabled;
297 
298   LOG("SetSharingMode for volume %s to %d canBeShared = %d",
299       NameStr(), (int)mSharingEnabled, (int)mCanBeShared);
300   sEventObserverList.Broadcast(this);
301 }
302 
303 void
SetFormatRequested(bool aFormatRequested)304 Volume::SetFormatRequested(bool aFormatRequested)
305 {
306   mFormatRequested = aFormatRequested;
307 
308   LOG("SetFormatRequested for volume %s to %d CanBeFormatted = %d",
309       NameStr(), (int)mFormatRequested, (int)CanBeFormatted());
310 }
311 
312 void
SetMountRequested(bool aMountRequested)313 Volume::SetMountRequested(bool aMountRequested)
314 {
315   mMountRequested = aMountRequested;
316 
317   LOG("SetMountRequested for volume %s to %d CanBeMounted = %d",
318       NameStr(), (int)mMountRequested, (int)CanBeMounted());
319 }
320 
321 void
SetUnmountRequested(bool aUnmountRequested)322 Volume::SetUnmountRequested(bool aUnmountRequested)
323 {
324   mUnmountRequested = aUnmountRequested;
325 
326   LOG("SetUnmountRequested for volume %s to %d CanBeMounted = %d",
327       NameStr(), (int)mUnmountRequested, (int)CanBeMounted());
328 }
329 
330 void
SetState(Volume::STATE aNewState)331 Volume::SetState(Volume::STATE aNewState)
332 {
333   MOZ_ASSERT(XRE_IsParentProcess());
334   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
335   if (aNewState == mState) {
336     return;
337   }
338   if (aNewState == nsIVolume::STATE_MOUNTED) {
339     mMountGeneration = ++sMountGeneration;
340     LOG("Volume %s (%u): changing state from %s to %s @ '%s' (%d observers) "
341         "mountGeneration = %d, locked = %d",
342         NameStr(), mId, StateStr(mState),
343         StateStr(aNewState), mMountPoint.get(), sEventObserverList.Length(),
344         mMountGeneration, (int)mMountLocked);
345   } else {
346     LOG("Volume %s (%u): changing state from %s to %s (%d observers)",
347         NameStr(), mId, StateStr(mState),
348         StateStr(aNewState), sEventObserverList.Length());
349   }
350 
351   switch (aNewState) {
352      case nsIVolume::STATE_NOMEDIA:
353        // Cover the startup case where we don't get insertion/removal events
354        mMediaPresent = false;
355        mIsSharing = false;
356        mUnmountRequested = false;
357        mMountRequested = false;
358        mIsUnmounting = false;
359        break;
360 
361      case nsIVolume::STATE_MOUNTED:
362      case nsIVolume::STATE_MOUNT_FAIL:
363        mMountRequested = false;
364        mIsFormatting = false;
365        mIsSharing = false;
366        mIsUnmounting = false;
367        break;
368 
369      case nsIVolume::STATE_FORMATTING:
370        mFormatRequested = false;
371        mIsFormatting = true;
372        mIsSharing = false;
373        mIsUnmounting = false;
374        break;
375 
376      case nsIVolume::STATE_SHARED:
377      case nsIVolume::STATE_SHAREDMNT:
378        // Covers startup cases. Normally, mIsSharing would be set to true
379        // when we issue the command to initiate the sharing process, but
380        // it's conceivable that a volume could already be in a shared state
381        // when b2g starts.
382        mIsSharing = true;
383        mIsUnmounting = false;
384        mIsFormatting = false;
385        break;
386 
387      case nsIVolume::STATE_UNMOUNTING:
388        mIsUnmounting = true;
389        mIsFormatting = false;
390        mIsSharing = false;
391        break;
392 
393      case nsIVolume::STATE_IDLE: // Fall through
394      case nsIVolume::STATE_CHECKMNT: // Fall through
395      default:
396        break;
397   }
398   mState = aNewState;
399   sEventObserverList.Broadcast(this);
400 }
401 
402 void
SetMountPoint(const nsCSubstring & aMountPoint)403 Volume::SetMountPoint(const nsCSubstring& aMountPoint)
404 {
405   MOZ_ASSERT(XRE_IsParentProcess());
406   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
407 
408   if (mMountPoint.Equals(aMountPoint)) {
409     return;
410   }
411   ResolveAndSetMountPoint(aMountPoint);
412 }
413 
414 void
StartMount(VolumeResponseCallback * aCallback)415 Volume::StartMount(VolumeResponseCallback* aCallback)
416 {
417   MOZ_ASSERT(XRE_IsParentProcess());
418   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
419 
420   StartCommand(new VolumeActionCommand(this, "mount", "", aCallback));
421 }
422 
423 void
StartUnmount(VolumeResponseCallback * aCallback)424 Volume::StartUnmount(VolumeResponseCallback* aCallback)
425 {
426   MOZ_ASSERT(XRE_IsParentProcess());
427   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
428 
429   StartCommand(new VolumeActionCommand(this, "unmount", "force", aCallback));
430 }
431 
432 void
StartFormat(VolumeResponseCallback * aCallback)433 Volume::StartFormat(VolumeResponseCallback* aCallback)
434 {
435   MOZ_ASSERT(XRE_IsParentProcess());
436   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
437 
438   StartCommand(new VolumeActionCommand(this, "format", "", aCallback));
439 }
440 
441 void
StartShare(VolumeResponseCallback * aCallback)442 Volume::StartShare(VolumeResponseCallback* aCallback)
443 {
444   MOZ_ASSERT(XRE_IsParentProcess());
445   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
446 
447   StartCommand(new VolumeActionCommand(this, "share", "ums", aCallback));
448 }
449 
450 void
StartUnshare(VolumeResponseCallback * aCallback)451 Volume::StartUnshare(VolumeResponseCallback* aCallback)
452 {
453   MOZ_ASSERT(XRE_IsParentProcess());
454   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
455 
456   StartCommand(new VolumeActionCommand(this, "unshare", "ums", aCallback));
457 }
458 
459 void
StartCommand(VolumeCommand * aCommand)460 Volume::StartCommand(VolumeCommand* aCommand)
461 {
462   MOZ_ASSERT(XRE_IsParentProcess());
463   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
464 
465   VolumeManager::PostCommand(aCommand);
466 }
467 
468 //static
469 void
RegisterVolumeObserver(Volume::EventObserver * aObserver,const char * aName)470 Volume::RegisterVolumeObserver(Volume::EventObserver* aObserver, const char* aName)
471 {
472   MOZ_ASSERT(XRE_IsParentProcess());
473   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
474 
475   sEventObserverList.AddObserver(aObserver);
476 
477   DBG("Added Volume Observer '%s' @%p, length = %u",
478       aName, aObserver, sEventObserverList.Length());
479 
480   // Send an initial event to the observer (for each volume)
481   size_t numVolumes = VolumeManager::NumVolumes();
482   for (size_t volIndex = 0; volIndex < numVolumes; volIndex++) {
483     RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
484     aObserver->Notify(vol);
485   }
486 }
487 
488 //static
489 void
UnregisterVolumeObserver(Volume::EventObserver * aObserver,const char * aName)490 Volume::UnregisterVolumeObserver(Volume::EventObserver* aObserver, const char* aName)
491 {
492   MOZ_ASSERT(XRE_IsParentProcess());
493   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
494 
495   sEventObserverList.RemoveObserver(aObserver);
496 
497   DBG("Removed Volume Observer '%s' @%p, length = %u",
498       aName, aObserver, sEventObserverList.Length());
499 }
500 
501 //static
502 void
UpdateMountLock(const nsACString & aVolumeName,const int32_t & aMountGeneration,const bool & aMountLocked)503 Volume::UpdateMountLock(const nsACString& aVolumeName,
504                         const int32_t& aMountGeneration,
505                         const bool& aMountLocked)
506 {
507   MOZ_ASSERT(XRE_IsParentProcess());
508   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
509 
510   RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
511   if (!vol || (vol->mMountGeneration != aMountGeneration)) {
512     return;
513   }
514   if (vol->mMountLocked != aMountLocked) {
515     vol->mMountLocked = aMountLocked;
516     DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked);
517     sEventObserverList.Broadcast(vol);
518   }
519 }
520 
521 void
HandleVoldResponse(int aResponseCode,nsCWhitespaceTokenizer & aTokenizer)522 Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer)
523 {
524   MOZ_ASSERT(XRE_IsParentProcess());
525   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
526 
527   // The volume name will have already been parsed, and the tokenizer will point
528   // to the token after the volume name
529   switch (aResponseCode) {
530     case ::ResponseCode::VolumeListResult: {
531       // Each line will look something like:
532       //
533       //  sdcard /mnt/sdcard 1
534       //
535       nsDependentCSubstring mntPoint(aTokenizer.nextToken());
536       SetMountPoint(mntPoint);
537       nsresult errCode;
538       nsCString state(aTokenizer.nextToken());
539       if (state.EqualsLiteral("X")) {
540         // Special state for creating fake volumes which can't be shared.
541         mCanBeShared = false;
542         SetState(nsIVolume::STATE_MOUNTED);
543       } else {
544         SetState((STATE)state.ToInteger(&errCode));
545       }
546       break;
547     }
548 
549     case ::ResponseCode::VolumeStateChange: {
550       // Format of the line looks something like:
551       //
552       //  Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
553       //
554       // So we parse out the state after the string " to "
555       while (aTokenizer.hasMoreTokens()) {
556         nsAutoCString token(aTokenizer.nextToken());
557         if (token.EqualsLiteral("to")) {
558           nsresult errCode;
559           token = aTokenizer.nextToken();
560           STATE newState = (STATE)(token.ToInteger(&errCode));
561           if (newState == nsIVolume::STATE_MOUNTED) {
562             // We set the state to STATE_CHECKMNT here, and the once the
563             // AutoMounter detects that the volume is actually accessible
564             // then the AutoMounter will set the volume as STATE_MOUNTED.
565             SetState(nsIVolume::STATE_CHECKMNT);
566           } else {
567             if (State() == nsIVolume::STATE_CHECKING && newState == nsIVolume::STATE_IDLE) {
568               LOG("Mount of volume '%s' failed", NameStr());
569               SetState(nsIVolume::STATE_MOUNT_FAIL);
570             } else {
571               SetState(newState);
572             }
573           }
574           break;
575         }
576       }
577       break;
578     }
579 
580     case ::ResponseCode::VolumeDiskInserted:
581       SetMediaPresent(true);
582       break;
583 
584     case ::ResponseCode::VolumeDiskRemoved: // fall-thru
585     case ::ResponseCode::VolumeBadRemoval:
586       SetMediaPresent(false);
587       break;
588 
589     default:
590       LOG("Volume: %s unrecognized reponse code (ignored)", NameStr());
591       break;
592   }
593 }
594 
595 } // namespace system
596 } // namespace mozilla
597