blob: b0205d1a2a80c13e3965fa9add41aaadb374e6cc [file] [log] [blame]
// Copyright 2019 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 "diagnostics/routines/urandom/urandom.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/process/process_handle.h>
#include <base/time/default_tick_clock.h>
#include "diagnostics/routines/diag_process_adapter_impl.h"
namespace diagnostics {
namespace {
constexpr char kUrandomExePath[] = "/usr/bin/urandom";
} // namespace
const char kUrandomProcessRunningMessage[] = "Test is still running.";
const char kUrandomRoutineSucceededMessage[] = "Test passed.";
const char kUrandomRoutineFailedMessage[] = "Test failed.";
const char kUrandomFailedToLaunchProcessMessage[] =
"Could not launch the urandom process.";
const char kUrandomProcessCrashedOrKilledMessage[] =
"The urandom process crashed or was killed.";
const char kUrandomFailedToPauseMessage[] = "Failed to pause urandom routine.";
const char kUrandomFailedToCancelMessage[] =
"Failed to cancel urandom routine.";
const char kUrandomFailedToStopMessage[] = "Failed to stop urandom routine.";
const char kUrandomInvalidParametersMessage[] =
"Invalid parameters for the urandom routine.";
const int64_t kUrandomLongRunningProgress = 99;
UrandomRoutine::UrandomRoutine(
const grpc_api::UrandomRoutineParameters& parameters)
: UrandomRoutine(parameters,
std::make_unique<base::DefaultTickClock>(),
std::make_unique<DiagProcessAdapterImpl>()) {}
UrandomRoutine::UrandomRoutine(
const grpc_api::UrandomRoutineParameters& parameters,
std::unique_ptr<base::TickClock> tick_clock_impl,
std::unique_ptr<DiagProcessAdapter> process_adapter)
: status_(grpc_api::ROUTINE_STATUS_READY),
parameters_(parameters),
process_adapter_(std::move(process_adapter)),
tick_clock_(std::move(tick_clock_impl)) {}
UrandomRoutine::~UrandomRoutine() {
// If the routine is still running, make sure to stop it so we aren't left
// with a zombie process.
if (status_ == grpc_api::ROUTINE_STATUS_RUNNING)
KillProcess(kUrandomFailedToStopMessage);
}
void UrandomRoutine::Start() {
DCHECK_EQ(status_, grpc_api::ROUTINE_STATUS_READY);
if (parameters_.length_seconds() == 0) {
status_ = grpc_api::ROUTINE_STATUS_PASSED;
output_ = kUrandomRoutineSucceededMessage;
return;
}
if (parameters_.length_seconds() < 0) {
status_ = grpc_api::ROUTINE_STATUS_ERROR;
output_ = kUrandomInvalidParametersMessage;
LOG(ERROR) << kUrandomInvalidParametersMessage;
return;
}
StartProcess();
}
void UrandomRoutine::Pause() {
// Kill the current process, but record the completion percentage.
DCHECK_EQ(status_, grpc_api::ROUTINE_STATUS_RUNNING);
if (KillProcess(kUrandomFailedToPauseMessage))
status_ = grpc_api::ROUTINE_STATUS_READY;
}
void UrandomRoutine::Resume() {
// Call the urandom program again, with a new calculated timeout based on the
// completion percentage and current time.
DCHECK_EQ(status_, grpc_api::ROUTINE_STATUS_READY);
StartProcess();
}
void UrandomRoutine::Cancel() {
// Kill the process and record the completion percentage.
DCHECK_EQ(status_, grpc_api::ROUTINE_STATUS_RUNNING);
if (KillProcess(kUrandomFailedToCancelMessage))
status_ = grpc_api::ROUTINE_STATUS_CANCELLED;
}
void UrandomRoutine::PopulateStatusUpdate(
grpc_api::GetRoutineUpdateResponse* response, bool include_output) {
// Because the urandom routine is non-interactive, we will never include a
// user message.
if (status_ == grpc_api::ROUTINE_STATUS_RUNNING)
CheckProcessStatus();
response->set_status(status_);
response->set_progress_percent(CalculateProgressPercent());
if (include_output)
response->set_output(output_);
}
grpc_api::DiagnosticRoutineStatus UrandomRoutine::GetStatus() {
return status_;
}
int UrandomRoutine::CalculateProgressPercent() {
if (status_ == grpc_api::ROUTINE_STATUS_PASSED ||
status_ == grpc_api::ROUTINE_STATUS_FAILED ||
status_ == grpc_api::ROUTINE_STATUS_ERROR)
return 100;
if (status_ == grpc_api::ROUTINE_STATUS_RUNNING) {
base::TimeTicks now_ticks = tick_clock_->NowTicks();
elapsed_time_ += now_ticks - start_ticks_;
start_ticks_ = now_ticks;
}
return std::min(
kUrandomLongRunningProgress,
100 * elapsed_time_ /
base::TimeDelta::FromSeconds(parameters_.length_seconds()));
}
void UrandomRoutine::StartProcess() {
status_ = grpc_api::ROUTINE_STATUS_RUNNING;
start_ticks_ = tick_clock_->NowTicks();
std::string time_delta_ms_value = std::to_string(
(base::TimeDelta::FromSeconds(parameters_.length_seconds()) -
elapsed_time_)
.InMilliseconds());
if (!process_adapter_->StartProcess(
std::vector<std::string>{kUrandomExePath,
"--time_delta_ms=" + time_delta_ms_value,
"--urandom_path=/dev/urandom"},
&handle_)) {
status_ = grpc_api::ROUTINE_STATUS_ERROR;
output_ = kUrandomFailedToLaunchProcessMessage;
LOG(ERROR) << kUrandomFailedToLaunchProcessMessage;
}
}
bool UrandomRoutine::KillProcess(const std::string& failure_message) {
DCHECK_NE(handle_, base::kNullProcessHandle);
// If the process has already exited, there's no need to kill it.
CheckProcessStatus();
if (status_ != grpc_api::ROUTINE_STATUS_RUNNING)
return false;
if (!process_adapter_->KillProcess(handle_)) {
// Reset the ProcessHandle, so we can't accidentally kill another process
// which has reused our old PID.
handle_ = base::kNullProcessHandle;
status_ = grpc_api::ROUTINE_STATUS_ERROR;
output_ = failure_message;
LOG(ERROR) << "Failed to kill process.";
return false;
}
elapsed_time_ += tick_clock_->NowTicks() - start_ticks_;
return true;
}
void UrandomRoutine::CheckProcessStatus() {
DCHECK_NE(handle_, base::kNullProcessHandle);
switch (process_adapter_->GetStatus(handle_)) {
case base::TERMINATION_STATUS_STILL_RUNNING:
output_ = kUrandomProcessRunningMessage;
break;
case base::TERMINATION_STATUS_NORMAL_TERMINATION:
handle_ = base::kNullProcessHandle;
status_ = grpc_api::ROUTINE_STATUS_PASSED;
output_ = kUrandomRoutineSucceededMessage;
break;
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
handle_ = base::kNullProcessHandle;
status_ = grpc_api::ROUTINE_STATUS_FAILED;
output_ = kUrandomRoutineFailedMessage;
break;
case base::TERMINATION_STATUS_LAUNCH_FAILED:
handle_ = base::kNullProcessHandle;
status_ = grpc_api::ROUTINE_STATUS_ERROR;
output_ = kUrandomFailedToLaunchProcessMessage;
break;
default:
handle_ = base::kNullProcessHandle;
status_ = grpc_api::ROUTINE_STATUS_ERROR;
output_ = kUrandomProcessCrashedOrKilledMessage;
break;
}
}
} // namespace diagnostics