Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
5 #include "nsVolumeService.h"
7 #include "Volume.h"
8 #include "VolumeManager.h"
9 #include "VolumeServiceIOThread.h"
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"
31 #define VOLUME_MANAGER_LOG_TAG "nsVolumeService"
32 #include "VolumeManagerLog.h"
34 #include <stdlib.h>
36 using namespace mozilla::dom;
37 using namespace mozilla::services;
39 namespace mozilla {
40 namespace system {
42 NS_IMPL_ISUPPORTS(nsVolumeService,
43 nsIVolumeService,
44 nsIDOMMozWakeLockListener)
46 StaticRefPtr<nsVolumeService> nsVolumeService::sSingleton;
48 // static
49 already_AddRefed<nsVolumeService>
50 nsVolumeService::GetSingleton()
51 {
52 MOZ_ASSERT(NS_IsMainThread());
54 if (!sSingleton) {
55 sSingleton = new nsVolumeService();
56 }
57 nsRefPtr<nsVolumeService> volumeService = sSingleton.get();
58 return volumeService.forget();
59 }
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 }
73 nsCOMPtr<nsIPowerManagerService> pmService =
74 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
75 if (pmService) {
76 pmService->RemoveWakeLockListener(sSingleton.get());
77 }
79 XRE_GetIOMessageLoop()->PostTask(
80 FROM_HERE,
81 NewRunnableFunction(ShutdownVolumeServiceIOThread));
83 sSingleton = nullptr;
84 }
86 nsVolumeService::nsVolumeService()
87 : mArrayMonitor("nsVolumeServiceArray")
88 {
89 sSingleton = this;
91 if (XRE_GetProcessType() != GeckoProcessType_Default) {
92 // Request the initial state for all volumes.
93 ContentChild::GetSingleton()->SendBroadcastVolume(NS_LITERAL_STRING(""));
94 return;
95 }
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));
103 nsCOMPtr<nsIPowerManagerService> pmService =
104 do_GetService(POWERMANAGERSERVICE_CONTRACTID);
105 if (!pmService) {
106 return;
107 }
108 pmService->AddWakeLockListener(this);
109 }
111 nsVolumeService::~nsVolumeService()
112 {
113 }
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 }
123 NS_IMETHODIMP
124 nsVolumeService::BroadcastVolume(const nsAString& aVolName)
125 {
126 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
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 }
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 }
165 nsCOMPtr<nsIObserverService> obs = GetObserverService();
166 NS_ENSURE_TRUE(obs, NS_NOINTERFACE);
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 }
174 NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult)
175 {
176 MonitorAutoLock autoLock(mArrayMonitor);
178 nsRefPtr<nsVolume> vol = FindVolumeByName(aVolName);
179 if (!vol) {
180 return NS_ERROR_NOT_AVAILABLE;
181 }
183 vol.forget(aResult);
184 return NS_OK;
185 }
187 NS_IMETHODIMP
188 nsVolumeService::GetVolumeByPath(const nsAString& aPath, nsIVolume **aResult)
189 {
190 NS_ConvertUTF16toUTF8 utf8Path(aPath);
191 char realPathBuf[PATH_MAX];
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 }
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.
218 strlcat(realPathBuf, "/", sizeof(realPathBuf));
220 MonitorAutoLock autoLock(mArrayMonitor);
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 }
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 }
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 }
257 NS_IMETHODIMP
258 nsVolumeService::GetVolumeNames(nsTArray<nsString>& aVolNames)
259 {
260 MonitorAutoLock autoLock(mArrayMonitor);
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 }
269 return NS_OK;
270 }
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 }
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());
290 nsRefPtr<nsVolume> vol = FindVolumeByMountLockName(aMountLockName);
291 if (vol) {
292 vol->UpdateMountLock(aMountLockState);
293 }
294 }
296 already_AddRefed<nsVolume>
297 nsVolumeService::FindVolumeByMountLockName(const nsAString& aMountLockName)
298 {
299 MonitorAutoLock autoLock(mArrayMonitor);
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 }
314 already_AddRefed<nsVolume>
315 nsVolumeService::FindVolumeByName(const nsAString& aName)
316 {
317 mArrayMonitor.AssertCurrentThreadOwns();
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 }
330 //static
331 already_AddRefed<nsVolume>
332 nsVolumeService::CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake /*= false*/)
333 {
334 MonitorAutoLock autoLock(mArrayMonitor);
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 }
348 void
349 nsVolumeService::UpdateVolume(nsIVolume* aVolume)
350 {
351 MOZ_ASSERT(NS_IsMainThread());
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 }
363 if (!vol->IsFake() && aIsFake) {
364 // Prevent an incoming fake volume from overriding an existing real volume.
365 return;
366 }
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 }
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 }
392 ContentChild::GetSingleton()->SendCreateFakeVolume(nsString(name), nsString(path));
393 return NS_OK;
394 }
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 }
414 ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state);
415 return NS_OK;
416 }
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 }
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());
442 mVolumeService->UpdateVolume(mVolume);
443 mVolumeService = nullptr;
444 mVolume = nullptr;
445 return NS_OK;
446 }
448 private:
449 nsRefPtr<nsVolumeService> mVolumeService;
450 nsRefPtr<nsVolume> mVolume;
451 };
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 }
466 } // namespace system
467 } // namespace mozilla