blob: 89da69743177fcfeaae197659f01bd04059fe876 [file] [log] [blame]
// 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 <utility>
#include <base/bind.h>
#include <base/callback.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/memory/free_deleter.h>
#include <base/notreached.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::OnceCallback<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(const MtpPoller&) = delete;
MtpPoller& operator=(const MtpPoller&) = delete;
~MtpPoller() override { Stop(); }
void WaitForEvent(LIBMTP_mtpdevice_t* mtp_device, EventCallback callback) {
DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
auto params = std::make_unique<Params>(this, std::move(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::BindOnce(&MtpPoller::DoEvents, base::Unretained(this)));
}
num_pending_++;
}
private:
struct Params {
Params(MtpPoller* poller, EventCallback callback)
: poller(poller), callback(std::move(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, std::move(params->callback));
}
void ProcessEvent(int ret_code,
LIBMTP_event_t event,
EventCallback callback) {
DCHECK(task_runner()->BelongsToCurrentThread());
{
base::AutoLock al(lock_);
num_pending_--;
}
main_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(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;
};
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::Contains(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::Contains(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::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DeviceManager::AddDevices,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(1));
return;
}
if (kEventAction == "remove") {
// libmtp still detects the mtp device as connected now, so add a 1 second
// wait to ensure libmtp does not detect the device.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DeviceManager::RemoveDevices,
weak_ptr_factory_.GetWeakPtr(), false /* !remove_all */),
base::TimeDelta::FromSeconds(1));
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::Contains(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::Contains(*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::BindOnce(&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::BindOnce(&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;
}
}
std::unique_ptr<LIBMTP_raw_device_t, base::FreeDeleter> scoped_raw_devices(
raw_devices);
// 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