1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
|
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/policy/remote_commands/device_command_reboot_job.h"
#include <utility>
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/reboot_notifications_scheduler.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/login/session/session_termination_manager.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "third_party/cros_system_api/dbus/power_manager/dbus-constants.h"
namespace policy {
namespace {
const char kKioskRebootDescription[] = "Reboot remote command (kiosk)";
const char kLoginScreenRebootDescription[] =
"Reboot remote command (login screen)";
const char kUserSessionRebootDescription[] =
"Reboot remote command (user session)";
const char kPayloadUserSessionRebootDelayField[] = "user_session_delay_seconds";
constexpr base::TimeDelta kDefaultUserSessionRebootDelay = base::Minutes(5);
std::optional<base::TimeDelta> ExtractUserSessionDelayFromCommandLine() {
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
const std::string delay_string = command_line->GetSwitchValueASCII(
ash::switches::kRemoteRebootCommandDelayInSecondsForTesting);
if (delay_string.empty()) {
return std::nullopt;
}
int delay_in_seconds;
if (!base::StringToInt(delay_string, &delay_in_seconds) ||
delay_in_seconds < 0) {
LOG(ERROR) << "Ignored "
<< ash::switches::kRemoteRebootCommandDelayInSecondsForTesting
<< " = " << delay_string;
return std::nullopt;
}
return base::Seconds(delay_in_seconds);
}
std::optional<base::TimeDelta> ExtractUserSessionDelayFromPayload(
const std::string& command_payload) {
const std::optional<base::Value::Dict> root =
base::JSONReader::ReadDict(command_payload);
if (!root) {
return std::nullopt;
}
std::optional<int> delay_in_seconds =
root->FindInt(kPayloadUserSessionRebootDelayField);
if (!delay_in_seconds || delay_in_seconds.value() < 0) {
return std::nullopt;
}
return base::Seconds(delay_in_seconds.value());
}
base::TimeTicks GetBootTime() {
return base::TimeTicks::Now() - base::SysInfo::Uptime();
}
} // namespace
DeviceCommandRebootJob::DeviceCommandRebootJob()
: DeviceCommandRebootJob(chromeos::PowerManagerClient::Get(),
ash::LoginState::Get(),
ash::SessionTerminationManager::Get(),
RebootNotificationsScheduler::Get(),
base::DefaultClock::GetInstance(),
base::DefaultTickClock::GetInstance(),
base::BindRepeating(GetBootTime)) {}
DeviceCommandRebootJob::DeviceCommandRebootJob(
chromeos::PowerManagerClient* power_manager_client,
ash::LoginState* loging_state,
ash::SessionTerminationManager* session_termination_manager,
RebootNotificationsScheduler* in_session_notifications_scheduler,
const base::Clock* clock,
const base::TickClock* tick_clock,
GetBootTimeCallback get_boot_time_callback)
: power_manager_client_(power_manager_client),
login_state_(loging_state),
session_termination_manager_(session_termination_manager),
in_session_notifications_scheduler_(in_session_notifications_scheduler),
in_session_reboot_timer_(clock, tick_clock),
clock_(clock),
get_boot_time_callback_(std::move(get_boot_time_callback)),
user_session_delay_(kDefaultUserSessionRebootDelay) {
DCHECK(get_boot_time_callback_);
}
DeviceCommandRebootJob::~DeviceCommandRebootJob() = default;
enterprise_management::RemoteCommand_Type DeviceCommandRebootJob::GetType()
const {
return enterprise_management::RemoteCommand_Type_DEVICE_REBOOT;
}
bool DeviceCommandRebootJob::ParseCommandPayload(
const std::string& command_payload) {
const std::optional<base::TimeDelta> commandline_delay =
ExtractUserSessionDelayFromCommandLine();
// Ignore payload if delay is set in command line.
if (commandline_delay) {
user_session_delay_ = commandline_delay.value();
return true;
}
const std::optional<base::TimeDelta> payload_delay =
ExtractUserSessionDelayFromPayload(command_payload);
if (payload_delay) {
user_session_delay_ = payload_delay.value();
return true;
}
// Don't fail even if delay is not supplied. Use default one.
return true;
}
void DeviceCommandRebootJob::RunImpl(CallbackWithResult result_callback) {
result_callback_ = std::move(result_callback);
// Determines the time delta between the command having been issued and the
// boot time of the system.
const base::TimeDelta delta = get_boot_time_callback_.Run() - issued_time();
// If the reboot command was issued before the system booted, we inform the
// server that the reboot succeeded. Otherwise, the reboot must still be
// performed and we invoke it.
if (delta.is_positive()) {
LOG(WARNING) << "Ignoring reboot command issued " << delta
<< " before current boot time";
return RunAsyncCallback(std::move(result_callback_), ResultType::kSuccess,
FROM_HERE);
}
if (!power_manager_client_) {
LOG(ERROR) << "Power manager is not initialized. Cannot reboot.";
return RunAsyncCallback(std::move(result_callback_), ResultType::kFailure,
FROM_HERE);
}
// Make sure `power_manager_client_` is available before requesting reboot.
// Continue from `PowerManagerBecameAvailable`. If availability state is
// known, call is immediate and synchronous.
power_manager_availability_observation_.Observe(power_manager_client_);
}
void DeviceCommandRebootJob::PowerManagerBecameAvailable(bool available) {
power_manager_availability_observation_.Reset();
if (!available) {
LOG(ERROR) << "Power manager is not available. Cannot reboot.";
return RunAsyncCallback(std::move(result_callback_), ResultType::kFailure,
FROM_HERE);
}
// The device is able to reboot immediately if it has no ongoing user session:
// if it runs in kiosk mode or is on login screen.
if (login_state_->IsKioskSession()) {
return DoReboot(kKioskRebootDescription);
}
if (!login_state_->IsUserLoggedIn()) {
return DoReboot(kLoginScreenRebootDescription);
}
RebootUserSession();
}
void DeviceCommandRebootJob::RebootUserSession() {
if (user_session_delay_.is_zero()) {
// Do not show the reboot notification if no delay.
return OnRebootTimeoutExpired();
}
const auto reboot_time = clock_->Now() + user_session_delay_;
in_session_notifications_scheduler_->SchedulePendingRebootNotifications(
base::BindOnce(&DeviceCommandRebootJob::OnRebootButtonClicked,
weak_factory_.GetWeakPtr()),
reboot_time, RebootNotificationsScheduler::Requester::kRebootCommand);
in_session_reboot_timer_.Start(
FROM_HERE, reboot_time,
base::BindOnce(&DeviceCommandRebootJob::OnRebootTimeoutExpired,
weak_factory_.GetWeakPtr()));
// TODO(b/265784089): Make reboot on user logout robust. If the browser
// crashes, all the reboot information is gone while it should be preserved.
session_termination_manager_->SetDeviceRebootOnSignoutForRemoteCommand(
base::BindOnce(&DeviceCommandRebootJob::OnSignout,
weak_factory_.GetWeakPtr()));
}
void DeviceCommandRebootJob::OnSignout() {
// `session_termination_manager_` will initiate the reboot, just report the
// command finished.
RunAsyncCallback(std::move(result_callback_), ResultType::kSuccess,
FROM_HERE);
}
void DeviceCommandRebootJob::OnRebootButtonClicked() {
ResetTriggeringEvents();
DoReboot(kUserSessionRebootDescription);
}
void DeviceCommandRebootJob::OnRebootTimeoutExpired() {
ResetTriggeringEvents();
in_session_notifications_scheduler_->SchedulePostRebootNotification();
DoReboot(kUserSessionRebootDescription);
}
void DeviceCommandRebootJob::ResetTriggeringEvents() {
in_session_notifications_scheduler_->CancelRebootNotifications(
RebootNotificationsScheduler::Requester::kRebootCommand);
in_session_reboot_timer_.Stop();
}
void DeviceCommandRebootJob::DoReboot(const std::string& reason) {
DCHECK(result_callback_);
// Posting the task with a callback just before reboot request does not
// guarantee the callback reaching `RemoteCommandsService` and is very
// unlikely to be reported to DMServer. So the callback is mostly used for
// testing purposes.
// The implementation relies on `RemoteCommandsQueue` running one command at
// the time: two reboot commands cannot exist simultaneously and will not
// compete for the notification. The
// callback is called at the very end of execution in order to not to have two
// commands executed simultaneously .
// TODO(b/252980103): Come up with a mechanism to deliver the execution result
// to DMServer.
RunAsyncCallback(std::move(result_callback_), ResultType::kSuccess,
FROM_HERE);
power_manager_client_->RequestRestart(
power_manager::REQUEST_RESTART_REMOTE_ACTION_REBOOT, reason);
}
// static
void DeviceCommandRebootJob::RunAsyncCallback(CallbackWithResult callback,
ResultType result,
base::Location from_where) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
from_where, base::BindOnce(std::move(callback), result, std::nullopt));
}
} // namespace policy
|