|
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 "nsVolumeService.h" |
|
6 |
|
7 #include "Volume.h" |
|
8 #include "VolumeManager.h" |
|
9 #include "VolumeServiceIOThread.h" |
|
10 |
|
11 #include "nsAutoPtr.h" |
|
12 #include "nsCOMPtr.h" |
|
13 #include "nsDependentSubstring.h" |
|
14 #include "nsIDOMWakeLockListener.h" |
|
15 #include "nsIObserver.h" |
|
16 #include "nsIObserverService.h" |
|
17 #include "nsIPowerManagerService.h" |
|
18 #include "nsISupportsUtils.h" |
|
19 #include "nsIVolume.h" |
|
20 #include "nsIVolumeService.h" |
|
21 #include "nsLocalFile.h" |
|
22 #include "nsServiceManagerUtils.h" |
|
23 #include "nsString.h" |
|
24 #include "nsTArray.h" |
|
25 #include "nsThreadUtils.h" |
|
26 #include "nsVolumeMountLock.h" |
|
27 #include "nsXULAppAPI.h" |
|
28 #include "mozilla/dom/ContentChild.h" |
|
29 #include "mozilla/Services.h" |
|
30 |
|
31 #define VOLUME_MANAGER_LOG_TAG "nsVolumeService" |
|
32 #include "VolumeManagerLog.h" |
|
33 |
|
34 #include <stdlib.h> |
|
35 |
|
36 using namespace mozilla::dom; |
|
37 using namespace mozilla::services; |
|
38 |
|
39 namespace mozilla { |
|
40 namespace system { |
|
41 |
|
42 NS_IMPL_ISUPPORTS(nsVolumeService, |
|
43 nsIVolumeService, |
|
44 nsIDOMMozWakeLockListener) |
|
45 |
|
46 StaticRefPtr<nsVolumeService> nsVolumeService::sSingleton; |
|
47 |
|
48 // static |
|
49 already_AddRefed<nsVolumeService> |
|
50 nsVolumeService::GetSingleton() |
|
51 { |
|
52 MOZ_ASSERT(NS_IsMainThread()); |
|
53 |
|
54 if (!sSingleton) { |
|
55 sSingleton = new nsVolumeService(); |
|
56 } |
|
57 nsRefPtr<nsVolumeService> volumeService = sSingleton.get(); |
|
58 return volumeService.forget(); |
|
59 } |
|
60 |
|
61 // static |
|
62 void |
|
63 nsVolumeService::Shutdown() |
|
64 { |
|
65 if (!sSingleton) { |
|
66 return; |
|
67 } |
|
68 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
69 sSingleton = nullptr; |
|
70 return; |
|
71 } |
|
72 |
|
73 nsCOMPtr<nsIPowerManagerService> pmService = |
|
74 do_GetService(POWERMANAGERSERVICE_CONTRACTID); |
|
75 if (pmService) { |
|
76 pmService->RemoveWakeLockListener(sSingleton.get()); |
|
77 } |
|
78 |
|
79 XRE_GetIOMessageLoop()->PostTask( |
|
80 FROM_HERE, |
|
81 NewRunnableFunction(ShutdownVolumeServiceIOThread)); |
|
82 |
|
83 sSingleton = nullptr; |
|
84 } |
|
85 |
|
86 nsVolumeService::nsVolumeService() |
|
87 : mArrayMonitor("nsVolumeServiceArray") |
|
88 { |
|
89 sSingleton = this; |
|
90 |
|
91 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
92 // Request the initial state for all volumes. |
|
93 ContentChild::GetSingleton()->SendBroadcastVolume(NS_LITERAL_STRING("")); |
|
94 return; |
|
95 } |
|
96 |
|
97 // Startup the IOThread side of things. The actual volume changes |
|
98 // are captured by the IOThread and forwarded to main thread. |
|
99 XRE_GetIOMessageLoop()->PostTask( |
|
100 FROM_HERE, |
|
101 NewRunnableFunction(InitVolumeServiceIOThread, this)); |
|
102 |
|
103 nsCOMPtr<nsIPowerManagerService> pmService = |
|
104 do_GetService(POWERMANAGERSERVICE_CONTRACTID); |
|
105 if (!pmService) { |
|
106 return; |
|
107 } |
|
108 pmService->AddWakeLockListener(this); |
|
109 } |
|
110 |
|
111 nsVolumeService::~nsVolumeService() |
|
112 { |
|
113 } |
|
114 |
|
115 // Callback for nsIDOMMozWakeLockListener |
|
116 NS_IMETHODIMP |
|
117 nsVolumeService::Callback(const nsAString& aTopic, const nsAString& aState) |
|
118 { |
|
119 CheckMountLock(aTopic, aState); |
|
120 return NS_OK; |
|
121 } |
|
122 |
|
123 NS_IMETHODIMP |
|
124 nsVolumeService::BroadcastVolume(const nsAString& aVolName) |
|
125 { |
|
126 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); |
|
127 |
|
128 if (aVolName.EqualsLiteral("")) { |
|
129 nsVolume::Array volumeArray; |
|
130 { |
|
131 // Copy the array since we don't want to call BroadcastVolume |
|
132 // while we're holding the lock. |
|
133 MonitorAutoLock autoLock(mArrayMonitor); |
|
134 volumeArray = mVolumeArray; |
|
135 } |
|
136 |
|
137 // We treat being passed the empty string as "broadcast all volumes" |
|
138 nsVolume::Array::size_type numVolumes = volumeArray.Length(); |
|
139 nsVolume::Array::index_type volIndex; |
|
140 for (volIndex = 0; volIndex < numVolumes; volIndex++) { |
|
141 const nsString& volName(volumeArray[volIndex]->Name()); |
|
142 if (!volName.EqualsLiteral("")) { |
|
143 // Note: The volume service is the only entity that should be able to |
|
144 // modify the array of volumes. So we shouldn't have any issues with |
|
145 // the array being modified under our feet (Since we're the volume |
|
146 // service the array can't change until after we finish iterating the |
|
147 // the loop). |
|
148 nsresult rv = BroadcastVolume(volName); |
|
149 NS_ENSURE_SUCCESS(rv, rv); |
|
150 } |
|
151 } |
|
152 return NS_OK; |
|
153 } |
|
154 nsRefPtr<nsVolume> vol; |
|
155 { |
|
156 MonitorAutoLock autoLock(mArrayMonitor); |
|
157 vol = FindVolumeByName(aVolName); |
|
158 } |
|
159 if (!vol) { |
|
160 ERR("BroadcastVolume: Unable to locate volume '%s'", |
|
161 NS_LossyConvertUTF16toASCII(aVolName).get()); |
|
162 return NS_ERROR_NOT_AVAILABLE; |
|
163 } |
|
164 |
|
165 nsCOMPtr<nsIObserverService> obs = GetObserverService(); |
|
166 NS_ENSURE_TRUE(obs, NS_NOINTERFACE); |
|
167 |
|
168 DBG("nsVolumeService::BroadcastVolume for '%s'", vol->NameStr().get()); |
|
169 NS_ConvertUTF8toUTF16 stateStr(vol->StateStr()); |
|
170 obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get()); |
|
171 return NS_OK; |
|
172 } |
|
173 |
|
174 NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult) |
|
175 { |
|
176 MonitorAutoLock autoLock(mArrayMonitor); |
|
177 |
|
178 nsRefPtr<nsVolume> vol = FindVolumeByName(aVolName); |
|
179 if (!vol) { |
|
180 return NS_ERROR_NOT_AVAILABLE; |
|
181 } |
|
182 |
|
183 vol.forget(aResult); |
|
184 return NS_OK; |
|
185 } |
|
186 |
|
187 NS_IMETHODIMP |
|
188 nsVolumeService::GetVolumeByPath(const nsAString& aPath, nsIVolume **aResult) |
|
189 { |
|
190 NS_ConvertUTF16toUTF8 utf8Path(aPath); |
|
191 char realPathBuf[PATH_MAX]; |
|
192 |
|
193 while (realpath(utf8Path.get(), realPathBuf) < 0) { |
|
194 if (errno != ENOENT) { |
|
195 ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno); |
|
196 return NSRESULT_FOR_ERRNO(); |
|
197 } |
|
198 // The pathname we were passed doesn't exist, so we try stripping off trailing |
|
199 // components until we get a successful call to realpath, or until we run out |
|
200 // of components (if we finally get to /something then we also stop). |
|
201 int32_t slashIndex = utf8Path.RFindChar('/'); |
|
202 if ((slashIndex == kNotFound) || (slashIndex == 0)) { |
|
203 errno = ENOENT; |
|
204 ERR("GetVolumeByPath: realpath on '%s' failed.", utf8Path.get()); |
|
205 return NSRESULT_FOR_ERRNO(); |
|
206 } |
|
207 utf8Path.Assign(Substring(utf8Path, 0, slashIndex)); |
|
208 } |
|
209 |
|
210 // The volume mount point is always a directory. Something like /mnt/sdcard |
|
211 // Once we have a full qualified pathname with symlinks removed (which is |
|
212 // what realpath does), we basically check if aPath starts with the mount |
|
213 // point, but we don't want to have /mnt/sdcard match /mnt/sdcardfoo but we |
|
214 // do want it to match /mnt/sdcard/foo |
|
215 // So we add a trailing slash to the mount point and the pathname passed in |
|
216 // prior to doing the comparison. |
|
217 |
|
218 strlcat(realPathBuf, "/", sizeof(realPathBuf)); |
|
219 |
|
220 MonitorAutoLock autoLock(mArrayMonitor); |
|
221 |
|
222 nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); |
|
223 nsVolume::Array::index_type volIndex; |
|
224 for (volIndex = 0; volIndex < numVolumes; volIndex++) { |
|
225 nsRefPtr<nsVolume> vol = mVolumeArray[volIndex]; |
|
226 NS_ConvertUTF16toUTF8 volMountPointSlash(vol->MountPoint()); |
|
227 volMountPointSlash.Append(NS_LITERAL_CSTRING("/")); |
|
228 nsDependentCSubstring testStr(realPathBuf, volMountPointSlash.Length()); |
|
229 if (volMountPointSlash.Equals(testStr)) { |
|
230 vol.forget(aResult); |
|
231 return NS_OK; |
|
232 } |
|
233 } |
|
234 return NS_ERROR_FILE_NOT_FOUND; |
|
235 } |
|
236 |
|
237 NS_IMETHODIMP |
|
238 nsVolumeService::CreateOrGetVolumeByPath(const nsAString& aPath, nsIVolume** aResult) |
|
239 { |
|
240 nsresult rv = GetVolumeByPath(aPath, aResult); |
|
241 if (rv == NS_OK) { |
|
242 return NS_OK; |
|
243 } |
|
244 |
|
245 // In order to support queries by the updater, we will fabricate a volume |
|
246 // from the pathname, so that the caller can determine the volume size. |
|
247 nsCOMPtr<nsIVolume> vol = new nsVolume(NS_LITERAL_STRING("fake"), |
|
248 aPath, nsIVolume::STATE_MOUNTED, |
|
249 -1 /* generation */, |
|
250 true /* isMediaPresent*/, |
|
251 false /* isSharing */, |
|
252 false /* isFormatting */); |
|
253 vol.forget(aResult); |
|
254 return NS_OK; |
|
255 } |
|
256 |
|
257 NS_IMETHODIMP |
|
258 nsVolumeService::GetVolumeNames(nsTArray<nsString>& aVolNames) |
|
259 { |
|
260 MonitorAutoLock autoLock(mArrayMonitor); |
|
261 |
|
262 nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); |
|
263 nsVolume::Array::index_type volIndex; |
|
264 for (volIndex = 0; volIndex < numVolumes; volIndex++) { |
|
265 nsRefPtr<nsVolume> vol = mVolumeArray[volIndex]; |
|
266 aVolNames.AppendElement(vol->Name()); |
|
267 } |
|
268 |
|
269 return NS_OK; |
|
270 } |
|
271 |
|
272 NS_IMETHODIMP |
|
273 nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult) |
|
274 { |
|
275 nsCOMPtr<nsIVolumeMountLock> mountLock = nsVolumeMountLock::Create(aVolumeName); |
|
276 if (!mountLock) { |
|
277 return NS_ERROR_NOT_AVAILABLE; |
|
278 } |
|
279 mountLock.forget(aResult); |
|
280 return NS_OK; |
|
281 } |
|
282 |
|
283 void |
|
284 nsVolumeService::CheckMountLock(const nsAString& aMountLockName, |
|
285 const nsAString& aMountLockState) |
|
286 { |
|
287 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); |
|
288 MOZ_ASSERT(NS_IsMainThread()); |
|
289 |
|
290 nsRefPtr<nsVolume> vol = FindVolumeByMountLockName(aMountLockName); |
|
291 if (vol) { |
|
292 vol->UpdateMountLock(aMountLockState); |
|
293 } |
|
294 } |
|
295 |
|
296 already_AddRefed<nsVolume> |
|
297 nsVolumeService::FindVolumeByMountLockName(const nsAString& aMountLockName) |
|
298 { |
|
299 MonitorAutoLock autoLock(mArrayMonitor); |
|
300 |
|
301 nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); |
|
302 nsVolume::Array::index_type volIndex; |
|
303 for (volIndex = 0; volIndex < numVolumes; volIndex++) { |
|
304 nsRefPtr<nsVolume> vol = mVolumeArray[volIndex]; |
|
305 nsString mountLockName; |
|
306 vol->GetMountLockName(mountLockName); |
|
307 if (mountLockName.Equals(aMountLockName)) { |
|
308 return vol.forget(); |
|
309 } |
|
310 } |
|
311 return nullptr; |
|
312 } |
|
313 |
|
314 already_AddRefed<nsVolume> |
|
315 nsVolumeService::FindVolumeByName(const nsAString& aName) |
|
316 { |
|
317 mArrayMonitor.AssertCurrentThreadOwns(); |
|
318 |
|
319 nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); |
|
320 nsVolume::Array::index_type volIndex; |
|
321 for (volIndex = 0; volIndex < numVolumes; volIndex++) { |
|
322 nsRefPtr<nsVolume> vol = mVolumeArray[volIndex]; |
|
323 if (vol->Name().Equals(aName)) { |
|
324 return vol.forget(); |
|
325 } |
|
326 } |
|
327 return nullptr; |
|
328 } |
|
329 |
|
330 //static |
|
331 already_AddRefed<nsVolume> |
|
332 nsVolumeService::CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake /*= false*/) |
|
333 { |
|
334 MonitorAutoLock autoLock(mArrayMonitor); |
|
335 |
|
336 nsRefPtr<nsVolume> vol; |
|
337 vol = FindVolumeByName(aName); |
|
338 if (vol) { |
|
339 return vol.forget(); |
|
340 } |
|
341 // Volume not found - add a new one |
|
342 vol = new nsVolume(aName); |
|
343 vol->SetIsFake(aIsFake); |
|
344 mVolumeArray.AppendElement(vol); |
|
345 return vol.forget(); |
|
346 } |
|
347 |
|
348 void |
|
349 nsVolumeService::UpdateVolume(nsIVolume* aVolume) |
|
350 { |
|
351 MOZ_ASSERT(NS_IsMainThread()); |
|
352 |
|
353 nsString volName; |
|
354 aVolume->GetName(volName); |
|
355 bool aIsFake; |
|
356 aVolume->GetIsFake(&aIsFake); |
|
357 nsRefPtr<nsVolume> vol = CreateOrFindVolumeByName(volName, aIsFake); |
|
358 if (vol->Equals(aVolume)) { |
|
359 // Nothing has really changed. Don't bother telling anybody. |
|
360 return; |
|
361 } |
|
362 |
|
363 if (!vol->IsFake() && aIsFake) { |
|
364 // Prevent an incoming fake volume from overriding an existing real volume. |
|
365 return; |
|
366 } |
|
367 |
|
368 vol->Set(aVolume); |
|
369 nsCOMPtr<nsIObserverService> obs = GetObserverService(); |
|
370 if (!obs) { |
|
371 return; |
|
372 } |
|
373 NS_ConvertUTF8toUTF16 stateStr(vol->StateStr()); |
|
374 obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get()); |
|
375 } |
|
376 |
|
377 NS_IMETHODIMP |
|
378 nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path) |
|
379 { |
|
380 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
381 nsRefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_INIT, |
|
382 -1 /* mountGeneration */, |
|
383 true /* isMediaPresent */, |
|
384 false /* isSharing */, |
|
385 false /* isFormatting */); |
|
386 vol->SetIsFake(true); |
|
387 vol->LogState(); |
|
388 UpdateVolume(vol.get()); |
|
389 return NS_OK; |
|
390 } |
|
391 |
|
392 ContentChild::GetSingleton()->SendCreateFakeVolume(nsString(name), nsString(path)); |
|
393 return NS_OK; |
|
394 } |
|
395 |
|
396 NS_IMETHODIMP |
|
397 nsVolumeService::SetFakeVolumeState(const nsAString& name, int32_t state) |
|
398 { |
|
399 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
400 nsRefPtr<nsVolume> vol; |
|
401 { |
|
402 MonitorAutoLock autoLock(mArrayMonitor); |
|
403 vol = FindVolumeByName(name); |
|
404 } |
|
405 if (!vol || !vol->IsFake()) { |
|
406 return NS_ERROR_NOT_AVAILABLE; |
|
407 } |
|
408 vol->SetState(state); |
|
409 vol->LogState(); |
|
410 UpdateVolume(vol.get()); |
|
411 return NS_OK; |
|
412 } |
|
413 |
|
414 ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state); |
|
415 return NS_OK; |
|
416 } |
|
417 |
|
418 /*************************************************************************** |
|
419 * The UpdateVolumeRunnable creates an nsVolume and updates the main thread |
|
420 * data structure while running on the main thread. |
|
421 */ |
|
422 class UpdateVolumeRunnable : public nsRunnable |
|
423 { |
|
424 public: |
|
425 UpdateVolumeRunnable(nsVolumeService* aVolumeService, const Volume* aVolume) |
|
426 : mVolumeService(aVolumeService), |
|
427 mVolume(new nsVolume(aVolume)) |
|
428 { |
|
429 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
430 } |
|
431 |
|
432 NS_IMETHOD Run() |
|
433 { |
|
434 MOZ_ASSERT(NS_IsMainThread()); |
|
435 DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d " |
|
436 "media %d sharing %d formatting %d", |
|
437 mVolume->NameStr().get(), mVolume->StateStr(), |
|
438 mVolume->MountGeneration(), (int)mVolume->IsMountLocked(), |
|
439 (int)mVolume->IsMediaPresent(), mVolume->IsSharing(), |
|
440 mVolume->IsFormatting()); |
|
441 |
|
442 mVolumeService->UpdateVolume(mVolume); |
|
443 mVolumeService = nullptr; |
|
444 mVolume = nullptr; |
|
445 return NS_OK; |
|
446 } |
|
447 |
|
448 private: |
|
449 nsRefPtr<nsVolumeService> mVolumeService; |
|
450 nsRefPtr<nsVolume> mVolume; |
|
451 }; |
|
452 |
|
453 void |
|
454 nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume) |
|
455 { |
|
456 DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d " |
|
457 "media %d sharing %d formatting %d", |
|
458 aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(), |
|
459 aVolume->MountGeneration(), (int)aVolume->IsMountLocked(), |
|
460 (int)aVolume->MediaPresent(), (int)aVolume->IsSharing(), |
|
461 (int)aVolume->IsFormatting()); |
|
462 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); |
|
463 NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume)); |
|
464 } |
|
465 |
|
466 } // namespace system |
|
467 } // namespace mozilla |