summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/nsVolumeService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/nsVolumeService.cpp')
-rw-r--r--dom/system/gonk/nsVolumeService.cpp553
1 files changed, 553 insertions, 0 deletions
diff --git a/dom/system/gonk/nsVolumeService.cpp b/dom/system/gonk/nsVolumeService.cpp
new file mode 100644
index 000000000..48d95c26a
--- /dev/null
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -0,0 +1,553 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVolumeService.h"
+
+#include "Volume.h"
+#include "VolumeManager.h"
+#include "VolumeServiceIOThread.h"
+
+#include "nsCOMPtr.h"
+#include "nsDependentSubstring.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIMutableArray.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIPowerManagerService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISupportsUtils.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#include "nsLocalFile.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsVolumeMountLock.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Services.h"
+#include "base/task.h"
+
+#undef VOLUME_MANAGER_LOG_TAG
+#define VOLUME_MANAGER_LOG_TAG "nsVolumeService"
+#include "VolumeManagerLog.h"
+
+#include <stdlib.h>
+
+using namespace mozilla::dom;
+using namespace mozilla::services;
+
+namespace mozilla {
+namespace system {
+
+NS_IMPL_ISUPPORTS(nsVolumeService,
+ nsIVolumeService,
+ nsIDOMMozWakeLockListener)
+
+StaticRefPtr<nsVolumeService> nsVolumeService::sSingleton;
+
+// static
+already_AddRefed<nsVolumeService>
+nsVolumeService::GetSingleton()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingleton) {
+ sSingleton = new nsVolumeService();
+ }
+ RefPtr<nsVolumeService> volumeService = sSingleton.get();
+ return volumeService.forget();
+}
+
+// static
+void
+nsVolumeService::Shutdown()
+{
+ if (!sSingleton) {
+ return;
+ }
+ if (!XRE_IsParentProcess()) {
+ sSingleton = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIPowerManagerService> pmService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (pmService) {
+ pmService->RemoveWakeLockListener(sSingleton.get());
+ }
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(ShutdownVolumeServiceIOThread));
+
+ sSingleton = nullptr;
+}
+
+nsVolumeService::nsVolumeService()
+ : mArrayMonitor("nsVolumeServiceArray"),
+ mGotVolumesFromParent(false)
+{
+ sSingleton = this;
+
+ if (!XRE_IsParentProcess()) {
+ // VolumeServiceIOThread and the WakeLock listener should only run in the
+ // parent, so we return early.
+ return;
+ }
+
+ // Startup the IOThread side of things. The actual volume changes
+ // are captured by the IOThread and forwarded to main thread.
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(InitVolumeServiceIOThread, this));
+
+ nsCOMPtr<nsIPowerManagerService> pmService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (!pmService) {
+ return;
+ }
+ pmService->AddWakeLockListener(this);
+}
+
+nsVolumeService::~nsVolumeService()
+{
+}
+
+// Callback for nsIDOMMozWakeLockListener
+NS_IMETHODIMP
+nsVolumeService::Callback(const nsAString& aTopic, const nsAString& aState)
+{
+ CheckMountLock(aTopic, aState);
+ return NS_OK;
+}
+
+void nsVolumeService::DumpNoLock(const char* aLabel)
+{
+ mArrayMonitor.AssertCurrentThreadOwns();
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+
+ if (numVolumes == 0) {
+ LOG("%s: No Volumes!", aLabel);
+ return;
+ }
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ vol->Dump(aLabel);
+ }
+}
+
+NS_IMETHODIMP
+nsVolumeService::Dump(const nsAString& aLabel)
+{
+ MonitorAutoLock autoLock(mArrayMonitor);
+ DumpNoLock(NS_LossyConvertUTF16toASCII(aLabel).get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult)
+{
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ RefPtr<nsVolume> vol = FindVolumeByName(aVolName);
+ if (!vol) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ vol.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::GetVolumeByPath(const nsAString& aPath, nsIVolume **aResult)
+{
+ NS_ConvertUTF16toUTF8 utf8Path(aPath);
+ char realPathBuf[PATH_MAX];
+
+ while (realpath(utf8Path.get(), realPathBuf) < 0) {
+ if (errno != ENOENT) {
+ ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno);
+ return NSRESULT_FOR_ERRNO();
+ }
+ // The pathname we were passed doesn't exist, so we try stripping off trailing
+ // components until we get a successful call to realpath, or until we run out
+ // of components (if we finally get to /something then we also stop).
+ int32_t slashIndex = utf8Path.RFindChar('/');
+ if ((slashIndex == kNotFound) || (slashIndex == 0)) {
+ errno = ENOENT;
+ ERR("GetVolumeByPath: realpath on '%s' failed.", utf8Path.get());
+ return NSRESULT_FOR_ERRNO();
+ }
+ utf8Path.Assign(Substring(utf8Path, 0, slashIndex));
+ }
+
+ // The volume mount point is always a directory. Something like /mnt/sdcard
+ // Once we have a full qualified pathname with symlinks removed (which is
+ // what realpath does), we basically check if aPath starts with the mount
+ // point, but we don't want to have /mnt/sdcard match /mnt/sdcardfoo but we
+ // do want it to match /mnt/sdcard/foo
+ // So we add a trailing slash to the mount point and the pathname passed in
+ // prior to doing the comparison.
+
+ strlcat(realPathBuf, "/", sizeof(realPathBuf));
+
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ NS_ConvertUTF16toUTF8 volMountPointSlash(vol->MountPoint());
+ volMountPointSlash.Append('/');
+ nsDependentCSubstring testStr(realPathBuf, volMountPointSlash.Length());
+ if (volMountPointSlash.Equals(testStr)) {
+ vol.forget(aResult);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+NS_IMETHODIMP
+nsVolumeService::CreateOrGetVolumeByPath(const nsAString& aPath, nsIVolume** aResult)
+{
+ nsresult rv = GetVolumeByPath(aPath, aResult);
+ if (rv == NS_OK) {
+ return NS_OK;
+ }
+
+ // In order to support queries by the updater, we will fabricate a volume
+ // from the pathname, so that the caller can determine the volume size.
+ nsCOMPtr<nsIVolume> vol = new nsVolume(NS_LITERAL_STRING("fake"),
+ aPath, nsIVolume::STATE_MOUNTED,
+ -1 /* generation */,
+ true /* isMediaPresent*/,
+ false /* isSharing */,
+ false /* isFormatting */,
+ true /* isFake */,
+ false /* isUnmounting */,
+ false /* isRemovable */,
+ false /* isHotSwappable*/);
+ vol.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::GetVolumeNames(nsIArray** aVolNames)
+{
+ NS_ENSURE_ARG_POINTER(aVolNames);
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ *aVolNames = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> volNames =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ nsCOMPtr<nsISupportsString> isupportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = isupportsString->SetData(vol->Name());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = volNames->AppendElement(isupportsString, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ volNames.forget(aVolNames);
+ return NS_OK;
+}
+
+void
+nsVolumeService::GetVolumesForIPC(nsTArray<VolumeInfo>* aResult)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ VolumeInfo* volInfo = aResult->AppendElement();
+
+ volInfo->name() = vol->mName;
+ volInfo->mountPoint() = vol->mMountPoint;
+ volInfo->volState() = vol->mState;
+ volInfo->mountGeneration() = vol->mMountGeneration;
+ volInfo->isMediaPresent() = vol->mIsMediaPresent;
+ volInfo->isSharing() = vol->mIsSharing;
+ volInfo->isFormatting() = vol->mIsFormatting;
+ volInfo->isFake() = vol->mIsFake;
+ volInfo->isUnmounting() = vol->mIsUnmounting;
+ volInfo->isRemovable() = vol->mIsRemovable;
+ volInfo->isHotSwappable() = vol->mIsHotSwappable;
+ }
+}
+
+void
+nsVolumeService::RecvVolumesFromParent(const nsTArray<VolumeInfo>& aVolumes)
+{
+ if (XRE_IsParentProcess()) {
+ // We are the parent. Therefore our volumes are already correct.
+ return;
+ }
+ if (mGotVolumesFromParent) {
+ // We've already done this, no need to do it again.
+ return;
+ }
+
+ for (uint32_t i = 0; i < aVolumes.Length(); i++) {
+ const VolumeInfo& volInfo(aVolumes[i]);
+ RefPtr<nsVolume> vol = new nsVolume(volInfo.name(),
+ volInfo.mountPoint(),
+ volInfo.volState(),
+ volInfo.mountGeneration(),
+ volInfo.isMediaPresent(),
+ volInfo.isSharing(),
+ volInfo.isFormatting(),
+ volInfo.isFake(),
+ volInfo.isUnmounting(),
+ volInfo.isRemovable(),
+ volInfo.isHotSwappable());
+ UpdateVolume(vol, false);
+ }
+}
+
+NS_IMETHODIMP
+nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult)
+{
+ nsCOMPtr<nsIVolumeMountLock> mountLock = nsVolumeMountLock::Create(aVolumeName);
+ if (!mountLock) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mountLock.forget(aResult);
+ return NS_OK;
+}
+
+void
+nsVolumeService::CheckMountLock(const nsAString& aMountLockName,
+ const nsAString& aMountLockState)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsVolume> vol = FindVolumeByMountLockName(aMountLockName);
+ if (vol) {
+ vol->UpdateMountLock(aMountLockState);
+ }
+}
+
+already_AddRefed<nsVolume>
+nsVolumeService::FindVolumeByMountLockName(const nsAString& aMountLockName)
+{
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ nsString mountLockName;
+ vol->GetMountLockName(mountLockName);
+ if (mountLockName.Equals(aMountLockName)) {
+ return vol.forget();
+ }
+ }
+ return nullptr;
+}
+
+already_AddRefed<nsVolume>
+nsVolumeService::FindVolumeByName(const nsAString& aName, nsVolume::Array::index_type* aIndex)
+{
+ mArrayMonitor.AssertCurrentThreadOwns();
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ if (vol->Name().Equals(aName)) {
+ if (aIndex) {
+ *aIndex = volIndex;
+ }
+ return vol.forget();
+ }
+ }
+ return nullptr;
+}
+
+void
+nsVolumeService::UpdateVolume(nsVolume* aVolume, bool aNotifyObservers)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MonitorAutoLock autoLock(mArrayMonitor);
+ nsVolume::Array::index_type volIndex;
+ RefPtr<nsVolume> vol = FindVolumeByName(aVolume->Name(), &volIndex);
+ if (!vol) {
+ mVolumeArray.AppendElement(aVolume);
+ } else if (vol->Equals(aVolume) || (!vol->IsFake() && aVolume->IsFake())) {
+ // Ignore if nothing changed or if a fake tries to override a real volume.
+ return;
+ } else {
+ mVolumeArray.ReplaceElementAt(volIndex, aVolume);
+ }
+ aVolume->UpdateMountLock(vol);
+ }
+
+ if (!aNotifyObservers) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (!obs) {
+ return;
+ }
+ NS_ConvertUTF8toUTF16 stateStr(aVolume->StateStr());
+ obs->NotifyObservers(aVolume, NS_VOLUME_STATE_CHANGED, stateStr.get());
+}
+
+NS_IMETHODIMP
+nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path)
+{
+ if (XRE_IsParentProcess()) {
+ RefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_INIT,
+ -1 /* mountGeneration */,
+ true /* isMediaPresent */,
+ false /* isSharing */,
+ false /* isFormatting */,
+ true /* isFake */,
+ false /* isUnmounting */,
+ false /* isRemovable */,
+ false /* isHotSwappable */);
+ vol->SetState(nsIVolume::STATE_MOUNTED);
+ vol->LogState();
+ UpdateVolume(vol.get());
+ return NS_OK;
+ }
+
+ ContentChild::GetSingleton()->SendCreateFakeVolume(nsString(name), nsString(path));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::SetFakeVolumeState(const nsAString& name, int32_t state)
+{
+ if (XRE_IsParentProcess()) {
+ RefPtr<nsVolume> vol;
+ {
+ MonitorAutoLock autoLock(mArrayMonitor);
+ vol = FindVolumeByName(name);
+ }
+ if (!vol || !vol->IsFake()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Clone the existing volume so we can replace it
+ RefPtr<nsVolume> volume = new nsVolume(vol);
+ volume->SetState(state);
+ volume->LogState();
+ UpdateVolume(volume.get());
+ return NS_OK;
+ }
+
+ ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::RemoveFakeVolume(const nsAString& name)
+{
+ if (XRE_IsParentProcess()) {
+ SetFakeVolumeState(name, nsIVolume::STATE_NOMEDIA);
+ RemoveVolumeByName(name);
+ return NS_OK;
+ }
+
+ ContentChild::GetSingleton()->SendRemoveFakeVolume(nsString(name));
+ return NS_OK;
+}
+
+void
+nsVolumeService::RemoveVolumeByName(const nsAString& aName)
+{
+ {
+ MonitorAutoLock autoLock(mArrayMonitor);
+ nsVolume::Array::index_type volIndex;
+ RefPtr<nsVolume> vol = FindVolumeByName(aName, &volIndex);
+ if (!vol) {
+ return;
+ }
+ mVolumeArray.RemoveElementAt(volIndex);
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (!obs) {
+ return;
+ }
+ obs->NotifyObservers(nullptr, NS_VOLUME_REMOVED, nsString(aName).get());
+ }
+}
+
+/***************************************************************************
+* The UpdateVolumeRunnable creates an nsVolume and updates the main thread
+* data structure while running on the main thread.
+*/
+class UpdateVolumeRunnable : public Runnable
+{
+public:
+ UpdateVolumeRunnable(nsVolumeService* aVolumeService, const Volume* aVolume)
+ : mVolumeService(aVolumeService),
+ mVolume(new nsVolume(aVolume))
+ {
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d "
+ "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
+ mVolume->NameStr().get(), mVolume->StateStr(),
+ mVolume->MountGeneration(), (int)mVolume->IsMountLocked(),
+ (int)mVolume->IsMediaPresent(), mVolume->IsSharing(),
+ mVolume->IsFormatting(), mVolume->IsUnmounting(),
+ (int)mVolume->IsRemovable(), (int)mVolume->IsHotSwappable());
+
+ mVolumeService->UpdateVolume(mVolume);
+ mVolumeService = nullptr;
+ mVolume = nullptr;
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsVolumeService> mVolumeService;
+ RefPtr<nsVolume> mVolume;
+};
+
+void
+nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume)
+{
+ DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d "
+ "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
+ aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(),
+ aVolume->MountGeneration(), (int)aVolume->IsMountLocked(),
+ (int)aVolume->MediaPresent(), (int)aVolume->IsSharing(),
+ (int)aVolume->IsFormatting(), (int)aVolume->IsUnmounting(),
+ (int)aVolume->IsRemovable(), (int)aVolume->IsHotSwappable());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume));
+}
+
+} // namespace system
+} // namespace mozilla