|
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 |