/* 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 "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsDependentSubstring.h" #include "nsIDOMWakeLockListener.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIPowerManagerService.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" #define VOLUME_MANAGER_LOG_TAG "nsVolumeService" #include "VolumeManagerLog.h" #include using namespace mozilla::dom; using namespace mozilla::services; namespace mozilla { namespace system { NS_IMPL_THREADSAFE_ISUPPORTS2(nsVolumeService, nsIVolumeService, nsIDOMMozWakeLockListener) StaticRefPtr nsVolumeService::sSingleton; // static already_AddRefed nsVolumeService::GetSingleton() { MOZ_ASSERT(NS_IsMainThread()); if (!sSingleton) { sSingleton = new nsVolumeService(); } nsRefPtr volumeService = sSingleton.get(); return volumeService.forget(); } // static void nsVolumeService::Shutdown() { if (!sSingleton) { return; } if (XRE_GetProcessType() != GeckoProcessType_Default) { sSingleton = nullptr; return; } nsCOMPtr pmService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); if (pmService) { pmService->RemoveWakeLockListener(sSingleton.get()); } XRE_GetIOMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(ShutdownVolumeServiceIOThread)); sSingleton = nullptr; } nsVolumeService::nsVolumeService() : mArrayMonitor("nsVolumeServiceArray") { sSingleton = this; if (XRE_GetProcessType() != GeckoProcessType_Default) { // Request the initial state for all volumes. ContentChild::GetSingleton()->SendBroadcastVolume(NS_LITERAL_STRING("")); return; } // Startup the IOThread side of things. The actual volume changes // are captured by the IOThread and forwarded to main thread. XRE_GetIOMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(InitVolumeServiceIOThread, this)); nsCOMPtr 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; } NS_IMETHODIMP nsVolumeService::BroadcastVolume(const nsAString& aVolName) { MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); if (aVolName.EqualsLiteral("")) { nsVolume::Array volumeArray; { // Copy the array since we don't want to call BroadcastVolume // while we're holding the lock. MonitorAutoLock autoLock(mArrayMonitor); volumeArray = mVolumeArray; } // We treat being passed the empty string as "broadcast all volumes" nsVolume::Array::size_type numVolumes = volumeArray.Length(); nsVolume::Array::index_type volIndex; for (volIndex = 0; volIndex < numVolumes; volIndex++) { const nsString& volName(volumeArray[volIndex]->Name()); if (!volName.EqualsLiteral("")) { // Note: The volume service is the only entity that should be able to // modify the array of volumes. So we shouldn't have any issues with // the array being modified under our feet (Since we're the volume // service the array can't change until after we finish iterating the // the loop). nsresult rv = BroadcastVolume(volName); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } nsRefPtr vol; { MonitorAutoLock autoLock(mArrayMonitor); vol = FindVolumeByName(aVolName); } if (!vol) { ERR("BroadcastVolume: Unable to locate volume '%s'", NS_LossyConvertUTF16toASCII(aVolName).get()); return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr obs = GetObserverService(); NS_ENSURE_TRUE(obs, NS_NOINTERFACE); DBG("nsVolumeService::BroadcastVolume for '%s'", vol->NameStr().get()); NS_ConvertUTF8toUTF16 stateStr(vol->StateStr()); obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get()); return NS_OK; } NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult) { MonitorAutoLock autoLock(mArrayMonitor); nsRefPtr 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++) { nsRefPtr vol = mVolumeArray[volIndex]; NS_ConvertUTF16toUTF8 volMountPointSlash(vol->MountPoint()); volMountPointSlash.Append(NS_LITERAL_CSTRING("/")); 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 vol = new nsVolume(NS_LITERAL_STRING("fake"), aPath, nsIVolume::STATE_MOUNTED, -1 /*generation*/); vol.forget(aResult); return NS_OK; } NS_IMETHODIMP nsVolumeService::GetVolumeNames(nsTArray& aVolNames) { MonitorAutoLock autoLock(mArrayMonitor); nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); nsVolume::Array::index_type volIndex; for (volIndex = 0; volIndex < numVolumes; volIndex++) { nsRefPtr vol = mVolumeArray[volIndex]; aVolNames.AppendElement(vol->Name()); } return NS_OK; } NS_IMETHODIMP nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult) { nsCOMPtr 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_GetProcessType() == GeckoProcessType_Default); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr vol = FindVolumeByMountLockName(aMountLockName); if (vol) { vol->UpdateMountLock(aMountLockState); } } already_AddRefed 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++) { nsRefPtr vol = mVolumeArray[volIndex]; nsString mountLockName; vol->GetMountLockName(mountLockName); if (mountLockName.Equals(aMountLockName)) { return vol.forget(); } } return nullptr; } already_AddRefed nsVolumeService::FindVolumeByName(const nsAString& aName) { mArrayMonitor.AssertCurrentThreadOwns(); nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); nsVolume::Array::index_type volIndex; for (volIndex = 0; volIndex < numVolumes; volIndex++) { nsRefPtr vol = mVolumeArray[volIndex]; if (vol->Name().Equals(aName)) { return vol.forget(); } } return nullptr; } //static already_AddRefed nsVolumeService::CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake /*= false*/) { MonitorAutoLock autoLock(mArrayMonitor); nsRefPtr vol; vol = FindVolumeByName(aName); if (vol) { return vol.forget(); } // Volume not found - add a new one vol = new nsVolume(aName); vol->SetIsFake(aIsFake); mVolumeArray.AppendElement(vol); return vol.forget(); } void nsVolumeService::UpdateVolume(nsIVolume* aVolume) { MOZ_ASSERT(NS_IsMainThread()); nsString volName; aVolume->GetName(volName); bool aIsFake; aVolume->GetIsFake(&aIsFake); nsRefPtr vol = CreateOrFindVolumeByName(volName, aIsFake); if (vol->Equals(aVolume)) { // Nothing has really changed. Don't bother telling anybody. return; } if (!vol->IsFake() && aIsFake) { // Prevent an incoming fake volume from overriding an existing real volume. return; } vol->Set(aVolume); nsCOMPtr obs = GetObserverService(); if (!obs) { return; } NS_ConvertUTF8toUTF16 stateStr(vol->StateStr()); obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get()); } NS_IMETHODIMP nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path) { if (XRE_GetProcessType() == GeckoProcessType_Default) { nsRefPtr vol = new nsVolume(name, path, nsIVolume::STATE_INIT, -1); vol->SetIsFake(true); 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_GetProcessType() == GeckoProcessType_Default) { nsRefPtr vol; { MonitorAutoLock autoLock(mArrayMonitor); vol = FindVolumeByName(name); } if (!vol || !vol->IsFake()) { return NS_ERROR_NOT_AVAILABLE; } vol->SetState(state); UpdateVolume(vol.get()); return NS_OK; } ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state); return NS_OK; } /*************************************************************************** * The UpdateVolumeRunnable creates an nsVolume and updates the main thread * data structure while running on the main thread. */ class UpdateVolumeRunnable : public nsRunnable { public: UpdateVolumeRunnable(nsVolumeService* aVolumeService, const Volume* aVolume) : mVolumeService(aVolumeService), mVolume(new nsVolume(aVolume)) { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d", mVolume->NameStr().get(), mVolume->StateStr(), mVolume->MountGeneration(), (int)mVolume->IsMountLocked()); mVolumeService->UpdateVolume(mVolume); mVolumeService = nullptr; mVolume = nullptr; return NS_OK; } private: nsRefPtr mVolumeService; nsRefPtr mVolume; }; void nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume) { DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d", aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(), aVolume->MountGeneration(), (int)aVolume->IsMountLocked()); MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume)); } } // namespace system } // namespace mozilla