chromium/chrome/browser/ash/crosapi/browser_launcher.h

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_CROSAPI_BROWSER_LAUNCHER_H_
#define CHROME_BROWSER_ASH_CROSAPI_BROWSER_LAUNCHER_H_

#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/crosapi/crosapi_id.h"
#include "chromeos/ash/components/standalone_browser/lacros_selection.h"
#include "components/nacl/common/buildflags.h"
#include "components/policy/core/common/values_util.h"
#include "mojo/public/cpp/platform/platform_channel.h"

namespace base {
struct LaunchOptions;
}  // namespace base

namespace user_manager {
class DeviceOwnershipWaiter;
}  // namespace user_manager

namespace crosapi {
class PrimaryProfileCreationWaiter;

// Manages launching and terminating Lacros process.
// TODO(crbug.com/40286595): Extract launching logic from BrowserManager to
// BrowserLauncher.
class BrowserLauncher {
 public:
  BrowserLauncher();

  BrowserLauncher(const BrowserLauncher&) = delete;
  BrowserLauncher& operator=(const BrowserLauncher&) = delete;

  ~BrowserLauncher();

  // Returns files to preload on launching at login screen.
  static std::vector<base::FilePath> GetPreloadFiles(
      const base::FilePath& lacros_dir);

  // Parameters used to launch Lacros that are calculated on a background
  // sequence.
  struct LaunchParamsFromBackground {
   public:
    LaunchParamsFromBackground();
    LaunchParamsFromBackground(LaunchParamsFromBackground&&);
    LaunchParamsFromBackground& operator=(LaunchParamsFromBackground&&);
    LaunchParamsFromBackground(const LaunchParamsFromBackground&) = delete;
    LaunchParamsFromBackground& operator=(const LaunchParamsFromBackground&) =
        delete;
    ~LaunchParamsFromBackground();

    // An fd for a log file.
    base::ScopedFD logfd;

    // Sets true if Lacros uses resource file sharing.
    bool enable_resource_file_sharing = false;

    // Any additional args to start lacros with.
    std::vector<std::string> lacros_additional_args;
  };

  // Parameters to handle command line and options used to launching Lacros.
  struct LaunchParams {
   public:
    LaunchParams(base::CommandLine command_line, base::LaunchOptions options);
    LaunchParams(LaunchParams&&);
    LaunchParams& operator=(LaunchParams&&);
    LaunchParams(const LaunchParams&) = delete;
    LaunchParams& operator=(const LaunchParams&) = delete;
    ~LaunchParams();

    base::CommandLine command_line;

    base::LaunchOptions options;
  };

  // Results from `LaunchProcess` and needs to be passed to
  // BrowserManager.
  struct LaunchResults {
   public:
    LaunchResults();
    LaunchResults(LaunchResults&&);
    LaunchResults& operator=(LaunchResults&&);
    LaunchResults(const LaunchResults&) = delete;
    LaunchResults& operator=(const LaunchResults&) = delete;
    ~LaunchResults();

    // ID for the current Crosapi connection.
    // Available only when lacros-chrome is running.
    CrosapiId crosapi_id;

    // Time when the lacros process was launched.
    base::TimeTicks lacros_launch_time;
  };

  // Reason of Lacros not being able to launch.
  enum class LaunchFailureReason {
    // Failed to launch due to unknown error.
    kUnknown,

    // Shutdown is requested from BrowserManager during the process launch.
    kShutdownRequested,
  };

  using LaunchCompletionCallback = base::OnceCallback<void(
      base::expected<LaunchResults, LaunchFailureReason>)>;

  // Launches a process of the given options, which are expected to be Lacros's
  // ones.
  // `Launch` will:
  // 1. Prepare launching on background thread to handle blocking resources.
  // 2. Wait for device owner and primary user if it's launching in the user
  // session.
  // 3. Launch lacros process with the params prepared at Step 1.
  //
  // Following is explanation for Arguments.
  // `chrome_path`: Initializes `command_line`.
  // `launching_at_login_screen`: Whether lacros is launching at login screen.
  // `postlogin_pipe_fd`: Pipe FDs through which Ash and Lacros exchange
  // post-login parameters.
  // `lacros_selection`: Whether "rootfs" or "stateful" lacros is selected.
  // `mojo_disconnection_cb`: Callback function setting up mojo connection.
  // `BrowserManager::OnMojoDisconnected` is called.
  // `is_keep_alive_enabled`: Whether `keep_alive_features` is empty.
  // `callback`: Callback function that will be called on launch process
  // completion.
  void Launch(const base::FilePath& chrome_path,
              bool launching_at_login_screen,
              ash::standalone_browser::LacrosSelection lacros_selection,
              base::OnceClosure mojo_disconnection_cb,
              bool is_keep_alive_enabled,
              LaunchCompletionCallback callback);

  // Writes post login data to the Lacros process, resets `postlogin_pipe_fd`
  // and then executes a callback.
  void ResumeLaunch(
      base::OnceCallback<
          void(base::expected<base::TimeTicks, LaunchFailureReason>)> callback);

  // Returns true if process is valid.
  bool IsProcessValid() const;

  // Triggers termination synchronously if process is running.
  // Does not block the thread because it does not wait for the process
  // termination.
  bool TriggerTerminate(int exit_code) const;

  // Waits for termination of the running process asynchronously during the
  // period given by the `timeout`, then invoke `callback`. On timeout, also
  // tries to terminate the process by sending a signal.
  // TODO(mayukoaiba): On calling this function, even before the termination
  // procedure is completed (i.e. before `callback` is called), IsProcessValid
  // will return false and LaunchProcess tries to create the next process, which
  // may be confusing for callers. We should fix this issue.
  void EnsureProcessTerminated(base::OnceClosure callback,
                               base::TimeDelta timeout);

