blob: b004c5c4ca3163a8cf19557d0e70796067507dae [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 <unordered_set>
#include <utility>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include "cros-disks/error_logger.h"
#include "cros-disks/mount_options.h"
#include "cros-disks/mounter.h"
#include "cros-disks/platform.h"
#include "cros-disks/quote.h"
#include "cros-disks/uri.h"
namespace cros_disks {
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;
// Prefix of the mount label option.
const char kMountOptionMountLabelPrefix[] = "mountlabel";
// Literal for mount option: "remount".
const char kMountOptionRemount[] = "remount";
// 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
MountManager::MountManager(const std::string& mount_root,
Platform* platform,
Metrics* metrics,
brillo::ProcessReaper* process_reaper)
: mount_root_(base::FilePath(mount_root)),
platform_(platform),
metrics_(metrics),
process_reaper_(process_reaper) {
CHECK(!mount_root_.empty()) << "Invalid mount root directory";
CHECK(mount_root_.IsAbsolute()) << "Mount root not absolute path";
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() calls MountPoint::Unmount() which may call back into
// a derived class.
}
bool MountManager::Initialize() {
return platform_->CreateDirectory(mount_root_.value()) &&
platform_->SetOwnership(mount_root_.value(), getuid(), getgid()) &&
platform_->SetPermissions(mount_root_.value(),
kMountRootDirectoryPermissions);
}
bool MountManager::StartSession() {
return true;
}
bool MountManager::StopSession() {
return UnmountAll();
}
MountErrorType MountManager::Mount(const std::string& source_path,
const std::string& filesystem_type,
std::vector<std::string> options,
std::string* mount_path) {
// Source is not necessary a path, but if it is let's resolve it to
// some real underlying object.
std::string real_path;
if (Uri::IsUri(source_path) || !ResolvePath(source_path, &real_path)) {
real_path = source_path;
}
if (real_path.empty()) {
LOG(ERROR) << "Failed to mount an invalid path";
return MOUNT_ERROR_INVALID_ARGUMENT;
}
if (!mount_path) {
LOG(ERROR) << "Invalid mount path argument";
return MOUNT_ERROR_INVALID_ARGUMENT;
}
if (RemoveParamsEqualTo(&options, kMountOptionRemount) == 0) {
return MountNewSource(real_path, filesystem_type, std::move(options),
mount_path);
} else {
return Remount(real_path, filesystem_type, std::move(options), mount_path);
}
}
MountErrorType MountManager::Remount(const std::string& source_path,
const std::string& /*filesystem_type*/,
std::vector<std::string> options,
std::string* mount_path) {
MountPoint* mount_point = FindMountBySource(source_path);
if (!mount_point) {
LOG(WARNING) << "Path " << quote(source_path) << " is not mounted yet";
return MOUNT_ERROR_PATH_NOT_MOUNTED;
}
bool read_only = IsReadOnlyMount(options);
// Perform the underlying mount operation.
MountErrorType error_type = mount_point->Remount(read_only);
if (error_type != MOUNT_ERROR_NONE) {
LOG(ERROR) << "Cannot remount path " << quote(source_path) << ": "
<< error_type;
return error_type;
}
*mount_path = mount_point->path().value();
LOG(INFO) << "Path " << quote(source_path) << " on " << quote(*mount_path)
<< " is remounted";
return error_type;
}
MountErrorType MountManager::MountNewSource(const std::string& source_path,
const std::string& filesystem_type,
std::vector<std::string> options,
std::string* mount_path) {
MountPoint* mp = FindMountBySource(source_path);
if (mp) {
// TODO(dats): Some obscure legacy. Why is this even needed?
if (mount_path->empty() || mp->path().value() == *mount_path) {
LOG(WARNING) << "Source " << redact(source_path)
<< " is already mounted to " << redact(mp->path());
*mount_path = mp->path().value();
return GetMountErrorOfReservedMountPath(mp->path());
}
LOG(ERROR) << "Source " << redact(source_path) << " is already mounted to "
<< redact(mp->path());
return MOUNT_ERROR_PATH_ALREADY_MOUNTED;
}
std::string mount_label;
if (GetParamValue(options, kMountOptionMountLabelPrefix, &mount_label)) {
RemoveParamsWithSameName(&options, kMountOptionMountLabelPrefix);
}
// 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.
base::FilePath actual_mount_path(*mount_path);
MountErrorType error =
CreateMountPathForSource(source_path, mount_label, &actual_mount_path);
if (error != MOUNT_ERROR_NONE) {
return error;
}
// Perform the underlying mount operation. If an error occurs,
// ShouldReserveMountPathOnError() is called to check if the mount path
// should be reserved.
bool mounted_as_read_only = false;
MountErrorType error_type = MOUNT_ERROR_UNKNOWN;
std::unique_ptr<MountPoint> mount_point = DoMount(
source_path, filesystem_type, std::move(options),
base::FilePath(actual_mount_path), &mounted_as_read_only, &error_type);
if (error_type == MOUNT_ERROR_NONE) {
LOG(INFO) << "Path " << quote(source_path) << " is mounted to "
<< quote(actual_mount_path);
DCHECK(mount_point);
} else if (ShouldReserveMountPathOnError(error_type)) {
LOG(INFO) << "Reserving mount path " << quote(actual_mount_path) << " for "
<< quote(source_path);
DCHECK(!mount_point);
ReserveMountPath(actual_mount_path, error_type);
// Create dummy mount point to associate with the mount path.
mount_point = MountPoint::CreateLeaking(base::FilePath(actual_mount_path));
} else {
LOG(ERROR) << "Cannot mount " << redact(source_path) << " of type "
<< quote(filesystem_type) << ": " << error_type;
platform_->RemoveEmptyDirectory(actual_mount_path.value());
return error_type;
}
mount_states_.insert({source_path, std::move(mount_point)});
*mount_path = actual_mount_path.value();
return error_type;
}
MountErrorType MountManager::Unmount(const std::string& path) {
// Determine whether the path is a source path or a mount path.
// Is path a source path?
MountPoint* mount_point = FindMountBySource(path);
if (!mount_point) {
// Not a source path. Is path a mount path?
mount_point = FindMountByMountPath(base::FilePath(path));
if (!mount_point) {
// Not a mount path either.
return MOUNT_ERROR_PATH_NOT_MOUNTED;
}
}
MountErrorType error_type = MOUNT_ERROR_NONE;
if (IsMountPathReserved(mount_point->path())) {
LOG(INFO) << "Removing mount path '" << mount_point->path()
<< "' from the reserved list";
UnreserveMountPath(mount_point->path());
} else {
error_type = mount_point->Unmount();
switch (error_type) {
case MOUNT_ERROR_NONE:
LOG(INFO) << "Unmounted " << quote(mount_point->path());
break;
case MOUNT_ERROR_PATH_NOT_MOUNTED:
LOG(WARNING) << "Not mounted " << quote(mount_point->path());
break;
default:
LOG(ERROR) << "Cannot unmount " << quote(mount_point->path()) << ": "
<< error_type;
return error_type;
}
}
platform_->RemoveEmptyDirectory(mount_point->path().value());
for (auto it = mount_states_.begin(); it != mount_states_.end(); ++it) {
if (it->second.get() == mount_point) {
mount_states_.erase(it);
break;
}
}
return error_type;
}
bool MountManager::UnmountAll() {
bool all_umounted = true;
// Enumerate all the mount paths and then unmount, as calling Unmount()
// modifies the cache.
std::vector<std::string> paths;
paths.reserve(mount_states_.size());
for (const auto& entry : mount_states_) {
paths.push_back(entry.second->path().value());
}
for (const auto& source_path : paths) {
if (Unmount(source_path) != MOUNT_ERROR_NONE) {
all_umounted = false;
}
}
return all_umounted;
}
bool MountManager::ResolvePath(const std::string& path,
std::string* real_path) {
return platform_->GetRealPath(path, real_path);
}
MountPoint* MountManager::FindMountBySource(const std::string& source) {
const auto it = mount_states_.find(source);
if (it == mount_states_.end())
return nullptr;
return it->second.get();
}
MountPoint* MountManager::FindMountByMountPath(const base::FilePath& path) {
for (auto& entry : mount_states_) {
if (entry.second->path() == path)
return entry.second.get();
}
return nullptr;
}
bool MountManager::RemoveMount(MountPoint* mount_point) {
for (auto it = mount_states_.begin(); it != mount_states_.end(); ++it) {
if (it->second.get() == mount_point) {
mount_states_.erase(it);
return true;
}
}
return false;
}
MountErrorType MountManager::CreateMountPathForSource(
const std::string& source,
const std::string& label,
base::FilePath* mount_path) {
base::FilePath actual_mount_path = *mount_path;
if (actual_mount_path.empty()) {
actual_mount_path = base::FilePath(SuggestMountPath(source));
if (!label.empty()) {
// Replace the basename(|actual_mount_path|) with |label|.
actual_mount_path = actual_mount_path.DirName().Append(label);
}
}
if (!IsValidMountPath(base::FilePath(actual_mount_path))) {
LOG(ERROR) << "Mount path " << quote(actual_mount_path) << " is invalid";
return MOUNT_ERROR_INVALID_PATH;
}
bool mount_path_created;
if (!mount_path->empty()) {
mount_path_created =
!IsMountPathReserved(actual_mount_path) &&
platform_->CreateOrReuseEmptyDirectory(actual_mount_path.value());
} else {
std::unordered_set<std::string> reserved_paths;
for (const auto& entry : reserved_mount_paths_) {
reserved_paths.insert(entry.first.value());
}
std::string path = actual_mount_path.value();
mount_path_created = platform_->CreateOrReuseEmptyDirectoryWithFallback(
&path, kMaxNumMountTrials, reserved_paths);
if (mount_path_created)
actual_mount_path = base::FilePath(path);
}
if (!mount_path_created) {
LOG(ERROR) << "Cannot create directory " << quote(actual_mount_path)
<< " to mount " << quote(source);
return MOUNT_ERROR_DIRECTORY_CREATION_FAILED;
}
*mount_path = actual_mount_path;
return MOUNT_ERROR_NONE;
}
bool MountManager::IsMountPathReserved(const base::FilePath& mount_path) const {
return base::Contains(reserved_mount_paths_, mount_path);
}
MountErrorType MountManager::GetMountErrorOfReservedMountPath(
const base::FilePath& mount_path) const {
const auto it = reserved_mount_paths_.find(mount_path);
return it != reserved_mount_paths_.end() ? it->second : MOUNT_ERROR_NONE;
}
void MountManager::ReserveMountPath(base::FilePath mount_path,
MountErrorType error_type) {
reserved_mount_paths_.insert({std::move(mount_path), error_type});
}
void MountManager::UnreserveMountPath(const base::FilePath& mount_path) {
reserved_mount_paths_.erase(mount_path);
}
std::vector<MountEntry> MountManager::GetMountEntries() const {
std::vector<MountEntry> mount_entries;
mount_entries.reserve(mount_states_.size());
for (const auto& entry : mount_states_) {
const std::string& source_path = entry.first;
const MountPoint& mount_point = *entry.second;
mount_entries.push_back(
{GetMountErrorOfReservedMountPath(mount_point.path()), source_path,
GetMountSourceType(), mount_point.path().value(),
mount_point.is_read_only()});
}
return mount_entries;
}
bool MountManager::ShouldReserveMountPathOnError(
MountErrorType error_type) const {
return false;
}
bool MountManager::IsPathImmediateChildOfParent(const base::FilePath& path,
const base::FilePath& parent) {
std::vector<std::string> path_components, parent_components;
path.StripTrailingSeparators().GetComponents(&path_components);
parent.StripTrailingSeparators().GetComponents(&parent_components);
if (path_components.size() != parent_components.size() + 1)
return false;
if (path_components.back() == base::FilePath::kCurrentDirectory ||
path_components.back() == base::FilePath::kParentDirectory) {
return false;
}
return std::equal(parent_components.begin(), parent_components.end(),
path_components.begin());
}
bool MountManager::IsValidMountPath(const base::FilePath& mount_path) const {
return IsPathImmediateChildOfParent(mount_path, mount_root_);
}
} // namespace cros_disks