| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/usb/cros_usb_detector.h" |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <utility> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/notifier_catalogs.h" |
| #include "ash/public/cpp/notification_utils.h" |
| #include "ash/webui/settings/public/constants/routes.mojom.h" |
| #include "base/check_deref.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/arc/arc_util.h" |
| #include "chrome/browser/ash/bruschetta/bruschetta_util.h" |
| #include "chrome/browser/ash/crostini/crostini_features.h" |
| #include "chrome/browser/ash/crostini/crostini_manager.h" |
| #include "chrome/browser/ash/crostini/crostini_pref_names.h" |
| #include "chrome/browser/ash/crostini/crostini_util.h" |
| #include "chrome/browser/ash/guest_os/guest_id.h" |
| #include "chrome/browser/ash/guest_os/guest_os_pref_names.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_features.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_util.h" |
| #include "chrome/browser/notifications/system_notification_helper.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/settings_window_manager_chromeos.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h" |
| #include "chromeos/ash/components/dbus/concierge/concierge_client.h" |
| #include "chromeos/ash/components/disks/disk.h" |
| #include "chromeos/ash/components/disks/disk_mount_manager.h" |
| #include "chromeos/ash/experiences/arc/arc_util.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/device_service.h" |
| #include "services/device/public/cpp/usb/usb_utils.h" |
| #include "services/device/public/mojom/usb_enumeration_options.mojom.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/chromeos/styles/cros_tokens_color_mappings.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr uint32_t kAllInterfacesMask = ~0U; |
| const char16_t kParallelsShortName[] = u"Parallels"; |
| const char16_t kParallelsName[] = u"Parallels Desktop"; |
| |
| // Not owned locally. |
| static CrosUsbDetector* g_cros_usb_detector = nullptr; |
| |
| const char kNotifierUsb[] = "crosusb.connected"; |
| |
| const uint16_t kWacomVendorId = 0x056a; |
| |
| std::u16string ProductLabelFromDevice( |
| const device::mojom::UsbDeviceInfo& device_info) { |
| std::u16string product_label = |
| l10n_util::GetStringUTF16(IDS_CROSUSB_UNKNOWN_DEVICE); |
| if (device_info.product_name.has_value() && |
| !device_info.product_name->empty()) { |
| product_label = device_info.product_name.value(); |
| } else if (device_info.manufacturer_name.has_value() && |
| !device_info.manufacturer_name->empty()) { |
| product_label = |
| l10n_util::GetStringFUTF16(IDS_CROSUSB_UNKNOWN_DEVICE_FROM_MANUFACTURER, |
| device_info.manufacturer_name.value()); |
| } |
| return product_label; |
| } |
| |
| uint32_t ClearMatchingInterfaces( |
| uint32_t in_mask, |
| const device::mojom::UsbDeviceFilter& filter, |
| const device::mojom::UsbDeviceInfo& device_info) { |
| uint32_t mask = in_mask; |
| |
| for (auto& config : device_info.configurations) { |
| for (auto& iface : config->interfaces) { |
| for (auto& alternate_info : iface->alternates) { |
| if (filter.has_class_code && |
| alternate_info->class_code != filter.class_code) { |
| continue; |
| } |
| if (filter.has_subclass_code && |
| alternate_info->subclass_code != filter.subclass_code) { |
| continue; |
| } |
| if (filter.has_protocol_code && |
| alternate_info->protocol_code != filter.protocol_code) { |
| continue; |
| } |
| if (filter.has_vendor_id && device_info.vendor_id != filter.vendor_id) { |
| continue; |
| } |
| if (iface->interface_number >= 32) { |
| LOG(ERROR) << "Interface number too high in USB descriptor"; |
| continue; |
| } |
| mask &= ~(1U << iface->interface_number); |
| } |
| } |
| } |
| |
| return mask; |
| } |
| |
| uint32_t GetUsbInterfaceBaseMask( |
| const device::mojom::UsbDeviceInfo& device_info) { |
| if (device_info.configurations.empty()) { |
| // No specific interfaces to clear. |
| return kAllInterfacesMask; |
| } |
| uint32_t mask = 0; |
| for (auto& config : device_info.configurations) { |
| for (auto& iface : config->interfaces) { |
| if (iface->interface_number >= 32) { |
| LOG(ERROR) << "Interface number too high in USB descriptor."; |
| continue; |
| } |
| mask |= (1U << iface->interface_number); |
| } |
| } |
| return mask; |
| } |
| |
| uint32_t GetFilteredInterfacesMask( |
| const std::vector<device::mojom::UsbDeviceFilterPtr>& filters, |
| const device::mojom::UsbDeviceInfo& device_info) { |
| uint32_t mask = GetUsbInterfaceBaseMask(device_info); |
| for (const auto& filter : filters) { |
| mask = ClearMatchingInterfaces(mask, *filter, device_info); |
| } |
| return mask; |
| } |
| |
| Profile* profile() { |
| return ProfileManager::GetActiveUserProfile(); |
| } |
| |
| crostini::CrostiniManager* manager() { |
| return crostini::CrostiniManager::GetForProfile(profile()); |
| } |
| |
| // Delegate for CrosUsb notification |
| class CrosUsbNotificationDelegate |
| : public message_center::NotificationDelegate { |
| public: |
| explicit CrosUsbNotificationDelegate(const std::string& notification_id, |
| std::string guid, |
| std::vector<std::string> vm_names, |
| std::string settings_sub_page) |
| : notification_id_(notification_id), |
| guid_(std::move(guid)), |
| vm_names_(std::move(vm_names)), |
| settings_sub_page_(std::move(settings_sub_page)), |
| disposition_(CrosUsbNotificationClosed::kUnknown) {} |
| |
| CrosUsbNotificationDelegate(const CrosUsbNotificationDelegate&) = delete; |
| CrosUsbNotificationDelegate& operator=(const CrosUsbNotificationDelegate&) = |
| delete; |
| |
| void Click(const std::optional<int>& button_index, |
| const std::optional<std::u16string>& reply) override { |
| disposition_ = CrosUsbNotificationClosed::kUnknown; |
| if (button_index && *button_index < static_cast<int>(vm_names_.size())) { |
| LOG(WARNING) |
| << "Share USB device with [some guest] notification was clicked"; |
| if (vm_names_[*button_index] == crostini::kCrostiniDefaultVmName) { |
| // When multi-container is enabled, show the settings page instead of |
| // directly attaching the device to the VM. Otherwise, the device is |
| // attached to the default container in the VM. |
| if (crostini::CrostiniFeatures::Get()->IsMultiContainerAllowed( |
| profile())) { |
| HandleShowSettings( |
| chromeos::settings::mojom::kCrostiniUsbPreferencesSubpagePath); |
| } else { |
| HandleConnectToGuest(crostini::DefaultContainerId()); |
| } |
| } else { |
| HandleConnectToGuest(vm_names_[*button_index]); |
| } |
| } else { |
| HandleShowSettings(settings_sub_page_); |
| } |
| } |
| |
| void Close(bool by_user) override { |
| if (by_user) { |
| disposition_ = CrosUsbNotificationClosed::kByUser; |
| } |
| } |
| |
| private: |
| ~CrosUsbNotificationDelegate() override = default; |
| void HandleConnectToGuest(const guest_os::GuestId& guest_id) { |
| disposition_ = CrosUsbNotificationClosed::kConnectToLinux; |
| CrosUsbDetector* detector = CrosUsbDetector::Get(); |
| if (detector) { |
| LOG(WARNING) |
| << "Handling guest connection, will attach USB device to guest"; |
| detector->AttachUsbDeviceToGuest(guest_id, guid_, base::DoNothing()); |
| return; |
| } |
| Close(false); |
| } |
| |
| void HandleConnectToGuest(const std::string& vm_name) { |
| HandleConnectToGuest(guest_os::GuestId(vm_name, "")); |
| } |
| |
| void HandleShowSettings(const std::string& sub_page) { |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(profile(), |
| sub_page); |
| Close(false); |
| } |
| |
| std::string notification_id_; |
| std::string guid_; |
| std::vector<std::string> vm_names_; |
| std::string settings_sub_page_; |
| CrosUsbNotificationClosed disposition_; |
| base::WeakPtrFactory<CrosUsbNotificationDelegate> weak_ptr_factory_{this}; |
| }; |
| |
| device::mojom::UsbDeviceFilterPtr UsbFilterByClassCode( |
| UsbClassCode device_class) { |
| auto filter = device::mojom::UsbDeviceFilter::New(); |
| filter->has_class_code = true; |
| filter->class_code = device_class; |
| return filter; |
| } |
| |
| device::mojom::UsbDeviceFilterPtr UsbFilterByClassAndSubclassCode( |
| UsbClassCode device_class, |
| UsbSubclassCode device_subclass) { |
| auto filter = device::mojom::UsbDeviceFilter::New(); |
| filter->has_class_code = true; |
| filter->class_code = device_class; |
| filter->has_subclass_code = true; |
| filter->subclass_code = device_subclass; |
| return filter; |
| } |
| |
| device::mojom::UsbDeviceFilterPtr UsbFilterByVendorId(uint16_t vendor_id) { |
| auto filter = device::mojom::UsbDeviceFilter::New(); |
| filter->has_vendor_id = true; |
| filter->vendor_id = vendor_id; |
| return filter; |
| } |
| |
| std::u16string CombineVmNames(const std::vector<std::u16string>& vm_names) { |
| std::u16string res; |
| size_t pos = 0; |
| while (pos < vm_names.size()) { |
| res.append(vm_names[pos]); |
| pos++; |
| if (pos < vm_names.size()) { |
| res.append(u" "); |
| res.append(l10n_util::GetStringUTF16(IDS_CROSUSB_NOTIFICATION_OR)); |
| res.append(u" "); |
| } |
| } |
| return res; |
| } |
| |
| // Returns true if user enables ARC on ARCVM enabled devices. |
| bool IsPlayStoreEnabledWithArcVmForProfile(const Profile* profile) { |
| return arc::IsArcPlayStoreEnabledForProfile(profile) && arc::IsArcVmEnabled(); |
| } |
| |
| void ShowNotificationForDevice(const std::string& guid, |
| const std::u16string& label) { |
| message_center::RichNotificationData rich_notification_data; |
| std::vector<std::string> vm_names; |
| std::string settings_sub_page; |
| std::u16string vm_name; |
| std::u16string vm_name_button_text; |
| std::vector<std::u16string> vm_names_in_notification; |
| rich_notification_data.small_image = gfx::Image( |
| gfx::CreateVectorIcon(vector_icons::kUsbIcon, 64, gfx::kGoogleBlue800)); |
| |
| rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary; |
| |
| if (crostini::CrostiniFeatures::Get()->IsEnabled(profile())) { |
| vm_name = l10n_util::GetStringUTF16(IDS_CROSTINI_LINUX); |
| rich_notification_data.buttons.emplace_back( |
| message_center::ButtonInfo(l10n_util::GetStringFUTF16( |
| IDS_CROSUSB_NOTIFICATION_BUTTON_CONNECT_TO_VM, vm_name))); |
| vm_names.emplace_back(crostini::kCrostiniDefaultVmName); |
| vm_names_in_notification.emplace_back(vm_name); |
| settings_sub_page = |
| chromeos::settings::mojom::kCrostiniUsbPreferencesSubpagePath; |
| } |
| if (plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile())) { |
| vm_name = kParallelsName; |
| vm_name_button_text = kParallelsShortName; |
| rich_notification_data.buttons.emplace_back( |
| message_center::ButtonInfo(l10n_util::GetStringFUTF16( |
| IDS_CROSUSB_NOTIFICATION_BUTTON_CONNECT_TO_VM, |
| vm_name_button_text))); |
| vm_names.emplace_back(plugin_vm::kPluginVmName); |
| vm_names_in_notification.emplace_back(vm_name); |
| settings_sub_page = |
| chromeos::settings::mojom::kPluginVmUsbPreferencesSubpagePath; |
| } |
| |
| if (IsPlayStoreEnabledWithArcVmForProfile(profile())) { |
| vm_name = l10n_util::GetStringUTF16(IDS_CROSUSB_NOTIFICATION_ARCVM); |
| vm_name_button_text = |
| l10n_util::GetStringUTF16(IDS_CROSUSB_NOTIFICATION_ARCVM_BUTTON); |
| rich_notification_data.buttons.emplace_back( |
| message_center::ButtonInfo(l10n_util::GetStringFUTF16( |
| IDS_CROSUSB_NOTIFICATION_BUTTON_CONNECT_TO_VM, |
| vm_name_button_text))); |
| vm_names.emplace_back(arc::kArcVmName); |
| vm_names_in_notification.emplace_back(vm_name); |
| settings_sub_page = |
| chromeos::settings::mojom::kArcVmUsbPreferencesSubpagePath; |
| } |
| |
| if (bruschetta::IsInstalled(profile(), bruschetta::GetBruschettaAlphaId())) { |
| vm_name = bruschetta::GetOverallVmName(profile()); |
| rich_notification_data.buttons.emplace_back( |
| message_center::ButtonInfo(l10n_util::GetStringFUTF16( |
| IDS_CROSUSB_NOTIFICATION_BUTTON_CONNECT_TO_VM, vm_name))); |
| vm_names.emplace_back(bruschetta::kBruschettaVmName); |
| vm_names_in_notification.emplace_back(vm_name); |
| settings_sub_page = |
| chromeos::settings::mojom::kBruschettaUsbPreferencesSubpagePath; |
| } |
| |
| DCHECK(vm_names_in_notification.size()); |
| std::u16string message = l10n_util::GetStringFUTF16( |
| IDS_CROSUSB_DEVICE_DETECTED_NOTIFICATION, label, |
| CombineVmNames(vm_names_in_notification)); |
| |
| if (vm_names.size() > 1) { |
| settings_sub_page = std::string(); |
| } |
| |
| std::string notification_id = CrosUsbDetector::MakeNotificationId(guid); |
| message_center::Notification notification( |
| message_center::NOTIFICATION_TYPE_MULTIPLE, notification_id, |
| l10n_util::GetStringUTF16(IDS_CROSUSB_DEVICE_DETECTED_NOTIFICATION_TITLE), |
| message, ui::ImageModel(), std::u16string(), GURL(), |
| message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT, |
| kNotifierUsb, |
| NotificationCatalogName::kCrosUSBDetector), |
| rich_notification_data, |
| base::MakeRefCounted<CrosUsbNotificationDelegate>( |
| notification_id, guid, std::move(vm_names), |
| std::move(settings_sub_page))); |
| SystemNotificationHelper::GetInstance()->Display(notification); |
| } |
| |
| class FilesystemUnmounter : public base::RefCounted<FilesystemUnmounter> { |
| public: |
| static void UnmountPaths(const std::set<std::string>& paths, |
| base::OnceCallback<void(bool success)> callback); |
| |
| private: |
| friend class base::RefCounted<FilesystemUnmounter>; |
| |
| explicit FilesystemUnmounter(base::OnceCallback<void(bool success)> callback) |
| : callback_(std::move(callback)) {} |
| ~FilesystemUnmounter() { std::move(callback_).Run(success_); } |
| |
| void OnUnmountPath(MountError mount_error); |
| |
| bool success_ = true; |
| base::OnceCallback<void(bool success)> callback_; |
| }; |
| |
| void FilesystemUnmounter::UnmountPaths( |
| const std::set<std::string>& paths, |
| base::OnceCallback<void(bool success)> callback) { |
| scoped_refptr<FilesystemUnmounter> unmounter = |
| new FilesystemUnmounter(std::move(callback)); |
| // When the last UnmountPath() calls completes, the ref count reaches zero |
| // and the destructor fires the callback. We can't use base::BarrierClosure() |
| // because we need to aggregate the MountError results. |
| for (const std::string& path : paths) { |
| disks::DiskMountManager::GetInstance()->UnmountPath( |
| path, base::BindOnce(&FilesystemUnmounter::OnUnmountPath, unmounter)); |
| } |
| } |
| |
| void FilesystemUnmounter::OnUnmountPath(MountError mount_error) { |
| if (mount_error != MountError::kSuccess) { |
| LOG(ERROR) << "Error unmounting USB drive: " << mount_error; |
| success_ = false; |
| } |
| } |
| |
| } // namespace |
| |
| CrosUsbDeviceInfo::CrosUsbDeviceInfo( |
| std::string guid, |
| std::u16string label, |
| std::optional<guest_os::GuestId> shared_guest_id, |
| uint16_t vendor_id, |
| uint16_t product_id, |
| std::string serial_number, |
| bool prompt_before_sharing) |
| : guid(guid), |
| label(label), |
| shared_guest_id(shared_guest_id), |
| vendor_id(vendor_id), |
| product_id(product_id), |
| serial_number(serial_number), |
| prompt_before_sharing(prompt_before_sharing) {} |
| CrosUsbDeviceInfo::CrosUsbDeviceInfo(const CrosUsbDeviceInfo&) = default; |
| CrosUsbDeviceInfo::~CrosUsbDeviceInfo() = default; |
| |
| std::string CrosUsbDetector::MakeNotificationId(const std::string& guid) { |
| return "cros:" + guid; |
| } |
| |
| CrosUsbDetector::DeviceClaim::DeviceClaim() = default; |
| |
| CrosUsbDetector::DeviceClaim::~DeviceClaim() = default; |
| |
| // static |
| CrosUsbDetector* CrosUsbDetector::Get() { |
| return g_cros_usb_detector; |
| } |
| |
| CrosUsbDetector::CrosUsbDetector() { |
| DCHECK(!g_cros_usb_detector); |
| g_cros_usb_detector = this; |
| |
| // If *ALL* interfaces of a device match the below list, no notification will |
| // be shown. |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_CDC_DATA)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_HID)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_PHYSICAL)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_AUDIO)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_STILL_IMAGE)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_MASS_STORAGE)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_VIDEO)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_BILLBOARD)); |
| guest_os_usb_int_all_filter_.emplace_back( |
| UsbFilterByClassCode(USB_CLASS_PERSONAL_HEALTHCARE)); |
| |
| // If *ANY* interfaces of a device match the below list, no notification will |
| // be shown. |
| guest_os_usb_int_any_filter_.emplace_back(UsbFilterByClassAndSubclassCode( |
| USB_CLASS_COMM, USB_COMM_SUBCLASS_ETHERNET)); |
| |
| // Wacom graphics tablets have a storage partition that is usually disabled |
| // when first used on other platforms with their driver. This causes them to |
| // have an interface with USB_CLASS_VENDOR_SPEC which causes the notification |
| // to show up. Their driver does not work on ChromeOS so this notification is |
| // shown every time the peripheral connects. |
| guest_os_usb_int_any_filter_.emplace_back( |
| UsbFilterByVendorId(kWacomVendorId)); |
| |
| CiceroneClient::Get()->AddObserver(this); |
| ConciergeClient::Get()->AddVmObserver(this); |
| VmPluginDispatcherClient::Get()->AddObserver(this); |
| disks::DiskMountManager::GetInstance()->AddObserver(this); |
| } |
| |
| CrosUsbDetector::~CrosUsbDetector() { |
| DCHECK_EQ(this, g_cros_usb_detector); |
| disks::DiskMountManager::GetInstance()->RemoveObserver(this); |
| CiceroneClient::Get()->RemoveObserver(this); |
| ConciergeClient::Get()->RemoveVmObserver(this); |
| VmPluginDispatcherClient::Get()->RemoveObserver(this); |
| g_cros_usb_detector = nullptr; |
| } |
| |
| void CrosUsbDetector::SetDeviceManagerForTesting( |
| mojo::PendingRemote<device::mojom::UsbDeviceManager> device_manager) { |
| DCHECK(!device_manager_) << "device_manager_ was already initialized"; |
| device_manager_.Bind(std::move(device_manager)); |
| } |
| |
| void CrosUsbDetector::AddUsbDeviceObserver(CrosUsbDeviceObserver* observer) { |
| usb_device_observers_.AddObserver(observer); |
| } |
| |
| void CrosUsbDetector::RemoveUsbDeviceObserver(CrosUsbDeviceObserver* observer) { |
| usb_device_observers_.RemoveObserver(observer); |
| } |
| |
| void CrosUsbDetector::SignalUsbDeviceObservers() { |
| for (auto& observer : usb_device_observers_) { |
| observer.OnUsbDevicesChanged(); |
| } |
| } |
| |
| std::vector<CrosUsbDeviceInfo> CrosUsbDetector::GetShareableDevices() const { |
| std::vector<CrosUsbDeviceInfo> result; |
| for (const auto& it : usb_devices_) { |
| const UsbDevice& device = it.second; |
| std::string serial_number = |
| device.info->serial_number.has_value() |
| ? base::UTF16ToASCII(device.info->serial_number.value()).c_str() |
| : ""; |
| result.emplace_back( |
| device.info->guid, device.label, device.shared_guest_id, |
| device.info->vendor_id, device.info->product_id, serial_number, |
| /*prompt_before_sharing=*/ |
| device.shared_guest_id.has_value() || !device.mount_points.empty()); |
| } |
| return result; |
| } |
| |
| CrosUsbDetector::UsbDevice::UsbDevice() = default; |
| CrosUsbDetector::UsbDevice::UsbDevice(UsbDevice&&) = default; |
| CrosUsbDetector::UsbDevice::~UsbDevice() = default; |
| |
| void CrosUsbDetector::ConnectToDeviceManager() { |
| // Tests may set a fake manager. |
| if (!device_manager_) { |
| content::GetDeviceService().BindUsbDeviceManager( |
| device_manager_.BindNewPipeAndPassReceiver()); |
| } |
| DCHECK(device_manager_); |
| device_manager_.set_disconnect_handler( |
| base::BindOnce(&CrosUsbDetector::OnDeviceManagerConnectionError, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // Listen for added/removed device events. |
| DCHECK(!client_receiver_.is_bound()); |
| device_manager_->EnumerateDevicesAndSetClient( |
| client_receiver_.BindNewEndpointAndPassRemote(), |
| base::BindOnce(&CrosUsbDetector::OnListAttachedDevices, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| bool CrosUsbDetector::ShouldShowNotification(const UsbDevice& device) { |
| PrefService* prefs = profile()->GetPrefs(); |
| if (!prefs->GetBoolean(ash::prefs::kUsbDetectorNotificationEnabled) || |
| !prefs->GetBoolean(guest_os::prefs::kGuestOsUSBNotificationEnabled)) { |
| return false; |
| } |
| |
| if (!crostini::CrostiniFeatures::Get()->IsEnabled(profile()) && |
| !plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile()) && |
| !IsPlayStoreEnabledWithArcVmForProfile(profile()) && |
| !bruschetta::IsInstalled(profile(), bruschetta::GetBruschettaAlphaId())) { |
| return false; |
| } |
| |
| bool all_filter_cleared = |
| GetFilteredInterfacesMask(guest_os_usb_int_all_filter_, *device.info) != |
| 0; |
| bool any_filter_cleared = |
| GetFilteredInterfacesMask(guest_os_usb_int_any_filter_, *device.info) == |
| GetUsbInterfaceBaseMask(*device.info); |
| return all_filter_cleared && any_filter_cleared; |
| } |
| |
| void CrosUsbDetector::OnContainerStarted( |
| const vm_tools::cicerone::ContainerStartedSignal& signal) { |
| const auto guest_id = |
| guest_os::GuestId(signal.vm_name(), signal.container_name()); |
| for (auto& it : usb_devices_) { |
| auto& device = it.second; |
| if (device.shared_guest_id == guest_id && device.guest_port.has_value()) { |
| VLOG(1) << "Connecting " << device.label << " to " << guest_id.vm_name |
| << ":" << guest_id.container_name; |
| AttachUsbDeviceToContainer(guest_id, *device.guest_port, |
| device.info->guid, base::DoNothing()); |
| } |
| } |
| } |
| |
| void CrosUsbDetector::OnLxdContainerDeleted( |
| const vm_tools::cicerone::LxdContainerDeletedSignal& signal) { |
| if (signal.status() == |
| vm_tools::cicerone::LxdContainerDeletedSignal_Status_DELETED) { |
| const auto guest_id = |
| guest_os::GuestId(signal.vm_name(), signal.container_name()); |
| for (auto& it : usb_devices_) { |
| auto& device = it.second; |
| if (device.shared_guest_id == guest_id) { |
| VLOG(1) << "Detaching " << device.label << " from deleted container " |
| << guest_id.vm_name << ":" << guest_id.container_name; |
| DetachUsbDeviceFromVm(guest_id.vm_name, device.info->guid, |
| base::DoNothing()); |
| } |
| } |
| } |
| } |
| |
| void CrosUsbDetector::OnVmStarted( |
| const vm_tools::concierge::VmStartedSignal& signal) { |
| ConnectSharedDevicesOnVmStartup(signal.name()); |
| } |
| |
| void CrosUsbDetector::OnVmStopped( |
| const vm_tools::concierge::VmStoppedSignal& signal) { |
| DisconnectSharedDevicesOnVmShutdown(signal.name()); |
| } |
| |
| void CrosUsbDetector::OnVmToolsStateChanged( |
| const vm_tools::plugin_dispatcher::VmToolsStateChangedSignal& signal) {} |
| |
| void CrosUsbDetector::OnVmStateChanged( |
| const vm_tools::plugin_dispatcher::VmStateChangedSignal& signal) { |
| if (signal.vm_state() == |
| vm_tools::plugin_dispatcher::VmState::VM_STATE_RUNNING) { |
| ConnectSharedDevicesOnVmStartup(signal.vm_name()); |
| } else if (signal.vm_state() == |
| vm_tools::plugin_dispatcher::VmState::VM_STATE_STOPPED) { |
| DisconnectSharedDevicesOnVmShutdown(signal.vm_name()); |
| } |
| } |
| |
| void CrosUsbDetector::OnMountEvent( |
| disks::DiskMountManager::MountEvent event, |
| MountError error_code, |
| const disks::DiskMountManager::MountPoint& mount_info) { |
| if (mount_info.mount_type != MountType::kDevice || |
| error_code != MountError::kSuccess) { |
| return; |
| } |
| |
| const auto* disk = |
| disks::DiskMountManager::GetInstance()->FindDiskBySourcePath( |
| mount_info.source_path); |
| |
| // This can be null if a drive is physically removed. |
| if (!disk) { |
| return; |
| } |
| |
| for (auto& it : usb_devices_) { |
| UsbDevice& device = it.second; |
| if (disk->bus_number() == |
| base::checked_cast<int64_t>(device.info->bus_number) && |
| disk->device_number() == |
| base::checked_cast<int64_t>(device.info->port_number)) { |
| bool was_empty = device.mount_points.empty(); |
| if (event == disks::DiskMountManager::MOUNTING) { |
| device.mount_points.insert(mount_info.mount_path); |
| } else { |
| device.mount_points.erase(mount_info.mount_path); |
| } |
| |
| if (!device.is_unmounting && was_empty != device.mount_points.empty()) { |
| SignalUsbDeviceObservers(); |
| } |
| return; |
| } |
| } |
| } |
| |
| std::string UsbDeviceIdentifier(device::mojom::UsbDeviceInfoPtr& device_info) { |
| std::string serial_number = |
| device_info->serial_number.has_value() |
| ? base::UTF16ToASCII(device_info->serial_number.value()).c_str() |
| : ""; |
| return base::StringPrintf("%d:%d:%s", device_info->vendor_id, |
| device_info->product_id, serial_number.c_str()); |
| } |
| |
| void CrosUsbDetector::OnDeviceChecked( |
| device::mojom::UsbDeviceInfoPtr device_info, |
| bool hide_notification, |
| bool allowed) { |
| if (!allowed) { |
| LOG(WARNING) << "Device not allowed by Permission Broker. vendor: 0x" |
| << std::hex << device_info->vendor_id << " product: 0x" |
| << device_info->product_id; |
| return; |
| } |
| |
| UsbDevice new_device; |
| |
| new_device.label = ProductLabelFromDevice(*device_info); |
| |
| // Storage devices already plugged in at log-in time will already be mounted. |
| for (const auto& disk : disks::DiskMountManager::GetInstance()->disks()) { |
| if (disk->bus_number() == |
| base::checked_cast<int64_t>(device_info->bus_number) && |
| disk->device_number() == |
| base::checked_cast<int64_t>(device_info->port_number) && |
| disk->is_mounted()) { |
| new_device.mount_points.insert(disk->mount_path()); |
| } |
| } |
| |
| // Copy fields prior to moving |device_info| and |new_device|. |
| std::string guid = device_info->guid; |
| std::u16string label = new_device.label; |
| |
| // If device exists in persistent passthrough dict, skip notifications and |
| // connect it to the appropriate guest. |
| PrefService* prefs = profile()->GetPrefs(); |
| const base::Value::Dict& persistent_passthrough_devices = |
| prefs->GetDict(guest_os::prefs::kGuestOsUSBPersistentPassthroughDevices); |
| |
| const std::string* device = persistent_passthrough_devices.FindString( |
| UsbDeviceIdentifier(device_info)); |
| |
| LOG(WARNING) << "Checking for persistence of USB device [" |
| << UsbDeviceIdentifier(device_info) << "], " |
| << (device == nullptr ? "not persisted" : "persisted"); |
| |
| new_device.info = std::move(device_info); |
| auto result = usb_devices_.emplace(guid, std::move(new_device)); |
| |
| if (!result.second) { |
| LOG(ERROR) << "Ignoring USB device " << label << " as guid already exists."; |
| return; |
| } |
| |
| SignalUsbDeviceObservers(); |
| |
| if (device) { |
| const std::string& device_ref = CHECK_DEREF(device); |
| std::optional<guest_os::GuestId> guest_id = |
| guest_os::Deserialize(device_ref); |
| if (guest_id.has_value()) { |
| LOG(WARNING) << "Persisted USB device has valid guestId: " |
| << guest_id->Serialize() << ", will attach"; |
| AttachUsbDeviceToGuest(guest_id.value(), guid, base::DoNothing()); |
| return; |
| } |
| } |
| |
| // Some devices should not trigger the notification. |
| if (hide_notification || !ShouldShowNotification(result.first->second)) { |
| VLOG(1) << "Not showing USB notification for " << label; |
| return; |
| } |
| |
| ShowNotificationForDevice(guid, label); |
| } |
| |
| void CrosUsbDetector::OnDeviceAdded(device::mojom::UsbDeviceInfoPtr device) { |
| CrosUsbDetector::OnDeviceAdded(std::move(device), false); |
| } |
| |
| void CrosUsbDetector::OnDeviceAdded(device::mojom::UsbDeviceInfoPtr device_info, |
| bool hide_notification) { |
| std::string guid = device_info->guid; |
| device_manager_->CheckAccess( |
| guid, base::BindOnce(&CrosUsbDetector::OnDeviceChecked, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(device_info), hide_notification)); |
| } |
| |
| void CrosUsbDetector::OnDeviceRemoved( |
| device::mojom::UsbDeviceInfoPtr device_info) { |
| SystemNotificationHelper::GetInstance()->Close( |
| CrosUsbDetector::MakeNotificationId(device_info->guid)); |
| |
| std::string guid = device_info->guid; |
| auto it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(ERROR) << "Unknown USB device removed: " |
| << ProductLabelFromDevice(*device_info); |
| return; |
| } |
| |
| if (it->second.shared_guest_id.has_value()) { |
| DetachUsbDeviceFromVm(it->second.shared_guest_id->vm_name, guid, |
| base::DoNothing()); |
| } |
| usb_devices_.erase(it); |
| SignalUsbDeviceObservers(); |
| } |
| |
| void CrosUsbDetector::OnDeviceManagerConnectionError() { |
| device_manager_.reset(); |
| client_receiver_.reset(); |
| ConnectToDeviceManager(); |
| } |
| |
| void CrosUsbDetector::ConnectSharedDevicesOnVmStartup( |
| const std::string& vm_name) { |
| // Reattach shared devices when the VM becomes available. |
| for (auto& it : usb_devices_) { |
| auto& device = it.second; |
| if (device.shared_guest_id.has_value() && |
| device.shared_guest_id->vm_name == vm_name) { |
| VLOG(1) << "Connecting " << device.label << " to " << vm_name; |
| LOG(WARNING) << "Connecting " << device.label << "to " << vm_name |
| << " on vm startup"; |
| // Clear any older guest_port setting. |
| device.guest_port = std::nullopt; |
| AttachUsbDeviceToGuest(*device.shared_guest_id, device.info->guid, |
| base::DoNothing()); |
| } |
| } |
| } |
| |
| void CrosUsbDetector::DisconnectSharedDevicesOnVmShutdown( |
| const std::string& vm_name) { |
| // Clear guest_port on shared devices when the VM shuts down. |
| for (auto& it : usb_devices_) { |
| auto& device = it.second; |
| if (device.shared_guest_id.has_value() && |
| device.shared_guest_id->vm_name == vm_name) { |
| VLOG(1) << device.label << " is disconnected from " << vm_name; |
| device.guest_port = std::nullopt; |
| } |
| } |
| } |
| |
| void CrosUsbDetector::AttachUsbDeviceToGuest( |
| const guest_os::GuestId& guest_id, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback) { |
| const auto& it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(WARNING) << "Attempted to attach device that does not exist: " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto& device = it->second; |
| |
| // If we tried to share a device to a VM that wasn't started, |
| // |shared_guest_id| would be set but |guest_port| would be empty. Once the VM |
| // is started, we go through this flow again. |
| if (device.guest_port.has_value()) { |
| if (device.shared_guest_id == guest_id) { |
| LOG(WARNING) << "Device " << device.label << " is already shared with vm " |
| << guest_id.vm_name; |
| std::move(callback).Run(true); |
| return; |
| } else if (device.shared_guest_id->vm_name == guest_id.vm_name && |
| device.shared_guest_id->container_name != |
| guest_id.container_name) { |
| // The device is already shared with VM but in wrong container. In case |
| // the new container is stopped, detach it from the old container first, |
| // so that it can be attached later. |
| DetachUsbDeviceFromContainer( |
| guest_id.vm_name, *device.guest_port, device.info->guid, |
| base::BindOnce(&CrosUsbDetector::ContainerAttachAfterDetach, |
| weak_ptr_factory_.GetWeakPtr(), guest_id, |
| *device.guest_port, guid, std::move(callback))); |
| return; |
| } |
| } |
| |
| UnmountFilesystems(guest_id, guid, std::move(callback)); |
| } |
| |
| void CrosUsbDetector::DetachUsbDeviceFromVm( |
| const std::string& vm_name, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback) { |
| const auto& it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(WARNING) << "Attempted to detach device that does not exist: " << guid; |
| std::move(callback).Run(/*success=*/true); |
| return; |
| } |
| |
| UsbDevice& device = it->second; |
| if (!device.shared_guest_id.has_value() || |
| device.shared_guest_id->vm_name != vm_name) { |
| LOG(WARNING) << "Failed to detach " << guid << " from " << vm_name |
| << ". It appears to be shared with " |
| << (device.shared_guest_id.has_value() |
| ? device.shared_guest_id->vm_name |
| : "[not shared]") |
| << " at port " |
| << (device.guest_port |
| ? base::NumberToString(*device.guest_port) |
| : "[not attached]") |
| << "."; |
| |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| |
| if (!device.guest_port) { |
| // The VM hasn't been started yet, attaching is in progress, or attaching |
| // failed. |
| // TODO(timloh): Check what happens if attaching to a different VM races |
| // with an in progress attach. |
| RelinquishDeviceClaim(guid); |
| device.shared_guest_id = std::nullopt; |
| SignalUsbDeviceObservers(); |
| std::move(callback).Run(/*success=*/true); |
| return; |
| } |
| |
| vm_tools::concierge::DetachUsbDeviceRequest request; |
| request.set_vm_name(vm_name); |
| request.set_owner_id(crostini::CryptohomeIdForProfile(profile())); |
| request.set_guest_port(*device.guest_port); |
| |
| ConciergeClient::Get()->DetachUsbDevice( |
| std::move(request), |
| base::BindOnce(&CrosUsbDetector::OnUsbDeviceDetachFinished, |
| weak_ptr_factory_.GetWeakPtr(), vm_name, guid, |
| std::move(callback))); |
| } |
| |
| void CrosUsbDetector::OnListAttachedDevices( |
| std::vector<device::mojom::UsbDeviceInfoPtr> devices) { |
| for (device::mojom::UsbDeviceInfoPtr& device_info : devices) { |
| CrosUsbDetector::OnDeviceAdded(std::move(device_info), |
| /*hide_notification*/ true); |
| } |
| } |
| |
| void CrosUsbDetector::UnmountFilesystems( |
| const guest_os::GuestId& guest_id, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback) { |
| auto it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(ERROR) << "Couldn't find device " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| it->second.is_unmounting = true; |
| FilesystemUnmounter::UnmountPaths( |
| it->second.mount_points, |
| base::BindOnce(&CrosUsbDetector::OnUnmountFilesystems, |
| weak_ptr_factory_.GetWeakPtr(), guest_id, guid, |
| std::move(callback))); |
| } |
| |
| void CrosUsbDetector::OnUnmountFilesystems( |
| const guest_os::GuestId& guest_id, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback, |
| bool unmount_success) { |
| auto it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(ERROR) << "Couldn't find device " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| UsbDevice& device = it->second; |
| device.is_unmounting = false; |
| |
| if (!unmount_success) { |
| // FilesystemUnmounter already logged the error. |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Detach first if device is attached elsewhere |
| if (device.guest_port.has_value()) { |
| LOG(WARNING) << "Device was attached already, detaching before attaching."; |
| DetachUsbDeviceFromVm(device.shared_guest_id->vm_name, guid, |
| base::BindOnce(&CrosUsbDetector::AttachAfterDetach, |
| weak_ptr_factory_.GetWeakPtr(), |
| guest_id, guid, std::move(callback))); |
| } else { |
| // The device isn't attached. |
| LOG(WARNING) << "Device was available (not attached to vm)."; |
| AttachAfterDetach(guest_id, guid, std::move(callback), |
| /*detach_success=*/true); |
| } |
| } |
| |
| void CrosUsbDetector::AttachAfterDetach( |
| const guest_os::GuestId& guest_id, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback, |
| bool detach_success) { |
| if (!detach_success) { |
| LOG(ERROR) << "Failed to detatch before attach"; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(ERROR) << "No device info for " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto& device = it->second; |
| |
| // Mark the USB device shared so that it will be shared when the VM starts |
| // if it isn't started yet. This also ensures the UI will show the device as |
| // shared. The guest_port will be set later. |
| device.shared_guest_id = guest_id; |
| |
| auto claim_it = devices_claimed_.find(guid); |
| if (claim_it != devices_claimed_.end()) { |
| if (claim_it->second.device_file.is_valid()) { |
| LOG(WARNING) << "Device was already claimed."; |
| // We take a dup here which will be closed if DoVmAttach fails. |
| base::ScopedFD device_fd(dup(claim_it->second.device_file.get())); |
| DoVmAttach(guest_id, device.info.Clone(), std::move(device_fd), |
| std::move(callback)); |
| } else { |
| LOG(WARNING) << "Device " << guid << " already claimed and awaiting fd."; |
| std::move(callback).Run(false); |
| } |
| return; |
| } |
| |
| VLOG(1) << "Opening " << guid; |
| |
| base::ScopedFD read_end, write_end; |
| if (!base::CreatePipe(&read_end, &write_end, /*non_blocking=*/true)) { |
| LOG(ERROR) << "Couldn't create pipe for " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| VLOG(1) << "Saving lifeline_fd " << write_end.get(); |
| devices_claimed_[guid].lifeline_file = std::move(write_end); |
| |
| // Open a file descriptor to pass to CrostiniManager & Concierge. |
| LOG(WARNING) << "Opening fd for device."; |
| device_manager_->OpenFileDescriptor( |
| guid, kAllInterfacesMask, mojo::PlatformHandle(std::move(read_end)), |
| base::BindOnce(&CrosUsbDetector::OnAttachUsbDeviceOpened, |
| weak_ptr_factory_.GetWeakPtr(), guest_id, |
| device.info.Clone(), std::move(callback))); |
| |
| // Close any associated notifications (the user isn't using them). This |
| // destroys the CrosUsbNotificationDelegate and vm_name and guid args may be |
| // invalid after Close. |
| SystemNotificationHelper::GetInstance()->Close( |
| CrosUsbDetector::MakeNotificationId(guid)); |
| } |
| |
| void CrosUsbDetector::OnAttachUsbDeviceOpened( |
| const guest_os::GuestId& guest_id, |
| device::mojom::UsbDeviceInfoPtr device_info, |
| base::OnceCallback<void(bool success)> callback, |
| base::File file) { |
| if (!file.IsValid()) { |
| LOG(ERROR) << "Permission broker refused access to USB device"; |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| devices_claimed_[device_info->guid].device_file = |
| base::ScopedFD(file.Duplicate().TakePlatformFile()); |
| if (!manager()) { |
| LOG(ERROR) << "Attaching device without Crostini manager instance"; |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| DoVmAttach(guest_id, device_info.Clone(), |
| base::ScopedFD(file.TakePlatformFile()), std::move(callback)); |
| } |
| |
| void CrosUsbDetector::DoVmAttach( |
| const guest_os::GuestId& guest_id, |
| device::mojom::UsbDeviceInfoPtr device_info, |
| base::ScopedFD fd, |
| base::OnceCallback<void(bool success)> callback) { |
| vm_tools::concierge::AttachUsbDeviceRequest request; |
| request.set_vm_name(guest_id.vm_name); |
| request.set_owner_id(crostini::CryptohomeIdForProfile(profile())); |
| request.set_bus_number(device_info->bus_number); |
| request.set_port_number(device_info->port_number); |
| request.set_vendor_id(device_info->vendor_id); |
| request.set_product_id(device_info->product_id); |
| |
| ConciergeClient::Get()->AttachUsbDevice( |
| std::move(fd), std::move(request), |
| base::BindOnce(&CrosUsbDetector::OnUsbDeviceAttachFinished, |
| weak_ptr_factory_.GetWeakPtr(), guest_id, |
| std::move(device_info), std::move(callback))); |
| } |
| |
| void CrosUsbDetector::OnUsbDeviceAttachFinished( |
| const guest_os::GuestId& guest_id, |
| device::mojom::UsbDeviceInfoPtr device_info, |
| base::OnceCallback<void(bool success)> callback, |
| std::optional<vm_tools::concierge::AttachUsbDeviceResponse> response) { |
| bool success = true; |
| if (!response) { |
| LOG(ERROR) << "Failed to attach USB device, empty dbus response"; |
| success = false; |
| } else if (!response->success()) { |
| LOG(ERROR) << "Failed to attach USB device, " << response->reason(); |
| success = false; |
| } |
| |
| if (success) { |
| auto it = usb_devices_.find(device_info->guid); |
| if (it == usb_devices_.end()) { |
| LOG(WARNING) << "Dbus response indicates successful attach but device " |
| << "info was missing for " << device_info->guid; |
| success = false; |
| } else { |
| it->second.shared_guest_id = guest_id; |
| it->second.guest_port = response->guest_port(); |
| } |
| } |
| |
| PrefService* prefs = profile()->GetPrefs(); |
| if (success) { |
| LOG(WARNING) << "Successful connection of " |
| << UsbDeviceIdentifier(device_info); |
| } |
| if (success && |
| prefs->GetBoolean( |
| guest_os::prefs::kGuestOsUSBPersistentPassthroughEnabled)) { |
| ScopedDictPrefUpdate update( |
| prefs, guest_os::prefs::kGuestOsUSBPersistentPassthroughDevices); |
| base::Value::Dict& devices = update.Get(); |
| std::string device_identifier = UsbDeviceIdentifier(device_info); |
| LOG(WARNING) << "After successful connection of " << device_identifier |
| << "to " << guest_id.Serialize() |
| << ", it is being added to persistency dictionary."; |
| // there are 3 possible scenarios here: |
| // 1 - device was not in list. in this case we definitely want to add it. |
| // 2 - device was in list for a different guest. in this case we want to |
| // override the previous state. |
| // 3 - device was in list, with the current guest. we already have to |
| // serialize the guest_id to check, so not much more different in |
| // comparing vs writing the same thing back again. |
| devices.Set(device_identifier, guest_id.Serialize()); |
| } |
| |
| if (success && !guest_id.container_name.empty()) { |
| AttachUsbDeviceToContainer(guest_id, response->guest_port(), |
| device_info->guid, std::move(callback)); |
| } else { |
| SignalUsbDeviceObservers(); |
| std::move(callback).Run(success); |
| } |
| } |
| |
| void CrosUsbDetector::AttachUsbDeviceToContainer( |
| const guest_os::GuestId& guest_id, |
| uint8_t guest_port, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback) { |
| vm_tools::cicerone::AttachUsbToContainerRequest request; |
| request.set_vm_name(guest_id.vm_name); |
| request.set_container_name(guest_id.container_name); |
| request.set_owner_id(crostini::CryptohomeIdForProfile(profile())); |
| request.set_port_num(static_cast<int32_t>(guest_port)); |
| |
| CiceroneClient::Get()->AttachUsbToContainer( |
| std::move(request), |
| base::BindOnce(&CrosUsbDetector::OnContainerAttachFinished, |
| weak_ptr_factory_.GetWeakPtr(), guest_id, guid, |
| std::move(callback))); |
| } |
| |
| void CrosUsbDetector::OnContainerAttachFinished( |
| const guest_os::GuestId& guest_id, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback, |
| std::optional<vm_tools::cicerone::AttachUsbToContainerResponse> response) { |
| bool success = true; |
| if (!response) { |
| LOG(ERROR) << "Failed to attach USB device, empty dbus response"; |
| success = false; |
| } else if (response->status() != |
| vm_tools::cicerone::AttachUsbToContainerResponse_Status_OK) { |
| LOG(ERROR) << "Failed to attach USB device, " << response->failure_reason(); |
| success = false; |
| } |
| |
| if (success) { |
| const auto& it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(WARNING) << "Dbus response indicates successful attach but device " |
| << "info was missing for " << guid; |
| success = false; |
| } else { |
| it->second.shared_guest_id = guest_id; |
| } |
| } |
| |
| SignalUsbDeviceObservers(); |
| std::move(callback).Run(success); |
| } |
| |
| void CrosUsbDetector::DetachUsbDeviceFromContainer( |
| const std::string& vm_name, |
| uint8_t guest_port, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback) { |
| vm_tools::cicerone::DetachUsbFromContainerRequest request; |
| request.set_vm_name(vm_name); |
| request.set_owner_id(crostini::CryptohomeIdForProfile(profile())); |
| request.set_port_num(static_cast<int32_t>(guest_port)); |
| |
| CiceroneClient::Get()->DetachUsbFromContainer( |
| std::move(request), |
| base::BindOnce(&CrosUsbDetector::OnContainerDetachFinished, |
| weak_ptr_factory_.GetWeakPtr(), vm_name, guid, |
| std::move(callback))); |
| } |
| |
| void CrosUsbDetector::OnContainerDetachFinished( |
| const std::string& vm_name, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback, |
| std::optional<vm_tools::cicerone::DetachUsbFromContainerResponse> |
| response) { |
| bool success = true; |
| if (!response) { |
| LOG(ERROR) << "Failed to attach USB device, empty dbus response"; |
| success = false; |
| } else if (response->status() != |
| vm_tools::cicerone::DetachUsbFromContainerResponse_Status_OK) { |
| LOG(ERROR) << "Failed to attach USB device, " << response->failure_reason(); |
| success = false; |
| } |
| |
| if (success) { |
| const auto& it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(WARNING) << "Dbus response indicates successful detach but device " |
| << "info was missing for " << guid; |
| success = false; |
| } else { |
| it->second.shared_guest_id->container_name = ""; |
| } |
| } |
| |
| SignalUsbDeviceObservers(); |
| std::move(callback).Run(success); |
| } |
| |
| void CrosUsbDetector::ContainerAttachAfterDetach( |
| const guest_os::GuestId& guest_id, |
| uint8_t guest_port, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback, |
| bool detach_success) { |
| if (!detach_success) { |
| LOG(ERROR) << "Failed to detach from container before attach"; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const auto& it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(ERROR) << "No device info for " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto& device = it->second; |
| if (device.shared_guest_id->vm_name != guest_id.vm_name) { |
| LOG(ERROR) << "Unexpected VM name for device " << guid; |
| std::move(callback).Run(false); |
| return; |
| } else if (device.guest_port != guest_port) { |
| LOG(ERROR) << "Unexpected guest port for device " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Set the container name so if the container is stopped, the device will be |
| // attached after the container starts. |
| device.shared_guest_id->container_name = guest_id.container_name; |
| AttachUsbDeviceToContainer(guest_id, guest_port, guid, std::move(callback)); |
| } |
| |
| void CrosUsbDetector::OnUsbDeviceDetachFinished( |
| const std::string& vm_name, |
| const std::string& guid, |
| base::OnceCallback<void(bool success)> callback, |
| std::optional<vm_tools::concierge::SuccessFailureResponse> response) { |
| bool success = true; |
| if (!response) { |
| LOG(ERROR) << "Failed to detach USB device, empty dbus response"; |
| success = false; |
| } else if (!response->success()) { |
| LOG(ERROR) << "Failed to detach USB device, " << response->failure_reason(); |
| success = false; |
| } |
| |
| auto it = usb_devices_.find(guid); |
| if (it == usb_devices_.end()) { |
| LOG(WARNING) << "Dbus response indicates successful detach but device info " |
| << "was missing for " << guid; |
| } else { |
| it->second.shared_guest_id = std::nullopt; |
| it->second.guest_port = std::nullopt; |
| } |
| RelinquishDeviceClaim(guid); |
| SignalUsbDeviceObservers(); |
| std::move(callback).Run(success); |
| } |
| |
| void CrosUsbDetector::RelinquishDeviceClaim(const std::string& guid) { |
| auto it = devices_claimed_.find(guid); |
| if (it != devices_claimed_.end()) { |
| VLOG(1) << "Closing lifeline_fd " << it->second.lifeline_file.get(); |
| devices_claimed_.erase(it); |
| } else { |
| LOG(ERROR) << "Relinquishing device with no prior claim: " << guid; |
| } |
| } |
| |
| } // namespace ash |