apmanager: abstraction for AP service.

Add an abstraction for AP service, which creates/manages hostapd process.
Update org.chromium.apmanager.Manager.CreateService method to be asynchronous,
where the service dbus path is return when the respective Service and Config
object completes the DBus interface registration.

While there, correctly register DBus object interfaces for each dbus object.

BUG=chromium:431760
TEST=unittests
CQ-DEPEND=CL:229600,CL:229430

Change-Id: I5bfb775d2cedb4d636b4f6963795c0d9180cc840
Reviewed-on: https://chromium-review.googlesource.com/229463
Reviewed-by: Paul Stewart <pstew@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Tested-by: Peter Qiu <zqiu@chromium.org>
Commit-Queue: Peter Qiu <zqiu@chromium.org>
diff --git a/service.cc b/service.cc
new file mode 100644
index 0000000..dcb5176
--- /dev/null
+++ b/service.cc
@@ -0,0 +1,134 @@
+// Copyright 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.
+
+#include "apmanager/service.h"
+
+#include <signal.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/errors/error.h>
+
+#include "apmanager/config.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+using std::string;
+
+namespace apmanager {
+
+// static.
+const char Service::kHostapdPath[] = "/sbin/hostapd";
+const char Service::kHostapdConfigPathFormat[] = "/tmp/hostapd-%d";
+const int Service::kTerminationTimeoutSeconds = 2;
+
+Service::Service(int service_identifier)
+    : org::chromium::apmanager::ServiceAdaptor(this),
+      service_identifier_(service_identifier),
+      service_path_(base::StringPrintf("%s/services/%d",
+                                       kManagerPath,
+                                       service_identifier)),
+      dbus_path_(dbus::ObjectPath(service_path_)),
+      config_(new Config(service_path_)) {
+  SetConfig(config_->dbus_path());
+}
+
+Service::~Service() {
+  // Stop hostapd process if still running.
+  if (IsHostapdRunning()) {
+    StopHostapdProcess();
+  }
+}
+
+void Service::RegisterAsync(ExportedObjectManager* object_manager,
+                            AsyncEventSequencer* sequencer) {
+  CHECK(!dbus_object_) << "Already registered";
+  dbus_object_.reset(
+      new chromeos::dbus_utils::DBusObject(
+          object_manager,
+          object_manager ? object_manager->GetBus() : nullptr,
+          dbus_path_));
+  RegisterWithDBusObject(dbus_object_.get());
+  dbus_object_->RegisterAsync(
+      sequencer->GetHandler("Service.RegisterAsync() failed.", true));
+
+  // Register Config DBus object.
+  config_->RegisterAsync(object_manager, sequencer);
+}
+
+bool Service::Start(chromeos::ErrorPtr* error) {
+  if (IsHostapdRunning()) {
+    chromeos::Error::AddTo(
+        error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+        "Service already running");
+    return false;
+  }
+
+  // Generate hostapd configuration content.
+  string config_str;
+  if (!config_->GenerateConfigFile(error, &config_str)) {
+    chromeos::Error::AddTo(
+        error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+        "Failed to generate config file");
+    return false;
+  }
+
+  // Write configuration to a file.
+  base::FilePath file_path(base::StringPrintf(kHostapdConfigPathFormat,
+                                              service_identifier_));
+  if (base::WriteFile(file_path, config_str.c_str(), config_str.size()) == -1) {
+    chromeos::Error::AddTo(
+        error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+        "Failed to write configuration to a file");
+    return false;
+  }
+
+  // Start hostapd process.
+  if (!StartHostapdProcess(file_path.value())) {
+    chromeos::Error::AddTo(
+        error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+        "Failed to start hostapd");
+    return false;
+  }
+
+  return true;
+}
+
+bool Service::Stop(chromeos::ErrorPtr* error) {
+  if (!IsHostapdRunning()) {
+    chromeos::Error::AddTo(
+        error, FROM_HERE, chromeos::errors::dbus::kDomain, kServiceError,
+        "Service is not currently running");
+    return false;
+  }
+
+  StopHostapdProcess();
+  return true;
+}
+
+bool Service::IsHostapdRunning() {
+  return hostapd_process_ && hostapd_process_->pid() != 0 &&
+         chromeos::Process::ProcessExists(hostapd_process_->pid());
+}
+
+bool Service::StartHostapdProcess(const string& config_file_path) {
+  hostapd_process_.reset(new chromeos::ProcessImpl());
+  hostapd_process_->AddArg(string(kHostapdPath));
+  hostapd_process_->AddArg(config_file_path);
+  if (!hostapd_process_->Start()) {
+    hostapd_process_.reset();
+    return false;
+  }
+  return true;
+}
+
+void Service::StopHostapdProcess() {
+  if (!hostapd_process_->Kill(SIGTERM, kTerminationTimeoutSeconds)) {
+    hostapd_process_->Kill(SIGKILL, kTerminationTimeoutSeconds);
+  }
+  hostapd_process_.reset();
+}
+
+}  // namespace apmanager