dom/system/gonk/Volume.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:1c774549a4ec
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 Volume::EventObserverList Volume::mEventObserverList;
18
19 // We have a feature where volumes can be locked when mounted. This
20 // is used to prevent a volume from being shared with the PC while
21 // it is actively being used (say for storing an update image)
22 //
23 // We use WakeLocks (a poor choice of name, but it does what we want)
24 // from the PowerManagerService to determine when we're locked.
25 // In particular we'll create a wakelock called volume-NAME-GENERATION
26 // (where NAME is the volume name, and GENERATION is its generation
27 // number), and if this wakelock is locked, then we'll prevent a volume
28 // from being shared.
29 //
30 // Implementation Details:
31 //
32 // Since the AutoMounter can only control when something gets mounted
33 // and not when it gets unmounted (for example: a user pulls the SDCard)
34 // and because Volume and nsVolume data structures are maintained on
35 // separate threads, we have the potential for some race conditions.
36 // We eliminate the race conditions by introducing the concept of a
37 // generation number. Every time a volume transitions to the Mounted
38 // state, it gets assigned a new generation number. Whenever the state
39 // of a Volume changes, we send the updated state and current generation
40 // number to the main thread where it gets updated in the nsVolume.
41 //
42 // Since WakeLocks can only be queried from the main-thread, the
43 // nsVolumeService looks for WakeLock status changes, and forwards
44 // the results to the IOThread.
45 //
46 // If the Volume (IOThread) recieves a volume update where the generation
47 // number mismatches, then the update is simply ignored.
48 //
49 // When a Volume (IOThread) initially becomes mounted, we assume it to
50 // be locked until we get our first update from nsVolume (MainThread).
51 static int32_t sMountGeneration = 0;
52
53 // We don't get media inserted/removed events at startup. So we
54 // assume it's present, and we'll be told that it's missing.
55 Volume::Volume(const nsCSubstring& aName)
56 : mMediaPresent(true),
57 mState(nsIVolume::STATE_INIT),
58 mName(aName),
59 mMountGeneration(-1),
60 mMountLocked(true), // Needs to agree with nsVolume::nsVolume
61 mSharingEnabled(false),
62 mCanBeShared(true),
63 mIsSharing(false),
64 mFormatRequested(false),
65 mMountRequested(false),
66 mUnmountRequested(false),
67 mIsFormatting(false)
68 {
69 DBG("Volume %s: created", NameStr());
70 }
71
72 void
73 Volume::SetIsSharing(bool aIsSharing)
74 {
75 if (aIsSharing == mIsSharing) {
76 return;
77 }
78 mIsSharing = aIsSharing;
79 LOG("Volume %s: IsSharing set to %d state %s",
80 NameStr(), (int)mIsSharing, StateStr(mState));
81 if (mIsSharing) {
82 mEventObserverList.Broadcast(this);
83 }
84 }
85
86 void
87 Volume::SetIsFormatting(bool aIsFormatting)
88 {
89 if (aIsFormatting == mIsFormatting) {
90 return;
91 }
92 mIsFormatting = aIsFormatting;
93 LOG("Volume %s: IsFormatting set to %d state %s",
94 NameStr(), (int)mIsFormatting, StateStr(mState));
95 if (mIsFormatting) {
96 mEventObserverList.Broadcast(this);
97 }
98 }
99
100 void
101 Volume::SetMediaPresent(bool aMediaPresent)
102 {
103 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
104 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
105
106 // mMediaPresent is slightly redunant to the state, however
107 // when media is removed (while Idle), we get the following:
108 // 631 Volume sdcard /mnt/sdcard disk removed (179:0)
109 // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media)
110 //
111 // And on media insertion, we get:
112 // 630 Volume sdcard /mnt/sdcard disk inserted (179:0)
113 // 605 Volume sdcard /mnt/sdcard state changed from 0 (No-Media) to 2 (Pending)
114 // 605 Volume sdcard /mnt/sdcard state changed from 2 (Pending) to 1 (Idle-Unmounted)
115 //
116 // On media removal while the media is mounted:
117 // 632 Volume sdcard /mnt/sdcard bad removal (179:1)
118 // 605 Volume sdcard /mnt/sdcard state changed from 4 (Mounted) to 5 (Unmounting)
119 // 605 Volume sdcard /mnt/sdcard state changed from 5 (Unmounting) to 1 (Idle-Unmounted)
120 // 631 Volume sdcard /mnt/sdcard disk removed (179:0)
121 // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media)
122 //
123 // When sharing with a PC, it goes Mounted -> Idle -> Shared
124 // When unsharing with a PC, it goes Shared -> Idle -> Mounted
125 //
126 // The AutoMounter needs to know whether the media is present or not when
127 // processing the Idle state.
128
129 if (mMediaPresent == aMediaPresent) {
130 return;
131 }
132
133 LOG("Volume: %s media %s", NameStr(), aMediaPresent ? "inserted" : "removed");
134 mMediaPresent = aMediaPresent;
135 mEventObserverList.Broadcast(this);
136 }
137
138 void
139 Volume::SetSharingEnabled(bool aSharingEnabled)
140 {
141 mSharingEnabled = aSharingEnabled;
142
143 LOG("SetSharingMode for volume %s to %d canBeShared = %d",
144 NameStr(), (int)mSharingEnabled, (int)mCanBeShared);
145 }
146
147 void
148 Volume::SetFormatRequested(bool aFormatRequested)
149 {
150 mFormatRequested = aFormatRequested;
151
152 LOG("SetFormatRequested for volume %s to %d CanBeFormatted = %d",
153 NameStr(), (int)mFormatRequested, (int)CanBeFormatted());
154 }
155
156 void
157 Volume::SetMountRequested(bool aMountRequested)
158 {
159 mMountRequested = aMountRequested;
160
161 LOG("SetMountRequested for volume %s to %d CanBeMounted = %d",
162 NameStr(), (int)mMountRequested, (int)CanBeMounted());
163 }
164
165 void
166 Volume::SetUnmountRequested(bool aUnmountRequested)
167 {
168 mUnmountRequested = aUnmountRequested;
169
170 LOG("SetUnmountRequested for volume %s to %d CanBeMounted = %d",
171 NameStr(), (int)mUnmountRequested, (int)CanBeMounted());
172 }
173
174 void
175 Volume::SetState(Volume::STATE aNewState)
176 {
177 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
178 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
179 if (aNewState == mState) {
180 return;
181 }
182 if (aNewState == nsIVolume::STATE_MOUNTED) {
183 mMountGeneration = ++sMountGeneration;
184 LOG("Volume %s: changing state from %s to %s @ '%s' (%d observers) "
185 "mountGeneration = %d, locked = %d",
186 NameStr(), StateStr(mState),
187 StateStr(aNewState), mMountPoint.get(), mEventObserverList.Length(),
188 mMountGeneration, (int)mMountLocked);
189 } else {
190 LOG("Volume %s: changing state from %s to %s (%d observers)",
191 NameStr(), StateStr(mState),
192 StateStr(aNewState), mEventObserverList.Length());
193 }
194
195 switch (aNewState) {
196 case nsIVolume::STATE_NOMEDIA:
197 // Cover the startup case where we don't get insertion/removal events
198 mMediaPresent = false;
199 mIsSharing = false;
200 mUnmountRequested = false;
201 mMountRequested = false;
202 break;
203
204 case nsIVolume::STATE_MOUNTED:
205 mMountRequested = false;
206 mIsFormatting = false;
207 mIsSharing = false;
208 break;
209 case nsIVolume::STATE_FORMATTING:
210 mFormatRequested = false;
211 mIsFormatting = true;
212 mIsSharing = false;
213 break;
214
215 case nsIVolume::STATE_SHARED:
216 case nsIVolume::STATE_SHAREDMNT:
217 // Covers startup cases. Normally, mIsSharing would be set to true
218 // when we issue the command to initiate the sharing process, but
219 // it's conceivable that a volume could already be in a shared state
220 // when b2g starts.
221 mIsSharing = true;
222 break;
223
224 case nsIVolume::STATE_IDLE:
225 break;
226 default:
227 break;
228 }
229 mState = aNewState;
230 mEventObserverList.Broadcast(this);
231 }
232
233 void
234 Volume::SetMountPoint(const nsCSubstring& aMountPoint)
235 {
236 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
237 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
238
239 if (mMountPoint.Equals(aMountPoint)) {
240 return;
241 }
242 mMountPoint = aMountPoint;
243 DBG("Volume %s: Setting mountpoint to '%s'", NameStr(), mMountPoint.get());
244 }
245
246 void
247 Volume::StartMount(VolumeResponseCallback* aCallback)
248 {
249 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
250 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
251
252 StartCommand(new VolumeActionCommand(this, "mount", "", aCallback));
253 }
254
255 void
256 Volume::StartUnmount(VolumeResponseCallback* aCallback)
257 {
258 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
259 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
260
261 StartCommand(new VolumeActionCommand(this, "unmount", "force", aCallback));
262 }
263
264 void
265 Volume::StartFormat(VolumeResponseCallback* aCallback)
266 {
267 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
268 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
269
270 StartCommand(new VolumeActionCommand(this, "format", "", aCallback));
271 }
272
273 void
274 Volume::StartShare(VolumeResponseCallback* aCallback)
275 {
276 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
277 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
278
279 StartCommand(new VolumeActionCommand(this, "share", "ums", aCallback));
280 }
281
282 void
283 Volume::StartUnshare(VolumeResponseCallback* aCallback)
284 {
285 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
286 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
287
288 StartCommand(new VolumeActionCommand(this, "unshare", "ums", aCallback));
289 }
290
291 void
292 Volume::StartCommand(VolumeCommand* aCommand)
293 {
294 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
295 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
296
297 VolumeManager::PostCommand(aCommand);
298 }
299
300 //static
301 void
302 Volume::RegisterObserver(Volume::EventObserver* aObserver)
303 {
304 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
305 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
306
307 mEventObserverList.AddObserver(aObserver);
308 // Send an initial event to the observer (for each volume)
309 size_t numVolumes = VolumeManager::NumVolumes();
310 for (size_t volIndex = 0; volIndex < numVolumes; volIndex++) {
311 RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
312 aObserver->Notify(vol);
313 }
314 }
315
316 //static
317 void
318 Volume::UnregisterObserver(Volume::EventObserver* aObserver)
319 {
320 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
321 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
322
323 mEventObserverList.RemoveObserver(aObserver);
324 }
325
326 //static
327 void
328 Volume::UpdateMountLock(const nsACString& aVolumeName,
329 const int32_t& aMountGeneration,
330 const bool& aMountLocked)
331 {
332 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
333 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
334
335 RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
336 if (!vol || (vol->mMountGeneration != aMountGeneration)) {
337 return;
338 }
339 if (vol->mMountLocked != aMountLocked) {
340 vol->mMountLocked = aMountLocked;
341 DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked);
342 mEventObserverList.Broadcast(vol);
343 }
344 }
345
346 void
347 Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer)
348 {
349 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
350 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
351
352 // The volume name will have already been parsed, and the tokenizer will point
353 // to the token after the volume name
354 switch (aResponseCode) {
355 case ResponseCode::VolumeListResult: {
356 // Each line will look something like:
357 //
358 // sdcard /mnt/sdcard 1
359 //
360 nsDependentCSubstring mntPoint(aTokenizer.nextToken());
361 SetMountPoint(mntPoint);
362 nsresult errCode;
363 nsCString state(aTokenizer.nextToken());
364 if (state.EqualsLiteral("X")) {
365 // Special state for creating fake volumes which can't be shared.
366 mCanBeShared = false;
367 SetState(nsIVolume::STATE_MOUNTED);
368 } else {
369 SetState((STATE)state.ToInteger(&errCode));
370 }
371 break;
372 }
373
374 case ResponseCode::VolumeStateChange: {
375 // Format of the line looks something like:
376 //
377 // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
378 //
379 // So we parse out the state after the string " to "
380 while (aTokenizer.hasMoreTokens()) {
381 nsAutoCString token(aTokenizer.nextToken());
382 if (token.Equals("to")) {
383 nsresult errCode;
384 token = aTokenizer.nextToken();
385 SetState((STATE)token.ToInteger(&errCode));
386 break;
387 }
388 }
389 break;
390 }
391
392 case ResponseCode::VolumeDiskInserted:
393 SetMediaPresent(true);
394 break;
395
396 case ResponseCode::VolumeDiskRemoved: // fall-thru
397 case ResponseCode::VolumeBadRemoval:
398 SetMediaPresent(false);
399 break;
400
401 default:
402 LOG("Volume: %s unrecognized reponse code (ignored)", NameStr());
403 break;
404 }
405 }
406
407 } // namespace system
408 } // namespace mozilla

mercurial