chromium/chromeos/ash/components/dbus/upstart/upstart_client.cc

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

#include "chromeos/ash/components/dbus/upstart/upstart_client.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"

namespace ash {

namespace {

constexpr char kUpstartServiceName[] = "com.ubuntu.Upstart";
constexpr char kUpstartJobInterface[] = "com.ubuntu.Upstart0_6.Job";
constexpr char kStartMethod[] = "Start";
constexpr char kRestartMethod[] = "Restart";
constexpr char kStopMethod[] = "Stop";

constexpr char kUpstartJobsPath[] = "/com/ubuntu/Upstart/jobs/";
constexpr char kMediaAnalyticsJob[] = "rtanalytics";

UpstartClient* g_instance = nullptr;

class UpstartClientImpl : public UpstartClient {
 public:
  explicit UpstartClientImpl(dbus::Bus* bus) : bus_(bus) {}

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

  ~UpstartClientImpl() override = default;

  // UpstartClient overrides:
  void StartJob(const std::string& job,
                const std::vector<std::string>& upstart_env,
                chromeos::VoidDBusMethodCallback callback) override {
    CallJobMethod(job, kStartMethod, upstart_env,
                  base::BindOnce(&UpstartClientImpl::OnVoidMethod,
                                 weak_ptr_factory_.GetWeakPtr(), job, "start",
                                 std::move(callback)));
  }

  void StartJobWithErrorDetails(
      const std::string& job,
      const std::vector<std::string>& upstart_env,
      StartJobWithErrorDetailsCallback callback) override {
    CallJobMethod(
        job, kStartMethod, upstart_env,
        base::BindOnce(&UpstartClientImpl::OnStartJobWithErrorDetails,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  void StopJob(const std::string& job,
               const std::vector<std::string>& upstart_env,
               chromeos::VoidDBusMethodCallback callback) override {
    CallJobMethod(job, kStopMethod, upstart_env,
                  base::BindOnce(&UpstartClientImpl::OnVoidMethod,
                                 weak_ptr_factory_.GetWeakPtr(), job, "stop",
                                 std::move(callback)));
  }

  void StartMediaAnalytics(const std::vector<std::string>& upstart_env,
                           chromeos::VoidDBusMethodCallback callback) override {
    StartJob(kMediaAnalyticsJob, upstart_env, std::move(callback));
  }

  void RestartMediaAnalytics(
      chromeos::VoidDBusMethodCallback callback) override {
    CallJobMethod(
        kMediaAnalyticsJob, kRestartMethod, {},
        base::BindOnce(&UpstartClientImpl::OnVoidMethod,
                       weak_ptr_factory_.GetWeakPtr(), kMediaAnalyticsJob,
                       "restart", std::move(callback)));
  }

  using UpstartClient::StopJob;

  void StopMediaAnalytics() override {
    StopJob(kMediaAnalyticsJob, {}, base::DoNothing());
  }

  void StopMediaAnalytics(chromeos::VoidDBusMethodCallback callback) override {
    StopJob(kMediaAnalyticsJob, {}, std::move(callback));
  }

 private:
  void CallJobMethod(const std::string& job,
                     const std::string& method,
                     const std::vector<std::string>& upstart_env,
                     dbus::ObjectProxy::ResponseOrErrorCallback callback) {
    dbus::ObjectProxy* job_proxy = bus_->GetObjectProxy(
        kUpstartServiceName, dbus::ObjectPath(kUpstartJobsPath + job));
    dbus::MethodCall method_call(kUpstartJobInterface, method);
    dbus::MessageWriter writer(&method_call);
    writer.AppendArrayOfStrings(upstart_env);
    writer.AppendBool(true /* wait for response */);
    job_proxy->CallMethodWithErrorResponse(
        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
        std::move(callback));
  }

  void OnVoidMethod(const std::string& job_for_logging,
                    const std::string& action_for_logging,
                    chromeos::VoidDBusMethodCallback callback,
                    dbus::Response* response,
                    dbus::ErrorResponse* error_response) {
    if (!response)
      LogError(job_for_logging, action_for_logging, error_response);
    std::move(callback).Run(response);
  }

  void OnStartJobWithErrorDetails(StartJobWithErrorDetailsCallback callback,
                                  dbus::Response* response,
                                  dbus::ErrorResponse* error_response) {
    std::optional<std::string> error_name;
    std::optional<std::string> error_message;
    if (!response && error_response) {
      // Error response may contain the error message as string.
      error_name = error_response->GetErrorName();
      dbus::MessageReader reader(error_response);
      std::string message;
      if (reader.PopString(&message))
        error_message = std::move(message);
    }
    std::move(callback).Run(response, std::move(error_name),
                            std::move(error_message));
  }

  void LogError(const std::string& job_for_logging,
                const std::string& action_for_logging,
                dbus::ErrorResponse* error_response) {
    std::string error_name;
    std::string error_message;
    if (error_response) {
      // Error response may contain the error message as string.
      error_name = error_response->GetErrorName();
      dbus::MessageReader reader(error_response);
      reader.PopString(&error_message);
    } else {
      // Method call failed without returning an error response. D-Bus itself is
      // not working for whatever reason.
      error_name = "unknown error";
    }
    LOG(ERROR) << "Failed to " << action_for_logging << " " << job_for_logging
               << ": " << error_name << ": " << error_message;
  }

  raw_ptr<dbus::Bus> bus_ = nullptr;

  // Note: This should remain the last member so it'll be destroyed and
  // invalidate its weak pointers before any other members are destroyed.
  base::WeakPtrFactory<UpstartClientImpl> weak_ptr_factory_{this};
};

}  // namespace

// static
const char UpstartClient::kAlreadyStartedError[] =
    "com.ubuntu.Upstart0_6.Error.AlreadyStarted";

UpstartClient::UpstartClient() {
  DCHECK(!g_instance);
  g_instance = this;
}

UpstartClient::~UpstartClient() {
  DCHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

// static
void UpstartClient::Initialize(dbus::Bus* bus) {
  DCHECK(bus);
  new UpstartClientImpl(bus);
}

// static
void UpstartClient::InitializeFake() {
  // Do not create a new fake if it was initialized early in a browser test (to
  // allow test properties to be set).
  if (!FakeUpstartClient::Get())
    new FakeUpstartClient();
}

// static
void UpstartClient::Shutdown() {
  DCHECK(g_instance);
  delete g_instance;
}

// static
UpstartClient* UpstartClient::Get() {
  return g_instance;
}

}  // namespace ash