  // Records Shutdown() request from BrowserManager.
  void Shutdown() { shutdown_requested_ = true; }

  // Returns reference to `process_` for testing.
  const base::Process& GetProcessForTesting() const;

  // Provides public API to call LaunchProcessWithParameters for testing.
  bool LaunchProcessForTesting(const LaunchParams& parameters);

  // Provides public API to call CreateLaunchParamsForTesting for testing.
  LaunchParams CreateLaunchParamsForTesting(
      const base::FilePath& chrome_path,
      const LaunchParamsFromBackground& params,
      bool launching_at_login_screen,
      std::optional<int> startup_fd,
      std::optional<int> read_pipe_fd,
      mojo::PlatformChannel& channel,
      ash::standalone_browser::LacrosSelection lacros_selection);

  // Creates postlogin pipe fd and returns the read fd. This is used to test
  // ResumeLaunch. Note that the reader is on the same process and does not
  // launch testing process.
  base::ScopedFD CreatePostLoginPipeForTesting();

  // Sets up additional flags for unit tests.
  // This function overwrites `command_line` with the desired flags.
  void SetUpAdditionalParametersForTesting(LaunchParamsFromBackground& params,
                                           LaunchParams& parameters) const;

  // Provides public API to call WaitForBackgroundWorkPreLaunch for testing.
  void WaitForBackgroundWorkPreLaunchForTesting(
      const base::FilePath& lacros_dir,
      bool clear_shared_resource_file,
      bool launching_at_login_screen,
      base::OnceClosure callback,
      LaunchParamsFromBackground& params);

  // TODO(crbug.com/40275396): Remove this once we refactored to use the
  // constructor.
  void set_device_ownership_waiter_for_testing(
      std::unique_ptr<user_manager::DeviceOwnershipWaiter>
          device_ownership_waiter);

  // Skips device ownership fetch. Use set_device_ownership_waiter_for_testing()
  // above if possible. Use this method only if your test must set up the
  // behavior before BrowserManager is initialized.
  // TODO(crbug.com/40275396): Remove this and set it from constructor.
  static void SkipDeviceOwnershipWaitForTesting(bool skip);

 private:
  // Waits for the prelaunch work running on background thread. `callback` is
  // called on background work completion and the output result is stored in
  // `params`.
  void WaitForBackgroundWorkPreLaunch(const base::FilePath& lacros_dir,
                                      bool clear_shared_resource_file,
                                      bool launching_at_login_screen,
                                      base::OnceClosure callback,
                                      LaunchParamsFromBackground& params);

  // Waits for the device owner being fetched from `UserManager` or the primary
  // user profile being fully created and then executes a callback. Should NOT
  // be called if Lacros is launching at the login screen since the device owner
  // nor the profile is not available until login.
  void WaitForDeviceOwnerFetchedAndThen(base::OnceClosure callback);
  void WaitForPrimaryProfileAddedAndThen(base::OnceClosure callback);

  // Launches lacros-chrome process after device owner and primary profile
  // become ready.
  void LaunchProcess(const base::FilePath& chrome_path,
                     std::unique_ptr<LaunchParamsFromBackground> params,
                     bool launching_at_login_screen,
                     ash::standalone_browser::LacrosSelection lacros_selection,
                     base::OnceClosure mojo_disconnection_cb,
                     bool is_keep_alive_enabled,
                     LaunchCompletionCallback callback);

  LaunchParams CreateLaunchParams(
      const base::FilePath& chrome_path,
      const LaunchParamsFromBackground& params,
      bool launching_at_login_screen,
      std::optional<int> startup_fd,
      std::optional<int> read_pipe_fd,
      mojo::PlatformChannel& channel,
      ash::standalone_browser::LacrosSelection lacros_selection);

  // Launches a process , which is executed in `LaunchProcess`.
  // This is also used for unittest.
  bool LaunchProcessWithParameters(const LaunchParams& parameters);

  // Writes post login data after waiting for device owner and profile to be
  // ready.
  void WritePostLoginData(
      base::OnceCallback<
          void(base::expected<base::TimeTicks, LaunchFailureReason>)> callback);

  // Process handle for the lacros_chrome process.
  base::Process process_;

  // Pipe FDs through which Ash and Lacros exchange post-login parameters.
  base::ScopedFD postlogin_pipe_fd_;

  // Used to delay an action until the definitive device owner is fetched.
  std::unique_ptr<user_manager::DeviceOwnershipWaiter> device_ownership_waiter_;

  // Used to wait for the primary user profile to be fully created.
  std::unique_ptr<PrimaryProfileCreationWaiter>
      primary_profile_creation_waiter_;

  // Tracks whether Shutdown() has been signalled by ash. This flag ensures any
  // new or existing lacros startup tasks are not executed during shutdown.
  bool shutdown_requested_ = false;

  // True if this is the first time that lacros is being launched from this ash
  // process. This value is used for resource sharing feature where ash deletes
  // cached shared resource file after ash is rebooted. Note that this flag
  // should not be reset on reloading as long as the ash process is not
  // relaunched.
  bool is_first_lacros_launch_ = true;

  // Indicates whether the delegate has been used.
  bool device_ownership_waiter_called_ = false;

  base::WeakPtrFactory<BrowserLauncher> weak_factory_{this};
};

}  // namespace crosapi

#endif  // CHROME_BROWSER_ASH_CROSAPI_BROWSER_LAUNCHER_H_