| // Copyright (c) 2014 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. |
| |
| // This class is most definitely NOT re-entrant. |
| |
| #include "login_manager/browser_job.h" |
| |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| #include <algorithm> |
| #include <queue> |
| #include <utility> |
| |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_util.h> |
| #include <chromeos/switches/chrome_switches.h> |
| |
| #include "login_manager/file_checker.h" |
| #include "login_manager/login_metrics.h" |
| #include "login_manager/subprocess.h" |
| #include "login_manager/system_utils.h" |
| |
| namespace login_manager { |
| |
| const char BrowserJobInterface::kLoginManagerFlag[] = "--login-manager"; |
| const char BrowserJobInterface::kLoginUserFlag[] = "--login-user="; |
| const char BrowserJobInterface::kLoginProfileFlag[] = "--login-profile="; |
| |
| const char BrowserJob::kFirstExecAfterBootFlag[] = "--first-exec-after-boot"; |
| |
| const int BrowserJob::kUseExtraArgsRuns = 3; |
| static_assert(BrowserJob::kUseExtraArgsRuns > 1, |
| "kUseExtraArgsRuns should be greater than 1 because extra " |
| "arguments could need one restart to apply them."); |
| |
| const int BrowserJob::kRestartTries = BrowserJob::kUseExtraArgsRuns + 2; |
| const time_t BrowserJob::kRestartWindowSeconds = 60; |
| |
| const char BrowserJobInterface::kGuestSessionFlag[] = "--bwsi"; |
| |
| namespace { |
| |
| constexpr char kVmoduleFlag[] = "--vmodule="; |
| constexpr char kEnableFeaturesFlag[] = "--enable-features="; |
| constexpr char kDisableFeaturesFlag[] = "--disable-features="; |
| constexpr char kEnableBlinkFeaturesFlag[] = "--enable-blink-features="; |
| constexpr char kSafeModeFlag[] = "--safe-mode"; |
| |
| // Erases all occurrences of |arg| within |args|. Returns true if any entries |
| // were removed or false otherwise. |
| bool RemoveArgs(std::vector<std::string>* args, const std::string& arg) { |
| std::vector<std::string>::iterator new_end = |
| std::remove(args->begin(), args->end(), arg); |
| if (new_end == args->end()) |
| return false; |
| |
| args->erase(new_end, args->end()); |
| return true; |
| } |
| |
| // Joins the values of all switches in |args| prefixed by |prefix| using |
| // |separator| and appends a merged version of the switch. If |keep_existing| is |
| // true, all earlier occurrences of the switch are preserved; otherwise, they |
| // are removed. |
| void MergeSwitches(std::vector<std::string>* args, |
| const std::string& prefix, |
| const std::string& separator, |
| bool keep_existing) { |
| std::string values; |
| auto head = args->begin(); |
| for (const auto arg : *args) { |
| bool match = base::StartsWith(arg, prefix, base::CompareCase::SENSITIVE); |
| if (match) { |
| if (!values.empty()) |
| values += separator; |
| values += arg.substr(prefix.size()); |
| } |
| if (!match || keep_existing) { |
| *head++ = arg; |
| } |
| } |
| if (head != args->end()) |
| args->erase(head, args->end()); |
| if (!values.empty()) |
| args->push_back(prefix + values); |
| } |
| |
| } // namespace |
| |
| BrowserJob::BrowserJob(const std::vector<std::string>& arguments, |
| const std::vector<std::string>& environment_variables, |
| FileChecker* checker, |
| LoginMetrics* metrics, |
| SystemUtils* utils, |
| const BrowserJob::Config& cfg, |
| std::unique_ptr<SubprocessInterface> subprocess) |
| : arguments_(arguments), |
| environment_variables_(environment_variables), |
| file_checker_(checker), |
| login_metrics_(metrics), |
| system_(utils), |
| start_times_(std::deque<time_t>(kRestartTries, 0)), |
| removed_login_manager_flag_(false), |
| session_already_started_(false), |
| config_(cfg), |
| subprocess_(std::move(subprocess)) { |
| // Take over managing kLoginManagerFlag. |
| if (RemoveArgs(&arguments_, kLoginManagerFlag)) { |
| removed_login_manager_flag_ = true; |
| login_arguments_.push_back(kLoginManagerFlag); |
| } |
| } |
| |
| BrowserJob::~BrowserJob() {} |
| |
| pid_t BrowserJob::CurrentPid() const { |
| return subprocess_->GetPid(); |
| } |
| |
| bool BrowserJob::IsGuestSession() { |
| return base::STLCount(arguments_, kGuestSessionFlag) > 0; |
| } |
| |
| bool BrowserJob::ShouldRunBrowser() { |
| return !file_checker_ || !file_checker_->exists(); |
| } |
| |
| bool BrowserJob::ShouldStop() const { |
| return (system_->time(nullptr) - start_times_.front() < |
| kRestartWindowSeconds); |
| } |
| |
| void BrowserJob::RecordTime() { |
| start_times_.push_back(system_->time(nullptr)); |
| start_times_.pop_front(); |
| DCHECK_EQ(kRestartTries, start_times_.size()); |
| } |
| |
| bool BrowserJob::RunInBackground() { |
| CHECK(login_metrics_); |
| bool first_boot = !login_metrics_->HasRecordedChromeExec(); |
| login_metrics_->RecordStats("chrome-exec"); |
| |
| extra_one_time_arguments_.clear(); |
| if (first_boot) |
| extra_one_time_arguments_.push_back(kFirstExecAfterBootFlag); |
| |
| const std::vector<std::string> argv(ExportArgv()); |
| const std::vector<std::string> env_vars(ExportEnvironmentVariables()); |
| LOG(INFO) << "Running browser " << base::JoinString(argv, " "); |
| RecordTime(); |
| if (config_.new_mount_namespace_for_guest && IsGuestSession()) { |
| LOG(INFO) << "Entering new mount namespace for browser."; |
| subprocess_->UseNewMountNamespace(); |
| } |
| return subprocess_->ForkAndExec(argv, env_vars); |
| } |
| |
| void BrowserJob::KillEverything(int signal, const std::string& message) { |
| if (subprocess_->GetPid() < 0) |
| return; |
| |
| LOG(INFO) << "Terminating process group for browser " << subprocess_->GetPid() |
| << " with signal " << signal << ": " << message; |
| subprocess_->KillEverything(signal); |
| } |
| |
| void BrowserJob::Kill(int signal, const std::string& message) { |
| const pid_t pid = subprocess_->GetPid(); |
| if (pid < 0) |
| return; |
| |
| LOG(INFO) << "Terminating browser process " << pid << " with signal " |
| << signal << ": " << message; |
| subprocess_->Kill(signal); |
| } |
| |
| void BrowserJob::WaitAndAbort(base::TimeDelta timeout) { |
| const pid_t pid = subprocess_->GetPid(); |
| if (pid < 0) |
| return; |
| |
| DLOG(INFO) << "Waiting up to " << timeout.InSeconds() << " seconds for " |
| << pid << "'s process group to exit"; |
| if (!system_->ProcessGroupIsGone(pid, timeout)) { |
| LOG(WARNING) << "Aborting browser process " << pid << "'s process group " |
| << timeout.InSeconds() << " seconds after sending signal"; |
| std::string message = base::StringPrintf("Browser took more than %" PRId64 |
| " seconds to exit after signal.", |
| timeout.InSeconds()); |
| KillEverything(SIGABRT, message); |
| } else { |
| DLOG(INFO) << "Cleaned up browser process " << pid; |
| } |
| } |
| |
| // When user logs in we want to restart chrome in browsing mode with |
| // user signed in. Hence we remove --login-manager flag and add |
| // --login-user=|account_id| and --login-profile=|userhash| flags. |
| void BrowserJob::StartSession(const std::string& account_id, |
| const std::string& userhash) { |
| if (!session_already_started_) { |
| login_arguments_.clear(); |
| login_arguments_.push_back(kLoginUserFlag + account_id); |
| login_arguments_.push_back(kLoginProfileFlag + userhash); |
| } |
| session_already_started_ = true; |
| } |
| |
| void BrowserJob::StopSession() { |
| login_arguments_.clear(); |
| if (removed_login_manager_flag_) { |
| login_arguments_.push_back(kLoginManagerFlag); |
| removed_login_manager_flag_ = false; |
| } |
| } |
| |
| const std::string BrowserJob::GetName() const { |
| base::FilePath exec_file(arguments_[0]); |
| return exec_file.BaseName().value(); |
| } |
| |
| void BrowserJob::SetArguments(const std::vector<std::string>& arguments) { |
| // Ensure we preserve the program name to be executed, if we have one. |
| std::string argv0; |
| if (!arguments_.empty()) |
| argv0 = arguments_[0]; |
| |
| arguments_ = arguments; |
| |
| if (!argv0.empty()) { |
| if (arguments_.size()) |
| arguments_[0] = argv0; |
| else |
| arguments_.push_back(argv0); |
| } |
| } |
| |
| void BrowserJob::SetExtraArguments(const std::vector<std::string>& arguments) { |
| extra_arguments_ = arguments; |
| } |
| |
| void BrowserJob::SetExtraEnvironmentVariables( |
| const std::vector<std::string>& env_vars) { |
| extra_environment_variables_ = env_vars; |
| } |
| |
| void BrowserJob::ClearPid() { |
| subprocess_->ClearPid(); |
| } |
| |
| std::vector<std::string> BrowserJob::ExportArgv() const { |
| std::vector<std::string> to_return(arguments_.begin(), arguments_.end()); |
| to_return.insert(to_return.end(), login_arguments_.begin(), |
| login_arguments_.end()); |
| |
| if (ShouldDropExtraArgumentsAndEnvironmentVariables()) { |
| LOG(WARNING) << "Dropping extra arguments and setting safe-mode switch due " |
| "to crashy browser."; |
| to_return.emplace_back(kSafeModeFlag); |
| } else { |
| to_return.insert(to_return.end(), extra_arguments_.begin(), |
| extra_arguments_.end()); |
| } |
| |
| if (!extra_one_time_arguments_.empty()) { |
| to_return.insert(to_return.end(), extra_one_time_arguments_.begin(), |
| extra_one_time_arguments_.end()); |
| } |
| |
| // Chrome doesn't support repeated switches. Merge switches containing |
| // comma-separated values that may be supplied via multiple sources (e.g. |
| // chrome_setup.cc, chrome://flags, Telemetry). |
| // |
| // --enable-features and --disable-features may be placed within sentinel |
| // values (--flag-switches-begin/end, --policy-switches-begin/end). To |
| // preserve those positions, keep the existing flags while also appending |
| // merged versions at the end of the command line. Chrome will use the final, |
| // merged flags: https://crbug.com/767266 |
| MergeSwitches(&to_return, kVmoduleFlag, ",", false /* keep_existing */); |
| MergeSwitches(&to_return, kEnableFeaturesFlag, ",", true /* keep_existing */); |
| MergeSwitches(&to_return, kDisableFeaturesFlag, ",", |
| true /* keep_existing */); |
| MergeSwitches(&to_return, kEnableBlinkFeaturesFlag, ",", |
| true /* keep_existing */); |
| |
| return to_return; |
| } |
| |
| std::vector<std::string> BrowserJob::ExportEnvironmentVariables() const { |
| std::vector<std::string> vars = environment_variables_; |
| if (!ShouldDropExtraArgumentsAndEnvironmentVariables()) { |
| vars.insert(vars.end(), extra_environment_variables_.begin(), |
| extra_environment_variables_.end()); |
| } |
| return vars; |
| } |
| |
| bool BrowserJob::ShouldDropExtraArgumentsAndEnvironmentVariables() const { |
| // Check start_time_with_extra_args != 0 so that test cases such as |
| // SetExtraArguments and ExportArgv pass without mocking time(). |
| const time_t start_time_with_extra_args = |
| start_times_[kRestartTries - kUseExtraArgsRuns]; |
| return (start_time_with_extra_args != 0 && |
| system_->time(nullptr) - start_time_with_extra_args < |
| kRestartWindowSeconds); |
| } |
| |
| } // namespace login_manager |