blob: 4192a0dc373735cc1a452d7b91e3c18b16153915 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "cros-disks/fuse_mount_manager.h"
#include <sys/mount.h>
#include <string>
#include <utility>
#include <vector>
#include <base/strings/string_util.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cros-disks/fuse_helper.h"
#include "cros-disks/fuse_mounter.h"
#include "cros-disks/metrics.h"
#include "cros-disks/mount_options.h"
#include "cros-disks/platform.h"
#include "cros-disks/uri.h"
using base::FilePath;
using std::set;
using std::string;
using std::vector;
using testing::ByMove;
using testing::DoAll;
using testing::Eq;
using testing::Invoke;
using testing::Return;
using testing::SetArgPointee;
using testing::UnorderedElementsAre;
using testing::_;
namespace base {
void PrintTo(const FilePath& fp, std::ostream* os) {
*os << fp.value();
}
} // namespace base
namespace cros_disks {
namespace {
const char kMountRoot[] = "/mntroot";
const char kWorkingDirRoot[] = "/wkdir";
const char kNoType[] = "";
const char kSomeMountpoint[] = "/mnt";
const Uri kSomeSource("fuse", "something");
// Mock Platform implementation for testing.
class MockPlatform : public Platform {
public:
MockPlatform() = default;
MOCK_CONST_METHOD2(Unmount, bool(const string& path, int flags));
MOCK_CONST_METHOD1(DirectoryExists, bool(const string& path));
MOCK_CONST_METHOD1(CreateDirectory, bool(const string& path));
MOCK_CONST_METHOD2(SetPermissions, bool(const string& path, mode_t mode));
MOCK_CONST_METHOD3(CreateTemporaryDirInDir,
bool(const string& dir,
const string& prefix,
string* path));
};
// Mock implementation of FUSEHelper.
class MockHelper : public FUSEHelper {
public:
MockHelper(const string& tag, const Platform* platform)
: FUSEHelper(tag, platform, FilePath("/sbin/" + tag), "fuse-" + tag) {}
MOCK_CONST_METHOD1(CanMount, bool(const Uri& src));
MOCK_CONST_METHOD1(GetTargetSuffix, string(const Uri& src));
MOCK_CONST_METHOD4(CreateMounter,
std::unique_ptr<FUSEMounter>(const FilePath& dir,
const Uri& source,
const FilePath& target,
const vector<string>& opts));
};
class MockMounter : public FUSEMounter {
public:
explicit MockMounter(const Platform* platform)
: FUSEMounter("foobar",
"/mnt",
"fuse",
MountOptions(),
platform,
"/bin/sh",
"root",
"",
{},
false) {}
MOCK_METHOD0(MountImpl, MountErrorType());
};
} // namespace
class FUSEMountManagerTest : public ::testing::Test {
public:
FUSEMountManagerTest()
: manager_(kMountRoot, kWorkingDirRoot, &platform_, &metrics_),
foo_(new MockHelper("foo", &platform_)),
bar_(new MockHelper("bar", &platform_)),
baz_(new MockHelper("baz", &platform_)) {
ON_CALL(platform_, Unmount(_, 0)).WillByDefault(Return(false));
ON_CALL(platform_, DirectoryExists(_)).WillByDefault(Return(true));
}
protected:
void RegisterHelper(std::unique_ptr<FUSEHelper> helper) {
manager_.RegisterHelper(std::move(helper));
}
MountErrorType DoMount(const string& type, const string& src) {
MountOptions mount_options;
return manager_.DoMount(src, type, {}, kSomeMountpoint, &mount_options);
}
Metrics metrics_;
MockPlatform platform_;
FUSEMountManager manager_;
std::unique_ptr<MockHelper> foo_;
std::unique_ptr<MockHelper> bar_;
std::unique_ptr<MockHelper> baz_;
};
// Verifies that CanMount returns false when there are no handlers registered.
TEST_F(FUSEMountManagerTest, CanMount_NoHandlers) {
EXPECT_FALSE(manager_.CanMount(kSomeSource.value()));
}
// Verifies that CanMount returns false when known helpers can't handle that.
TEST_F(FUSEMountManagerTest, CanMount_NotHandled) {
EXPECT_CALL(*foo_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*bar_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*baz_, CanMount(_)).WillOnce(Return(false));
RegisterHelper(std::move(foo_));
RegisterHelper(std::move(bar_));
RegisterHelper(std::move(baz_));
EXPECT_FALSE(manager_.CanMount(kSomeSource.value()));
}
// Verify that CanMount returns true when there is a helper that can handle
// this source.
TEST_F(FUSEMountManagerTest, CanMount) {
EXPECT_CALL(*foo_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*bar_, CanMount(_)).WillOnce(Return(true));
EXPECT_CALL(*baz_, CanMount(_)).Times(0);
RegisterHelper(std::move(foo_));
RegisterHelper(std::move(bar_));
RegisterHelper(std::move(baz_));
EXPECT_TRUE(manager_.CanMount(kSomeSource.value()));
}
// Verify that SuggestMountPath dispatches query for name to the correct helper.
TEST_F(FUSEMountManagerTest, SuggestMountPath) {
EXPECT_CALL(*foo_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*foo_, GetTargetSuffix(_)).Times(0);
EXPECT_CALL(*bar_, CanMount(_)).WillOnce(Return(true));
EXPECT_CALL(*bar_, GetTargetSuffix(kSomeSource)).WillOnce(Return("suffix"));
EXPECT_CALL(*baz_, CanMount(_)).Times(0);
EXPECT_CALL(*baz_, GetTargetSuffix(_)).Times(0);
RegisterHelper(std::move(foo_));
RegisterHelper(std::move(bar_));
RegisterHelper(std::move(baz_));
EXPECT_EQ("/mntroot/suffix", manager_.SuggestMountPath(kSomeSource.value()));
}
// Verify that DoUnmount delegates unmount directly to platform.
TEST_F(FUSEMountManagerTest, DoUnmount) {
EXPECT_CALL(platform_, Unmount(kSomeSource.value(), 0))
.WillOnce(Return(true));
EXPECT_CALL(platform_, Unmount("foobar", 0)).WillOnce(Return(false));
EXPECT_CALL(platform_, Unmount(kSomeSource.value(), MNT_DETACH))
.WillOnce(Return(true));
EXPECT_EQ(MOUNT_ERROR_NONE, manager_.DoUnmount(kSomeSource.value(), {}));
EXPECT_NE(MOUNT_ERROR_NONE, manager_.DoUnmount("foobar", {}));
EXPECT_EQ(MOUNT_ERROR_NONE,
manager_.DoUnmount(kSomeSource.value(), {"lazy"}));
EXPECT_EQ(MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS,
manager_.DoUnmount(kSomeSource.value(), {"foo"}));
}
// Verify that DoMount fails when there are not helpers.
TEST_F(FUSEMountManagerTest, DoMount_NoHandlers) {
EXPECT_EQ(MOUNT_ERROR_UNKNOWN_FILESYSTEM,
DoMount(kNoType, kSomeSource.value()));
}
// Verify that DoMount fails when helpers don't handle this source.
TEST_F(FUSEMountManagerTest, DoMount_NotHandled) {
EXPECT_CALL(*foo_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*bar_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*baz_, CanMount(_)).WillOnce(Return(false));
RegisterHelper(std::move(foo_));
RegisterHelper(std::move(bar_));
RegisterHelper(std::move(baz_));
EXPECT_EQ(MOUNT_ERROR_UNKNOWN_FILESYSTEM,
DoMount(kNoType, kSomeSource.value()));
}
// Verify that DoMount delegates mounting to the correct helpers when
// dispatching by source description.
TEST_F(FUSEMountManagerTest, DoMount_BySource) {
EXPECT_CALL(*foo_, CanMount(_)).WillOnce(Return(false));
EXPECT_CALL(*bar_, CanMount(_)).WillRepeatedly(Return(true));
EXPECT_CALL(*baz_, CanMount(_)).Times(0);
EXPECT_CALL(*foo_, CreateMounter(_, _, _, _)).Times(0);
EXPECT_CALL(platform_, CreateTemporaryDirInDir(kWorkingDirRoot, _, _))
.WillOnce(DoAll(SetArgPointee<2>("/blah"), Return(true)));
EXPECT_CALL(platform_, SetPermissions("/blah", 0755)).WillOnce(Return(true));
MockMounter* mounter = new MockMounter(&platform_);
EXPECT_CALL(*mounter, MountImpl()).WillOnce(Return(MOUNT_ERROR_NONE));
std::unique_ptr<FUSEMounter> ptr(mounter);
EXPECT_CALL(*bar_, CreateMounter(FilePath("/blah"), kSomeSource,
FilePath(kSomeMountpoint), _))
.WillOnce(Return(ByMove(std::move(ptr))));
EXPECT_CALL(*baz_, CreateMounter(_, _, _, _)).Times(0);
RegisterHelper(std::move(foo_));
RegisterHelper(std::move(bar_));
RegisterHelper(std::move(baz_));
EXPECT_EQ(MOUNT_ERROR_NONE, DoMount(kNoType, kSomeSource.value()));
}
} // namespace cros_disks