/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "UDisksProvider.h" #include "ServiceBroker.h" #include "guilib/LocalizeStrings.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" #include "platform/posix/PosixMountProvider.h" CUDiskDevice::CUDiskDevice(const char *DeviceKitUDI): m_DeviceKitUDI(DeviceKitUDI) { m_isMounted = false; m_isMountedByUs = false; m_isRemovable = false; m_isPartition = false; m_isFileSystem = false; m_isSystemInternal = false; m_isOptical = false; m_PartitionSize = 0; Update(); } void CUDiskDevice::Update() { CVariant properties = CDBusUtil::GetAll("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device"); m_isFileSystem = properties["IdUsage"].asString() == "filesystem"; if (m_isFileSystem) { m_UDI = properties["IdUuid"].asString(); m_Label = properties["IdLabel"].asString(); m_FileSystem = properties["IdType"].asString(); } else { m_UDI.clear(); m_Label.clear(); m_FileSystem.clear(); } m_isMounted = properties["DeviceIsMounted"].asBoolean(); if (m_isMounted && !properties["DeviceMountPaths"].empty()) m_MountPath = properties["DeviceMountPaths"][0].asString(); else m_MountPath.clear(); m_PartitionSize = properties["PartitionSize"].asUnsignedInteger(); m_isPartition = properties["DeviceIsPartition"].asBoolean(); m_isSystemInternal = properties["DeviceIsSystemInternal"].asBoolean(); m_isOptical = properties["DeviceIsOpticalDisc"].asBoolean(); if (m_isPartition) { CVariant isRemovable = CDBusUtil::GetVariant("org.freedesktop.UDisks", properties["PartitionSlave"].asString().c_str(), "org.freedesktop.UDisks.Device", "DeviceIsRemovable"); if ( !isRemovable.isNull() ) m_isRemovable = isRemovable.asBoolean(); else m_isRemovable = false; } else m_isRemovable = properties["DeviceIsRemovable"].asBoolean(); } bool CUDiskDevice::Mount() { if (!m_isMounted && !m_isSystemInternal && m_isFileSystem) { CLog::Log(LOGDEBUG, "UDisks: Mounting {}", m_DeviceKitUDI); CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemMount"); message.AppendArgument(""); const char *array[] = {}; message.AppendArgument(array, 0); DBusMessage *reply = message.SendSystem(); if (reply) { char *mountPoint; if (dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &mountPoint, DBUS_TYPE_INVALID)) { m_MountPath = mountPoint; CLog::Log(LOGDEBUG, "UDisks: Successfully mounted {} on {}", m_DeviceKitUDI, mountPoint); m_isMountedByUs = m_isMounted = true; } } return m_isMounted; } else CLog::Log(LOGDEBUG, "UDisks: Is not able to mount {}", ToString()); return false; } bool CUDiskDevice::UnMount() { if (m_isMounted && !m_isSystemInternal && m_isFileSystem) { CDBusMessage message("org.freedesktop.UDisks", m_DeviceKitUDI.c_str(), "org.freedesktop.UDisks.Device", "FilesystemUnmount"); const char *array[1]; message.AppendArgument(array, 0); DBusMessage *reply = message.SendSystem(); if (reply) m_isMountedByUs = m_isMounted = false; return !m_isMounted; } else CLog::Log(LOGDEBUG, "UDisks: Is not able to unmount {}", ToString()); return false; } CMediaSource CUDiskDevice::ToMediaShare() const { CMediaSource source; source.strPath = m_MountPath; if (m_Label.empty()) { std::string strSize = StringUtils::SizeToString(m_PartitionSize); source.strName = StringUtils::Format("{} {}", strSize, g_localizeStrings.Get(155)); } else source.strName = m_Label; if (m_isOptical) source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; else if (m_isSystemInternal) source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; else source.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE; source.m_ignore = true; return source; } bool CUDiskDevice::IsApproved() const { return (m_isFileSystem && m_isMounted && m_UDI.length() > 0 && (m_FileSystem.length() > 0 && m_FileSystem != "swap") && m_MountPath != "/" && m_MountPath != "/boot") || m_isOptical; } bool CUDiskDevice::IsOptical() const { return m_isOptical; } MEDIA_DETECT::STORAGE::Type CUDiskDevice::GetStorageType() const { if (IsOptical()) return MEDIA_DETECT::STORAGE::Type::OPTICAL; return MEDIA_DETECT::STORAGE::Type::UNKNOWN; } bool CUDiskDevice::IsMounted() const { return m_isMounted; } std::string CUDiskDevice::GetDisplayName() const { return m_Label; } std::string CUDiskDevice::GetMountPoint() const { return m_MountPath; } bool CUDiskDevice::IsSystemInternal() const { return m_isSystemInternal; } MEDIA_DETECT::STORAGE::StorageDevice CUDiskDevice::ToStorageDevice() const { MEDIA_DETECT::STORAGE::StorageDevice device; device.label = GetDisplayName(); device.path = GetMountPoint(); device.type = GetStorageType(); return device; } #define BOOL2SZ(b) ((b) ? "true" : "false") std::string CUDiskDevice::ToString() const { return StringUtils::Format("DeviceUDI {}: IsFileSystem {} HasFileSystem {} " "IsSystemInternal {} IsMounted {} IsRemovable {} IsPartition {} " "IsOptical {}", m_DeviceKitUDI, BOOL2SZ(m_isFileSystem), m_FileSystem, BOOL2SZ(m_isSystemInternal), BOOL2SZ(m_isMounted), BOOL2SZ(m_isRemovable), BOOL2SZ(m_isPartition), BOOL2SZ(m_isOptical)); } CUDisksProvider::CUDisksProvider() { //! @todo do not use dbus_connection_pop_message() that requires the use of a //! private connection if (!m_connection.Connect(DBUS_BUS_SYSTEM, true)) { return; } dbus_connection_set_exit_on_disconnect(m_connection, false); CDBusError error; dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UDisks'", error); dbus_connection_flush(m_connection); if (error) { error.Log("UDisks: Failed to attach to signal"); m_connection.Destroy(); } } CUDisksProvider::~CUDisksProvider() { for (auto& itr : m_AvailableDevices) delete itr.second; m_AvailableDevices.clear(); } void CUDisksProvider::Initialize() { CLog::Log(LOGDEBUG, "Selected UDisks as storage provider"); m_DaemonVersion = atoi(CDBusUtil::GetVariant("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "DaemonVersion").asString().c_str()); CLog::Log(LOGDEBUG, "UDisks: DaemonVersion {}", m_DaemonVersion); CLog::Log(LOGDEBUG, "UDisks: Querying available devices"); std::vector devices = EnumerateDisks(); for (unsigned int i = 0; i < devices.size(); i++) DeviceAdded(devices[i].c_str(), NULL); } bool CUDisksProvider::Eject(const std::string& mountpath) { std::string path(mountpath); URIUtils::RemoveSlashAtEnd(path); for (auto& itr : m_AvailableDevices) { CUDiskDevice* device = itr.second; if (device->GetMountPoint() == path) return device->UnMount(); } return false; } std::vector CUDisksProvider::GetDiskUsage() { CPosixMountProvider legacy; return legacy.GetDiskUsage(); } bool CUDisksProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) { bool result = false; if (m_connection) { dbus_connection_read_write(m_connection, 0); DBusMessagePtr msg(dbus_connection_pop_message(m_connection)); if (msg) { char *object; if (dbus_message_get_args (msg.get(), NULL, DBUS_TYPE_OBJECT_PATH, &object, DBUS_TYPE_INVALID)) { result = true; if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceAdded")) DeviceAdded(object, callback); else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceRemoved")) DeviceRemoved(object, callback); else if (dbus_message_is_signal(msg.get(), "org.freedesktop.UDisks", "DeviceChanged")) DeviceChanged(object, callback); } } } return result; } bool CUDisksProvider::HasUDisks() { return CDBusUtil::TryMethodCall(DBUS_BUS_SYSTEM, "org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices"); } void CUDisksProvider::DeviceAdded(const char *object, IStorageEventsCallback *callback) { CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceAdded ({})", object); if (m_AvailableDevices[object]) { CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceAdded on an indexed disk"); delete m_AvailableDevices[object]; } CUDiskDevice *device = NULL; device = new CUDiskDevice(object); m_AvailableDevices[object] = device; // optical drives should be always mounted by default unless explicitly disabled by the user if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_handleMounting || (device->IsOptical() && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_autoMountOpticalMedia)) { device->Mount(); } CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceAdded - {}", device->ToString()); if (device->IsMounted() && device->IsApproved()) { CLog::Log(LOGINFO, "UDisks: Added {}", device->GetMountPoint()); if (callback) callback->OnStorageAdded(device->ToStorageDevice()); } } void CUDisksProvider::DeviceRemoved(const char *object, IStorageEventsCallback *callback) { CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceRemoved ({})", object); CUDiskDevice *device = m_AvailableDevices[object]; if (device) { if (device->IsMounted() && callback) callback->OnStorageUnsafelyRemoved(device->ToStorageDevice()); delete m_AvailableDevices[object]; m_AvailableDevices.erase(object); } } void CUDisksProvider::DeviceChanged(const char *object, IStorageEventsCallback *callback) { CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceChanged ({})", object); CUDiskDevice *device = m_AvailableDevices[object]; if (device == NULL) { CLog::Log(LOGWARNING, "UDisks: Inconsistency found! DeviceChanged on an unindexed disk"); DeviceAdded(object, callback); } else { bool mounted = device->IsMounted(); // make sure to not silently remount ejected usb thumb drives that user wants to eject // optical drives should be always mounted by default unless explicitly disabled by the user const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); if (!mounted && device->IsOptical()) { if (advancedSettings->m_handleMounting || advancedSettings->m_autoMountOpticalMedia) { device->Mount(); } } device->Update(); if (!mounted && device->IsMounted() && callback) callback->OnStorageAdded(device->ToStorageDevice()); else if (mounted && !device->IsMounted() && callback) callback->OnStorageSafelyRemoved(device->ToStorageDevice()); CLog::Log(LOGDEBUG, LOGDBUS, "UDisks: DeviceChanged - {}", device->ToString()); } } std::vector CUDisksProvider::EnumerateDisks() { std::vector devices; CDBusMessage message("org.freedesktop.UDisks", "/org/freedesktop/UDisks", "org.freedesktop.UDisks", "EnumerateDevices"); DBusMessage *reply = message.SendSystem(); if (reply) { char** disks = NULL; int length = 0; if (dbus_message_get_args (reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &disks, &length, DBUS_TYPE_INVALID)) { for (int i = 0; i < length; i++) devices.emplace_back(disks[i]); dbus_free_string_array(disks); } } return devices; } void CUDisksProvider::GetDisks(VECSOURCES& devices, bool EnumerateRemovable) { for (auto& itr : m_AvailableDevices) { CUDiskDevice* device = itr.second; if (device && device->IsApproved() && device->IsSystemInternal() != EnumerateRemovable) devices.push_back(device->ToMediaShare()); } }