blob: be76e495447c4a10603010967a0ff0b8be18a287 [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.
// Implements cros-disks::MountManager. See mount-manager.h for details.
#include "cros-disks/mount_manager.h"
#include <sys/mount.h>
#include <unistd.h>
#include <algorithm>
#include <utility>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include "cros-disks/mount_options.h"
#include "cros-disks/platform.h"
using base::FilePath;
using std::pair;
using std::set;
using std::string;
using std::vector;
namespace {
// Permissions to set on the mount root directory (u+rwx,og+rx).
const mode_t kMountRootDirectoryPermissions =
S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
// Permissions to set on the mount directory (u+rwx,g+rwx).
const mode_t kMountDirectoryPermissions = S_IRWXU | S_IRWXG;
// Prefix of the mount label option.
const char kMountOptionMountLabelPrefix[] = "mountlabel=";
// Literal for mount option: "remount".
const char kMountOptionRemount[] = "remount";
// Literal for unmount option: "lazy".
const char kUnmountOptionLazy[] = "lazy";
// Maximum number of trials on creating a mount directory using
// Platform::CreateOrReuseEmptyDirectoryWithFallback().
// A value of 100 seems reasonable and enough to handle directory name
// collisions under common scenarios.
const unsigned kMaxNumMountTrials = 100;
} // namespace
namespace cros_disks {
MountManager::MountManager(const string& mount_root,
Platform* platform,
Metrics* metrics)
: mount_root_(mount_root), platform_(platform), metrics_(metrics) {
CHECK(!mount_root_.empty()) << "Invalid mount root directory";
CHECK(platform_) << "Invalid platform object";
CHECK(metrics_) << "Invalid metrics object";
}
MountManager::~MountManager() {
// UnmountAll() should be called from a derived class instead of this base
// class as UnmountAll() eventually calls DoUnmount(), which is a pure
// virtual function and may crash when being invoked here.
}
bool MountManager::Initialize() {
return platform_->CreateDirectory(mount_root_) &&
platform_->SetOwnership(mount_root_, getuid(), getgid()) &&
platform_->SetPermissions(mount_root_, kMountRootDirectoryPermissions);
}
bool MountManager::StartSession() {
return true;
}
bool MountManager::StopSession() {
return true;
}
bool MountManager::CanUnmount(const string& path) const {
return CanMount(path) || IsPathImmediateChildOfParent(path, mount_root_);
}
MountErrorType MountManager::Mount(const string& source_path,
const string& filesystem_type,
const vector<string>& options,
string* mount_path) {
if (source_path.empty()) {
LOG(ERROR) << "Failed to mount an empty path";
return MOUNT_ERROR_INVALID_ARGUMENT;
}
if (!mount_path) {
LOG(ERROR) << "Invalid mount path argument";
return MOUNT_ERROR_INVALID_ARGUMENT;
}
if (find(options.begin(), options.end(), kMountOptionRemount) ==
options.end()) {
return MountNewSource(source_path, filesystem_type, options, mount_path);
} else {
return Remount(source_path, filesystem_type, options, mount_path);
}
}
MountErrorType MountManager::Remount(const string& source_path,
const string& filesystem_type,
const vector<string>& options,
string* mount_path) {
if (!GetMountPathFromCache(source_path, mount_path)) {
LOG(WARNING) << "Path '" << source_path << "' is not mounted yet";
return MOUNT_ERROR_PATH_NOT_MOUNTED;
}
vector<string> updated_options = options;
string mount_label;
ExtractMountLabelFromOptions(&updated_options, &mount_label);
// Perform the underlying mount operation.
MountOptions applied_options;
MountErrorType error_type =
DoMount(source_path, filesystem_type, updated_options, *mount_path,
&applied_options);
if (error_type != MOUNT_ERROR_NONE) {
LOG(ERROR) << "Failed to remount path '" << source_path << "'";
return error_type;
}
LOG(INFO) << "Path '" << source_path << "' on '" << *mount_path
<< "' is remounted with read_only="
<< applied_options.IsReadOnlyOptionSet();
AddOrUpdateMountStateCache(source_path, *mount_path,
applied_options.IsReadOnlyOptionSet());
return error_type;
}
MountErrorType MountManager::MountNewSource(const string& source_path,
const string& filesystem_type,
const vector<string>& options,
string* mount_path) {
string actual_mount_path;
if (GetMountPathFromCache(source_path, &actual_mount_path)) {
LOG(WARNING) << "Path '" << source_path << "' is already mounted to '"
<< actual_mount_path << "'";
// TODO(benchan): Should probably compare filesystem type and mount options
// with those used in previous mount.
if (mount_path->empty() || *mount_path == actual_mount_path) {
*mount_path = actual_mount_path;
return GetMountErrorOfReservedMountPath(actual_mount_path);
} else {
return MOUNT_ERROR_PATH_ALREADY_MOUNTED;
}
}
vector<string> updated_options = options;
string mount_label;
ExtractMountLabelFromOptions(&updated_options, &mount_label);
// Create a directory and set up its ownership/permissions for mounting
// the source path. If an error occurs, ShouldReserveMountPathOnError()
// is not called to reserve the mount path as a reserved mount path still
// requires a proper mount directory.
if (mount_path->empty()) {
actual_mount_path = SuggestMountPath(source_path);
if (!mount_label.empty()) {
// Replace the basename(|actual_mount_path|) with |mount_label|.
actual_mount_path =
FilePath(actual_mount_path).DirName().Append(mount_label).value();
}
} else {
actual_mount_path = *mount_path;
}
if (!IsValidMountPath(actual_mount_path)) {
LOG(ERROR) << "Mount path '" << actual_mount_path << "' is invalid";
return MOUNT_ERROR_INVALID_PATH;
}
bool mount_path_created;
if (mount_path->empty()) {
mount_path_created = platform_->CreateOrReuseEmptyDirectoryWithFallback(
&actual_mount_path, kMaxNumMountTrials, GetReservedMountPaths());
} else {
mount_path_created =
!IsMountPathReserved(actual_mount_path) &&
platform_->CreateOrReuseEmptyDirectory(actual_mount_path);
}
if (!mount_path_created) {
LOG(ERROR) << "Failed to create directory '" << actual_mount_path
<< "' to mount '" << source_path << "'";
return MOUNT_ERROR_DIRECTORY_CREATION_FAILED;
}
if (!platform_->SetOwnership(actual_mount_path, getuid(),
platform_->mount_group_id()) ||
!platform_->SetPermissions(actual_mount_path,
kMountDirectoryPermissions)) {
LOG(ERROR) << "Failed to set ownership and permissions of directory '"
<< actual_mount_path << "' to mount '" << source_path << "'";
platform_->RemoveEmptyDirectory(actual_mount_path);
return MOUNT_ERROR_DIRECTORY_CREATION_FAILED;
}
// Perform the underlying mount operation. If an error occurs,
// ShouldReserveMountPathOnError() is called to check if the mount path
// should be reserved.
MountOptions applied_options;
MountErrorType error_type =
DoMount(source_path, filesystem_type, updated_options, actual_mount_path,
&applied_options);
if (error_type == MOUNT_ERROR_NONE) {
LOG(INFO) << "Path '" << source_path << "' is mounted to '"
<< actual_mount_path << "'";
} else if (ShouldReserveMountPathOnError(error_type)) {
LOG(INFO) << "Reserving mount path '" << actual_mount_path << "' for '"
<< source_path << "'";
ReserveMountPath(actual_mount_path, error_type);
} else {
LOG(ERROR) << "Failed to mount path '" << source_path << "'";
platform_->RemoveEmptyDirectory(actual_mount_path);
return error_type;
}
AddOrUpdateMountStateCache(source_path, actual_mount_path,
applied_options.IsReadOnlyOptionSet());
*mount_path = actual_mount_path;
return error_type;
}
MountErrorType MountManager::Unmount(const string& path,
const vector<string>& options) {
if (path.empty()) {
LOG(ERROR) << "Failed to unmount an empty path";
return MOUNT_ERROR_INVALID_ARGUMENT;
}
// Determine whether the path is a source path or a mount path.
string mount_path;
if (!GetMountPathFromCache(path, &mount_path)) { // is a source path?
if (!IsMountPathInCache(path)) { // is a mount path?
LOG(ERROR) << "Path '" << path << "' is not mounted";
return MOUNT_ERROR_PATH_NOT_MOUNTED;
}
mount_path = path;
}
MountErrorType error_type = MOUNT_ERROR_NONE;
if (IsMountPathReserved(mount_path)) {
LOG(INFO) << "Removing mount path '" << mount_path
<< "' from the reserved list";
UnreserveMountPath(mount_path);
} else {
error_type = DoUnmount(mount_path, options);
if (error_type == MOUNT_ERROR_NONE) {
LOG(INFO) << "Unmounted '" << mount_path << "'";
} else {
LOG(ERROR) << "Failed to unmount '" << mount_path << "'";
return error_type;
}
}
RemoveMountPathFromCache(mount_path);
platform_->RemoveEmptyDirectory(mount_path);
return error_type;
}
bool MountManager::UnmountAll() {
bool all_umounted = true;
vector<string> options;
// Make a copy of the mount path cache before iterating through it
// as Unmount modifies the cache.
MountStateMap mount_states_copy = mount_states_;
for (const auto& mount_state : mount_states_copy) {
if (Unmount(mount_state.first, options) != MOUNT_ERROR_NONE) {
all_umounted = false;
}
}
return all_umounted;
}
void MountManager::AddOrUpdateMountStateCache(const string& source_path,
const string& mount_path,
bool is_read_only) {
MountState mount_state;
mount_state.mount_path = mount_path;
mount_state.is_read_only = is_read_only;
mount_states_[source_path] = mount_state;
}
bool MountManager::GetSourcePathFromCache(const string& mount_path,
string* source_path) const {
CHECK(source_path) << "Invalid source path argument";
for (const auto& path_pair : mount_states_) {
if (path_pair.second.mount_path == mount_path) {
*source_path = path_pair.first;
return true;
}
}
return false;
}
bool MountManager::GetMountPathFromCache(const string& source_path,
string* mount_path) const {
CHECK(mount_path) << "Invalid mount path argument";
MountState mount_state;
if (!GetMountStateFromCache(source_path, &mount_state)) {
return false;
}
*mount_path = mount_state.mount_path;
return true;
}
bool MountManager::GetMountStateFromCache(const string& source_path,
MountState* mount_state) const {
CHECK(mount_state) << "Invalid mount state argument";
MountStateMap::const_iterator path_iterator = mount_states_.find(source_path);
if (path_iterator == mount_states_.end())
return false;
*mount_state = path_iterator->second;
return true;
}
bool MountManager::IsMountPathInCache(const string& mount_path) const {
for (const auto& path_pair : mount_states_) {
if (path_pair.second.mount_path == mount_path)
return true;
}
return false;
}
bool MountManager::RemoveMountPathFromCache(const string& mount_path) {
for (MountStateMap::iterator path_iterator = mount_states_.begin();
path_iterator != mount_states_.end(); ++path_iterator) {
if (path_iterator->second.mount_path == mount_path) {
mount_states_.erase(path_iterator);
return true;
}
}
return false;
}
bool MountManager::IsMountPathReserved(const string& mount_path) const {
return ContainsKey(reserved_mount_paths_, mount_path);
}
MountErrorType MountManager::GetMountErrorOfReservedMountPath(
const string& mount_path) const {
ReservedMountPathMap::const_iterator path_iterator =
reserved_mount_paths_.find(mount_path);
if (path_iterator != reserved_mount_paths_.end()) {
return path_iterator->second;
}
return MOUNT_ERROR_NONE;
}
set<string> MountManager::GetReservedMountPaths() const {
set<string> reserved_paths;
for (const auto& path_pair : reserved_mount_paths_) {
reserved_paths.insert(path_pair.first);
}
return reserved_paths;
}
void MountManager::ReserveMountPath(const string& mount_path,
MountErrorType error_type) {
reserved_mount_paths_.insert(std::make_pair(mount_path, error_type));
}
void MountManager::UnreserveMountPath(const string& mount_path) {
reserved_mount_paths_.erase(mount_path);
}
vector<MountEntry> MountManager::GetMountEntries() const {
vector<MountEntry> mount_entries;
for (const auto& entry : mount_states_) {
const string& source_path = entry.first;
const MountState& mount_state = entry.second;
const string& mount_path = mount_state.mount_path;
bool is_read_only = mount_state.is_read_only;
MountErrorType error_type = GetMountErrorOfReservedMountPath(mount_path);
mount_entries.push_back(MountEntry(error_type, source_path,
GetMountSourceType(), mount_path,
is_read_only));
}
return mount_entries;
}
bool MountManager::ExtractMountLabelFromOptions(
vector<string>* options, std::string* mount_label) const {
CHECK(options) << "Invalid mount options argument";
CHECK(mount_label) << "Invalid mount label argument";
mount_label->clear();
bool found_mount_label = false;
vector<string>::iterator option_iterator = options->begin();
while (option_iterator != options->end()) {
if (!base::StartsWith(*option_iterator, kMountOptionMountLabelPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
++option_iterator;
continue;
}
found_mount_label = true;
*mount_label =
option_iterator->substr(strlen(kMountOptionMountLabelPrefix));
option_iterator = options->erase(option_iterator);
}
return found_mount_label;
}
bool MountManager::ExtractUnmountOptions(const vector<string>& options,
int* unmount_flags) const {
CHECK(unmount_flags) << "Invalid unmount flags argument";
*unmount_flags = 0;
for (const auto& option : options) {
if (option == kUnmountOptionLazy) {
*unmount_flags |= MNT_DETACH;
} else {
LOG(ERROR) << "Got unsupported unmount option: " << option;
return false;
}
}
return true;
}
bool MountManager::ShouldReserveMountPathOnError(
MountErrorType error_type) const {
return false;
}
bool MountManager::IsPathImmediateChildOfParent(const string& path,
const string& parent) const {
vector<FilePath::StringType> path_components, parent_components;
FilePath(path).StripTrailingSeparators().GetComponents(&path_components);
FilePath(parent).StripTrailingSeparators().GetComponents(&parent_components);
if (path_components.size() != parent_components.size() + 1)
return false;
return std::equal(parent_components.begin(), parent_components.end(),
path_components.begin());
}
bool MountManager::IsValidMountPath(const std::string& mount_path) const {
return IsPathImmediateChildOfParent(mount_path, mount_root_);
}
} // namespace cros_disks