/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AutoMounter.h" #include "nsVolumeService.h" #include "AutoMounterSetting.h" #include "base/message_loop.h" #include "base/task.h" #include "mozilla/AutoRestore.h" #include "mozilla/FileUtils.h" #include "mozilla/Hal.h" #include "mozilla/StaticPtr.h" #include "MozMtpServer.h" #include "MozMtpStorage.h" #include "nsCharSeparatedTokenizer.h" #include "nsMemory.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" #include "OpenFileFinder.h" #include "Volume.h" #include "VolumeManager.h" #include "nsIStatusReporter.h" USING_MTP_NAMESPACE /************************************************************************** * * The following "switch" files are available for monitoring usb * connections: * * /sys/devices/virtual/switch/usb_connected/state * /sys/devices/virtual/switch/usb_configuration/state * * Under gingerbread, only the usb_configuration seems to be available. * Starting with honeycomb, usb_connected was also added. * * When a cable insertion/removal occurs, then a uevent similar to the * following will be generted: * * change@/devices/virtual/switch/usb_configuration * ACTION=change * DEVPATH=/devices/virtual/switch/usb_configuration * SUBSYSTEM=switch * SWITCH_NAME=usb_configuration * SWITCH_STATE=0 * SEQNUM=5038 * * SWITCH_STATE will be 0 after a removal and 1 after an insertion * **************************************************************************/ #define USB_CONFIGURATION_SWITCH_NAME NS_LITERAL_STRING("usb_configuration") #define GB_SYS_UMS_ENABLE "/sys/devices/virtual/usb_composite/usb_mass_storage/enable" #define GB_SYS_USB_CONFIGURED "/sys/devices/virtual/switch/usb_configuration/state" #define ICS_SYS_USB_FUNCTIONS "/sys/devices/virtual/android_usb/android0/functions" #define ICS_SYS_UMS_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mass_storage" #define ICS_SYS_MTP_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mtp" #define ICS_SYS_USB_STATE "/sys/devices/virtual/android_usb/android0/state" #undef USE_DEBUG // MozMtpDatabase.h also defines USE_DEBUG #define USE_DEBUG 0 #undef LOG #undef LOGW #undef ERR #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounter", ## args) #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "AutoMounter", ## args) #define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounter", ## args) #undef DBG #if USE_DEBUG #define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "AutoMounter" , ## args) #else #define DBG(args...) #endif namespace mozilla { namespace system { #define SYS_USB_CONFIG "sys.usb.config" #define PERSIST_SYS_USB_CONFIG "persist.sys.usb.config" #define USB_FUNC_ADB "adb" #define USB_FUNC_MTP "mtp" #define USB_FUNC_NONE "none" #define USB_FUNC_RNDIS "rndis" #define USB_FUNC_UMS "mass_storage" #define USB_FUNC_DEFAULT "default" class AutoMounter; static void SetAutoMounterStatus(int32_t aStatus); /***************************************************************************/ inline const char* SwitchStateStr(const hal::SwitchEvent& aEvent) { return aEvent.status() == hal::SWITCH_STATE_ON ? "plugged" : "unplugged"; } /***************************************************************************/ static bool IsUsbCablePluggedIn() { #if 0 // Use this code when bug 745078 gets fixed (or use whatever the // appropriate method is) return GetCurrentSwitchEvent(SWITCH_USB) == hal::SWITCH_STATE_ON; #else // Until then, just go read the file directly if (access(ICS_SYS_USB_STATE, F_OK) == 0) { char usbState[20]; if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) { DBG("IsUsbCablePluggedIn: state = '%s'", usbState); return strcmp(usbState, "CONFIGURED") == 0 || strcmp(usbState, "CONNECTED") == 0; } ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno)); return false; } bool configured; if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) { return configured; } ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno)); return false; #endif } static bool IsUsbConfigured() { if (access(ICS_SYS_USB_STATE, F_OK) == 0) { char usbState[20]; if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) { DBG("IsUsbConfigured: state = '%s'", usbState); return strcmp(usbState, "CONFIGURED") == 0; } ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno)); return false; } bool configured; if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) { return configured; } ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno)); return false; } /***************************************************************************/ // The AutoVolumeManagerStateObserver allows the AutoMounter to know when // the volume manager changes state (i.e. it has finished initialization) class AutoVolumeManagerStateObserver : public VolumeManager::StateObserver { public: virtual void Notify(const VolumeManager::StateChangedEvent& aEvent); }; // The AutoVolumeEventObserver allows the AutoMounter to know about card // insertion and removal, as well as state changes in the volume. class AutoVolumeEventObserver : public Volume::EventObserver { public: virtual void Notify(Volume* const& aEvent); }; class AutoMounterResponseCallback : public VolumeResponseCallback { public: AutoMounterResponseCallback() : mErrorCount(0) { } protected: virtual void ResponseReceived(const VolumeCommand* aCommand); private: const static int kMaxErrorCount = 3; // Max number of errors before we give up int mErrorCount; }; /***************************************************************************/ class AutoMounter { public: NS_INLINE_DECL_REFCOUNTING(AutoMounter) typedef nsTArray> VolumeArray; AutoMounter() : mState(STATE_IDLE), mResponseCallback(new AutoMounterResponseCallback), mMode(AUTOMOUNTER_DISABLE) { VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver); Volume::RegisterVolumeObserver(&mVolumeEventObserver, "AutoMounter"); // It's possible that the VolumeManager is already in the READY state, // so we call CheckVolumeSettings here to cover that case. Otherwise, // we'll pick it up when the VolumeManage state changes to VOLUMES_READY. CheckVolumeSettings(); DBG("Calling UpdateState from constructor"); UpdateState(); } void CheckVolumeSettings() { if (VolumeManager::State() != VolumeManager::VOLUMES_READY) { DBG("CheckVolumeSettings: VolumeManager is NOT READY yet"); return; } DBG("CheckVolumeSettings: VolumeManager is READY"); // The VolumeManager knows about all of the volumes from vold. We now // know the names of all of the volumes, so we can find out what the // initial sharing settings are set to. VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); VolumeManager::VolumeArray::index_type i; for (i = 0; i < numVolumes; i++) { RefPtr vol = VolumeManager::GetVolume(i); if (vol) { // We need to pick up the intial value of the // ums.volume.NAME.enabled setting. AutoMounterSetting::CheckVolumeSettings(vol->Name()); // Note: eventually CheckVolumeSettings will call // AutoMounter::SetSharingMode, which will in turn call // UpdateState if needed. } } } void UpdateState(); void GetStatus(bool& umsAvail, bool& umsConfigured, bool& umsEnabled, bool& mtpAvail, bool& mtpConfigured, bool& mtpEnabled, bool& rndisConfigured); nsresult Dump(nsACString& desc); void ConfigureUsbFunction(const char* aUsbFunc); bool StartMtpServer(); void StopMtpServer(); void StartUmsSharing(); void StopUmsSharing(); const char* ModeStr(int32_t aMode) { switch (aMode) { case AUTOMOUNTER_DISABLE: return "Disable"; case AUTOMOUNTER_ENABLE_UMS: return "Enable-UMS"; case AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED: return "DisableWhenUnplugged"; case AUTOMOUNTER_ENABLE_MTP: return "Enable-MTP"; } return "??? Unknown ???"; } bool IsModeEnabled(int32_t aMode) { return aMode == AUTOMOUNTER_ENABLE_MTP || aMode == AUTOMOUNTER_ENABLE_UMS; } void SetMode(int32_t aMode) { if ((aMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && (mMode == AUTOMOUNTER_DISABLE)) { // If it's already disabled, then leave it as disabled. // AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED implies "enabled until unplugged" aMode = AUTOMOUNTER_DISABLE; } if (aMode == AUTOMOUNTER_DISABLE && mMode == AUTOMOUNTER_ENABLE_UMS && IsUsbCablePluggedIn()) { // On many devices (esp non-Samsung), we can't force the disable, so we // need to defer until the USB cable is actually unplugged. // See bug 777043. // // Otherwise our attempt to disable it will fail, and we'll wind up in a bad // state where the AutoMounter thinks that Sharing has been turned off, but // the files are actually still being Shared because the attempt to unshare // failed. LOG("Attempting to disable UMS. Deferring until USB cable is unplugged."); aMode = AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED; } if (aMode != mMode) { LOG("Changing mode from '%s' to '%s'", ModeStr(mMode), ModeStr(aMode)); mMode = aMode; DBG("Calling UpdateState due to mode set to %d", mMode); UpdateState(); } } void SetSharingMode(const nsACString& aVolumeName, bool aAllowSharing) { RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); if (!vol) { return; } if (vol->IsSharingEnabled() == aAllowSharing) { return; } vol->SetUnmountRequested(false); vol->SetMountRequested(false); vol->SetSharingEnabled(aAllowSharing); DBG("Calling UpdateState due to volume %s sharing set to %d", vol->NameStr(), (int)aAllowSharing); UpdateState(); } void FormatVolume(const nsACString& aVolumeName) { RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); if (!vol) { return; } if (vol->IsFormatRequested()) { return; } vol->SetUnmountRequested(false); vol->SetMountRequested(false); vol->SetFormatRequested(true); DBG("Calling UpdateState due to volume %s formatting set to %d", vol->NameStr(), (int)vol->IsFormatRequested()); UpdateState(); } void MountVolume(const nsACString& aVolumeName) { RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); if (!vol) { return; } vol->SetUnmountRequested(false); if (vol->IsMountRequested() || vol->mState == nsIVolume::STATE_MOUNTED) { return; } vol->SetMountRequested(true); DBG("Calling UpdateState due to volume %s mounting set to %d", vol->NameStr(), (int)vol->IsMountRequested()); UpdateState(); } void UnmountVolume(const nsACString& aVolumeName) { RefPtr vol = VolumeManager::FindVolumeByName(aVolumeName); if (!vol) { return; } if (vol->IsUnmountRequested()) { return; } vol->SetMountRequested(false); vol->SetUnmountRequested(true); DBG("Calling UpdateState due to volume %s unmounting set to %d", vol->NameStr(), (int)vol->IsUnmountRequested()); UpdateState(); } protected: ~AutoMounter() { Volume::UnregisterVolumeObserver(&mVolumeEventObserver, "AutoMounter"); VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver); } private: enum STATE { // IDLE - Nothing is being shared STATE_IDLE, // We've detected that conditions are right to enable mtp. So we've // set sys.usb.config to include mtp, and we're waiting for the USB // subsystem to be "configured". Once mtp shows up in // then we know // that its been configured and we can open /dev/mtp_usb STATE_MTP_CONFIGURING, // mtp has been configured (i.e. mtp now shows up in // /sys/devices/virtual/android_usb/android0/functions so we can start // the mtp server. STATE_MTP_STARTED, // The mtp server has reported sessionStarted. We'll leave this state // when we receive sessionEnded. STATE_MTP_CONNECTED, // We've added mass_storage (aka UMS) to sys.usb.config and we're waiting for // mass_storage to appear in /sys/devices/virtual/android_usb/android0/functions STATE_UMS_CONFIGURING, // mass_storage has been configured and we can start sharing once the user // enables it. STATE_UMS_CONFIGURED, // USB Tethering is enabled STATE_RNDIS_CONFIGURED, }; const char *StateStr(STATE aState) { switch (aState) { case STATE_IDLE: return "IDLE"; case STATE_MTP_CONFIGURING: return "MTP_CONFIGURING"; case STATE_MTP_CONNECTED: return "MTP_CONNECTED"; case STATE_MTP_STARTED: return "MTP_STARTED"; case STATE_UMS_CONFIGURING: return "UMS_CONFIGURING"; case STATE_UMS_CONFIGURED: return "UMS_CONFIGURED"; case STATE_RNDIS_CONFIGURED: return "RNDIS_CONFIGURED"; } return "STATE_???"; } void SetState(STATE aState) { const char *oldStateStr = StateStr(mState); mState = aState; const char *newStateStr = StateStr(mState); LOG("AutoMounter state changed from %s to %s", oldStateStr, newStateStr); } STATE mState; AutoVolumeEventObserver mVolumeEventObserver; AutoVolumeManagerStateObserver mVolumeManagerStateObserver; RefPtr mResponseCallback; int32_t mMode; MozMtpStorage::Array mMozMtpStorage; }; static StaticRefPtr sAutoMounter; static StaticRefPtr sMozMtpServer; // The following is for status reporter enum STATE_REPORTER_STATE { REPORTER_UNREGISTERED, REPORTER_REGISTERED }; static int status_reporter_progress = REPORTER_UNREGISTERED; nsresult getState(nsACString &desc) { sAutoMounter->Dump(desc); return NS_OK; } NS_STATUS_REPORTER_IMPLEMENT(AutoMounter, "AutoMounter", getState); /***************************************************************************/ void AutoVolumeManagerStateObserver::Notify(const VolumeManager::StateChangedEvent &) { LOG("VolumeManager state changed event: %s", VolumeManager::StateStr()); if (!sAutoMounter) { return; } // In the event that the VolumeManager just entered the VOLUMES_READY state, // we call CheckVolumeSettings here (it's possible that this method never // gets called if the VolumeManager was already in the VOLUMES_READY state // by the time the AutoMounter was constructed). sAutoMounter->CheckVolumeSettings(); DBG("Calling UpdateState due to VolumeManagerStateObserver"); sAutoMounter->UpdateState(); } void AutoVolumeEventObserver::Notify(Volume * const &) { if (!sAutoMounter) { return; } DBG("Calling UpdateState due to VolumeEventStateObserver"); sAutoMounter->UpdateState(); } void AutoMounterResponseCallback::ResponseReceived(const VolumeCommand* aCommand) { if (WasSuccessful()) { DBG("Calling UpdateState due to Volume::OnSuccess"); mErrorCount = 0; sAutoMounter->UpdateState(); return; } ERR("Command '%s' failed: %d '%s'", aCommand->CmdStr(), ResponseCode(), ResponseStr().get()); if (++mErrorCount < kMaxErrorCount) { DBG("Calling UpdateState due to VolumeResponseCallback::OnError"); sAutoMounter->UpdateState(); } } static bool IsUsbFunctionEnabled(const char* aConfig, const char* aUsbFunc) { nsAutoCString config(aConfig); nsCCharSeparatedTokenizer tokenizer(config, ','); while (tokenizer.hasMoreTokens()) { nsAutoCString token(tokenizer.nextToken()); if (token.Equals(aUsbFunc)) { DBG("IsUsbFunctionEnabled('%s', '%s'): returning true", aConfig, aUsbFunc); return true; } } DBG("IsUsbFunctionEnabled('%s', '%s'): returning false", aConfig, aUsbFunc); return false; } static void SetUsbFunction(const char* aUsbFunc) { char oldSysUsbConfig[PROPERTY_VALUE_MAX]; property_get(SYS_USB_CONFIG, oldSysUsbConfig, ""); if (strcmp(oldSysUsbConfig, USB_FUNC_NONE) == 0) { // It's quite possible that sys.usb.config may have the value "none". We // convert that to an empty string here, and at the end we convert the // empty string back to "none". oldSysUsbConfig[0] = '\0'; } if (IsUsbFunctionEnabled(oldSysUsbConfig, aUsbFunc)) { // The function is already configured. Nothing else to do. DBG("SetUsbFunction('%s') - already set - nothing to do", aUsbFunc); return; } char newSysUsbConfig[PROPERTY_VALUE_MAX]; if (strcmp(aUsbFunc, USB_FUNC_MTP) == 0) { // We're enabling MTP. For this we'll wind up using mtp, or mtp,adb strlcpy(newSysUsbConfig, USB_FUNC_MTP, sizeof(newSysUsbConfig)); } else if (strcmp(aUsbFunc, USB_FUNC_UMS) == 0) { // We're enabling UMS. For this we make the assumption that the persisted // property has mass_storage enabled. property_get(PERSIST_SYS_USB_CONFIG, newSysUsbConfig, ""); } else if (strcmp(aUsbFunc, USB_FUNC_DEFAULT) == 0) { // Set the property as PERSIST_SYS_USB_CONFIG property_get(PERSIST_SYS_USB_CONFIG, newSysUsbConfig, ""); } else { printf_stderr("AutoMounter::SetUsbFunction Unrecognized aUsbFunc '%s'\n", aUsbFunc); MOZ_ASSERT(0); return; } // Make sure the new value that we write into sys.usb.config keeps the adb // (or non-adb) of the current string. if (IsUsbFunctionEnabled(oldSysUsbConfig, USB_FUNC_ADB)) { // ADB was turned on - keep it on. if (!IsUsbFunctionEnabled(newSysUsbConfig, USB_FUNC_ADB)) { // Add adb to the new string strlcat(newSysUsbConfig, ",", sizeof(newSysUsbConfig)); strlcat(newSysUsbConfig, USB_FUNC_ADB, sizeof(newSysUsbConfig)); } } else { // ADB was turned off - keep it off if (IsUsbFunctionEnabled(newSysUsbConfig, USB_FUNC_ADB)) { // Remove ADB from the new string. if (strcmp(newSysUsbConfig, USB_FUNC_ADB) == 0) { newSysUsbConfig[0] = '\0'; } else { nsAutoCString withoutAdb(newSysUsbConfig); withoutAdb.ReplaceSubstring( "," USB_FUNC_ADB, ""); strlcpy(newSysUsbConfig, withoutAdb.get(), sizeof(newSysUsbConfig)); } } } // If the persisted function didn't have mass_storage (this happens on // the nexus 4/5, then we can get to here and have oldSysUsbConfig equal // to newSysUsbConfig. So we need to check for that. if (strcmp(oldSysUsbConfig, newSysUsbConfig) == 0) { DBG("SetUsbFunction('%s') %s is already set to '%s' - nothing to do", aUsbFunc, SYS_USB_CONFIG, newSysUsbConfig); return; } if (newSysUsbConfig[0] == '\0') { // Convert the empty string back to the string "none" strlcpy(newSysUsbConfig, USB_FUNC_NONE, sizeof(newSysUsbConfig)); } LOG("SetUsbFunction(%s) %s from '%s' to '%s'", aUsbFunc, SYS_USB_CONFIG, oldSysUsbConfig, newSysUsbConfig); property_set(SYS_USB_CONFIG, newSysUsbConfig); } bool AutoMounter::StartMtpServer() { if (sMozMtpServer) { // Mtp Server is already running - nothing to do return true; } LOG("Starting MtpServer"); // For debugging, Change the #if 0 to #if 1, and then attach gdb during // the 5 second interval below. Otherwise, configuring MTP will cause adb // (and thus gdb) to get bounced. #if 0 LOG("Sleeping"); PRTime now = PR_Now(); PRTime stopTime = now + 5000000; while (PR_Now() < stopTime) { LOG("Sleeping..."); sleep(1); } LOG("Sleep done"); #endif sMozMtpServer = new MozMtpServer(); if (!sMozMtpServer->Init()) { sMozMtpServer = nullptr; return false; } VolumeArray::index_type volIndex; VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); for (volIndex = 0; volIndex < numVolumes; volIndex++) { RefPtr vol = VolumeManager::GetVolume(volIndex); RefPtr storage = new MozMtpStorage(vol, sMozMtpServer); mMozMtpStorage.AppendElement(storage); } sMozMtpServer->Run(); return true; } void AutoMounter::StopMtpServer() { LOG("Stopping MtpServer"); mMozMtpStorage.Clear(); sMozMtpServer = nullptr; } /***************************************************************************/ void AutoMounter::UpdateState() { static bool inUpdateState = false; if (inUpdateState) { // When UpdateState calls SetISharing, this causes a volume state // change to occur, which would normally cause UpdateState to be called // again. We want the volume state change to go out (so that device // storage will see the change in sharing state), but since we're // already in UpdateState we just want to prevent recursion from screwing // things up. return; } AutoRestore inUpdateStateDetector(inUpdateState); inUpdateState = true; MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); // If the following preconditions are met: // - UMS is available (i.e. compiled into the kernel) // - UMS is enabled // - AutoMounter is enabled // - USB cable is plugged in // then we will try to unmount and share // otherwise we will try to unshare and mount. if (VolumeManager::State() != VolumeManager::VOLUMES_READY) { // The volume manager isn't in a ready state, so there // isn't anything else that we can do. LOG("UpdateState: VolumeManager not ready yet"); return; } if (mResponseCallback->IsPending()) { // We only deal with one outstanding volume command at a time, // so we need to wait for it to finish. return; } // Calling setprop sys.usb.config mtp,adb (or adding mass_storage) will // cause /sys/devices/virtual/android_usb/android0/state to go: // CONFIGURED -> DISCONNECTED -> CONNECTED -> CONFIGURED // // Since IsUsbCablePluggedIn returns state == CONFIGURED, it will look // like a cable pull and replugin. bool umsAvail, umsConfigured, umsEnabled; bool mtpAvail, mtpConfigured, mtpEnabled; bool rndisConfigured; bool usbCablePluggedIn = IsUsbCablePluggedIn(); GetStatus(umsAvail, umsConfigured, umsEnabled, mtpAvail, mtpConfigured, mtpEnabled, rndisConfigured); bool enabled = mtpEnabled || umsEnabled; if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) { // DISABLE_WHEN_UNPLUGGED implies already enabled. enabled = usbCablePluggedIn; if (!usbCablePluggedIn) { mMode = AUTOMOUNTER_DISABLE; mtpEnabled = false; umsEnabled = false; } } DBG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d rndis:%d mode:%d usb:%d mState:%s", umsAvail, umsConfigured, umsEnabled, mtpAvail, mtpConfigured, mtpEnabled, rndisConfigured, mMode, usbCablePluggedIn, StateStr(mState)); switch (mState) { case STATE_IDLE: if (!usbCablePluggedIn) { // Stay in the IDLE state. We'll get a CONNECTED or CONFIGURED // UEvent when the usb cable is plugged in. break; } if (rndisConfigured) { // USB Tethering uses RNDIS. We'll just wait until its turned off. SetState(STATE_RNDIS_CONFIGURED); break; } if (mtpEnabled) { if (mtpConfigured) { // The USB layer has already been configured. Now we can go ahead // and start the MTP server. This particular codepath will not // normally be taken, but it could happen if you stop and restart // b2g while sys.usb.config is set to enable mtp. if (StartMtpServer()) { SetState(STATE_MTP_STARTED); } else { if (umsAvail) { // Unable to start MTP. Go back to UMS. LOG("UpdateState: StartMtpServer failed, switch to UMS"); SetUsbFunction(USB_FUNC_UMS); SetState(STATE_UMS_CONFIGURING); } else { LOG("UpdateState: StartMtpServer failed, keep idle state"); SetUsbFunction(USB_FUNC_DEFAULT); } } } else { // We need to configure USB to use mtp. Wait for it to be configured // before we start the MTP server. SetUsbFunction(USB_FUNC_MTP); SetState(STATE_MTP_CONFIGURING); } } else if (umsConfigured) { // UMS is already configured. SetState(STATE_UMS_CONFIGURED); } else if (umsAvail) { // We do this whether or not UMS is enabled. With UMS, it's the // sharing of the volume which is significant. What is important // is that we don't leave it in MTP mode when MTP isn't enabled. SetUsbFunction(USB_FUNC_UMS); SetState(STATE_UMS_CONFIGURING); } break; case STATE_MTP_CONFIGURING: // While configuring, the USB configuration state will change from // CONFIGURED -> CONNECTED -> DISCONNECTED -> CONNECTED -> CONFIGURED // so we don't check for cable unplugged here. if (mtpEnabled && mtpConfigured) { // The USB layer has been configured. Now we can go ahead and start // the MTP server. if (StartMtpServer()) { SetState(STATE_MTP_STARTED); } else { // Unable to start MTP. Go back to UMS. SetUsbFunction(USB_FUNC_UMS); SetState(STATE_UMS_CONFIGURING); } break; } if (rndisConfigured) { SetState(STATE_RNDIS_CONFIGURED); break; } break; case STATE_MTP_STARTED: if (usbCablePluggedIn && mtpConfigured && mtpEnabled) { // Everything is still good. Leave the MTP server running break; } DBG("STATE_MTP_STARTED: About to StopMtpServer " "mtpConfigured = %d mtpEnabled = %d usbCablePluggedIn: %d", mtpConfigured, mtpEnabled, usbCablePluggedIn); StopMtpServer(); if (rndisConfigured) { SetState(STATE_RNDIS_CONFIGURED); break; } if (umsAvail) { // Switch back to UMS SetUsbFunction(USB_FUNC_UMS); SetState(STATE_UMS_CONFIGURING); break; } // if ums/rndis is not available and mtp is disable, // restore the usb function as PERSIST_SYS_USB_CONFIG. SetUsbFunction(USB_FUNC_DEFAULT); SetState(STATE_IDLE); break; case STATE_UMS_CONFIGURING: if (mtpEnabled) { // MTP was enabled. Start reconfiguring. SetState(STATE_MTP_CONFIGURING); SetUsbFunction(USB_FUNC_MTP); break; } if (rndisConfigured) { SetState(STATE_RNDIS_CONFIGURED); break; } // While configuring, the USB configuration state will change from // CONFIGURED -> CONNECTED -> DISCONNECTED -> CONNECTED -> CONFIGURED // so we don't check for cable unplugged here. However, having said // that, we'll often sit in this state while the cable is unplugged, // since we might not get any events until the cable gets plugged back // in. This is why we need to check for mtpEnabled once we get the // configured event. if (umsConfigured) { SetState(STATE_UMS_CONFIGURED); } break; case STATE_UMS_CONFIGURED: if (usbCablePluggedIn) { if (mtpEnabled) { // MTP was enabled. Start reconfiguring. SetState(STATE_MTP_CONFIGURING); SetUsbFunction(USB_FUNC_MTP); break; } if (umsConfigured && umsEnabled) { // This is the normal state when UMS is enabled. break; } } if (rndisConfigured) { SetState(STATE_RNDIS_CONFIGURED); break; } SetState(STATE_IDLE); break; case STATE_RNDIS_CONFIGURED: if (usbCablePluggedIn && rndisConfigured) { // Normal state when RNDIS is enabled. break; } SetState(STATE_IDLE); break; default: SetState(STATE_IDLE); break; } bool tryToShare = umsEnabled && usbCablePluggedIn; LOG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d mode:%d usb:%d tryToShare:%d state:%s", umsAvail, umsConfigured, umsEnabled, mtpAvail, mtpConfigured, mtpEnabled, mMode, usbCablePluggedIn, tryToShare, StateStr(mState)); bool filesOpen = false; static unsigned filesOpenDelayCount = 0; VolumeArray::index_type volIndex; VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); for (volIndex = 0; volIndex < numVolumes; volIndex++) { RefPtr vol = VolumeManager::GetVolume(volIndex); Volume::STATE volState = vol->State(); if (vol->State() == nsIVolume::STATE_MOUNTED) { LOG("UpdateState: Volume %s is %s and %s @ %s gen %d locked %d sharing %s", vol->NameStr(), vol->StateStr(), vol->MediaPresent() ? "inserted" : "missing", vol->MountPoint().get(), vol->MountGeneration(), (int)vol->IsMountLocked(), vol->CanBeShared() ? (vol->IsSharingEnabled() ? (vol->IsSharing() ? "en-y" : "en-n") : "dis") : "x"); if (vol->IsSharing() && !usbCablePluggedIn) { // We call SetIsSharing(true) below to indicate intent to share. This // causes a state change which notifys apps, and they'll close any // open files, which will initiate the change away from the mounted // state and into the sharing state. Normally, when the volume // transitions back to the mounted state, then vol->mIsSharing gets set // to false. However, if the user pulls the USB cable before we // actually start sharing, then the volume never actually leaves // the mounted state (and hence never transitions from // sharing -> mounted), and mIsSharing never gets set back to false. // So we clear mIsSharing here. vol->SetIsSharing(false); } } else { LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(), vol->MediaPresent() ? "inserted" : "missing"); } if (!vol->MediaPresent()) { // No media - nothing we can do continue; } if (vol->State() == nsIVolume::STATE_CHECKMNT) { // vold reports the volume is "Mounted". Need to check if the volume is // accessible by statfs(). Once it can be accessed, set the volume as // STATE_MOUNTED, otherwise, post a delay task of UpdateState to check it // again. struct statfs fsbuf; int rc = MOZ_TEMP_FAILURE_RETRY(statfs(vol->MountPoint().get(), &fsbuf)); if (rc == -1) { // statfs() failed. Stay in STATE_CHECKMNT. Any failures here // are probably non-recoverable, so we need to wait until // something else changes the state back to IDLE/UNMOUNTED, etc. ERR("statfs failed for '%s': errno = %d (%s)", vol->NameStr(), errno, strerror(errno)); continue; } static int delay = 250; if (fsbuf.f_blocks == 0) { if (delay <= 4000) { LOG("UpdateState: Volume '%s' is inaccessible, checking again in %d msec", vol->NameStr(), delay); MessageLoopForIO::current()-> PostDelayedTask(NewRunnableMethod(this, &AutoMounter::UpdateState), delay); delay *= 2; } else { LOG("UpdateState: Volume '%s' is inaccessible, giving up", vol->NameStr()); } continue; } else { delay = 250; vol->SetState(nsIVolume::STATE_MOUNTED); } } if ((tryToShare && vol->IsSharingEnabled()) || vol->IsFormatRequested() || vol->IsUnmountRequested()) { switch (volState) { // We're going to try to unmount the volume case nsIVolume::STATE_MOUNTED: { if (vol->IsMountLocked()) { // The volume is currently locked, so leave it in the mounted // state. LOGW("UpdateState: Mounted volume %s is locked, not sharing or formatting", vol->NameStr()); break; } // Mark the volume as if we've started sharing/formatting/unmmounting. // This will cause apps which watch device storage notifications to see // the volume go into the different state, and prompt them to close any // open files that they might have. if (tryToShare && vol->IsSharingEnabled()) { vol->SetIsSharing(true); } else if (vol->IsFormatRequested()){ vol->SetIsFormatting(true); } else if (vol->IsUnmountRequested()){ vol->SetIsUnmounting(true); } // Check to see if there are any open files on the volume and // don't initiate the unmount while there are open files. OpenFileFinder::Info fileInfo; OpenFileFinder fileFinder(vol->MountPoint()); if (fileFinder.First(&fileInfo)) { LOGW("The following files are open under '%s'", vol->MountPoint().get()); do { LOGW(" PID: %d file: '%s' app: '%s' comm: '%s' exe: '%s'\n", fileInfo.mPid, fileInfo.mFileName.get(), fileInfo.mAppName.get(), fileInfo.mComm.get(), fileInfo.mExe.get()); } while (fileFinder.Next(&fileInfo)); LOGW("UpdateState: Mounted volume %s has open files, not sharing or formatting", vol->NameStr()); // Check again in a few seconds to see if the files are closed. // Since we're trying to share the volume, this implies that we're // plugged into the PC via USB and this in turn implies that the // battery is charging, so we don't need to be too concerned about // wasting battery here. // // If we just detected that there were files open, then we use // a short timer. We will have told the apps that we're trying // trying to share, and they'll be closing their files. This makes // the sharing more responsive. If after a few seconds, the apps // haven't closed their files, then we back off. int delay = 1000; if (filesOpenDelayCount > 10) { delay = 5000; } MessageLoopForIO::current()-> PostDelayedTask(NewRunnableMethod(this, &AutoMounter::UpdateState), delay); filesOpen = true; break; } // Volume is mounted, we need to unmount before // we can share. LOG("UpdateState: Unmounting %s", vol->NameStr()); vol->StartUnmount(mResponseCallback); return; // UpdateState will be called again when the Unmount command completes } case nsIVolume::STATE_IDLE: case nsIVolume::STATE_MOUNT_FAIL: { LOG("UpdateState: Volume %s is %s", vol->NameStr(), vol->StateStr()); if (vol->IsFormatting() && !vol->IsFormatRequested()) { vol->SetFormatRequested(false); if (!(tryToShare && vol->IsSharingEnabled()) && volState == nsIVolume::STATE_IDLE) { LOG("UpdateState: Mounting %s", vol->NameStr()); vol->StartMount(mResponseCallback); break; } } // If there are format and share requests in the same time, // we should do format first then share. if (vol->IsFormatRequested()) { // Volume is unmounted. We can go ahead and format. LOG("UpdateState: Formatting %s", vol->NameStr()); vol->StartFormat(mResponseCallback); } else if (tryToShare && vol->IsSharingEnabled() && volState == nsIVolume::STATE_IDLE) { // Volume is unmounted. We can go ahead and share. LOG("UpdateState: Sharing %s", vol->NameStr()); vol->StartShare(mResponseCallback); } return; // UpdateState will be called again when the Share/Format command completes } default: { // Not in a state that we can do anything about. break; } } } else { // We're going to try and unshare and remount the volumes switch (volState) { case nsIVolume::STATE_SHARED: { // Volume is shared. We can go ahead and unshare. LOG("UpdateState: Unsharing %s", vol->NameStr()); vol->StartUnshare(mResponseCallback); return; // UpdateState will be called again when the Unshare command completes } case nsIVolume::STATE_IDLE: { if (!vol->IsUnmountRequested()) { // Volume is unmounted and mount-requested, try to mount. LOG("UpdateState: Mounting %s", vol->NameStr()); vol->StartMount(mResponseCallback); } return; // UpdateState will be called again when Mount command completes } default: { // Not in a state that we can do anything about. break; } } } } int32_t status = AUTOMOUNTER_STATUS_DISABLED; if (filesOpen) { filesOpenDelayCount++; status = AUTOMOUNTER_STATUS_FILES_OPEN; } else if (enabled) { filesOpenDelayCount = 0; status = AUTOMOUNTER_STATUS_ENABLED; } SetAutoMounterStatus(status); } /***************************************************************************/ void AutoMounter::GetStatus(bool& umsAvail, bool& umsConfigured, bool& umsEnabled, bool& mtpAvail, bool& mtpConfigured, bool& mtpEnabled, bool& rndisConfigured) { umsAvail = false; umsConfigured = false; umsEnabled = false; mtpAvail = false; mtpConfigured = false; mtpEnabled = false; rndisConfigured = false; if (access(ICS_SYS_USB_FUNCTIONS, F_OK) != 0) { return; } char functionsStr[60]; if (!ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) { ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno)); functionsStr[0] = '\0'; } DBG("GetStatus: USB functions: '%s'", functionsStr); bool usbConfigured = IsUsbConfigured(); // On the Nexus 4/5, it advertises that the UMS usb function is available, // but we have a further requirement that mass_storage be in the // persist.sys.usb.config property. char persistSysUsbConfig[PROPERTY_VALUE_MAX]; property_get(PERSIST_SYS_USB_CONFIG, persistSysUsbConfig, ""); if (IsUsbFunctionEnabled(persistSysUsbConfig, USB_FUNC_UMS)) { umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0); } if (umsAvail) { umsConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_UMS) != nullptr; umsEnabled = (mMode == AUTOMOUNTER_ENABLE_UMS) || ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && umsConfigured); } else { umsConfigured = false; umsEnabled = false; } mtpAvail = (access(ICS_SYS_MTP_DIRECTORY, F_OK) == 0); if (mtpAvail) { mtpConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_MTP) != nullptr; mtpEnabled = (mMode == AUTOMOUNTER_ENABLE_MTP) || ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && mtpConfigured); } else { mtpConfigured = false; mtpEnabled = false; } rndisConfigured = strstr(functionsStr, USB_FUNC_RNDIS) != nullptr; } nsresult AutoMounter::Dump(nsACString& desc) { DBG("GetState!"); bool umsAvail, umsConfigured, umsEnabled; bool mtpAvail, mtpConfigured, mtpEnabled; bool rndisConfigured; GetStatus(umsAvail, umsConfigured, umsEnabled, mtpAvail, mtpConfigured, mtpEnabled, rndisConfigured); if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) { // DISABLE_WHEN_UNPLUGGED implies already enabled. if (!IsUsbCablePluggedIn()) { mMode = AUTOMOUNTER_DISABLE; mtpEnabled = false; umsEnabled = false; } } // Automounter information desc += "Current Mode:"; desc += ModeStr(mMode); desc += "|"; desc += "Current State:"; desc += StateStr(mState); desc += "|"; desc += "UMS Status:"; if (umsAvail) { desc += "Available"; } else { desc += "UnAvailable"; } desc += ","; if (umsConfigured) { desc += "Configured"; } else { desc += "Un-Configured"; } desc += ","; if (umsEnabled) { desc += "Enabled"; } else { desc += "Disabled"; } desc += "|"; desc += "MTP Status:"; if (mtpAvail) { desc += "Available"; } else { desc += "UnAvailable"; } desc += ","; if (mtpConfigured) { desc += "Configured"; } else { desc += "Un-Configured"; } desc += ","; if (mtpEnabled) { desc += "Enabled"; } else { desc += "Disabled"; } // Volume information VolumeArray::index_type volIndex; VolumeArray::size_type numVolumes = VolumeManager::NumVolumes(); for (volIndex = 0; volIndex < numVolumes; volIndex++) { RefPtr vol = VolumeManager::GetVolume(volIndex); desc += "|"; desc += vol->NameStr(); desc += ":"; desc += vol->StateStr(); desc += "@"; desc += vol->MountPoint().get(); if (!vol->MediaPresent()) { continue; } if (vol->CanBeShared()) { desc += ",CanBeShared"; } if (vol->CanBeFormatted()) { desc += ",CanBeFormatted"; } if (vol->CanBeMounted()) { desc += ",CanBeMounted"; } if (vol->IsRemovable()) { desc += ",Removable"; } if (vol->IsHotSwappable()) { desc += ",HotSwappable"; } } return NS_OK; } static void InitAutoMounterIOThread() { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(!sAutoMounter); sAutoMounter = new AutoMounter(); } static void ShutdownAutoMounterIOThread() { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); sAutoMounter = nullptr; ShutdownVolumeManager(); } static void SetAutoMounterModeIOThread(const int32_t& aMode) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(sAutoMounter); sAutoMounter->SetMode(aMode); } static void SetAutoMounterSharingModeIOThread(const nsCString& aVolumeName, const bool& aAllowSharing) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(sAutoMounter); sAutoMounter->SetSharingMode(aVolumeName, aAllowSharing); } static void AutoMounterFormatVolumeIOThread(const nsCString& aVolumeName) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(sAutoMounter); sAutoMounter->FormatVolume(aVolumeName); } static void AutoMounterMountVolumeIOThread(const nsCString& aVolumeName) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(sAutoMounter); sAutoMounter->MountVolume(aVolumeName); } static void AutoMounterUnmountVolumeIOThread(const nsCString& aVolumeName) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(sAutoMounter); sAutoMounter->UnmountVolume(aVolumeName); } static void UsbCableEventIOThread() { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); if (!sAutoMounter) { return; } DBG("Calling UpdateState due to USBCableEvent"); sAutoMounter->UpdateState(); } /************************************************************************** * * Public API * * Since the AutoMounter runs in IO Thread context, we need to switch * to IOThread context before we can do anything. * **************************************************************************/ class UsbCableObserver final : public hal::SwitchObserver { ~UsbCableObserver() { hal::UnregisterSwitchObserver(hal::SWITCH_USB, this); } public: NS_INLINE_DECL_REFCOUNTING(UsbCableObserver) UsbCableObserver() { hal::RegisterSwitchObserver(hal::SWITCH_USB, this); } virtual void Notify(const hal::SwitchEvent& aEvent) { DBG("UsbCable switch device: %d state: %s\n", aEvent.device(), SwitchStateStr(aEvent)); XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(UsbCableEventIOThread)); } }; static StaticRefPtr sUsbCableObserver; static StaticRefPtr sAutoMounterSetting; void InitAutoMounter() { InitVolumeManager(); sAutoMounterSetting = new AutoMounterSetting(); XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(InitAutoMounterIOThread)); // Switch Observers need to run on the main thread, so we need to // start it here and have it send events to the AutoMounter running // on the IO Thread. sUsbCableObserver = new UsbCableObserver(); // Register status reporter into reporter manager if(status_reporter_progress == REPORTER_UNREGISTERED) { NS_RegisterStatusReporter(new NS_STATUS_REPORTER_NAME(AutoMounter)); } status_reporter_progress = REPORTER_REGISTERED; } int32_t GetAutoMounterStatus() { if (sAutoMounterSetting) { return sAutoMounterSetting->GetStatus(); } return AUTOMOUNTER_STATUS_DISABLED; } //static void SetAutoMounterStatus(int32_t aStatus) { if (sAutoMounterSetting) { sAutoMounterSetting->SetStatus(aStatus); } } void SetAutoMounterMode(int32_t aMode) { XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(SetAutoMounterModeIOThread, aMode)); } void SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing) { XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(SetAutoMounterSharingModeIOThread, aVolumeName, aAllowSharing)); } void AutoMounterFormatVolume(const nsCString& aVolumeName) { XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(AutoMounterFormatVolumeIOThread, aVolumeName)); } void AutoMounterMountVolume(const nsCString& aVolumeName) { XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(AutoMounterMountVolumeIOThread, aVolumeName)); } void AutoMounterUnmountVolume(const nsCString& aVolumeName) { XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(AutoMounterUnmountVolumeIOThread, aVolumeName)); } void ShutdownAutoMounter() { if (sAutoMounter) { DBG("ShutdownAutoMounter: About to StopMtpServer"); sAutoMounter->StopMtpServer(); // Unregister status reporter into reporter manager if(status_reporter_progress == REPORTER_REGISTERED) { NS_UnregisterStatusReporter(new NS_STATUS_REPORTER_NAME(AutoMounter)); } status_reporter_progress = REPORTER_UNREGISTERED; } sAutoMounterSetting = nullptr; sUsbCableObserver = nullptr; XRE_GetIOMessageLoop()->PostTask( NewRunnableFunction(ShutdownAutoMounterIOThread)); } } // system } // mozilla