|  | // Copyright 2012 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chromeos/ash/components/disks/disk_mount_manager.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <type_traits> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/barrier_closure.h" | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/observer_list.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/system/sys_info.h" | 
|  | #include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h" | 
|  | #include "chromeos/ash/components/disks/disk.h" | 
|  | #include "chromeos/ash/components/disks/suspend_unmount_manager.h" | 
|  |  | 
|  | namespace ash::disks { | 
|  | namespace { | 
|  |  | 
|  | using base::BindOnce; | 
|  |  | 
|  | DiskMountManager* g_disk_mount_manager = nullptr; | 
|  |  | 
|  | struct UnmountDeviceRecursivelyCallbackData { | 
|  | explicit UnmountDeviceRecursivelyCallbackData( | 
|  | DiskMountManager::UnmountDeviceRecursivelyCallbackType in_callback) | 
|  | : callback(std::move(in_callback)) {} | 
|  |  | 
|  | DiskMountManager::UnmountDeviceRecursivelyCallbackType callback; | 
|  | MountError error_code = MountError::kSuccess; | 
|  | }; | 
|  |  | 
|  | void OnAllUnmountDeviceRecursively( | 
|  | std::unique_ptr<UnmountDeviceRecursivelyCallbackData> cb_data) { | 
|  | std::move(cb_data->callback).Run(cb_data->error_code); | 
|  | } | 
|  |  | 
|  | std::string FormatFileSystemTypeToString(FormatFileSystemType filesystem) { | 
|  | switch (filesystem) { | 
|  | case FormatFileSystemType::kUnknown: | 
|  | return ""; | 
|  | case FormatFileSystemType::kVfat: | 
|  | return "vfat"; | 
|  | case FormatFileSystemType::kExfat: | 
|  | return "exfat"; | 
|  | case FormatFileSystemType::kNtfs: | 
|  | return "ntfs"; | 
|  | } | 
|  | NOTREACHED_IN_MIGRATION() | 
|  | << "Unknown filesystem type " << static_cast<int>(filesystem); | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | // The DiskMountManager implementation. | 
|  | class DiskMountManagerImpl : public DiskMountManager, | 
|  | public CrosDisksClient::Observer { | 
|  | public: | 
|  | DiskMountManagerImpl() { cros_disks_client_->AddObserver(this); } | 
|  |  | 
|  | DiskMountManagerImpl(const DiskMountManagerImpl&) = delete; | 
|  | DiskMountManagerImpl& operator=(const DiskMountManagerImpl&) = delete; | 
|  |  | 
|  | ~DiskMountManagerImpl() override { cros_disks_client_->RemoveObserver(this); } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void AddObserver(DiskMountManager::Observer* observer) override { | 
|  | observers_.AddObserver(observer); | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void RemoveObserver(DiskMountManager::Observer* observer) override { | 
|  | observers_.RemoveObserver(observer); | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void MountPath(const std::string& source_path, | 
|  | const std::string& source_format, | 
|  | const std::string& mount_label, | 
|  | const std::vector<std::string>& mount_options, | 
|  | MountType type, | 
|  | MountAccessMode access_mode, | 
|  | MountPathCallback callback) override { | 
|  | if (const auto [_, ok] = | 
|  | mount_callbacks_.try_emplace(source_path, std::move(callback)); | 
|  | !ok) { | 
|  | LOG(ERROR) << "Disk '" << source_path << "' is already being mounted"; | 
|  | std::move(callback).Run(MountError::kPathAlreadyMounted, | 
|  | {source_path, "", type}); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const Disks::const_iterator it = disks_.find(source_path); | 
|  | if (it != disks_.end()) { | 
|  | VLOG(1) << "Disk '" << source_path << "' is already registered"; | 
|  | DCHECK(*it); | 
|  | DCHECK_EQ((*it)->device_path(), source_path); | 
|  | } else { | 
|  | VLOG(1) << "Disk '" << source_path << "' is not registered yet"; | 
|  | } | 
|  |  | 
|  | // Hidden and non-existent devices should not be mounted. | 
|  | if (type == MountType::kDevice && | 
|  | (it == disks_.end() || (*it)->is_hidden())) { | 
|  | VLOG(1) << "Disk '" << source_path << "' should not be mounted"; | 
|  | OnMountCompleted({source_path, {}, type, MountError::kInternalError}); | 
|  | return; | 
|  | } | 
|  |  | 
|  | VLOG(1) << "Mounting '" << source_path << "'..."; | 
|  | cros_disks_client_->Mount( | 
|  | source_path, source_format, mount_label, mount_options, access_mode, | 
|  | RemountOption::kMountNewDevice, | 
|  | BindOnce(&DiskMountManagerImpl::OnMount, weak_ptr_factory_.GetWeakPtr(), | 
|  | source_path, type)); | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void UnmountPath(const std::string& mount_path, | 
|  | UnmountPathCallback callback) override { | 
|  | UnmountChildMounts(mount_path); | 
|  | VLOG(1) << "Unmounting '" << mount_path << "'..."; | 
|  | cros_disks_client_->Unmount(mount_path, | 
|  | BindOnce(&DiskMountManagerImpl::OnUnmountPath, | 
|  | weak_ptr_factory_.GetWeakPtr(), | 
|  | std::move(callback), mount_path)); | 
|  | } | 
|  |  | 
|  | void RemountAllRemovableDrives(MountAccessMode mode) override { | 
|  | // TODO(yamaguchi): Retry for tentative remount failures. crbug.com/661455 | 
|  | for (const auto& disk : disks_) { | 
|  | DCHECK(disk); | 
|  | if (disk->is_read_only_hardware()) { | 
|  | // Read-only devices can be mounted in RO mode only. No need to remount. | 
|  | continue; | 
|  | } | 
|  | if (!disk->is_mounted()) { | 
|  | continue; | 
|  | } | 
|  | RemountRemovableDrive(*disk, mode); | 
|  | } | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void FormatMountedDevice(const std::string& mount_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label) override { | 
|  | MountPoints::const_iterator mount_point = mount_points_.find(mount_path); | 
|  | if (mount_point == mount_points_.end()) { | 
|  | LOG(ERROR) << "Cannot find mount point '" << mount_path << "'"; | 
|  | // We can't call OnFormatCompleted until |pending_format_changes_| has | 
|  | // been populated. | 
|  | NotifyFormatStatusUpdate(FORMAT_COMPLETED, FormatError::kUnknownError, | 
|  | mount_path, label); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string device_path = mount_point->source_path; | 
|  | const std::string filesystem_str = FormatFileSystemTypeToString(filesystem); | 
|  | pending_format_changes_[device_path] = {filesystem_str, label}; | 
|  |  | 
|  | Disks::const_iterator disk = disks_.find(device_path); | 
|  | if (disk == disks_.end()) { | 
|  | LOG(ERROR) << "Cannot find device '" << device_path << "'"; | 
|  | OnFormatCompleted(FormatError::kUnknownError, device_path); | 
|  | return; | 
|  | } | 
|  | if (disk->get()->is_read_only()) { | 
|  | LOG(ERROR) << "Device '" << device_path << "' is read-only"; | 
|  | OnFormatCompleted(FormatError::kDeviceNotAllowed, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (filesystem == FormatFileSystemType::kUnknown) { | 
|  | LOG(ERROR) << "Unknown filesystem passed to FormatMountedDevice"; | 
|  | OnFormatCompleted(FormatError::kUnsupportedFilesystem, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | UnmountPath(disk->get()->mount_path(), | 
|  | BindOnce(&DiskMountManagerImpl::OnUnmountPathForFormat, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, | 
|  | filesystem, label)); | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void SinglePartitionFormatDevice(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label) override { | 
|  | Disks::const_iterator disk_iter = disks_.find(device_path); | 
|  | if (disk_iter == disks_.end()) { | 
|  | LOG(ERROR) << "Cannot find device '" << device_path << "'"; | 
|  | OnPartitionCompleted(device_path, filesystem, label, | 
|  | PartitionError::kInvalidDevicePath); | 
|  | return; | 
|  | } | 
|  |  | 
|  | UnmountDeviceRecursively( | 
|  | device_path, | 
|  | BindOnce(&DiskMountManagerImpl::OnUnmountDeviceForSinglePartitionFormat, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, filesystem, | 
|  | label)); | 
|  | } | 
|  |  | 
|  | void RenameMountedDevice(const std::string& mount_path, | 
|  | const std::string& volume_name) override { | 
|  | MountPoints::const_iterator mount_point = mount_points_.find(mount_path); | 
|  | if (mount_point == mount_points_.end()) { | 
|  | LOG(ERROR) << "Cannot find mount point '" << mount_path << "'"; | 
|  | // We can't call OnRenameCompleted until |pending_rename_changes_| has | 
|  | // been populated. | 
|  | NotifyRenameStatusUpdate(RENAME_COMPLETED, RenameError::kUnknownError, | 
|  | mount_path, volume_name); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string device_path = mount_point->source_path; | 
|  | pending_rename_changes_[device_path] = volume_name; | 
|  |  | 
|  | Disks::const_iterator iter = disks_.find(device_path); | 
|  | if (iter == disks_.end()) { | 
|  | LOG(ERROR) << "Cannot find device '" << device_path << "'"; | 
|  | OnRenameCompleted(RenameError::kUnknownError, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (iter->get()->is_read_only()) { | 
|  | LOG(ERROR) << "Device '" << device_path << "' is read-only"; | 
|  | OnRenameCompleted(RenameError::kDeviceNotAllowed, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | UnmountPath( | 
|  | iter->get()->mount_path(), | 
|  | BindOnce(&DiskMountManagerImpl::OnUnmountPathForRename, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, volume_name)); | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void UnmountDeviceRecursively( | 
|  | const std::string& device_path, | 
|  | UnmountDeviceRecursivelyCallbackType callback) override { | 
|  | std::vector<std::string> devices_to_unmount; | 
|  |  | 
|  | // Get list of all devices to unmount. | 
|  | for (const auto& disk : disks_) { | 
|  | DCHECK(disk); | 
|  | if (!disk->mount_path().empty() && | 
|  | base::StartsWith(disk->device_path(), device_path)) { | 
|  | devices_to_unmount.push_back(disk->mount_path()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Is there anything to unmount? | 
|  | if (devices_to_unmount.empty()) { | 
|  | const auto it = disks_.find(device_path); | 
|  | if (it == disks_.end()) { | 
|  | LOG(ERROR) << "Cannot find device '" << device_path << "'"; | 
|  | std::move(callback).Run(MountError::kInvalidDevicePath); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Nothing to unmount. | 
|  | DCHECK(*it); | 
|  | DCHECK_EQ((*it)->device_path(), device_path); | 
|  | DCHECK_EQ((*it)->mount_path(), ""); | 
|  | LOG(WARNING) << "Disk '" << device_path << "' is already unmounted"; | 
|  | std::move(callback).Run(MountError::kSuccess); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // There is something to unmount. | 
|  | std::unique_ptr<UnmountDeviceRecursivelyCallbackData> cb_data = | 
|  | std::make_unique<UnmountDeviceRecursivelyCallbackData>( | 
|  | std::move(callback)); | 
|  | UnmountDeviceRecursivelyCallbackData* raw_cb_data = cb_data.get(); | 
|  | base::RepeatingClosure done_callback = base::BarrierClosure( | 
|  | devices_to_unmount.size(), | 
|  | BindOnce(&OnAllUnmountDeviceRecursively, std::move(cb_data))); | 
|  |  | 
|  | for (const std::string& device : devices_to_unmount) { | 
|  | VLOG(1) << "Unmounting '" << device << "'..."; | 
|  | cros_disks_client_->Unmount( | 
|  | device, BindOnce(&DiskMountManagerImpl::OnUnmountDeviceRecursively, | 
|  | weak_ptr_factory_.GetWeakPtr(), raw_cb_data, device, | 
|  | done_callback)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | void EnsureMountInfoRefreshed(EnsureMountInfoRefreshedCallback callback, | 
|  | bool force) override { | 
|  | if (!force && already_refreshed_) { | 
|  | std::move(callback).Run(true); | 
|  | return; | 
|  | } | 
|  |  | 
|  | refresh_callbacks_.push_back(std::move(callback)); | 
|  | if (refresh_callbacks_.size() == 1) { | 
|  | // If there's no in-flight refreshing task, start it. | 
|  | cros_disks_client_->EnumerateDevices( | 
|  | BindOnce(&DiskMountManagerImpl::RefreshAfterEnumerateDevices, | 
|  | weak_ptr_factory_.GetWeakPtr()), | 
|  | BindOnce(&DiskMountManagerImpl::RefreshCompleted, | 
|  | weak_ptr_factory_.GetWeakPtr(), false)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | const Disks& disks() const override { return disks_; } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | const Disk* FindDiskBySourcePath( | 
|  | const std::string& source_path) const override { | 
|  | const Disks::const_iterator it = disks_.find(source_path); | 
|  | return it == disks_.end() ? nullptr : it->get(); | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | const MountPoints& mount_points() const override { return mount_points_; } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | bool AddDiskForTest(std::unique_ptr<Disk> disk) override { | 
|  | const auto [it, ok] = disks_.insert(std::move(disk)); | 
|  | LOG_IF(ERROR, !ok) << "Cannot add a duplicate disk '" | 
|  | << (*it)->device_path() << "'"; | 
|  | return ok; | 
|  | } | 
|  |  | 
|  | // DiskMountManager override. | 
|  | // Corresponding disk should be added to the manager before this is called. | 
|  | bool AddMountPointForTest(const MountPoint& mount_point) override { | 
|  | if (mount_point.mount_type == MountType::kDevice && | 
|  | disks_.count(mount_point.source_path) == 0) { | 
|  | LOG(ERROR) << "Device mount point '" << mount_point.mount_path | 
|  | << "' should have a disk entry '" << mount_point.source_path | 
|  | << "'"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const auto [it, ok] = mount_points_.insert(mount_point); | 
|  | DCHECK_EQ(it->mount_path, mount_point.mount_path); | 
|  | LOG_IF(ERROR, !ok) << "Cannot add a duplicate mount point '" | 
|  | << mount_point.mount_path << "'"; | 
|  | return ok; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // A struct to represent information about a format changes. | 
|  | struct FormatChange { | 
|  | // new file system type | 
|  | std::string file_system_type; | 
|  | // New volume name | 
|  | std::string volume_name; | 
|  | }; | 
|  |  | 
|  | // Stores new volume name and file system type for a device on which | 
|  | // formatting is invoked on, so that OnFormatCompleted can set it back to | 
|  | // |disks_|. The key is a device_path and the value is a FormatChange. | 
|  | std::map<std::string, FormatChange> pending_format_changes_; | 
|  |  | 
|  | // Stores device path are being partitioning. | 
|  | // It allows preventing auto-mount of the disks in this set. | 
|  | std::set<std::string> pending_partitioning_disks_; | 
|  |  | 
|  | // Stores new volume name for a device on which renaming is invoked on, so | 
|  | // that OnRenameCompleted can set it back to |disks_|. The key is a | 
|  | // device_path and the value is new volume_name. | 
|  | std::map<std::string, std::string> pending_rename_changes_; | 
|  |  | 
|  | // Called on D-Bus CrosDisksClient::Mount() is done. | 
|  | void OnMount(const std::string& source_path, MountType type, bool result) { | 
|  | // When succeeds, OnMountCompleted will be called by "MountCompleted", | 
|  | // signal instead. Do nothing now. | 
|  | if (result) | 
|  | return; | 
|  |  | 
|  | OnMountCompleted({source_path, {}, type, MountError::kInternalError}); | 
|  | } | 
|  |  | 
|  | void RemountRemovableDrive(const Disk& disk, MountAccessMode access_mode) { | 
|  | const std::string& mount_path = disk.mount_path(); | 
|  | MountPoints::const_iterator mount_point = mount_points_.find(mount_path); | 
|  | if (mount_point == mount_points_.end()) { | 
|  | // Not in mount_points_. This happens when the mount_points and disks_ are | 
|  | // inconsistent. | 
|  | LOG(ERROR) << "Cannot find mount point '" << mount_path << "'"; | 
|  | OnMountCompleted({disk.device_path(), mount_path, MountType::kDevice, | 
|  | MountError::kPathNotMounted}); | 
|  | return; | 
|  | } | 
|  |  | 
|  | cros_disks_client_->Mount( | 
|  | mount_point->source_path, std::string(), std::string(), {}, access_mode, | 
|  | RemountOption::kRemountExistingDevice, | 
|  | BindOnce(&DiskMountManagerImpl::OnMount, weak_ptr_factory_.GetWeakPtr(), | 
|  | mount_point->source_path, mount_point->mount_type)); | 
|  | } | 
|  |  | 
|  | // Unmounts all mount points whose source path is transitively parented by | 
|  | // |mount_path|. | 
|  | void UnmountChildMounts(std::string mount_path) { | 
|  | DCHECK(!mount_path.empty()); | 
|  |  | 
|  | // Let's make sure mount path has trailing slash. | 
|  | if (mount_path.back() != '/') | 
|  | mount_path += '/'; | 
|  |  | 
|  | // Paths to unmount, indexed by source path. | 
|  | std::map<std::string, std::string> paths_to_unmount; | 
|  |  | 
|  | // For the already known mount points, use the mount path. | 
|  | for (const MountPoint& mount_point : mount_points_) { | 
|  | if (base::StartsWith(mount_point.source_path, mount_path)) | 
|  | paths_to_unmount.try_emplace(mount_point.source_path, | 
|  | mount_point.mount_path); | 
|  | } | 
|  |  | 
|  | // For the mount points that are not registered yet, use the source path. | 
|  | for (const auto& [source_path, _] : mount_callbacks_) { | 
|  | if (base::StartsWith(source_path, mount_path)) | 
|  | paths_to_unmount.try_emplace(source_path, source_path); | 
|  | } | 
|  |  | 
|  | for (const auto& [_, path_to_unmount] : paths_to_unmount) { | 
|  | UnmountPath(path_to_unmount, {}); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Callback for UnmountDeviceRecursively. | 
|  | void OnUnmountDeviceRecursively(UnmountDeviceRecursivelyCallbackData* cb_data, | 
|  | const std::string& mount_path, | 
|  | base::OnceClosure done_callback, | 
|  | const MountError error) { | 
|  | if (error == MountError::kPathNotMounted || | 
|  | error == MountError::kInvalidPath || error == MountError::kSuccess) { | 
|  | // Do standard processing for Unmount event. | 
|  | OnUnmountPath(UnmountPathCallback(), mount_path, error); | 
|  | } else { | 
|  | LOG(ERROR) << "Cannot unmount '" << mount_path << "': " << error; | 
|  | // This causes the last non-success error to be reported. | 
|  | cb_data->error_code = error; | 
|  | } | 
|  |  | 
|  | std::move(done_callback).Run(); | 
|  | } | 
|  |  | 
|  | // CrosDisksClient::Observer override. | 
|  | void OnMountCompleted(const MountPoint& entry) override { | 
|  | if (const auto it = deferred_mount_events_.find(entry.source_path); | 
|  | it != deferred_mount_events_.end()) { | 
|  | it->second.push_back(entry); | 
|  | VLOG(1) << "Added mount_path '" << entry.mount_path | 
|  | << "' to deferred mount events"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | bool want_to_keep = entry.mount_error == MountError::kSuccess; | 
|  | MountError mount_error = MountError::kSuccess; | 
|  | if (entry.mount_type == MountType::kDevice) { | 
|  | if (entry.mount_error == MountError::kUnknownFilesystem) { | 
|  | mount_error = MountError::kUnknownFilesystem; | 
|  | want_to_keep = true; | 
|  | } else if (entry.mount_error == MountError::kUnsupportedFilesystem) { | 
|  | mount_error = MountError::kUnsupportedFilesystem; | 
|  | want_to_keep = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | const MountPoint mount_info{ | 
|  | entry.source_path, entry.mount_path, entry.mount_type, mount_error, 100, | 
|  | entry.read_only}; | 
|  |  | 
|  | // If the device is corrupted but it's still possible to format it, it will | 
|  | // be fake mounted. | 
|  | if (want_to_keep) { | 
|  | VLOG(1) << "Mounted '" << mount_info.source_path << "' as '" | 
|  | << mount_info.mount_path << "'"; | 
|  | const auto [it, ok] = mount_points_.insert(mount_info); | 
|  | if (!ok) { | 
|  | DCHECK_EQ(it->mount_path, mount_info.mount_path); | 
|  | // const_cast is Ok since we're not modifying it->mount_path. | 
|  | const_cast<MountPoint&>(*it) = mount_info; | 
|  | VLOG(1) << "Updated mount point '" << mount_info.mount_path << "'"; | 
|  | } | 
|  | } else { | 
|  | if (base::SysInfo::IsRunningOnChromeOS()) { | 
|  | LOG(ERROR) << "Cannot mount '" << mount_info.source_path << "' as '" | 
|  | << mount_info.mount_path << "': " << entry.mount_error; | 
|  | } | 
|  | if (const MountPoints::const_iterator it = | 
|  | mount_points_.find(mount_info.mount_path); | 
|  | it != mount_points_.end()) { | 
|  | VLOG(1) << "Removed mount point '" << mount_info.mount_path << "'"; | 
|  | mount_points_.erase(it); | 
|  | } | 
|  | } | 
|  |  | 
|  | const Disks::const_iterator disk_it = disks_.find(mount_info.source_path); | 
|  | Disk* const disk = disk_it != disks_.end() ? disk_it->get() : nullptr; | 
|  |  | 
|  | if (want_to_keep && mount_info.mount_type == MountType::kDevice && | 
|  | !mount_info.source_path.empty() && !mount_info.mount_path.empty() && | 
|  | disk) { | 
|  | DCHECK(disk); | 
|  | DCHECK_EQ(disk->device_path(), mount_info.source_path); | 
|  |  | 
|  | // Store whether the disk was mounted in read-only mode due to a policy. | 
|  | disk->set_write_disabled_by_policy(!disk->is_read_only_hardware() && | 
|  | entry.read_only); | 
|  |  | 
|  | // Right now, a number of operations (format, rename, unmount) rely on the | 
|  | // mount path being set even if the disk isn't mounted. cros-disks also | 
|  | // does some tracking of non-mounted mount paths. | 
|  | disk->SetMountPath(mount_info.mount_path); | 
|  | disk->set_mounted(entry.mount_error == MountError::kSuccess); | 
|  | } | 
|  |  | 
|  | // Observers may read the values of disks_. So notify them after tweaking | 
|  | // values of disks_. | 
|  | if (auto it = mount_callbacks_.find(entry.source_path); | 
|  | it != mount_callbacks_.end()) { | 
|  | DCHECK_EQ(it->first, entry.source_path); | 
|  | VLOG(1) << "Calling mount callback for '" << entry.source_path | 
|  | << "' with error = " << entry.mount_error; | 
|  | std::move(it->second).Run(entry.mount_error, mount_info); | 
|  | mount_callbacks_.erase(std::move(it)); | 
|  | } else { | 
|  | LOG(ERROR) << "No mount callback for '" << entry.source_path << "'"; | 
|  | } | 
|  |  | 
|  | NotifyMountStatusUpdate(MOUNTING, entry.mount_error, mount_info); | 
|  |  | 
|  | if (disk) { | 
|  | DCHECK(disk_it != disks_.end()); | 
|  | disk->set_is_first_mount(false); | 
|  | if (!want_to_keep) { | 
|  | VLOG(1) << "Removed Disk '" << disk->device_path() << "'"; | 
|  | disks_.erase(disk_it); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // CrosDisksClient::Observer override. | 
|  | void OnMountProgress(const MountPoint& entry) override { | 
|  | DCHECK_EQ(entry.mount_error, MountError::kInProgress); | 
|  |  | 
|  | const auto [it, ok] = mount_points_.insert( | 
|  | {entry.source_path, entry.mount_path, entry.mount_type, | 
|  | MountError::kInProgress, entry.progress_percent, entry.read_only}); | 
|  | if (ok) { | 
|  | DCHECK_EQ(it->mount_path, entry.mount_path); | 
|  | VLOG(1) << "Added in-progress mount point '" << entry.mount_path | 
|  | << "' with " << entry.progress_percent << "%"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // const_cast is Ok since we're not modifying it->mount_path. | 
|  | MountPoint& mount_point = const_cast<MountPoint&>(*it); | 
|  | DCHECK_EQ(mount_point.mount_path, entry.mount_path); | 
|  | DCHECK_EQ(mount_point.source_path, entry.source_path); | 
|  | DCHECK_EQ(mount_point.mount_type, entry.mount_type); | 
|  | DCHECK_EQ(mount_point.mount_error, MountError::kInProgress); | 
|  | DCHECK_EQ(mount_point.read_only, entry.read_only); | 
|  |  | 
|  | mount_point.progress_percent = entry.progress_percent; | 
|  | VLOG(1) << "Updated in-progress mount point '" << entry.mount_path | 
|  | << "' to " << entry.progress_percent << "%"; | 
|  | } | 
|  |  | 
|  | // Callback for UnmountPath. | 
|  | void OnUnmountPath(UnmountPathCallback callback, | 
|  | const std::string& mount_path, | 
|  | MountError error) { | 
|  | if (error == MountError::kSuccess) { | 
|  | VLOG(1) << "Unmounted '" << mount_path << "'"; | 
|  | } else { | 
|  | LOG(ERROR) << "Cannot unmount '" << mount_path << "': " << error; | 
|  | if (error == MountError::kPathNotMounted || | 
|  | error == MountError::kInvalidPath) { | 
|  | // The path was already unmounted by something else. | 
|  | error = MountError::kSuccess; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (const MountPoints::const_iterator mount_point = | 
|  | mount_points_.find(mount_path); | 
|  | mount_point != mount_points_.end()) { | 
|  | NotifyMountStatusUpdate(UNMOUNTING, error, *mount_point); | 
|  |  | 
|  | if (error == MountError::kSuccess) { | 
|  | if (const Disks::const_iterator disk = | 
|  | disks_.find(mount_point->source_path); | 
|  | disk != disks_.end()) { | 
|  | DCHECK(*disk); | 
|  | (*disk)->clear_mount_path(); | 
|  | (*disk)->set_mounted(false); | 
|  | } | 
|  |  | 
|  | mount_points_.erase(mount_point); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (callback) | 
|  | std::move(callback).Run(error); | 
|  | } | 
|  |  | 
|  | void OnUnmountPathForFormat(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label, | 
|  | MountError error_code) { | 
|  | if (error_code == MountError::kSuccess && disks_.count(device_path) != 0) { | 
|  | FormatUnmountedDevice(device_path, filesystem, label); | 
|  | } else { | 
|  | OnFormatCompleted(FormatError::kUnknownError, device_path); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnUnmountDeviceForSinglePartitionFormat(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label, | 
|  | MountError error_code) { | 
|  | if (error_code != MountError::kSuccess || disks_.count(device_path) == 0) { | 
|  | OnPartitionCompleted(device_path, filesystem, label, | 
|  | PartitionError::kUnknownError); | 
|  | return; | 
|  | } | 
|  |  | 
|  | SinglePartitionFormatUnmountedDevice(device_path, filesystem, label); | 
|  | } | 
|  |  | 
|  | // Starts device formatting. | 
|  | void FormatUnmountedDevice(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label) { | 
|  | Disks::const_iterator disk = disks_.find(device_path); | 
|  | DCHECK(disk != disks_.end() && disk->get()->mount_path().empty()); | 
|  |  | 
|  | base::UmaHistogramEnumeration("FileBrowser.FormatFileSystemType", | 
|  | filesystem); | 
|  |  | 
|  | cros_disks_client_->Format( | 
|  | device_path, FormatFileSystemTypeToString(filesystem), label, | 
|  | BindOnce(&DiskMountManagerImpl::OnFormatStarted, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, label)); | 
|  | } | 
|  |  | 
|  | // Callback for Format. | 
|  | void OnFormatStarted(const std::string& device_path, | 
|  | const std::string& device_label, | 
|  | bool success) { | 
|  | if (!success) { | 
|  | OnFormatCompleted(FormatError::kUnknownError, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | NotifyFormatStatusUpdate(FORMAT_STARTED, FormatError::kSuccess, device_path, | 
|  | device_label); | 
|  | } | 
|  |  | 
|  | // CrosDisksClient::Observer override. | 
|  | void OnFormatCompleted(FormatError error_code, | 
|  | const std::string& device_path) override { | 
|  | std::string device_label; | 
|  | auto pending_change = pending_format_changes_.find(device_path); | 
|  | if (pending_change != pending_format_changes_.end()) { | 
|  | device_label = pending_change->second.volume_name; | 
|  | } | 
|  |  | 
|  | auto iter = disks_.find(device_path); | 
|  |  | 
|  | // disk might have been removed by now? | 
|  | if (iter != disks_.end()) { | 
|  | Disk* const disk = iter->get(); | 
|  | DCHECK(disk); | 
|  |  | 
|  | if (pending_change != pending_format_changes_.end() && | 
|  | error_code == FormatError::kSuccess) { | 
|  | disk->set_device_label(pending_change->second.volume_name); | 
|  | disk->set_file_system_type(pending_change->second.file_system_type); | 
|  | } | 
|  | } | 
|  |  | 
|  | pending_format_changes_.erase(device_path); | 
|  |  | 
|  | EnsureMountInfoRefreshed(base::DoNothing(), true /* force */); | 
|  |  | 
|  | NotifyFormatStatusUpdate(FORMAT_COMPLETED, error_code, device_path, | 
|  | device_label); | 
|  | } | 
|  |  | 
|  | void SinglePartitionFormatUnmountedDevice(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label) { | 
|  | Disks::const_iterator disk = disks_.find(device_path); | 
|  | DCHECK(disk != disks_.end() && disk->get()->mount_path().empty()); | 
|  |  | 
|  | pending_partitioning_disks_.insert(disk->get()->device_path()); | 
|  |  | 
|  | NotifyPartitionStatusUpdate(PARTITION_STARTED, PartitionError::kSuccess, | 
|  | device_path, label); | 
|  |  | 
|  | cros_disks_client_->SinglePartitionFormat( | 
|  | disk->get()->file_path(), | 
|  | BindOnce(&DiskMountManagerImpl::OnPartitionCompleted, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, filesystem, | 
|  | label)); | 
|  | } | 
|  |  | 
|  | void OnPartitionCompleted(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label, | 
|  | PartitionError error_code) { | 
|  | auto iter = disks_.find(device_path); | 
|  |  | 
|  | // disk might have been removed by now? | 
|  | if (iter != disks_.end()) { | 
|  | Disk* const disk = iter->get(); | 
|  | DCHECK(disk); | 
|  |  | 
|  | if (error_code == PartitionError::kSuccess) { | 
|  | EnsureMountInfoRefreshed( | 
|  | BindOnce(&DiskMountManagerImpl::OnRefreshAfterPartition, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, filesystem, | 
|  | label), | 
|  | true /* force */); | 
|  | } | 
|  |  | 
|  | } else { | 
|  | // Remove disk from pending partitioning list if disk removed. | 
|  | pending_partitioning_disks_.erase(device_path); | 
|  | } | 
|  |  | 
|  | NotifyPartitionStatusUpdate(PARTITION_COMPLETED, error_code, device_path, | 
|  | label); | 
|  | } | 
|  |  | 
|  | void OnRefreshAfterPartition(const std::string& device_path, | 
|  | FormatFileSystemType filesystem, | 
|  | const std::string& label, | 
|  | bool success) { | 
|  | Disks::const_iterator device_disk = disks_.find(device_path); | 
|  | if (device_disk == disks_.end()) { | 
|  | LOG(ERROR) << "Device not found, maybe ejected"; | 
|  | pending_partitioning_disks_.erase(device_path); | 
|  | NotifyPartitionStatusUpdate(PARTITION_COMPLETED, | 
|  | PartitionError::kInvalidDevicePath, | 
|  | device_path, label); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string new_partition_device_path; | 
|  | // Find new partition using common storage path with parent device. | 
|  | for (const auto& candidate : disks_) { | 
|  | if (candidate->storage_device_path() == | 
|  | device_disk->get()->storage_device_path() && | 
|  | !candidate->is_parent()) { | 
|  | new_partition_device_path = candidate->device_path(); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (new_partition_device_path.empty()) { | 
|  | LOG(ERROR) << "New partition couldn't be found"; | 
|  | pending_partitioning_disks_.erase(device_path); | 
|  | NotifyPartitionStatusUpdate(PARTITION_COMPLETED, | 
|  | PartitionError::kInvalidDevicePath, | 
|  | device_path, label); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const std::string filesystem_str = FormatFileSystemTypeToString(filesystem); | 
|  | pending_format_changes_[new_partition_device_path] = {filesystem_str, | 
|  | label}; | 
|  |  | 
|  | // It's expected the disks (parent device and new partition) are not | 
|  | // mounted, but try unmounting before starting format if it got | 
|  | // mounted through another flow. | 
|  | UnmountDeviceRecursively( | 
|  | device_path, BindOnce(&DiskMountManagerImpl::OnUnmountPathForFormat, | 
|  | weak_ptr_factory_.GetWeakPtr(), | 
|  | new_partition_device_path, filesystem, label)); | 
|  |  | 
|  | // It's ok to remove it from pending partitioning as format flow started. | 
|  | pending_partitioning_disks_.erase(device_path); | 
|  | } | 
|  |  | 
|  | void OnUnmountPathForRename(const std::string& device_path, | 
|  | const std::string& volume_name, | 
|  | MountError error_code) { | 
|  | if (error_code != MountError::kSuccess || disks_.count(device_path) == 0) { | 
|  | OnRenameCompleted(RenameError::kUnknownError, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | RenameUnmountedDevice(device_path, volume_name); | 
|  | } | 
|  |  | 
|  | // Start device renaming | 
|  | void RenameUnmountedDevice(const std::string& device_path, | 
|  | const std::string& volume_name) { | 
|  | const Disks::const_iterator disk = disks_.find(device_path); | 
|  | DCHECK(disk != disks_.end() && disk->get()->mount_path().empty()); | 
|  |  | 
|  | cros_disks_client_->Rename( | 
|  | device_path, volume_name, | 
|  | BindOnce(&DiskMountManagerImpl::OnRenameStarted, | 
|  | weak_ptr_factory_.GetWeakPtr(), device_path, volume_name)); | 
|  | } | 
|  |  | 
|  | // Callback for Rename. | 
|  | void OnRenameStarted(const std::string& device_path, | 
|  | const std::string& volume_name, | 
|  | bool success) { | 
|  | if (!success) { | 
|  | OnRenameCompleted(RenameError::kUnknownError, device_path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | NotifyRenameStatusUpdate(RENAME_STARTED, RenameError::kSuccess, device_path, | 
|  | volume_name); | 
|  | } | 
|  |  | 
|  | // CrosDisksClient::Observer override. | 
|  | void OnRenameCompleted(RenameError error_code, | 
|  | const std::string& device_path) override { | 
|  | std::string device_label; | 
|  | auto pending_change = pending_rename_changes_.find(device_path); | 
|  | if (pending_change != pending_rename_changes_.end()) { | 
|  | device_label = pending_change->second; | 
|  | } | 
|  |  | 
|  | auto iter = disks_.find(device_path); | 
|  |  | 
|  | // disk might have been removed by now? | 
|  | if (iter != disks_.end()) { | 
|  | Disk* const disk = iter->get(); | 
|  | DCHECK(disk); | 
|  |  | 
|  | if (pending_change != pending_rename_changes_.end() && | 
|  | error_code == RenameError::kSuccess) | 
|  | disk->set_device_label(pending_change->second); | 
|  | } | 
|  |  | 
|  | pending_rename_changes_.erase(device_path); | 
|  |  | 
|  | NotifyRenameStatusUpdate(RENAME_COMPLETED, error_code, device_path, | 
|  | device_label); | 
|  | } | 
|  |  | 
|  | // Fire observer mount events that were deferred due to an in-progress | 
|  | // GetDeviceProperties() call. | 
|  | void RunDeferredMountEvents(const std::string& device_path) { | 
|  | auto mount_events_iter = deferred_mount_events_.find(device_path); | 
|  | if (mount_events_iter == deferred_mount_events_.end()) | 
|  | return; | 
|  | std::vector<MountPoint> entries = std::move(mount_events_iter->second); | 
|  | deferred_mount_events_.erase(mount_events_iter); | 
|  | for (const MountPoint& entry : entries) | 
|  | OnMountCompleted(entry); | 
|  | } | 
|  |  | 
|  | // Callback for GetDeviceProperties. | 
|  | void OnGetDeviceProperties(const DiskInfo& disk_info) { | 
|  | if (disk_info.is_virtual()) { | 
|  | RunDeferredMountEvents(disk_info.device_path()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | VLOG(1) << "Found disk '" << disk_info.device_path() << "'"; | 
|  | // Delete previous disk info for this path: | 
|  | bool is_new = true; | 
|  | bool is_first_mount = false; | 
|  | std::string base_mount_path = std::string(); | 
|  |  | 
|  | if (Disks::iterator it = disks_.find(disk_info.device_path()); | 
|  | it != disks_.end()) { | 
|  | is_first_mount = (*it)->is_first_mount(); | 
|  | base_mount_path = (*it)->base_mount_path(); | 
|  | disks_.erase(std::move(it)); | 
|  | is_new = false; | 
|  | } | 
|  |  | 
|  | // If the device was mounted by the instance, apply recorded parameter. | 
|  | // Otherwise, default to false. | 
|  | // Lookup by |device_path| which we pass to cros-disks when mounting a | 
|  | // device in |VolumeManager::OnDiskEvent()|. | 
|  | const MountPoints::const_iterator mount_point = | 
|  | mount_points_.find(disk_info.mount_path()); | 
|  | const bool write_disabled_by_policy = | 
|  | mount_point != mount_points_.end() && mount_point->read_only; | 
|  | std::unique_ptr<Disk> disk = std::make_unique<Disk>( | 
|  | disk_info, write_disabled_by_policy, base_mount_path); | 
|  | if (!is_new) { | 
|  | disk->set_is_first_mount(is_first_mount); | 
|  | } | 
|  |  | 
|  | const auto [it, ok] = disks_.insert(std::move(disk)); | 
|  | DCHECK(ok); | 
|  | NotifyDiskStatusUpdate(is_new ? DISK_ADDED : DISK_CHANGED, **it); | 
|  | RunDeferredMountEvents(disk_info.device_path()); | 
|  | } | 
|  |  | 
|  | // Part of EnsureMountInfoRefreshed(). Called after the list of devices are | 
|  | // enumerated. | 
|  | void RefreshAfterEnumerateDevices(const std::vector<std::string>& devices) { | 
|  | std::set<std::string> current_device_set(devices.begin(), devices.end()); | 
|  | for (Disks::iterator iter = disks_.begin(); iter != disks_.end();) { | 
|  | if (current_device_set.count(iter->get()->device_path()) == 0) { | 
|  | iter = disks_.erase(iter); | 
|  | } else { | 
|  | ++iter; | 
|  | } | 
|  | } | 
|  | RefreshDeviceAtIndex(devices, 0); | 
|  | } | 
|  |  | 
|  | // Part of EnsureMountInfoRefreshed(). Called for each device to refresh info. | 
|  | void RefreshDeviceAtIndex(const std::vector<std::string>& devices, | 
|  | size_t index) { | 
|  | if (index == devices.size()) { | 
|  | // All devices info retrieved. Proceed to enumerate mount point info. | 
|  | cros_disks_client_->EnumerateMountEntries( | 
|  | BindOnce(&DiskMountManagerImpl::RefreshAfterEnumerateMountEntries, | 
|  | weak_ptr_factory_.GetWeakPtr()), | 
|  | BindOnce(&DiskMountManagerImpl::RefreshCompleted, | 
|  | weak_ptr_factory_.GetWeakPtr(), false)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | cros_disks_client_->GetDeviceProperties( | 
|  | devices[index], | 
|  | BindOnce(&DiskMountManagerImpl::RefreshAfterGetDeviceProperties, | 
|  | weak_ptr_factory_.GetWeakPtr(), devices, index + 1), | 
|  | BindOnce(&DiskMountManagerImpl::RefreshDeviceAtIndex, | 
|  | weak_ptr_factory_.GetWeakPtr(), devices, index + 1)); | 
|  | } | 
|  |  | 
|  | // Part of EnsureMountInfoRefreshed(). | 
|  | void RefreshAfterGetDeviceProperties(const std::vector<std::string>& devices, | 
|  | size_t next_index, | 
|  | const DiskInfo& disk_info) { | 
|  | OnGetDeviceProperties(disk_info); | 
|  | RefreshDeviceAtIndex(devices, next_index); | 
|  | } | 
|  |  | 
|  | // Part of EnsureMountInfoRefreshed(). Called after mount entries are listed. | 
|  | void RefreshAfterEnumerateMountEntries( | 
|  | const std::vector<MountPoint>& entries) { | 
|  | for (const MountPoint& entry : entries) | 
|  | OnMountCompleted(entry); | 
|  | RefreshCompleted(true); | 
|  | } | 
|  |  | 
|  | // Part of EnsureMountInfoRefreshed(). Called when the refreshing is done. | 
|  | void RefreshCompleted(bool success) { | 
|  | already_refreshed_ = true; | 
|  | for (auto& callback : refresh_callbacks_) | 
|  | std::move(callback).Run(success); | 
|  | refresh_callbacks_.clear(); | 
|  | } | 
|  |  | 
|  | // CrosDisksClient::Observer override. | 
|  | void OnMountEvent(const MountEventType event, | 
|  | const std::string& device_path) override { | 
|  | VLOG(1) << "OnMountEvent: " << event << " for '" << device_path << "'"; | 
|  |  | 
|  | switch (event) { | 
|  | case MountEventType::kDiskAdded: | 
|  | // Ensure we have an entry indicating we're waiting for | 
|  | // GetDeviceProperties() to complete. | 
|  | deferred_mount_events_[device_path]; | 
|  | cros_disks_client_->GetDeviceProperties( | 
|  | device_path, | 
|  | BindOnce(&DiskMountManagerImpl::OnGetDeviceProperties, | 
|  | weak_ptr_factory_.GetWeakPtr()), | 
|  | base::DoNothing()); | 
|  | return; | 
|  |  | 
|  | case MountEventType::kDiskRemoved: | 
|  | // Search and remove disks that are no longer present. | 
|  | if (Disks::const_iterator it = disks_.find(device_path); | 
|  | it != disks_.end()) { | 
|  | DCHECK(*it); | 
|  | NotifyDiskStatusUpdate(DISK_REMOVED, **it); | 
|  | disks_.erase(std::move(it)); | 
|  | } | 
|  | return; | 
|  |  | 
|  | case MountEventType::kDeviceAdded: | 
|  | NotifyDeviceStatusUpdate(DEVICE_ADDED, device_path); | 
|  | return; | 
|  |  | 
|  | case MountEventType::kDeviceRemoved: | 
|  | NotifyDeviceStatusUpdate(DEVICE_REMOVED, device_path); | 
|  | return; | 
|  |  | 
|  | case MountEventType::kDeviceScanned: | 
|  | NotifyDeviceStatusUpdate(DEVICE_SCANNED, device_path); | 
|  | return; | 
|  |  | 
|  | case MountEventType::kDiskChanged: | 
|  | break; | 
|  | } | 
|  |  | 
|  | LOG(ERROR) << "Unexpected mount event " << event << " for '" << device_path | 
|  | << "'"; | 
|  | } | 
|  |  | 
|  | // Notifies all observers about disk status update. | 
|  | void NotifyDiskStatusUpdate(DiskEvent event, const Disk& disk) { | 
|  | for (auto& observer : observers_) { | 
|  | // Skip mounting of new partitioned disks while waiting for the format. | 
|  | if (IsPendingPartitioningDisk(disk.device_path())) { | 
|  | continue; | 
|  | } | 
|  | disk.is_auto_mountable() ? observer.OnAutoMountableDiskEvent(event, disk) | 
|  | : observer.OnBootDeviceDiskEvent(event, disk); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Notifies all observers about device status update. | 
|  | void NotifyDeviceStatusUpdate(DeviceEvent event, | 
|  | const std::string& device_path) { | 
|  | for (auto& observer : observers_) | 
|  | observer.OnDeviceEvent(event, device_path); | 
|  | } | 
|  |  | 
|  | // Notifies all observers about mount completion. | 
|  | void NotifyMountStatusUpdate(MountEvent event, | 
|  | MountError error_code, | 
|  | const MountPoint& mount_info) { | 
|  | for (auto& observer : observers_) | 
|  | observer.OnMountEvent(event, error_code, mount_info); | 
|  | } | 
|  |  | 
|  | void NotifyFormatStatusUpdate(FormatEvent event, | 
|  | FormatError error_code, | 
|  | const std::string& device_path, | 
|  | const std::string& device_label) { | 
|  | for (auto& observer : observers_) | 
|  | observer.OnFormatEvent(event, error_code, device_path, device_label); | 
|  | } | 
|  |  | 
|  | void NotifyPartitionStatusUpdate(PartitionEvent event, | 
|  | PartitionError error_code, | 
|  | const std::string& device_path, | 
|  | const std::string& device_label) { | 
|  | for (auto& observer : observers_) | 
|  | observer.OnPartitionEvent(event, error_code, device_path, device_label); | 
|  | } | 
|  |  | 
|  | void NotifyRenameStatusUpdate(RenameEvent event, | 
|  | RenameError error_code, | 
|  | const std::string& device_path, | 
|  | const std::string& device_label) { | 
|  | for (auto& observer : observers_) | 
|  | observer.OnRenameEvent(event, error_code, device_path, device_label); | 
|  | } | 
|  |  | 
|  | bool IsPendingPartitioningDisk(const std::string& device_path) { | 
|  | if (base::Contains(pending_partitioning_disks_, device_path)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // If device path doesn't match check whether if it's a child path. | 
|  | for (const auto& disk : pending_partitioning_disks_) { | 
|  | if (base::StartsWith(device_path, disk, base::CompareCase::SENSITIVE)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Mount event change observers. | 
|  | base::ObserverList<DiskMountManager::Observer> observers_; | 
|  |  | 
|  | const raw_ptr<CrosDisksClient> cros_disks_client_ = CrosDisksClient::Get(); | 
|  |  | 
|  | // The list of disks found. | 
|  | Disks disks_; | 
|  |  | 
|  | // Callbacks of mount points in the process of being mounted, indexed by | 
|  | // source path. | 
|  | using MountCallbacks = std::map<std::string, MountPathCallback>; | 
|  | MountCallbacks mount_callbacks_; | 
|  |  | 
|  | // Known mount points. | 
|  | MountPoints mount_points_; | 
|  |  | 
|  | // A map entry with a key of the device path will be created upon calling | 
|  | // GetDeviceProperties(), for deferring mount events, and removed once it has | 
|  | // completed. This prevents a race resulting in mount events being fired with | 
|  | // the corresponding Disk entry unexpectedly missing. | 
|  | std::map<std::string, std::vector<MountPoint>> deferred_mount_events_; | 
|  |  | 
|  | bool already_refreshed_ = false; | 
|  | std::vector<EnsureMountInfoRefreshedCallback> refresh_callbacks_; | 
|  |  | 
|  | SuspendUnmountManager suspend_unmount_manager_{this}; | 
|  |  | 
|  | base::WeakPtrFactory<DiskMountManagerImpl> weak_ptr_factory_{this}; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | DiskMountManager::Observer::~Observer() { | 
|  | DCHECK(!IsInObserverList()); | 
|  | } | 
|  |  | 
|  | bool DiskMountManager::AddDiskForTest(std::unique_ptr<Disk> disk) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool DiskMountManager::AddMountPointForTest(const MountPoint& mount_point) { | 
|  | return false; | 
|  | } | 
|  | // static | 
|  | void DiskMountManager::Initialize() { | 
|  | if (g_disk_mount_manager) { | 
|  | LOG(WARNING) << "DiskMountManager was already initialized"; | 
|  | return; | 
|  | } | 
|  | g_disk_mount_manager = new DiskMountManagerImpl(); | 
|  | VLOG(1) << "DiskMountManager initialized"; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void DiskMountManager::InitializeForTesting( | 
|  | DiskMountManager* disk_mount_manager) { | 
|  | if (g_disk_mount_manager) { | 
|  | LOG(WARNING) << "DiskMountManager was already initialized"; | 
|  | return; | 
|  | } | 
|  | g_disk_mount_manager = disk_mount_manager; | 
|  | VLOG(1) << "DiskMountManager initialized"; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void DiskMountManager::Shutdown() { | 
|  | if (!g_disk_mount_manager) { | 
|  | LOG(WARNING) << "DiskMountManager::Shutdown() called with NULL manager"; | 
|  | return; | 
|  | } | 
|  | delete g_disk_mount_manager; | 
|  | g_disk_mount_manager = nullptr; | 
|  | VLOG(1) << "DiskMountManager Shutdown completed"; | 
|  | } | 
|  |  | 
|  | // static | 
|  | DiskMountManager* DiskMountManager::GetInstance() { | 
|  | return g_disk_mount_manager; | 
|  | } | 
|  |  | 
|  | }  // namespace ash::disks |