// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/cros/mount_library.h" #include <set> #include "base/message_loop.h" #include "base/string_util.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "content/browser/browser_thread.h" const char* kLibraryNotLoaded = "Cros Library not loaded"; namespace chromeos { MountLibrary::Disk::Disk(const std::string& device_path, const std::string& mount_path, const std::string& system_path, const std::string& file_path, const std::string& device_label, const std::string& drive_label, const std::string& parent_path, DeviceType device_type, uint64 total_size, bool is_parent, bool is_read_only, bool has_media, bool on_boot_device) : device_path_(device_path), mount_path_(mount_path), system_path_(system_path), file_path_(file_path), device_label_(device_label), drive_label_(drive_label), parent_path_(parent_path), device_type_(device_type), total_size_(total_size), is_parent_(is_parent), is_read_only_(is_read_only), has_media_(has_media), on_boot_device_(on_boot_device) { // Add trailing slash to mount path. if (mount_path_.length() && mount_path_.at(mount_path_.length() -1) != '/') mount_path_ = mount_path_.append("/"); } class MountLibraryImpl : public MountLibrary { public: MountLibraryImpl() : mount_status_connection_(NULL) { if (CrosLibrary::Get()->EnsureLoaded()) Init(); else LOG(ERROR) << kLibraryNotLoaded; } virtual ~MountLibraryImpl() { if (mount_status_connection_) DisconnectMountEventMonitor(mount_status_connection_); } // MountLibrary overrides. virtual void AddObserver(Observer* observer) OVERRIDE { observers_.AddObserver(observer); } virtual void RemoveObserver(Observer* observer) OVERRIDE { observers_.RemoveObserver(observer); } virtual void MountPath(const char* device_path) OVERRIDE { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!CrosLibrary::Get()->EnsureLoaded()) { OnMountRemovableDevice(device_path, NULL, MOUNT_METHOD_ERROR_LOCAL, kLibraryNotLoaded); return; } MountRemovableDevice(device_path, &MountLibraryImpl::MountRemovableDeviceCallback, this); } virtual void UnmountPath(const char* device_path) OVERRIDE { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!CrosLibrary::Get()->EnsureLoaded()) { OnUnmountRemovableDevice(device_path, MOUNT_METHOD_ERROR_LOCAL, kLibraryNotLoaded); return; } UnmountRemovableDevice(device_path, &MountLibraryImpl::UnmountRemovableDeviceCallback, this); } virtual void RequestMountInfoRefresh() OVERRIDE { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!CrosLibrary::Get()->EnsureLoaded()) { OnRequestMountInfo(NULL, 0, MOUNT_METHOD_ERROR_LOCAL, kLibraryNotLoaded); return; } RequestMountInfo(&MountLibraryImpl::RequestMountInfoCallback, this); } virtual void RefreshDiskProperties(const Disk* disk) OVERRIDE { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(disk); if (!CrosLibrary::Get()->EnsureLoaded()) { OnGetDiskProperties(disk->device_path().c_str(), NULL, MOUNT_METHOD_ERROR_LOCAL, kLibraryNotLoaded); return; } GetDiskProperties(disk->device_path().c_str(), &MountLibraryImpl::GetDiskPropertiesCallback, this); } const DiskMap& disks() const OVERRIDE { return disks_; } private: // Callback for MountRemovableDevice method. static void MountRemovableDeviceCallback(void* object, const char* device_path, const char* mount_path, MountMethodErrorType error, const char* error_message) { DCHECK(object); MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object); self->OnMountRemovableDevice(device_path, mount_path, error, error_message); } // Callback for UnmountRemovableDevice method. static void UnmountRemovableDeviceCallback(void* object, const char* device_path, const char* mount_path, MountMethodErrorType error, const char* error_message) { DCHECK(object); MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object); self->OnUnmountRemovableDevice(device_path, error, error_message); } // Callback for disk information retrieval calls. static void GetDiskPropertiesCallback(void* object, const char* device_path, const DiskInfo* disk, MountMethodErrorType error, const char* error_message) { DCHECK(object); MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object); self->OnGetDiskProperties(device_path, disk, error, error_message); } // Callback for RequestMountInfo call. static void RequestMountInfoCallback(void* object, const char** devices, size_t device_len, MountMethodErrorType error, const char* error_message) { DCHECK(object); MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object); self->OnRequestMountInfo(devices, device_len, error, error_message); } // This method will receive events that are caused by drive status changes. static void MonitorMountEventsHandler(void* object, MountEventType evt, const char* device_path) { DCHECK(object); MountLibraryImpl* self = static_cast<MountLibraryImpl*>(object); self->OnMountEvent(evt, device_path); } void OnMountRemovableDevice(const char* device_path, const char* mount_path, MountMethodErrorType error, const char* error_message) { DCHECK(device_path); if (error == MOUNT_METHOD_ERROR_NONE && device_path && mount_path) { std::string path(device_path); DiskMap::iterator iter = disks_.find(path); if (iter == disks_.end()) { // disk might have been removed by now? return; } Disk* disk = iter->second; DCHECK(disk); disk->set_mount_path(mount_path); FireDiskStatusUpdate(MOUNT_DISK_MOUNTED, disk); } else { LOG(WARNING) << "Mount request failed for device " << device_path << ", with error: " << (error_message ? error_message : "Unknown"); } } void OnUnmountRemovableDevice(const char* device_path, MountMethodErrorType error, const char* error_message) { DCHECK(device_path); if (error == MOUNT_METHOD_ERROR_NONE && device_path) { std::string path(device_path); DiskMap::iterator iter = disks_.find(path); if (iter == disks_.end()) { // disk might have been removed by now? return; } Disk* disk = iter->second; DCHECK(disk); disk->clear_mount_path(); FireDiskStatusUpdate(MOUNT_DISK_UNMOUNTED, disk); } else { LOG(WARNING) << "Unmount request failed for device " << device_path << ", with error: " << (error_message ? error_message : "Unknown"); } } void OnGetDiskProperties(const char* device_path, const DiskInfo* disk1, MountMethodErrorType error, const char* error_message) { DCHECK(device_path); if (error == MOUNT_METHOD_ERROR_NONE && device_path) { // TODO(zelidrag): Find a better way to filter these out before we // fetch the properties: // Ignore disks coming from the device we booted the system from. // This cast is temporal solution, until we merge DiskInfo and // DiskInfoAdvanced into single interface. const DiskInfoAdvanced* disk = reinterpret_cast<const DiskInfoAdvanced*>(disk1); if (disk->on_boot_device()) return; LOG(WARNING) << "Found disk " << device_path; // Delete previous disk info for this path: bool is_new = true; std::string device_path_string(device_path); DiskMap::iterator iter = disks_.find(device_path_string); if (iter != disks_.end()) { delete iter->second; disks_.erase(iter); is_new = false; } std::string path; std::string mountpath; std::string systempath; std::string filepath; std::string devicelabel; std::string drivelabel; std::string parentpath; if (disk->path() != NULL) path = disk->path(); if (disk->mount_path() != NULL) mountpath = disk->mount_path(); if (disk->system_path() != NULL) systempath = disk->system_path(); if (disk->file_path() != NULL) filepath = disk->file_path(); if (disk->label() != NULL) devicelabel = disk->label(); if (disk->drive_label() != NULL) drivelabel = disk->drive_label(); if (disk->partition_slave() != NULL) parentpath = disk->partition_slave(); Disk* new_disk = new Disk(path, mountpath, systempath, filepath, devicelabel, drivelabel, parentpath, disk->device_type(), disk->size(), disk->is_drive(), disk->is_read_only(), disk->has_media(), disk->on_boot_device()); disks_.insert( std::pair<std::string, Disk*>(device_path_string, new_disk)); FireDiskStatusUpdate(is_new ? MOUNT_DISK_ADDED : MOUNT_DISK_CHANGED, new_disk); } else { LOG(WARNING) << "Property retrieval request failed for device " << device_path << ", with error: " << (error_message ? error_message : "Unknown"); } } void OnRequestMountInfo(const char** devices, size_t devices_len, MountMethodErrorType error, const char* error_message) { std::set<std::string> current_device_set; if (error == MOUNT_METHOD_ERROR_NONE && devices && devices_len) { // Initiate properties fetch for all removable disks, bool found_disk = false; for (size_t i = 0; i < devices_len; i++) { if (!devices[i]) { NOTREACHED(); continue; } current_device_set.insert(std::string(devices[i])); found_disk = true; // Initiate disk property retrieval for each relevant device path. GetDiskProperties(devices[i], &MountLibraryImpl::GetDiskPropertiesCallback, this); } } else if (error != MOUNT_METHOD_ERROR_NONE) { LOG(WARNING) << "Request mount info retrieval request failed with error: " << (error_message ? error_message : "Unknown"); } // Search and remove disks that are no longer present. for (MountLibrary::DiskMap::iterator iter = disks_.begin(); iter != disks_.end(); ) { if (current_device_set.find(iter->first) == current_device_set.end()) { Disk* disk = iter->second; FireDiskStatusUpdate(MOUNT_DISK_REMOVED, disk); delete iter->second; disks_.erase(iter++); } else { ++iter; } } } void OnMountEvent(MountEventType evt, const char* device_path) { if (!device_path) return; MountLibraryEventType type = MOUNT_DEVICE_ADDED; switch (evt) { case DISK_ADDED: case DISK_CHANGED: { GetDiskProperties(device_path, &MountLibraryImpl::GetDiskPropertiesCallback, this); return; } case DISK_REMOVED: { // Search and remove disks that are no longer present. MountLibrary::DiskMap::iterator iter = disks_.find(std::string(device_path)); if (iter != disks_.end()) { Disk* disk = iter->second; FireDiskStatusUpdate(MOUNT_DISK_REMOVED, disk); delete iter->second; disks_.erase(iter); } return; } case DEVICE_ADDED: { type = MOUNT_DEVICE_ADDED; break; } case DEVICE_REMOVED: { type = MOUNT_DEVICE_REMOVED; break; } case DEVICE_SCANNED: { type = MOUNT_DEVICE_SCANNED; break; } } FireDeviceStatusUpdate(type, std::string(device_path)); } void Init() { // Getting the monitor status so that the daemon starts up. mount_status_connection_ = MonitorMountEvents( &MonitorMountEventsHandler, this); } void FireDiskStatusUpdate(MountLibraryEventType evt, const Disk* disk) { // Make sure we run on UI thread. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); FOR_EACH_OBSERVER( Observer, observers_, DiskChanged(evt, disk)); } void FireDeviceStatusUpdate(MountLibraryEventType evt, const std::string& device_path) { // Make sure we run on UI thread. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); FOR_EACH_OBSERVER( Observer, observers_, DeviceChanged(evt, device_path)); } // Mount event change observers. ObserverList<Observer> observers_; // A reference to the mount api, to allow callbacks when the mount // status changes. MountEventConnection mount_status_connection_; // The list of disks found. MountLibrary::DiskMap disks_; DISALLOW_COPY_AND_ASSIGN(MountLibraryImpl); }; class MountLibraryStubImpl : public MountLibrary { public: MountLibraryStubImpl() {} virtual ~MountLibraryStubImpl() {} // MountLibrary overrides. virtual void AddObserver(Observer* observer) OVERRIDE {} virtual void RemoveObserver(Observer* observer) OVERRIDE {} virtual const DiskMap& disks() const OVERRIDE { return disks_; } virtual void RequestMountInfoRefresh() OVERRIDE {} virtual void MountPath(const char* device_path) OVERRIDE {} virtual void UnmountPath(const char* device_path) OVERRIDE {} virtual bool IsBootPath(const char* device_path) OVERRIDE { return true; } private: // The list of disks found. DiskMap disks_; DISALLOW_COPY_AND_ASSIGN(MountLibraryStubImpl); }; // static MountLibrary* MountLibrary::GetImpl(bool stub) { if (stub) return new MountLibraryStubImpl(); else return new MountLibraryImpl(); } } // namespace chromeos // Allows InvokeLater without adding refcounting. This class is a Singleton and // won't be deleted until it's last InvokeLater is run. DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::MountLibraryImpl);