blob: faaa9a7ba88d4c82d600f151345b0bcf5694da94 [file] [log] [blame]
// Copyright (c) 2012 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/disk_manager.h"
#include <stdlib.h>
#include <sys/mount.h>
#include <time.h>
#include <memory>
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <brillo/process/process_reaper.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cros-disks/device_ejector.h"
#include "cros-disks/disk.h"
#include "cros-disks/disk_monitor.h"
#include "cros-disks/exfat_mounter.h"
#include "cros-disks/filesystem.h"
#include "cros-disks/metrics.h"
#include "cros-disks/mount_point.h"
#include "cros-disks/mounter.h"
#include "cros-disks/ntfs_mounter.h"
#include "cros-disks/platform.h"
namespace cros_disks {
namespace {
using testing::_;
using testing::Return;
const char kMountRootDirectory[] = "/media/removable";
class MockDeviceEjector : public DeviceEjector {
public:
MockDeviceEjector() : DeviceEjector(nullptr) {}
MOCK_METHOD(bool, Eject, (const std::string&), (override));
};
class MockDiskMonitor : public DiskMonitor {
public:
MockDiskMonitor() = default;
bool Initialize() override { return true; }
MOCK_METHOD(std::vector<Disk>, EnumerateDisks, (), (const, override));
MOCK_METHOD(bool,
GetDiskByDevicePath,
(const base::FilePath&, Disk*),
(const, override));
};
class MockPlatform : public Platform {
public:
MockPlatform() = default;
MOCK_METHOD(MountErrorType,
Unmount,
(const std::string&, int),
(const, override));
};
class MockMountPoint : public MountPoint {
public:
explicit MockMountPoint(const base::FilePath& path) : MountPoint(path) {}
~MockMountPoint() override { DestructorUnmount(); }
MOCK_METHOD(MountErrorType, UnmountImpl, (), (override));
};
} // namespace
class DiskManagerTest : public ::testing::Test {
public:
DiskManagerTest()
: manager_(kMountRootDirectory,
&platform_,
&metrics_,
&process_reaper_,
&monitor_,
&ejector_) {}
protected:
Metrics metrics_;
MockPlatform platform_;
brillo::ProcessReaper process_reaper_;
MockDeviceEjector ejector_;
MockDiskMonitor monitor_;
DiskManager manager_;
};
TEST_F(DiskManagerTest, CreateExFATMounter) {
Disk disk;
disk.device_file = "/dev/sda1";
Filesystem filesystem("exfat");
filesystem.mounter_type = ExFATMounter::kMounterType;
std::string target_path = "/media/disk";
std::vector<std::string> options = {"rw", "nodev", "noexec", "nosuid"};
auto mounter = manager_.CreateMounter(disk, filesystem, target_path, options);
EXPECT_NE(nullptr, mounter.get());
EXPECT_EQ(filesystem.mount_type, mounter->filesystem_type());
EXPECT_EQ("rw,nodev,noexec,nosuid", mounter->mount_options().ToString());
}
TEST_F(DiskManagerTest, CreateNTFSMounter) {
Disk disk;
disk.device_file = "/dev/sda1";
Filesystem filesystem("ntfs");
filesystem.mounter_type = NTFSMounter::kMounterType;
std::string target_path = "/media/disk";
std::vector<std::string> options = {"rw", "nodev", "noexec", "nosuid"};
auto mounter = manager_.CreateMounter(disk, filesystem, target_path, options);
EXPECT_NE(nullptr, mounter.get());
EXPECT_EQ(filesystem.mount_type, mounter->filesystem_type());
EXPECT_EQ("rw,nodev,noexec,nosuid", mounter->mount_options().ToString());
}
TEST_F(DiskManagerTest, CreateVFATSystemMounter) {
Disk disk;
disk.device_file = "/dev/sda1";
Filesystem filesystem("vfat");
filesystem.extra_mount_options = {"utf8", "shortname=mixed"};
std::string target_path = "/media/disk";
std::vector<std::string> options = {"rw", "nodev", "noexec", "nosuid"};
// Override the time zone to make this test deterministic.
// This test uses AWST (Perth, Australia), which is UTC+8, as the test time
// zone. However, the TZ environment variable is the time to be added to local
// time to get to UTC, hence the negative.
setenv("TZ", "UTC-8", 1);
auto mounter = manager_.CreateMounter(disk, filesystem, target_path, options);
EXPECT_NE(nullptr, mounter.get());
EXPECT_EQ(filesystem.mount_type, mounter->filesystem_type());
EXPECT_EQ("utf8,shortname=mixed,time_offset=480,rw,nodev,noexec,nosuid",
mounter->mount_options().ToString());
}
TEST_F(DiskManagerTest, CreateExt4SystemMounter) {
Disk disk;
disk.device_file = "/dev/sda1";
std::string target_path = "/media/disk";
// "mand" is not a whitelisted option, so it should be skipped.
std::vector<std::string> options = {"rw", "nodev", "noexec", "nosuid",
"mand"};
Filesystem filesystem("ext4");
auto mounter = manager_.CreateMounter(disk, filesystem, target_path, options);
EXPECT_NE(nullptr, mounter.get());
EXPECT_EQ(filesystem.mount_type, mounter->filesystem_type());
EXPECT_EQ("rw,nodev,noexec,nosuid", mounter->mount_options().ToString());
}
TEST_F(DiskManagerTest, GetFilesystem) {
EXPECT_EQ(nullptr, manager_.GetFilesystem("nonexistent-fs"));
Filesystem normal_fs("normal-fs");
EXPECT_EQ(nullptr, manager_.GetFilesystem(normal_fs.type));
manager_.RegisterFilesystem(normal_fs);
EXPECT_NE(nullptr, manager_.GetFilesystem(normal_fs.type));
}
TEST_F(DiskManagerTest, RegisterFilesystem) {
const std::map<std::string, Filesystem>& filesystems = manager_.filesystems_;
EXPECT_EQ(0, filesystems.size());
EXPECT_TRUE(filesystems.find("nonexistent") == filesystems.end());
Filesystem fat_fs("fat");
fat_fs.accepts_user_and_group_id = true;
manager_.RegisterFilesystem(fat_fs);
EXPECT_EQ(1, filesystems.size());
EXPECT_TRUE(filesystems.find(fat_fs.type) != filesystems.end());
Filesystem vfat_fs("vfat");
vfat_fs.accepts_user_and_group_id = true;
manager_.RegisterFilesystem(vfat_fs);
EXPECT_EQ(2, filesystems.size());
EXPECT_TRUE(filesystems.find(vfat_fs.type) != filesystems.end());
}
TEST_F(DiskManagerTest, CanMount) {
EXPECT_TRUE(manager_.CanMount("/dev/sda1"));
EXPECT_TRUE(manager_.CanMount("/devices/block/sda/sda1"));
EXPECT_TRUE(manager_.CanMount("/sys/devices/block/sda/sda1"));
EXPECT_FALSE(manager_.CanMount("/media/removable/disk1"));
EXPECT_FALSE(manager_.CanMount("/media/removable/disk1/"));
EXPECT_FALSE(manager_.CanMount("/media/removable/disk 1"));
EXPECT_FALSE(manager_.CanMount("/media/archive/test.zip"));
EXPECT_FALSE(manager_.CanMount("/media/archive/test.zip/"));
EXPECT_FALSE(manager_.CanMount("/media/archive/test 1.zip"));
EXPECT_FALSE(manager_.CanMount("/media/removable/disk1/test.zip"));
EXPECT_FALSE(manager_.CanMount("/media/removable/disk1/test 1.zip"));
EXPECT_FALSE(manager_.CanMount("/media/removable/disk1/dir1/test.zip"));
EXPECT_FALSE(manager_.CanMount("/media/removable/test.zip/test1.zip"));
EXPECT_FALSE(manager_.CanMount("/home/chronos/user/Downloads/test1.zip"));
EXPECT_FALSE(manager_.CanMount("/home/chronos/user/GCache/test1.zip"));
EXPECT_FALSE(
manager_.CanMount("/home/chronos"
"/u-0123456789abcdef0123456789abcdef01234567"
"/Downloads/test1.zip"));
EXPECT_FALSE(
manager_.CanMount("/home/chronos"
"/u-0123456789abcdef0123456789abcdef01234567"
"/GCache/test1.zip"));
EXPECT_FALSE(manager_.CanMount(""));
EXPECT_FALSE(manager_.CanMount("/tmp"));
EXPECT_FALSE(manager_.CanMount("/media/removable"));
EXPECT_FALSE(manager_.CanMount("/media/removable/"));
EXPECT_FALSE(manager_.CanMount("/media/archive"));
EXPECT_FALSE(manager_.CanMount("/media/archive/"));
EXPECT_FALSE(manager_.CanMount("/home/chronos/user/Downloads"));
EXPECT_FALSE(manager_.CanMount("/home/chronos/user/Downloads/"));
}
TEST_F(DiskManagerTest, DoMountDiskWithNonexistentSourcePath) {
std::string filesystem_type = "ext3";
std::string source_path = "/dev/nonexistent-path";
std::string mount_path = "/tmp/cros-disks-test";
MountOptions applied_options;
MountErrorType mount_error = MOUNT_ERROR_UNKNOWN;
std::unique_ptr<MountPoint> mount_point = manager_.DoMount(
source_path, filesystem_type, {} /* options */,
base::FilePath(mount_path), &applied_options, &mount_error);
EXPECT_FALSE(mount_point);
EXPECT_EQ(MOUNT_ERROR_INVALID_DEVICE_PATH, mount_error);
}
TEST_F(DiskManagerTest, EjectDevice) {
const base::FilePath kMountPath("/media/removable/disk");
Disk disk;
std::unique_ptr<MockMountPoint> mount_point =
std::make_unique<MockMountPoint>(kMountPath);
EXPECT_CALL(*mount_point, UnmountImpl()).WillOnce(Return(MOUNT_ERROR_NONE));
disk.device_file = "/dev/sda";
disk.media_type = DEVICE_MEDIA_USB;
EXPECT_CALL(ejector_, Eject("/dev/sda")).Times(0);
std::unique_ptr<MountPoint> wrapped_mount_point =
manager_.MaybeWrapMountPointForEject(std::move(mount_point), disk);
EXPECT_EQ(MOUNT_ERROR_NONE, wrapped_mount_point->Unmount());
mount_point = std::make_unique<MockMountPoint>(kMountPath);
EXPECT_CALL(*mount_point, UnmountImpl()).WillOnce(Return(MOUNT_ERROR_NONE));
disk.device_file = "/dev/sr0";
disk.media_type = DEVICE_MEDIA_OPTICAL_DISC;
EXPECT_CALL(ejector_, Eject("/dev/sr0")).WillOnce(Return(true));
wrapped_mount_point =
manager_.MaybeWrapMountPointForEject(std::move(mount_point), disk);
EXPECT_EQ(MOUNT_ERROR_NONE, wrapped_mount_point->Unmount());
mount_point = std::make_unique<MockMountPoint>(kMountPath);
EXPECT_CALL(*mount_point, UnmountImpl()).WillOnce(Return(MOUNT_ERROR_NONE));
disk.device_file = "/dev/sr1";
disk.media_type = DEVICE_MEDIA_DVD;
EXPECT_CALL(ejector_, Eject("/dev/sr1")).WillOnce(Return(true));
wrapped_mount_point =
manager_.MaybeWrapMountPointForEject(std::move(mount_point), disk);
EXPECT_EQ(MOUNT_ERROR_NONE, wrapped_mount_point->Unmount());
}
TEST_F(DiskManagerTest, EjectDeviceWhenUnmountFailed) {
const base::FilePath kMountPath("/media/removable/disk");
Disk disk;
disk.device_file = "/dev/sr0";
disk.media_type = DEVICE_MEDIA_OPTICAL_DISC;
std::unique_ptr<MockMountPoint> mount_point =
std::make_unique<MockMountPoint>(kMountPath);
EXPECT_CALL(*mount_point, UnmountImpl())
.WillRepeatedly(Return(MOUNT_ERROR_UNKNOWN));
EXPECT_CALL(ejector_, Eject("/dev/sr0")).Times(0);
std::unique_ptr<MountPoint> wrapped_mount_point =
manager_.MaybeWrapMountPointForEject(std::move(mount_point), disk);
EXPECT_EQ(MOUNT_ERROR_UNKNOWN, wrapped_mount_point->Unmount());
}
TEST_F(DiskManagerTest, EjectDeviceWhenExplicitlyDisabled) {
const base::FilePath kMountPath("/media/removable/disk");
Disk disk;
disk.device_file = "/dev/sr0";
disk.media_type = DEVICE_MEDIA_OPTICAL_DISC;
std::unique_ptr<MockMountPoint> mount_point =
std::make_unique<MockMountPoint>(kMountPath);
EXPECT_CALL(*mount_point, UnmountImpl()).WillOnce(Return(MOUNT_ERROR_NONE));
manager_.eject_device_on_unmount_ = false;
EXPECT_CALL(ejector_, Eject("/dev/sr0")).Times(0);
std::unique_ptr<MountPoint> wrapped_mount_point =
manager_.MaybeWrapMountPointForEject(std::move(mount_point), disk);
EXPECT_EQ(MOUNT_ERROR_NONE, wrapped_mount_point->Unmount());
}
TEST_F(DiskManagerTest, EjectDeviceWhenReleased) {
const base::FilePath kMountPath("/media/removable/disk");
Disk disk;
disk.device_file = "/dev/sr0";
disk.media_type = DEVICE_MEDIA_OPTICAL_DISC;
std::unique_ptr<MockMountPoint> mount_point =
std::make_unique<MockMountPoint>(kMountPath);
EXPECT_CALL(*mount_point, UnmountImpl()).Times(0);
EXPECT_CALL(ejector_, Eject("/dev/sr0")).Times(0);
std::unique_ptr<MountPoint> wrapped_mount_point =
manager_.MaybeWrapMountPointForEject(std::move(mount_point), disk);
wrapped_mount_point->Release();
EXPECT_EQ(MOUNT_ERROR_PATH_NOT_MOUNTED, wrapped_mount_point->Unmount());
}
} // namespace cros_disks