| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mtpd/device_manager.h" |
| |
| #include <libudev.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| |
| #include <memory> |
| #include <set> |
| |
| #include <base/bind.h> |
| #include <base/callback.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/memory/free_deleter.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/threading/thread.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "mtpd/device_event_delegate.h" |
| |
| namespace mtpd { |
| |
| namespace { |
| |
| // For GetObjectHandles PTP operations, this tells GetObjectHandles to only |
| // list the objects of the root of a store. |
| // Use this when referring to the root node in the context of ReadDirectory(). |
| // This is an implementation detail that is not exposed to the outside. |
| const uint32_t kPtpGohRootParent = 0xFFFFFFFF; |
| |
| // Used to identify a PTP USB device interface. |
| const char kPtpUsbInterfaceClass[] = "6"; |
| const char kPtpUsbInterfaceSubClass[] = "1"; |
| const char kPtpUsbInterfaceProtocol[] = "1"; |
| |
| // Used to identify a vendor-specific USB device interface. |
| // Manufacturers sometimes do not report MTP/PTP capable devices using the |
| // well known PTP interface class. See libgphoto2 and libmtp device databases |
| // for examples. |
| const char kVendorSpecificUsbInterfaceClass[] = "255"; |
| |
| const char kUsbPrefix[] = "usb"; |
| const char kUDevEventType[] = "udev"; |
| const char kUDevUsbSubsystem[] = "usb"; |
| |
| std::string RawDeviceToString(const LIBMTP_raw_device_t& device) { |
| return base::StringPrintf("%s:%u,%d", kUsbPrefix, device.bus_location, |
| device.devnum); |
| } |
| |
| std::string StorageToString(const std::string& usb_bus_str, |
| uint32_t storage_id) { |
| return base::StringPrintf("%s:%u", usb_bus_str.c_str(), storage_id); |
| } |
| |
| struct LibmtpFileDeleter { |
| void operator()(LIBMTP_file_t* file) { LIBMTP_destroy_file_t(file); } |
| }; |
| |
| using ScopedMtpFile = std::unique_ptr<LIBMTP_file_t, LibmtpFileDeleter>; |
| ScopedMtpFile CreateScopedMtpFile(LIBMTP_mtpdevice_t* mtp_device, |
| uint32_t file_id) { |
| return ScopedMtpFile((file_id == kRootFileId) |
| ? LIBMTP_new_file_t() |
| : LIBMTP_Get_Filemetadata(mtp_device, file_id)); |
| } |
| |
| } // namespace |
| |
| class DeviceManager::MtpPoller : public base::Thread { |
| public: |
| using EventCallback = base::Callback<void(int ret_code, |
| LIBMTP_event_t event)>; |
| |
| explicit MtpPoller(scoped_refptr<base::SingleThreadTaskRunner> task_runner) |
| : Thread("MTP poller"), main_thread_task_runner_(task_runner) {} |
| ~MtpPoller() override { |
| Stop(); |
| } |
| |
| void WaitForEvent(LIBMTP_mtpdevice_t* mtp_device, |
| const EventCallback& callback) { |
| DCHECK(main_thread_task_runner_->BelongsToCurrentThread()); |
| |
| auto params = std::make_unique<Params>(this, callback); |
| int ret = LIBMTP_Read_Event_Async(mtp_device, &ReadEventFunction, |
| params.get()); |
| if (ret) { |
| LOG(ERROR) << "LIBMTP_Read_Event_Async error " << ret; |
| return; |
| } |
| // On success, libmtp takes ownership of |params|, and transfers it to the |
| // event handler function on an event. |
| params.release(); |
| |
| base::AutoLock al(lock_); |
| if (num_pending_ == 0) { |
| // NOTE: There's a logical 'race' here where multiple DoEvents() can be |
| // posted to the task runner. This happens when an MTP device is rapidly |
| // disconnected and reconnected. If a device is removed, then a new device |
| // is added between the |num_pending_| decrement in ProcessEvent() and |
| // checking the loop condition in DoEvents(), this condition will be hit |
| // and a new task is posted while the current one is running. When all |
| // devices are eventually removed, the extra task will run and finish |
| // immediately since |num_pending_| should be 0. |
| task_runner()->PostTask( |
| FROM_HERE, base::Bind(&MtpPoller::DoEvents, base::Unretained(this))); |
| } |
| num_pending_++; |
| } |
| |
| private: |
| struct Params { |
| Params(MtpPoller* poller, const EventCallback& callback) |
| : poller(poller), callback(callback) {} |
| |
| MtpPoller* poller; |
| EventCallback callback; |
| }; |
| |
| void DoEvents() { |
| DCHECK(task_runner()->BelongsToCurrentThread()); |
| |
| base::AutoLock al(lock_); |
| while (num_pending_ > 0) { |
| base::AutoUnlock aul(lock_); |
| // libmtp requires a non-zero timeout. A day should be reasonable. |
| struct timeval tv = {86400, 0}; |
| int err = LIBMTP_Handle_Events_Timeout_Completed(&tv, nullptr); |
| if (err) { |
| LOG(ERROR) << "LIBMTP_Handle_Events_Timeout_Completed error " << err; |
| } |
| } |
| } |
| |
| static void ReadEventFunction(int ret_code, LIBMTP_event_t event, |
| uint32_t unused_extra, void* user_data) { |
| std::unique_ptr<Params> params(static_cast<Params*>(user_data)); |
| params->poller->ProcessEvent(ret_code, event, params->callback); |
| } |
| |
| void ProcessEvent(int ret_code, LIBMTP_event_t event, |
| const EventCallback& callback) { |
| DCHECK(task_runner()->BelongsToCurrentThread()); |
| { |
| base::AutoLock al(lock_); |
| num_pending_--; |
| } |
| main_thread_task_runner_->PostTask(FROM_HERE, base::Bind( |
| callback, ret_code, event)); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> const main_thread_task_runner_; |
| |
| // Protects |num_pending_|. |
| base::Lock lock_; |
| // Number of waiters waiting for an event. |
| int num_pending_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(MtpPoller); |
| }; |
| |
| DeviceManager::DeviceManager(DeviceEventDelegate* delegate) |
| : udev_(udev_new()), |
| udev_monitor_(nullptr), |
| udev_monitor_fd_(-1), |
| delegate_(delegate), |
| weak_ptr_factory_(this) { |
| // Set up udev monitoring. |
| CHECK(delegate_); |
| CHECK(udev_); |
| udev_monitor_ = udev_monitor_new_from_netlink(udev_, kUDevEventType); |
| CHECK(udev_monitor_); |
| int ret = udev_monitor_filter_add_match_subsystem_devtype( |
| udev_monitor_, kUDevUsbSubsystem, nullptr); |
| CHECK_EQ(0, ret); |
| ret = udev_monitor_enable_receiving(udev_monitor_); |
| CHECK_EQ(0, ret); |
| udev_monitor_fd_ = udev_monitor_get_fd(udev_monitor_); |
| CHECK_GE(udev_monitor_fd_, 0); |
| |
| // Initialize libmtp. |
| LIBMTP_Init(); |
| |
| // Create and start the libmtp poller thread. |
| mtp_poller_ = std::make_unique<MtpPoller>( |
| base::ThreadTaskRunnerHandle::Get()); |
| CHECK(mtp_poller_->Start()); |
| |
| // Trigger a device scan. |
| AddDevices(); |
| } |
| |
| DeviceManager::~DeviceManager() { |
| udev_monitor_unref(udev_monitor_); |
| udev_unref(udev_); |
| RemoveDevices(true /* remove all */); |
| } |
| |
| // static |
| bool DeviceManager::ParseStorageName(const std::string& storage_name, |
| std::string* usb_bus_str, |
| uint32_t* storage_id) { |
| std::vector<std::string> split_str = base::SplitString( |
| storage_name, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (split_str.size() != 3) |
| return false; |
| |
| if (split_str[0] != kUsbPrefix) |
| return false; |
| |
| uint32_t id = 0; |
| if (!base::StringToUint(split_str[2], &id)) |
| return false; |
| |
| *usb_bus_str = base::StringPrintf("%s:%s", kUsbPrefix, split_str[1].c_str()); |
| *storage_id = id; |
| return true; |
| } |
| |
| int DeviceManager::GetDeviceEventDescriptor() const { |
| return udev_monitor_fd_; |
| } |
| |
| void DeviceManager::ProcessDeviceEvents() { |
| udev_device* dev = udev_monitor_receive_device(udev_monitor_); |
| if (!dev) |
| return; |
| HandleDeviceNotification(dev); |
| udev_device_unref(dev); |
| } |
| |
| std::vector<std::string> DeviceManager::EnumerateStorages() { |
| std::vector<std::string> ret; |
| for (const auto& device : device_map_) { |
| const std::string& usb_bus_str = device.first; |
| for (const auto& storage : device.second.storage_map) { |
| std::string storage_str = StorageToString(usb_bus_str, storage.first); |
| ret.push_back(storage_str); |
| LOG(INFO) << "Found storage: " << storage_str; |
| } |
| } |
| return ret; |
| } |
| |
| bool DeviceManager::HasStorage(const std::string& storage_name) { |
| return GetStorageInfo(storage_name) != nullptr; |
| } |
| |
| const StorageInfo* DeviceManager::GetStorageInfo( |
| const std::string& storage_name) { |
| std::string usb_bus_str; |
| uint32_t storage_id = 0; |
| if (!ParseStorageName(storage_name, &usb_bus_str, &storage_id)) |
| return nullptr; |
| |
| MtpDeviceMap::const_iterator device_it = device_map_.find(usb_bus_str); |
| if (device_it == device_map_.end()) |
| return nullptr; |
| |
| const MtpStorageMap& storage_map = device_it->second.storage_map; |
| MtpStorageMap::const_iterator storage_it = storage_map.find(storage_id); |
| return storage_it != storage_map.end() ? &storage_it->second : nullptr; |
| } |
| |
| const StorageInfo* DeviceManager::GetStorageInfoFromDevice( |
| const std::string& storage_name) { |
| std::string usb_bus_str; |
| uint32_t storage_id = 0; |
| if (!ParseStorageName(storage_name, &usb_bus_str, &storage_id)) |
| return nullptr; |
| |
| MtpDeviceMap::iterator device_it = device_map_.find(usb_bus_str); |
| if (device_it == device_map_.end()) |
| return nullptr; |
| |
| // Update |storage_map| with the latest storage info. |
| MtpStorageMap& storage_map = device_it->second.storage_map; |
| LIBMTP_mtpdevice_t* mtp_device = device_it->second.device; |
| LIBMTP_Get_Storage(mtp_device, LIBMTP_STORAGE_SORTBY_NOTSORTED); |
| for (LIBMTP_devicestorage_t* storage = mtp_device->storage; storage; |
| storage = storage->next) { |
| MtpStorageMap::iterator storage_it = storage_map.find(storage->id); |
| // If |storage->id| does not exist in the map, just ignore here. It should |
| // be added at AddOrUpdateDevices. |
| if (storage_it == storage_map.end()) |
| continue; |
| |
| storage_it->second.Update(*storage); |
| } |
| |
| // Returns StorageInfo of |storage_id|. |
| MtpStorageMap::const_iterator new_storage_it = storage_map.find(storage_id); |
| return new_storage_it != storage_map.end() ? &new_storage_it->second |
| : nullptr; |
| } |
| |
| bool DeviceManager::ReadDirectoryEntryIds(const std::string& storage_name, |
| uint32_t file_id, |
| std::vector<uint32_t>* out) { |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| |
| if (file_id == kRootFileId) |
| file_id = kPtpGohRootParent; |
| |
| uint32_t* children; |
| int ret = LIBMTP_Get_Children(mtp_device, storage_id, file_id, &children); |
| if (ret < 0) |
| return false; |
| |
| if (ret > 0) { |
| for (int i = 0; i < ret; ++i) |
| out->push_back(children[i]); |
| free(children); |
| } |
| return true; |
| } |
| |
| bool DeviceManager::GetFileInfo(const std::string& storage_name, |
| const std::vector<uint32_t> file_ids, |
| std::vector<FileEntry>* out) { |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| |
| for (size_t i = 0; i < file_ids.size(); ++i) { |
| uint32_t file_id = file_ids[i]; |
| auto file = CreateScopedMtpFile(mtp_device, file_id); |
| if (!file) |
| continue; |
| |
| // LIBMTP_Get_Filemetadata() does not know how to handle the root node, so |
| // fill in relevant fields in the struct manually. The rest of the struct |
| // has already been initialized by LIBMTP_new_file_t(). |
| if (file_id == kRootFileId) { |
| file->storage_id = storage_id; |
| file->filename = strdup("/"); |
| file->filetype = LIBMTP_FILETYPE_FOLDER; |
| } |
| |
| out->push_back(FileEntry(*file)); |
| } |
| return true; |
| } |
| |
| bool DeviceManager::ReadFileChunk(const std::string& storage_name, |
| uint32_t file_id, |
| uint32_t offset, |
| uint32_t count, |
| std::vector<uint8_t>* out) { |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| return ReadFileChunk(mtp_device, file_id, offset, count, out); |
| } |
| |
| bool DeviceManager::CopyFileFromLocal(const std::string& storage_name, |
| const uint32_t file_descriptor, |
| const uint32_t parent_id, |
| const std::string& file_name) { |
| // Get device. |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| |
| // Get file size. |
| struct stat file_stat; |
| if (fstat(file_descriptor, &file_stat) != 0) |
| return false; |
| |
| // Create a new file |
| ScopedMtpFile new_file(LIBMTP_new_file_t()); |
| new_file->filename = strdup(file_name.c_str()); |
| new_file->filesize = file_stat.st_size; |
| new_file->parent_id = parent_id; |
| |
| // Transfer a file. |
| int transfer_status = LIBMTP_Send_File_From_File_Descriptor( |
| mtp_device, file_descriptor, new_file.get(), nullptr, nullptr); |
| return transfer_status == 0; |
| } |
| |
| bool DeviceManager::DeleteObject(const std::string& storage_name, |
| const uint32_t object_id) { |
| // Get the device. |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| return DeleteObjectInternal(mtp_device, storage_id, object_id); |
| } |
| |
| bool DeviceManager::RenameObject(const std::string& storage_name, |
| const uint32_t object_id, |
| const std::string& new_name) { |
| // Get the device. |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| |
| // The root node cannot be renamed. |
| if (object_id == kRootFileId) |
| return false; |
| |
| // Check the object exists. |
| auto file = CreateScopedMtpFile(mtp_device, object_id); |
| if (!file) |
| return false; |
| |
| // Rename the object. While libmtp provides LIBMTP_Set_Folder_Name and other |
| // methods for other types, they result in the same call of |
| // set_object_filename. |
| return LIBMTP_Set_File_Name(mtp_device, file.get(), new_name.c_str()) == 0; |
| } |
| |
| bool DeviceManager::CreateDirectory(const std::string& storage_name, |
| const uint32_t parent_id, |
| const std::string& directory_name) { |
| // Do not allow to create a directory with empty string. |
| if (directory_name.empty()) |
| return false; |
| |
| // Get the device. |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| uint32_t storage_id = 0; |
| if (!GetDeviceAndStorageId(storage_name, &mtp_device, &storage_id)) |
| return false; |
| |
| // Creates a directory. |
| std::unique_ptr<char, base::FreeDeleter> new_directory_name( |
| strdup(directory_name.c_str())); |
| int new_directory_object_id = LIBMTP_Create_Folder( |
| mtp_device, new_directory_name.get(), parent_id, storage_id); |
| if (!strcmp(new_directory_name.get(), directory_name.c_str())) |
| return new_directory_object_id > 0; |
| |
| // When directory name is changed, handle it as an error. |
| if (new_directory_object_id > 0) |
| DeleteObjectInternal(mtp_device, storage_id, new_directory_object_id); |
| return false; |
| } |
| |
| bool DeviceManager::AddStorageForTest(const std::string& storage_name, |
| const StorageInfo& storage_info) { |
| std::string device_location; |
| uint32_t storage_id; |
| if (!ParseStorageName(storage_name, &device_location, &storage_id)) |
| return false; |
| |
| MtpDeviceMap::iterator it = device_map_.find(device_location); |
| if (it == device_map_.end()) { |
| // New device case. |
| MtpStorageMap new_storage_map; |
| new_storage_map.insert(std::make_pair(storage_id, storage_info)); |
| MtpDevice new_mtp_device(nullptr, new_storage_map); |
| device_map_.insert(std::make_pair(device_location, new_mtp_device)); |
| return true; |
| } |
| |
| // Existing device case. |
| // There should be no real LIBMTP_mtpdevice_t device for this dummy storage. |
| MtpDevice& existing_mtp_device = it->second; |
| if (existing_mtp_device.device) |
| return false; |
| |
| // And the storage should not already exist. |
| MtpStorageMap& existing_mtp_storage_map = existing_mtp_device.storage_map; |
| if (base::ContainsKey(existing_mtp_storage_map, storage_id)) |
| return false; |
| |
| existing_mtp_storage_map.insert(std::make_pair(storage_id, storage_info)); |
| return true; |
| } |
| |
| bool DeviceManager::ReadDirectory(LIBMTP_mtpdevice_t* device, |
| uint32_t storage_id, |
| uint32_t file_id, |
| std::vector<FileEntry>* out) { |
| LIBMTP_file_t* file = |
| LIBMTP_Get_Files_And_Folders(device, storage_id, file_id); |
| while (file) { |
| ScopedMtpFile current_file(file); |
| file = file->next; |
| out->push_back(FileEntry(*current_file)); |
| } |
| return true; |
| } |
| |
| bool DeviceManager::ReadFileChunk(LIBMTP_mtpdevice_t* device, |
| uint32_t file_id, |
| uint32_t offset, |
| uint32_t count, |
| std::vector<uint8_t>* out) { |
| // The root node is a virtual node and cannot be read from. |
| if (file_id == kRootFileId) |
| return false; |
| |
| uint8_t* data = nullptr; |
| uint32_t bytes_read = 0; |
| int transfer_status = |
| LIBMTP_Get_File_Chunk(device, file_id, offset, count, &data, &bytes_read); |
| |
| // Own |data| in a scoper so it gets freed when this function returns. |
| std::unique_ptr<uint8_t, base::FreeDeleter> scoped_data(data); |
| |
| if (transfer_status != 0 || bytes_read != count) |
| return false; |
| |
| for (size_t i = 0; i < count; ++i) |
| out->push_back(data[i]); |
| return true; |
| } |
| |
| bool DeviceManager::DeleteObjectInternal(LIBMTP_mtpdevice_t* mtp_device, |
| const uint32_t storage_id, |
| const uint32_t object_id) { |
| // The root node cannot be deleted. |
| if (object_id == kRootFileId) |
| return false; |
| |
| // Check the object exists. |
| auto file = CreateScopedMtpFile(mtp_device, object_id); |
| if (!file) |
| return false; |
| |
| // If the object is a directory, check it is empty. |
| if (file->filetype == LIBMTP_FILETYPE_FOLDER) { |
| uint32_t* children; |
| int num_of_children = |
| LIBMTP_Get_Children(mtp_device, storage_id, object_id, &children); |
| if (num_of_children > 0) |
| free(children); |
| |
| if (num_of_children != 0) |
| return false; |
| } |
| |
| // Delete an object. |
| return LIBMTP_Delete_Object(mtp_device, object_id) == 0; |
| } |
| |
| bool DeviceManager::GetFileInfoInternal(LIBMTP_mtpdevice_t* device, |
| uint32_t storage_id, |
| uint32_t file_id, |
| FileEntry* out) { |
| auto file = CreateScopedMtpFile(device, file_id); |
| if (!file) |
| return false; |
| |
| // LIBMTP_Get_Filemetadata() does not know how to handle the root node, so |
| // fill in relevant fields in the struct manually. The rest of the struct has |
| // already been initialized by LIBMTP_new_file_t(). |
| if (file_id == kRootFileId) { |
| file->storage_id = storage_id; |
| file->filename = strdup("/"); |
| file->filetype = LIBMTP_FILETYPE_FOLDER; |
| } |
| |
| *out = FileEntry(*file); |
| return true; |
| } |
| |
| bool DeviceManager::GetDeviceAndStorageId(const std::string& storage_name, |
| LIBMTP_mtpdevice_t** mtp_device, |
| uint32_t* storage_id) { |
| std::string usb_bus_str; |
| uint32_t id = 0; |
| if (!ParseStorageName(storage_name, &usb_bus_str, &id)) |
| return false; |
| |
| MtpDeviceMap::const_iterator device_it = device_map_.find(usb_bus_str); |
| if (device_it == device_map_.end()) |
| return false; |
| |
| if (!base::ContainsKey(device_it->second.storage_map, id)) |
| return false; |
| |
| *storage_id = id; |
| *mtp_device = device_it->second.device; |
| return true; |
| } |
| |
| void DeviceManager::HandleDeviceNotification(udev_device* device) { |
| const char* action = udev_device_get_property_value(device, "ACTION"); |
| const char* interface = udev_device_get_property_value(device, "INTERFACE"); |
| if (!action || !interface) |
| return; |
| |
| // Check the USB interface. Since this gets called many times by udev for a |
| // given physical action, use the udev "INTERFACE" event property as a quick |
| // way of getting one unique and interesting udev event for a given physical |
| // action. At the same time, do some light filtering and ignore events for |
| // uninteresting devices. |
| const std::string kEventInterface(interface); |
| std::vector<std::string> split_usb_interface = base::SplitString( |
| kEventInterface, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (split_usb_interface.size() != 3) |
| return; |
| |
| // Check to see if the device has a vendor-specific interface class. |
| // In this case, continue and let libmtp figure it out. |
| const std::string& usb_interface_class = split_usb_interface[0]; |
| const std::string& usb_interface_subclass = split_usb_interface[1]; |
| const std::string& usb_interface_protocol = split_usb_interface[2]; |
| bool is_interesting_device = |
| (usb_interface_class == kVendorSpecificUsbInterfaceClass); |
| if (!is_interesting_device) { |
| // Many MTP/PTP devices have this PTP interface. |
| is_interesting_device = |
| (usb_interface_class == kPtpUsbInterfaceClass && |
| usb_interface_subclass == kPtpUsbInterfaceSubClass && |
| usb_interface_protocol == kPtpUsbInterfaceProtocol); |
| } |
| if (!is_interesting_device) |
| return; |
| |
| // Handle the action. |
| const std::string kEventAction(action); |
| if (kEventAction == "add") { |
| // Some devices do not respond well when immediately probed. Thus there is |
| // a 1 second wait here to give the device to settle down. |
| base::MessageLoopForIO::current()->task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&DeviceManager::AddDevices, weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromSeconds(1)); |
| return; |
| } |
| if (kEventAction == "remove") { |
| RemoveDevices(false /* !remove_all */); |
| return; |
| } |
| // udev notes the existence of other actions like "change" and "move", but |
| // they have never been observed with real MTP/PTP devices in testing. |
| } |
| |
| void DeviceManager::AddDevices() { |
| AddOrUpdateDevices(true /* add */, ""); |
| } |
| |
| void DeviceManager::UpdateDevice(const std::string& usb_bus_name) { |
| AddOrUpdateDevices(false /* update */, usb_bus_name); |
| } |
| |
| void DeviceManager::AddOrUpdateDevices( |
| bool add_update, |
| const std::string& changed_usb_device_name) { |
| // Get raw devices. |
| LIBMTP_raw_device_t* raw_devices = nullptr; |
| int raw_devices_count = 0; |
| LIBMTP_error_number_t err = |
| LIBMTP_Detect_Raw_Devices(&raw_devices, &raw_devices_count); |
| if (err == LIBMTP_ERROR_NO_DEVICE_ATTACHED) { |
| LOG(INFO) << "LIBMTP_Detect_Raw_Devices failed with NO_DEVICE_ATTACHED"; |
| return; |
| } |
| if (err != LIBMTP_ERROR_NONE) { |
| LOG(ERROR) << "LIBMTP_Detect_Raw_Devices failed with " << err; |
| return; |
| } |
| std::unique_ptr<LIBMTP_raw_device_t, base::FreeDeleter> scoped_raw_devices( |
| raw_devices); |
| // Iterate through raw devices. Look for target device, if updating. |
| for (int i = 0; i < raw_devices_count; ++i) { |
| const std::string usb_bus_str = RawDeviceToString(raw_devices[i]); |
| |
| if (add_update) { |
| // Skip devices that have already been opened. |
| if (base::ContainsKey(device_map_, usb_bus_str)) |
| continue; |
| } else { |
| // Skip non-target device. |
| if (usb_bus_str != changed_usb_device_name) |
| continue; |
| } |
| |
| LIBMTP_mtpdevice_t* mtp_device = nullptr; |
| if (add_update) { |
| // Open the mtp device. |
| mtp_device = LIBMTP_Open_Raw_Device_Uncached(&raw_devices[i]); |
| if (!mtp_device) { |
| LOG(ERROR) << "LIBMTP_Open_Raw_Device_Uncached failed for " |
| << usb_bus_str; |
| continue; |
| } |
| } else { |
| mtp_device = device_map_[usb_bus_str].device; |
| |
| // For existing devices, update the storage lists. |
| if (LIBMTP_Get_Storage(mtp_device, LIBMTP_STORAGE_SORTBY_NOTSORTED) < 0) { |
| LOG(ERROR) << "LIBMTP_Get_Storage failed for " << usb_bus_str; |
| return; |
| } |
| } |
| |
| // Fetch fallback vendor / product info. |
| std::unique_ptr<char, base::FreeDeleter> duplicated_string; |
| duplicated_string.reset(LIBMTP_Get_Manufacturername(mtp_device)); |
| std::string fallback_vendor; |
| if (duplicated_string.get()) |
| fallback_vendor = duplicated_string.get(); |
| |
| duplicated_string.reset(LIBMTP_Get_Modelname(mtp_device)); |
| std::string fallback_product; |
| if (duplicated_string.get()) |
| fallback_product = duplicated_string.get(); |
| |
| MtpStorageMap new_storage_map; |
| MtpStorageMap* storage_map_ptr; |
| if (add_update) |
| storage_map_ptr = &new_storage_map; |
| else |
| storage_map_ptr = &device_map_[usb_bus_str].storage_map; |
| |
| // Compute the set of storage ids that are contained in the mtpd's |
| // storage_map but not in the latest device info. They are removed storages. |
| std::set<uint32_t> removed_storage_ids; |
| for (const auto& it : *storage_map_ptr) |
| removed_storage_ids.insert(it.first); |
| |
| for (LIBMTP_devicestorage_t* storage = mtp_device->storage; storage; |
| storage = storage->next) { |
| removed_storage_ids.erase(storage->id); |
| } |
| // Iterate through storages on the device and remove storages that are no |
| // longer on the device. |
| for (const auto& storage_id : removed_storage_ids) { |
| storage_map_ptr->erase(storage_id); |
| delegate_->StorageDetached(StorageToString(usb_bus_str, storage_id)); |
| } |
| |
| // Iterate through storages on the device and add any that are missing. |
| for (LIBMTP_devicestorage_t* storage = mtp_device->storage; storage; |
| storage = storage->next) { |
| if (base::ContainsKey(*storage_map_ptr, storage->id)) |
| continue; |
| const std::string storage_name = |
| StorageToString(usb_bus_str, storage->id); |
| StorageInfo info(storage_name, raw_devices[i].device_entry, *storage, |
| fallback_vendor, fallback_product); |
| bool storage_added = |
| storage_map_ptr->insert(std::make_pair(storage->id, info)).second; |
| CHECK(storage_added); |
| delegate_->StorageAttached(storage_name); |
| LOG(INFO) << "Added storage " << storage_name; |
| } |
| if (!add_update) { |
| LOG(INFO) << "Updated device " << usb_bus_str << " with " |
| << storage_map_ptr->size() << " storages"; |
| return; |
| } |
| if (storage_map_ptr->empty()) { |
| // Devices such as the Pixel 2 may expose a fake PTP interface in "No data |
| // transfer" mode (b/135955589) with 0 storage. In this case mtpd |
| // shouldn't keep the handle open ideally. |
| device_map_.erase(usb_bus_str); |
| LIBMTP_Release_Device(mtp_device); |
| LOG(INFO) << "Ignoring device with 0 storage " << usb_bus_str; |
| return; |
| } |
| mtp_poller_->WaitForEvent(mtp_device, |
| base::Bind(&DeviceManager::HandleMtpEvent, |
| weak_ptr_factory_.GetWeakPtr(), |
| usb_bus_str)); |
| bool device_added = |
| device_map_ |
| .insert(std::make_pair( |
| usb_bus_str, |
| MtpDevice(mtp_device, *storage_map_ptr))) |
| .second; |
| CHECK(device_added); |
| LOG(INFO) << "Added device " << usb_bus_str << " with " |
| << storage_map_ptr->size() << " storages"; |
| } |
| } |
| |
| void DeviceManager::HandleMtpEvent(const std::string& usb_bus_name, |
| int ret_code, |
| LIBMTP_event_t event) { |
| LOG(INFO) << "HandleMtpEvent " << usb_bus_name << " ret_code " << ret_code; |
| if (ret_code != LIBMTP_HANDLER_RETURN_OK) { |
| return; |
| } |
| |
| if (event == LIBMTP_EVENT_STORE_ADDED || |
| event == LIBMTP_EVENT_STORE_REMOVED) { |
| UpdateDevice(usb_bus_name); |
| } |
| |
| auto it = device_map_.find(usb_bus_name); |
| if (it == device_map_.end()) { |
| return; |
| } |
| |
| mtp_poller_->WaitForEvent( |
| it->second.device, base::Bind(&DeviceManager::HandleMtpEvent, |
| weak_ptr_factory_.GetWeakPtr(), |
| usb_bus_name)); |
| } |
| |
| void DeviceManager::RemoveDevices(bool remove_all) { |
| LIBMTP_raw_device_t* raw_devices = nullptr; |
| int raw_devices_count = 0; |
| |
| if (!remove_all) { |
| LIBMTP_error_number_t err = |
| LIBMTP_Detect_Raw_Devices(&raw_devices, &raw_devices_count); |
| if (!(err == LIBMTP_ERROR_NONE || err == LIBMTP_ERROR_NO_DEVICE_ATTACHED)) { |
| LOG(ERROR) << "LIBMTP_Detect_Raw_Devices failed with " << err; |
| return; |
| } |
| } |
| |
| // Populate |devices_set| with all known attached devices. |
| std::set<std::string> devices_set; |
| for (const auto& device : device_map_) |
| devices_set.insert(device.first); |
| |
| // And remove the ones that are still attached. |
| for (int i = 0; i < raw_devices_count; ++i) |
| devices_set.erase(RawDeviceToString(raw_devices[i])); |
| |
| // The ones left in the set are the detached devices. |
| for (const auto& device : devices_set) { |
| LOG(INFO) << "Removed " << device; |
| MtpDeviceMap::iterator device_it = device_map_.find(device); |
| if (device_it == device_map_.end()) { |
| NOTREACHED(); |
| continue; |
| } |
| |
| // Remove all the storages on that device. |
| const std::string& usb_bus_str = device_it->first; |
| const MtpStorageMap& storage_map = device_it->second.storage_map; |
| for (const auto& storage : storage_map) { |
| delegate_->StorageDetached(StorageToString(usb_bus_str, storage.first)); |
| } |
| |
| // Delete the device's map entry and cleanup. |
| LIBMTP_mtpdevice_t* mtp_device = device_it->second.device; |
| device_map_.erase(device_it); |
| |
| // |mtp_device| can be NULL in testing. |
| if (!mtp_device) |
| continue; |
| |
| // When |remove_all| is false, the device has already been detached |
| // and this runs after the fact. As such, this call will very |
| // likely fail and spew a bunch of error messages. Call it anyway to |
| // let libmtp do any cleanup it can. |
| LIBMTP_Release_Device(mtp_device); |
| } |
| } |
| |
| } // namespace mtpd |