blob: 04fc16c379d22219c310f39fcf73e2026ab8bc7e [file] [log] [blame]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cros-disks/mount_point.h"
#include <utility>
#include <base/check.h>
#include <base/containers/contains.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include "cros-disks/platform.h"
#include "cros-disks/quote.h"
namespace cros_disks {
std::unique_ptr<MountPoint> MountPoint::CreateUnmounted(
MountPointData data, const Platform* const platform) {
std::unique_ptr<MountPoint> mount_point =
std::make_unique<MountPoint>(std::move(data), platform);
mount_point->is_mounted_ = false;
return mount_point;
}
std::unique_ptr<MountPoint> MountPoint::Mount(MountPointData data,
const Platform* const platform,
MountError* const error) {
DCHECK(error);
*error = platform->Mount(data.source, data.mount_path.value(),
data.filesystem_type, data.flags, data.data);
if (*error != MountError::kSuccess) {
return nullptr;
}
return std::make_unique<MountPoint>(std::move(data), platform);
}
MountPoint::MountPoint(MountPointData data, const Platform* platform)
: data_(std::move(data)), platform_(platform) {
DCHECK(!path().empty());
}
MountError MountPoint::Unmount() {
MountError error = MountError::kPathNotMounted;
if (is_mounted_) {
// To prevent the umount() syscall from blocking for too long (b/258344222),
// request the FUSE process termination if this process is "safe" to kill.
if (process_ && is_read_only()) {
process_->KillPidNamespace();
}
error = platform_->Unmount(data_.mount_path, data_.filesystem_type);
if (error == MountError::kSuccess || error == MountError::kPathNotMounted) {
is_mounted_ = false;
if (eject_) {
std::move(eject_).Run();
}
}
}
process_.reset();
if (launcher_exit_callback_) {
DCHECK_EQ(MountError::kInProgress, data_.error);
data_.error = MountError::kCancelled;
std::move(launcher_exit_callback_).Run(MountError::kCancelled);
}
if (!is_mounted_ && must_remove_dir_ &&
platform_->RemoveEmptyDirectory(data_.mount_path.value())) {
must_remove_dir_ = false;
}
return error;
}
MountError MountPoint::Remount(bool read_only) {
if (!is_mounted_) {
return MountError::kPathNotMounted;
}
uint64_t flags = data_.flags;
if (read_only) {
flags |= MS_RDONLY;
} else {
flags &= ~MS_RDONLY;
}
const MountError error =
platform_->Mount(data_.source, data_.mount_path.value(),
data_.filesystem_type, flags | MS_REMOUNT, data_.data);
if (error == MountError::kSuccess) {
data_.flags = flags;
}
return error;
}
MountError MountPoint::ConvertLauncherExitCodeToMountError(
const int exit_code) const {
if (exit_code == 0) {
return MountError::kSuccess;
}
if (base::Contains(password_needed_exit_codes_, exit_code)) {
return MountError::kNeedPassword;
}
return MountError::kMountProgramFailed;
}
void MountPoint::OnLauncherExit(const int exit_code) {
DCHECK_EQ(MountError::kInProgress, data_.error);
data_.error = ConvertLauncherExitCodeToMountError(exit_code);
DCHECK_NE(MountError::kInProgress, data_.error);
if (process_) {
// Record the FUSE launcher's exit code in Metrics.
if (metrics_) {
metrics_->RecordAction("Fuse.Start", data_.filesystem_type,
Process::ExitCode(exit_code), process_->Elapsed());
}
if (exit_code != 0) {
if (!LOG_IS_ON(INFO)) {
for (const auto& s : process_->GetCapturedOutput()) {
LOG(ERROR) << process_->GetProgramName() << "[" << process_->pid()
<< "]: " << s;
}
}
} else {
LOG(INFO) << "FUSE daemon " << quote(process_->GetProgramName())
<< " serving " << data_.filesystem_type << " "
<< redact(data_.mount_path) << " started in "
<< process_->Elapsed();
}
}
if (launcher_exit_callback_) {
std::move(launcher_exit_callback_).Run(data_.error);
}
}
bool MountPoint::ParseProgressMessage(std::string_view message,
int* const percent) {
if (message.empty() || message.back() != '%') {
return false;
}
// |message| ends with a percent sign '%'
message.remove_suffix(1);
// Extract the number before the percent sign.
std::string_view::size_type i = message.size();
while (i > 0 && base::IsAsciiDigit(message[i - 1])) {
i--;
}
message.remove_prefix(i);
DCHECK(percent);
return base::StringToInt(message, percent) && *percent >= 0 &&
*percent <= 100;
}
void MountPoint::OnProgress(const std::string_view message) {
int percent;
if (!ParseProgressMessage(message, &percent)) {
return;
}
progress_percent_ = percent;
if (progress_callback_) {
progress_callback_.Run(this);
}
}
void MountPoint::SetProcess(std::unique_ptr<Process> process,
Metrics* const metrics,
std::vector<int> password_needed_exit_codes) {
DCHECK(!process_);
process_ = std::move(process);
DCHECK(process_);
DCHECK(!metrics_);
metrics_ = metrics;
password_needed_exit_codes_ = std::move(password_needed_exit_codes);
DCHECK_EQ(MountError::kSuccess, data_.error);
data_.error = MountError::kInProgress;
process_->SetLauncherExitCallback(
base::BindOnce(&MountPoint::OnLauncherExit, GetWeakPtr()));
process_->SetOutputCallback(
base::BindRepeating(&MountPoint::OnProgress, GetWeakPtr()));
}
} // namespace cros_disks