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