Commit 62067965 authored by Anand K. Mistry's avatar Anand K. Mistry Committed by Commit Bot

Return error code from UnmountDeviceRecursively.

At the same time, fix up two outstanding TODOs:
1. A potential memory leak due to lack of callback data ownership.
2. Not correctly reporting unmount errors on a multi-parition device.

Also add some tests for UnmountDeviceRecursively.

BUG=873903

Change-Id: I3e99afa4e0185b18c1d7077a5c65fc225a5751b3
Reviewed-on: https://chromium-review.googlesource.com/1179421Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Reviewed-by: default avatarToni Baržić <tbarzic@chromium.org>
Commit-Queue: Noel Gordon <noel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584627}
parent d8b7d82f
...@@ -142,8 +142,7 @@ void FakeDiskMountManager::RenameMountedDevice(const std::string& mount_path, ...@@ -142,8 +142,7 @@ void FakeDiskMountManager::RenameMountedDevice(const std::string& mount_path,
void FakeDiskMountManager::UnmountDeviceRecursively( void FakeDiskMountManager::UnmountDeviceRecursively(
const std::string& device_path, const std::string& device_path,
const UnmountDeviceRecursivelyCallbackType& callback) { UnmountDeviceRecursivelyCallbackType callback) {}
}
bool FakeDiskMountManager::AddDiskForTest( bool FakeDiskMountManager::AddDiskForTest(
std::unique_ptr<chromeos::disks::Disk> disk) { std::unique_ptr<chromeos::disks::Disk> disk) {
......
...@@ -98,7 +98,7 @@ class FakeDiskMountManager : public chromeos::disks::DiskMountManager { ...@@ -98,7 +98,7 @@ class FakeDiskMountManager : public chromeos::disks::DiskMountManager {
const std::string& volume_name) override; const std::string& volume_name) override;
void UnmountDeviceRecursively( void UnmountDeviceRecursively(
const std::string& device_path, const std::string& device_path,
const UnmountDeviceRecursivelyCallbackType& callback) override; UnmountDeviceRecursivelyCallbackType callback) override;
bool AddDiskForTest(std::unique_ptr<chromeos::disks::Disk> disk) override; bool AddDiskForTest(std::unique_ptr<chromeos::disks::Disk> disk) override;
bool AddMountPointForTest(const MountPointInfo& mount_point) override; bool AddMountPointForTest(const MountPointInfo& mount_point) override;
......
...@@ -22,6 +22,10 @@ ...@@ -22,6 +22,10 @@
#include "chrome/common/extensions/api/image_writer_private.h" #include "chrome/common/extensions/api/image_writer_private.h"
#include "extensions/common/extension_id.h" #include "extensions/common/extension_id.h"
#if defined(OS_CHROMEOS)
#include "chromeos/disks/disk_mount_manager.h"
#endif
namespace image_writer_api = extensions::api::image_writer_private; namespace image_writer_api = extensions::api::image_writer_private;
namespace base { namespace base {
...@@ -183,7 +187,8 @@ class Operation : public base::RefCountedThreadSafe<Operation> { ...@@ -183,7 +187,8 @@ class Operation : public base::RefCountedThreadSafe<Operation> {
// Unmounts all volumes on |device_path_|. // Unmounts all volumes on |device_path_|.
void UnmountVolumes(const base::Closure& continuation); void UnmountVolumes(const base::Closure& continuation);
// Starts the write after unmounting. // Starts the write after unmounting.
void UnmountVolumesCallback(const base::Closure& continuation, bool success); void UnmountVolumesCallback(const base::Closure& continuation,
chromeos::MountError error_code);
// Starts the ImageBurner write. Note that target_path is the file path of // Starts the ImageBurner write. Note that target_path is the file path of
// the device where device_path has been a system device path. // the device where device_path has been a system device path.
void StartWriteOnUIThread(const std::string& target_path, void StartWriteOnUIThread(const std::string& target_path,
......
...@@ -62,11 +62,11 @@ void Operation::UnmountVolumes(const base::Closure& continuation) { ...@@ -62,11 +62,11 @@ void Operation::UnmountVolumes(const base::Closure& continuation) {
} }
void Operation::UnmountVolumesCallback(const base::Closure& continuation, void Operation::UnmountVolumesCallback(const base::Closure& continuation,
bool success) { chromeos::MountError error_code) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!success) { if (error_code != chromeos::MOUNT_ERROR_NONE) {
LOG(ERROR) << "Volume unmounting failed."; LOG(ERROR) << "Volume unmounting failed with error code " << error_code;
PostTask(base::Bind(&Operation::Error, this, error::kUnmountVolumesError)); PostTask(base::Bind(&Operation::Error, this, error::kUnmountVolumesError));
return; return;
} }
......
...@@ -74,9 +74,10 @@ FakeDiskMountManager::~FakeDiskMountManager() {} ...@@ -74,9 +74,10 @@ FakeDiskMountManager::~FakeDiskMountManager() {}
void FakeDiskMountManager::UnmountDeviceRecursively( void FakeDiskMountManager::UnmountDeviceRecursively(
const std::string& device_path, const std::string& device_path,
const UnmountDeviceRecursivelyCallbackType& callback) { UnmountDeviceRecursivelyCallbackType callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::ThreadTaskRunnerHandle::Get()->PostTask(
base::BindOnce(callback, true)); FROM_HERE,
base::BindOnce(std::move(callback), chromeos::MOUNT_ERROR_NONE));
} }
#endif #endif
......
...@@ -70,7 +70,7 @@ class FakeDiskMountManager : public chromeos::disks::MockDiskMountManager { ...@@ -70,7 +70,7 @@ class FakeDiskMountManager : public chromeos::disks::MockDiskMountManager {
void UnmountDeviceRecursively( void UnmountDeviceRecursively(
const std::string& device_path, const std::string& device_path,
const UnmountDeviceRecursivelyCallbackType& callback) override; UnmountDeviceRecursivelyCallbackType callback) override;
private: private:
DiskMap disks_; DiskMap disks_;
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/barrier_closure.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -35,6 +36,20 @@ constexpr char kDefaultFormatVFAT[] = "vfat"; ...@@ -35,6 +36,20 @@ constexpr char kDefaultFormatVFAT[] = "vfat";
constexpr char kDeviceNotFound[] = "Device could not be found"; constexpr char kDeviceNotFound[] = "Device could not be found";
DiskMountManager* g_disk_mount_manager = NULL; DiskMountManager* g_disk_mount_manager = NULL;
struct UnmountDeviceRecursivelyCallbackData {
UnmountDeviceRecursivelyCallbackData(
DiskMountManager::UnmountDeviceRecursivelyCallbackType in_callback)
: callback(std::move(in_callback)) {}
DiskMountManager::UnmountDeviceRecursivelyCallbackType callback;
MountError error_code = MOUNT_ERROR_NONE;
};
void OnAllUnmountDeviceRecursively(
std::unique_ptr<UnmountDeviceRecursivelyCallbackData> cb_data) {
std::move(cb_data->callback).Run(cb_data->error_code);
}
// The DiskMountManager implementation. // The DiskMountManager implementation.
class DiskMountManagerImpl : public DiskMountManager, class DiskMountManagerImpl : public DiskMountManager,
public CrosDisksClient::Observer { public CrosDisksClient::Observer {
...@@ -179,7 +194,7 @@ class DiskMountManagerImpl : public DiskMountManager, ...@@ -179,7 +194,7 @@ class DiskMountManagerImpl : public DiskMountManager,
// DiskMountManager override. // DiskMountManager override.
void UnmountDeviceRecursively( void UnmountDeviceRecursively(
const std::string& device_path, const std::string& device_path,
const UnmountDeviceRecursivelyCallbackType& callback) override { UnmountDeviceRecursivelyCallbackType callback) override {
std::vector<std::string> devices_to_unmount; std::vector<std::string> devices_to_unmount;
// Get list of all devices to unmount. // Get list of all devices to unmount.
...@@ -197,38 +212,29 @@ class DiskMountManagerImpl : public DiskMountManager, ...@@ -197,38 +212,29 @@ class DiskMountManagerImpl : public DiskMountManager,
if (disks_.find(device_path) == disks_.end()) { if (disks_.find(device_path) == disks_.end()) {
LOG(WARNING) << "Unmount recursive request failed for device " LOG(WARNING) << "Unmount recursive request failed for device "
<< device_path << ", with error: " << kDeviceNotFound; << device_path << ", with error: " << kDeviceNotFound;
callback.Run(false); std::move(callback).Run(MOUNT_ERROR_INVALID_DEVICE_PATH);
return; return;
} }
// Nothing to unmount. // Nothing to unmount.
callback.Run(true); std::move(callback).Run(MOUNT_ERROR_NONE);
return; return;
} }
// We will send the same callback data object to all Unmount calls and use std::unique_ptr<UnmountDeviceRecursivelyCallbackData> cb_data =
// it to synchronize callbacks. std::make_unique<UnmountDeviceRecursivelyCallbackData>(
// Note: this implementation has a potential memory leak issue. For std::move(callback));
// example if this instance is destructed before all the callbacks for UnmountDeviceRecursivelyCallbackData* raw_cb_data = cb_data.get();
// Unmount are invoked, the memory pointed by |cb_data| will be leaked. base::RepeatingClosure done_callback = base::BarrierClosure(
// It is because the UnmountDeviceRecursivelyCallbackData keeps how devices_to_unmount.size(),
// many times OnUnmountDeviceRecursively callback is called and when base::BindOnce(&OnAllUnmountDeviceRecursively, std::move(cb_data)));
// all the callbacks are called, |cb_data| will be deleted in the method.
// However destructing the instance before all callback invocations will
// cancel all pending callbacks, so that the |cb_data| would never be
// deleted.
// Fortunately, in the real scenario, the instance will be destructed
// only for ShutDown. So, probably the memory would rarely be leaked.
// TODO(hidehiko): Fix the issue.
UnmountDeviceRecursivelyCallbackData* cb_data =
new UnmountDeviceRecursivelyCallbackData(
callback, devices_to_unmount.size());
for (size_t i = 0; i < devices_to_unmount.size(); ++i) { for (size_t i = 0; i < devices_to_unmount.size(); ++i) {
cros_disks_client_->Unmount( cros_disks_client_->Unmount(
devices_to_unmount[i], UNMOUNT_OPTIONS_NONE, devices_to_unmount[i], UNMOUNT_OPTIONS_NONE,
base::BindOnce(&DiskMountManagerImpl::OnUnmountDeviceRecursively, base::BindOnce(&DiskMountManagerImpl::OnUnmountDeviceRecursively,
weak_ptr_factory_.GetWeakPtr(), cb_data, weak_ptr_factory_.GetWeakPtr(), raw_cb_data,
devices_to_unmount[i])); devices_to_unmount[i], done_callback));
} }
} }
...@@ -312,18 +318,6 @@ class DiskMountManagerImpl : public DiskMountManager, ...@@ -312,18 +318,6 @@ class DiskMountManagerImpl : public DiskMountManager,
// device_path and the value is new volume_name. // device_path and the value is new volume_name.
std::map<std::string, std::string> pending_rename_changes_; std::map<std::string, std::string> pending_rename_changes_;
struct UnmountDeviceRecursivelyCallbackData {
UnmountDeviceRecursivelyCallbackData(
const UnmountDeviceRecursivelyCallbackType& in_callback,
int in_num_pending_callbacks)
: callback(in_callback),
num_pending_callbacks(in_num_pending_callbacks) {
}
const UnmountDeviceRecursivelyCallbackType callback;
size_t num_pending_callbacks;
};
// Called on D-Bus CrosDisksClient::Mount() is done. // Called on D-Bus CrosDisksClient::Mount() is done.
void OnMount(const std::string& source_path, MountType type, bool result) { void OnMount(const std::string& source_path, MountType type, bool result) {
// When succeeds, OnMountCompleted will be called by "MountCompleted", // When succeeds, OnMountCompleted will be called by "MountCompleted",
...@@ -388,24 +382,20 @@ class DiskMountManagerImpl : public DiskMountManager, ...@@ -388,24 +382,20 @@ class DiskMountManagerImpl : public DiskMountManager,
// Callback for UnmountDeviceRecursively. // Callback for UnmountDeviceRecursively.
void OnUnmountDeviceRecursively(UnmountDeviceRecursivelyCallbackData* cb_data, void OnUnmountDeviceRecursively(UnmountDeviceRecursivelyCallbackData* cb_data,
const std::string& mount_path, const std::string& mount_path,
base::OnceClosure done_callback,
MountError error_code) { MountError error_code) {
bool success = error_code == MOUNT_ERROR_NONE; if (error_code == MOUNT_ERROR_NONE) {
if (success) {
// Do standard processing for Unmount event. // Do standard processing for Unmount event.
OnUnmountPath(UnmountPathCallback(), mount_path, MOUNT_ERROR_NONE); OnUnmountPath(UnmountPathCallback(), mount_path, MOUNT_ERROR_NONE);
VLOG(1) << mount_path << " unmounted."; VLOG(1) << mount_path << " unmounted.";
} else {
// This causes the last non-success error to be reported.
// TODO(amistry): We could ignore certain errors such as
// MOUNT_ERROR_PATH_NOT_MOUNTED, or prioritise more important ones.
cb_data->error_code = error_code;
} }
// This is safe as long as all callbacks are called on the same thread as
// UnmountDeviceRecursively.
cb_data->num_pending_callbacks--;
if (cb_data->num_pending_callbacks == 0) { std::move(done_callback).Run();
// This code has a problem that the |success| status used here is for the
// last "unmount" callback, but not whether all unmounting is succeeded.
// TODO(hidehiko): Fix the issue.
cb_data->callback.Run(success);
delete cb_data;
}
} }
// CrosDisksClient::Observer override. // CrosDisksClient::Observer override.
......
...@@ -85,7 +85,8 @@ class CHROMEOS_EXPORT DiskMountManager { ...@@ -85,7 +85,8 @@ class CHROMEOS_EXPORT DiskMountManager {
// A callback function type which is called after UnmountDeviceRecursively // A callback function type which is called after UnmountDeviceRecursively
// finishes. // finishes.
typedef base::Callback<void(bool)> UnmountDeviceRecursivelyCallbackType; typedef base::OnceCallback<void(MountError error_code)>
UnmountDeviceRecursivelyCallbackType;
// A callback type for UnmountPath method. // A callback type for UnmountPath method.
typedef base::Callback<void(MountError error_code)> UnmountPathCallback; typedef base::Callback<void(MountError error_code)> UnmountPathCallback;
...@@ -186,7 +187,7 @@ class CHROMEOS_EXPORT DiskMountManager { ...@@ -186,7 +187,7 @@ class CHROMEOS_EXPORT DiskMountManager {
// Unmounts device_path and all of its known children. // Unmounts device_path and all of its known children.
virtual void UnmountDeviceRecursively( virtual void UnmountDeviceRecursively(
const std::string& device_path, const std::string& device_path,
const UnmountDeviceRecursivelyCallbackType& callback) = 0; UnmountDeviceRecursivelyCallbackType callback) = 0;
// Used in tests to initialize the manager's disk and mount point sets. // Used in tests to initialize the manager's disk and mount point sets.
// Default implementation does noting. It just fails. // Default implementation does noting. It just fails.
......
...@@ -26,6 +26,7 @@ using chromeos::disks::DiskMountManager; ...@@ -26,6 +26,7 @@ using chromeos::disks::DiskMountManager;
using chromeos::CrosDisksClient; using chromeos::CrosDisksClient;
using chromeos::DBusThreadManager; using chromeos::DBusThreadManager;
using chromeos::FakeCrosDisksClient; using chromeos::FakeCrosDisksClient;
using chromeos::MountError;
using chromeos::MountType; using chromeos::MountType;
using chromeos::disks::MountCondition; using chromeos::disks::MountCondition;
...@@ -554,7 +555,7 @@ class DiskMountManagerTest : public testing::Test { ...@@ -554,7 +555,7 @@ class DiskMountManagerTest : public testing::Test {
} }
// Adds a new mount point to the disk mount manager. // Adds a new mount point to the disk mount manager.
// If the moutn point is a device mount point, disk with its source path // If the mount point is a device mount point, disk with its source path
// should already be added to the disk mount manager. // should already be added to the disk mount manager.
void AddTestMountPoint(const TestMountPointInfo& mount_point) { void AddTestMountPoint(const TestMountPointInfo& mount_point) {
EXPECT_TRUE(DiskMountManager::GetInstance()->AddMountPointForTest( EXPECT_TRUE(DiskMountManager::GetInstance()->AddMountPointForTest(
...@@ -1392,4 +1393,139 @@ TEST_F(DiskMountManagerTest, Rename_ConsecutiveRenameCalls) { ...@@ -1392,4 +1393,139 @@ TEST_F(DiskMountManagerTest, Rename_ConsecutiveRenameCalls) {
kDevice1MountPath)); kDevice1MountPath));
} }
void SaveUnmountResult(MountError* save_error,
base::OnceClosure done_callback,
MountError error_code) {
*save_error = error_code;
std::move(done_callback).Run();
}
TEST_F(DiskMountManagerTest, UnmountDeviceRecursively) {
base::RunLoop run_loop;
auto disk_sda =
Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));
auto disk_sda1 = Disk::Builder()
.SetDevicePath("/dev/sda1")
.SetMountPath("/mount/path1")
.Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));
auto disk_sda2 = Disk::Builder()
.SetDevicePath("/dev/sda2")
.SetMountPath("/mount/path2")
.Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda2)));
MountError error_code = chromeos::MOUNT_ERROR_UNKNOWN;
DiskMountManager::GetInstance()->UnmountDeviceRecursively(
"/dev/sda",
base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(chromeos::MOUNT_ERROR_NONE, error_code);
}
TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_NoMounted) {
base::RunLoop run_loop;
auto disk_sda =
Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));
auto disk_sda1 = Disk::Builder().SetDevicePath("/dev/sda1").Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));
MountError error_code = chromeos::MOUNT_ERROR_UNKNOWN;
DiskMountManager::GetInstance()->UnmountDeviceRecursively(
"/dev/sda",
base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(0, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ(chromeos::MOUNT_ERROR_NONE, error_code);
}
TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_NoDisk) {
base::RunLoop run_loop;
auto disk_sda =
Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));
auto disk_sda1 = Disk::Builder().SetDevicePath("/dev/sda1").Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));
MountError error_code = chromeos::MOUNT_ERROR_UNKNOWN;
// Unmount sdB instead of sdA.
DiskMountManager::GetInstance()->UnmountDeviceRecursively(
"/dev/sdb",
base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(0, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ(chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH, error_code);
}
void SetUnmountError(FakeCrosDisksClient* client, MountError error_code) {
client->MakeUnmountFail(error_code);
}
TEST_F(DiskMountManagerTest, UnmountDeviceRecursively_FailFirst) {
base::RunLoop run_loop;
auto disk_sda =
Disk::Builder().SetDevicePath("/dev/sda").SetIsParent(true).Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda)));
auto disk_sda1 = Disk::Builder()
.SetDevicePath("/dev/sda1")
.SetMountPath("/mount/path1")
.Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda1)));
auto disk_sda2 = Disk::Builder()
.SetDevicePath("/dev/sda2")
.SetMountPath("/mount/path2")
.Build();
EXPECT_TRUE(
DiskMountManager::GetInstance()->AddDiskForTest(std::move(disk_sda2)));
// Fail the first unmount, but make the second succeed.
fake_cros_disks_client_->MakeUnmountFail(
chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS);
fake_cros_disks_client_->set_unmount_listener(
base::Bind(&SetUnmountError, base::Unretained(fake_cros_disks_client_),
chromeos::MOUNT_ERROR_NONE));
MountError error_code = chromeos::MOUNT_ERROR_UNKNOWN;
DiskMountManager::GetInstance()->UnmountDeviceRecursively(
"/dev/sda",
base::BindOnce(&SaveUnmountResult, base::Unretained(&error_code),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(2, fake_cros_disks_client_->unmount_call_count());
EXPECT_EQ(chromeos::UNMOUNT_OPTIONS_NONE,
fake_cros_disks_client_->last_unmount_options());
EXPECT_EQ(chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS, error_code);
}
} // namespace } // namespace
...@@ -50,10 +50,9 @@ class MockDiskMountManager : public DiskMountManager { ...@@ -50,10 +50,9 @@ class MockDiskMountManager : public DiskMountManager {
MOCK_METHOD1(FormatMountedDevice, void(const std::string&)); MOCK_METHOD1(FormatMountedDevice, void(const std::string&));
MOCK_METHOD2(RenameMountedDevice, MOCK_METHOD2(RenameMountedDevice,
void(const std::string&, const std::string&)); void(const std::string&, const std::string&));
MOCK_METHOD2( MOCK_METHOD2(UnmountDeviceRecursively,
UnmountDeviceRecursively,
void(const std::string&, void(const std::string&,
const DiskMountManager::UnmountDeviceRecursivelyCallbackType&)); DiskMountManager::UnmountDeviceRecursivelyCallbackType));
// Invokes fake device insert events. // Invokes fake device insert events.
void NotifyDeviceInsertEvents(); void NotifyDeviceInsertEvents();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment