michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsVolumeService.h" michael@0: michael@0: #include "Volume.h" michael@0: #include "VolumeManager.h" michael@0: #include "VolumeServiceIOThread.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDependentSubstring.h" michael@0: #include "nsIDOMWakeLockListener.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIPowerManagerService.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsIVolume.h" michael@0: #include "nsIVolumeService.h" michael@0: #include "nsLocalFile.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsVolumeMountLock.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: #define VOLUME_MANAGER_LOG_TAG "nsVolumeService" michael@0: #include "VolumeManagerLog.h" michael@0: michael@0: #include michael@0: michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::services; michael@0: michael@0: namespace mozilla { michael@0: namespace system { michael@0: michael@0: NS_IMPL_ISUPPORTS(nsVolumeService, michael@0: nsIVolumeService, michael@0: nsIDOMMozWakeLockListener) michael@0: michael@0: StaticRefPtr nsVolumeService::sSingleton; michael@0: michael@0: // static michael@0: already_AddRefed michael@0: nsVolumeService::GetSingleton() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!sSingleton) { michael@0: sSingleton = new nsVolumeService(); michael@0: } michael@0: nsRefPtr volumeService = sSingleton.get(); michael@0: return volumeService.forget(); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsVolumeService::Shutdown() michael@0: { michael@0: if (!sSingleton) { michael@0: return; michael@0: } michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: sSingleton = nullptr; michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr pmService = michael@0: do_GetService(POWERMANAGERSERVICE_CONTRACTID); michael@0: if (pmService) { michael@0: pmService->RemoveWakeLockListener(sSingleton.get()); michael@0: } michael@0: michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(ShutdownVolumeServiceIOThread)); michael@0: michael@0: sSingleton = nullptr; michael@0: } michael@0: michael@0: nsVolumeService::nsVolumeService() michael@0: : mArrayMonitor("nsVolumeServiceArray") michael@0: { michael@0: sSingleton = this; michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: // Request the initial state for all volumes. michael@0: ContentChild::GetSingleton()->SendBroadcastVolume(NS_LITERAL_STRING("")); michael@0: return; michael@0: } michael@0: michael@0: // Startup the IOThread side of things. The actual volume changes michael@0: // are captured by the IOThread and forwarded to main thread. michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(InitVolumeServiceIOThread, this)); michael@0: michael@0: nsCOMPtr pmService = michael@0: do_GetService(POWERMANAGERSERVICE_CONTRACTID); michael@0: if (!pmService) { michael@0: return; michael@0: } michael@0: pmService->AddWakeLockListener(this); michael@0: } michael@0: michael@0: nsVolumeService::~nsVolumeService() michael@0: { michael@0: } michael@0: michael@0: // Callback for nsIDOMMozWakeLockListener michael@0: NS_IMETHODIMP michael@0: nsVolumeService::Callback(const nsAString& aTopic, const nsAString& aState) michael@0: { michael@0: CheckMountLock(aTopic, aState); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::BroadcastVolume(const nsAString& aVolName) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: michael@0: if (aVolName.EqualsLiteral("")) { michael@0: nsVolume::Array volumeArray; michael@0: { michael@0: // Copy the array since we don't want to call BroadcastVolume michael@0: // while we're holding the lock. michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: volumeArray = mVolumeArray; michael@0: } michael@0: michael@0: // We treat being passed the empty string as "broadcast all volumes" michael@0: nsVolume::Array::size_type numVolumes = volumeArray.Length(); michael@0: nsVolume::Array::index_type volIndex; michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: const nsString& volName(volumeArray[volIndex]->Name()); michael@0: if (!volName.EqualsLiteral("")) { michael@0: // Note: The volume service is the only entity that should be able to michael@0: // modify the array of volumes. So we shouldn't have any issues with michael@0: // the array being modified under our feet (Since we're the volume michael@0: // service the array can't change until after we finish iterating the michael@0: // the loop). michael@0: nsresult rv = BroadcastVolume(volName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: nsRefPtr vol; michael@0: { michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: vol = FindVolumeByName(aVolName); michael@0: } michael@0: if (!vol) { michael@0: ERR("BroadcastVolume: Unable to locate volume '%s'", michael@0: NS_LossyConvertUTF16toASCII(aVolName).get()); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsCOMPtr obs = GetObserverService(); michael@0: NS_ENSURE_TRUE(obs, NS_NOINTERFACE); michael@0: michael@0: DBG("nsVolumeService::BroadcastVolume for '%s'", vol->NameStr().get()); michael@0: NS_ConvertUTF8toUTF16 stateStr(vol->StateStr()); michael@0: obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult) michael@0: { michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: michael@0: nsRefPtr vol = FindVolumeByName(aVolName); michael@0: if (!vol) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: vol.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::GetVolumeByPath(const nsAString& aPath, nsIVolume **aResult) michael@0: { michael@0: NS_ConvertUTF16toUTF8 utf8Path(aPath); michael@0: char realPathBuf[PATH_MAX]; michael@0: michael@0: while (realpath(utf8Path.get(), realPathBuf) < 0) { michael@0: if (errno != ENOENT) { michael@0: ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno); michael@0: return NSRESULT_FOR_ERRNO(); michael@0: } michael@0: // The pathname we were passed doesn't exist, so we try stripping off trailing michael@0: // components until we get a successful call to realpath, or until we run out michael@0: // of components (if we finally get to /something then we also stop). michael@0: int32_t slashIndex = utf8Path.RFindChar('/'); michael@0: if ((slashIndex == kNotFound) || (slashIndex == 0)) { michael@0: errno = ENOENT; michael@0: ERR("GetVolumeByPath: realpath on '%s' failed.", utf8Path.get()); michael@0: return NSRESULT_FOR_ERRNO(); michael@0: } michael@0: utf8Path.Assign(Substring(utf8Path, 0, slashIndex)); michael@0: } michael@0: michael@0: // The volume mount point is always a directory. Something like /mnt/sdcard michael@0: // Once we have a full qualified pathname with symlinks removed (which is michael@0: // what realpath does), we basically check if aPath starts with the mount michael@0: // point, but we don't want to have /mnt/sdcard match /mnt/sdcardfoo but we michael@0: // do want it to match /mnt/sdcard/foo michael@0: // So we add a trailing slash to the mount point and the pathname passed in michael@0: // prior to doing the comparison. michael@0: michael@0: strlcat(realPathBuf, "/", sizeof(realPathBuf)); michael@0: michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: michael@0: nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); michael@0: nsVolume::Array::index_type volIndex; michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: nsRefPtr vol = mVolumeArray[volIndex]; michael@0: NS_ConvertUTF16toUTF8 volMountPointSlash(vol->MountPoint()); michael@0: volMountPointSlash.Append(NS_LITERAL_CSTRING("/")); michael@0: nsDependentCSubstring testStr(realPathBuf, volMountPointSlash.Length()); michael@0: if (volMountPointSlash.Equals(testStr)) { michael@0: vol.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::CreateOrGetVolumeByPath(const nsAString& aPath, nsIVolume** aResult) michael@0: { michael@0: nsresult rv = GetVolumeByPath(aPath, aResult); michael@0: if (rv == NS_OK) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // In order to support queries by the updater, we will fabricate a volume michael@0: // from the pathname, so that the caller can determine the volume size. michael@0: nsCOMPtr vol = new nsVolume(NS_LITERAL_STRING("fake"), michael@0: aPath, nsIVolume::STATE_MOUNTED, michael@0: -1 /* generation */, michael@0: true /* isMediaPresent*/, michael@0: false /* isSharing */, michael@0: false /* isFormatting */); michael@0: vol.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::GetVolumeNames(nsTArray& aVolNames) michael@0: { michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: michael@0: nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); michael@0: nsVolume::Array::index_type volIndex; michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: nsRefPtr vol = mVolumeArray[volIndex]; michael@0: aVolNames.AppendElement(vol->Name()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult) michael@0: { michael@0: nsCOMPtr mountLock = nsVolumeMountLock::Create(aVolumeName); michael@0: if (!mountLock) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: mountLock.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsVolumeService::CheckMountLock(const nsAString& aMountLockName, michael@0: const nsAString& aMountLockState) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsRefPtr vol = FindVolumeByMountLockName(aMountLockName); michael@0: if (vol) { michael@0: vol->UpdateMountLock(aMountLockState); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsVolumeService::FindVolumeByMountLockName(const nsAString& aMountLockName) michael@0: { michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: michael@0: nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); michael@0: nsVolume::Array::index_type volIndex; michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: nsRefPtr vol = mVolumeArray[volIndex]; michael@0: nsString mountLockName; michael@0: vol->GetMountLockName(mountLockName); michael@0: if (mountLockName.Equals(aMountLockName)) { michael@0: return vol.forget(); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsVolumeService::FindVolumeByName(const nsAString& aName) michael@0: { michael@0: mArrayMonitor.AssertCurrentThreadOwns(); michael@0: michael@0: nsVolume::Array::size_type numVolumes = mVolumeArray.Length(); michael@0: nsVolume::Array::index_type volIndex; michael@0: for (volIndex = 0; volIndex < numVolumes; volIndex++) { michael@0: nsRefPtr vol = mVolumeArray[volIndex]; michael@0: if (vol->Name().Equals(aName)) { michael@0: return vol.forget(); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: //static michael@0: already_AddRefed michael@0: nsVolumeService::CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake /*= false*/) michael@0: { michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: michael@0: nsRefPtr vol; michael@0: vol = FindVolumeByName(aName); michael@0: if (vol) { michael@0: return vol.forget(); michael@0: } michael@0: // Volume not found - add a new one michael@0: vol = new nsVolume(aName); michael@0: vol->SetIsFake(aIsFake); michael@0: mVolumeArray.AppendElement(vol); michael@0: return vol.forget(); michael@0: } michael@0: michael@0: void michael@0: nsVolumeService::UpdateVolume(nsIVolume* aVolume) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsString volName; michael@0: aVolume->GetName(volName); michael@0: bool aIsFake; michael@0: aVolume->GetIsFake(&aIsFake); michael@0: nsRefPtr vol = CreateOrFindVolumeByName(volName, aIsFake); michael@0: if (vol->Equals(aVolume)) { michael@0: // Nothing has really changed. Don't bother telling anybody. michael@0: return; michael@0: } michael@0: michael@0: if (!vol->IsFake() && aIsFake) { michael@0: // Prevent an incoming fake volume from overriding an existing real volume. michael@0: return; michael@0: } michael@0: michael@0: vol->Set(aVolume); michael@0: nsCOMPtr obs = GetObserverService(); michael@0: if (!obs) { michael@0: return; michael@0: } michael@0: NS_ConvertUTF8toUTF16 stateStr(vol->StateStr()); michael@0: obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path) michael@0: { michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: nsRefPtr vol = new nsVolume(name, path, nsIVolume::STATE_INIT, michael@0: -1 /* mountGeneration */, michael@0: true /* isMediaPresent */, michael@0: false /* isSharing */, michael@0: false /* isFormatting */); michael@0: vol->SetIsFake(true); michael@0: vol->LogState(); michael@0: UpdateVolume(vol.get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ContentChild::GetSingleton()->SendCreateFakeVolume(nsString(name), nsString(path)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsVolumeService::SetFakeVolumeState(const nsAString& name, int32_t state) michael@0: { michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: nsRefPtr vol; michael@0: { michael@0: MonitorAutoLock autoLock(mArrayMonitor); michael@0: vol = FindVolumeByName(name); michael@0: } michael@0: if (!vol || !vol->IsFake()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: vol->SetState(state); michael@0: vol->LogState(); michael@0: UpdateVolume(vol.get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /*************************************************************************** michael@0: * The UpdateVolumeRunnable creates an nsVolume and updates the main thread michael@0: * data structure while running on the main thread. michael@0: */ michael@0: class UpdateVolumeRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: UpdateVolumeRunnable(nsVolumeService* aVolumeService, const Volume* aVolume) michael@0: : mVolumeService(aVolumeService), michael@0: mVolume(new nsVolume(aVolume)) michael@0: { michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d " michael@0: "media %d sharing %d formatting %d", michael@0: mVolume->NameStr().get(), mVolume->StateStr(), michael@0: mVolume->MountGeneration(), (int)mVolume->IsMountLocked(), michael@0: (int)mVolume->IsMediaPresent(), mVolume->IsSharing(), michael@0: mVolume->IsFormatting()); michael@0: michael@0: mVolumeService->UpdateVolume(mVolume); michael@0: mVolumeService = nullptr; michael@0: mVolume = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mVolumeService; michael@0: nsRefPtr mVolume; michael@0: }; michael@0: michael@0: void michael@0: nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume) michael@0: { michael@0: DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d " michael@0: "media %d sharing %d formatting %d", michael@0: aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(), michael@0: aVolume->MountGeneration(), (int)aVolume->IsMountLocked(), michael@0: (int)aVolume->MediaPresent(), (int)aVolume->IsSharing(), michael@0: (int)aVolume->IsFormatting()); michael@0: MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); michael@0: NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume)); michael@0: } michael@0: michael@0: } // namespace system michael@0: } // namespace mozilla