shill -- connect dhcp config to proxies and provider.

In addition, spawn dhcpcd on request.

Cleanup the Makefile a bit. Don't do lazy initialization of dbus_control so that
the connection can be passed to DHCPProvider.

BUG=chromium-os:16013
TEST=modified device_info to request a DHCPConfig for a DeviceStub.

Change-Id: Ib3b032b25bd5b071635816635bf6066cc3b386d5
Reviewed-on: http://gerrit.chromium.org/gerrit/2024
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Chris Masone <cmasone@chromium.org>
diff --git a/Makefile b/Makefile
index 0ef535f..35b8a2e 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,6 @@
 
 DBUS_HEADERS = $(DBUS_ADAPTOR_HEADERS) $(DBUS_PROXY_HEADERS)
 
-SHILL_LIB = shill_lib.a
 SHILL_OBJS = \
 	dbus_adaptor.o \
 	dbus_control.o \
@@ -93,23 +92,20 @@
 
 $(SHILL_OBJS): $(DBUS_HEADERS)
 
-$(SHILL_LIB): $(SHILL_OBJS)
-	$(AR) rcs $@ $^
-
-$(SHILL_BIN): $(SHILL_MAIN_OBJ) $(SHILL_LIB)
+$(SHILL_BIN): $(SHILL_MAIN_OBJ) $(SHILL_OBJS)
 	$(CXX) $(CXXFLAGS) $(INCLUDE_DIRS) $(LIB_DIRS) $(LDFLAGS) $^ $(LIBS) \
-	-o $@
+		-o $@
 
 $(TEST_BIN): CXXFLAGS += -DUNIT_TEST
-$(TEST_BIN): $(TEST_OBJS) $(SHILL_LIB)
+$(TEST_BIN): $(TEST_OBJS) $(SHILL_OBJS)
 	$(CXX) $(CXXFLAGS) $(TEST_INCLUDE_DIRS) $(TEST_LIB_DIRS) $(LDFLAGS) $^ \
 		$(TEST_LIBS) -o $@
 
 # NB(quiche): statically link gmock, gtest, as test device will not have them
 wifi_integrationtest: CXXFLAGS += -DUNIT_TEST
-wifi_integrationtest: wifi_integrationtest.o $(SHILL_LIB)
+wifi_integrationtest: wifi_integrationtest.o $(SHILL_OBJS)
 	$(CXX) $(CXXFLAGS) $(TEST_INCLUDE_DIRS) $(TEST_LIB_DIRS) $(LDFLAGS) $^ \
 		$(BASE_LIBS) -Wl,-Bstatic -lgmock -lgtest -Wl,-Bdynamic -o $@
 
 clean:
-	rm -rf *.o $(DBUS_HEADERS) $(SHILL_BIN) $(SHILL_LIB) $(TEST_BIN)
+	rm -rf *.o $(DBUS_HEADERS) $(SHILL_BIN) $(TEST_BIN)
diff --git a/dbus_control.cc b/dbus_control.cc
index ba118fb..af19925 100644
--- a/dbus_control.cc
+++ b/dbus_control.cc
@@ -19,31 +19,27 @@
 DBusControl::~DBusControl() {}
 
 ManagerAdaptorInterface *DBusControl::CreateManagerAdaptor(Manager *manager) {
-  EnsureConnection();
   connection_->request_name(ManagerDBusAdaptor::kInterfaceName);
   return new(std::nothrow) ManagerDBusAdaptor(connection_.get(), manager);
 }
 
 ServiceAdaptorInterface *DBusControl::CreateServiceAdaptor(Service *service) {
-  EnsureConnection();
   connection_->request_name(ServiceDBusAdaptor::kInterfaceName);
   return new(std::nothrow) ServiceDBusAdaptor(connection_.get(), service);
 }
 
 DeviceAdaptorInterface *DBusControl::CreateDeviceAdaptor(Device *device) {
-  EnsureConnection();
   connection_->request_name(DeviceDBusAdaptor::kInterfaceName);
   return new(std::nothrow) DeviceDBusAdaptor(connection_.get(), device);
 }
 
-void DBusControl::EnsureConnection() {
-  if (!connection_.get()) {
-    dispatcher_.reset(new(std::nothrow) DBus::Glib::BusDispatcher());
-    CHECK(dispatcher_.get()) << "Failed to create a dbus-dispatcher";
-    DBus::default_dispatcher = dispatcher_.get();
-    dispatcher_->attach(NULL);
-    connection_.reset(new DBus::Connection(DBus::Connection::SystemBus()));
-  }
+void DBusControl::Init() {
+  CHECK(!connection_.get());
+  dispatcher_.reset(new(std::nothrow) DBus::Glib::BusDispatcher());
+  CHECK(dispatcher_.get()) << "Failed to create a dbus-dispatcher";
+  DBus::default_dispatcher = dispatcher_.get();
+  dispatcher_->attach(NULL);
+  connection_.reset(new DBus::Connection(DBus::Connection::SystemBus()));
 }
 
 }  // namespace shill
diff --git a/dbus_control.h b/dbus_control.h
index 4ec148e..68796c3 100644
--- a/dbus_control.h
+++ b/dbus_control.h
@@ -22,7 +22,7 @@
   ServiceAdaptorInterface *CreateServiceAdaptor(Service *service);
   DeviceAdaptorInterface *CreateDeviceAdaptor(Device *device);
 
-  void EnsureConnection();
+  void Init();
   DBus::Connection *connection() { return connection_.get(); }
 
  private:
diff --git a/dhcp_config.cc b/dhcp_config.cc
index fbba26e..36849a6 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -5,15 +5,82 @@
 #include "shill/dhcp_config.h"
 
 #include <base/logging.h>
+#include <glib.h>
+
+#include "shill/dhcpcd_proxy.h"
+#include "shill/dhcp_provider.h"
 
 namespace shill {
 
-DHCPConfig::DHCPConfig(const Device &device) : IPConfig(device) {
-  VLOG(2) << "DHCPConfig created.";
+const char DHCPConfig::kDHCPCDPath[] = "/sbin/dhcpcd";
+
+DHCPConfig::DHCPConfig(DHCPProvider *provider, const Device &device)
+    : IPConfig(device),
+      provider_(provider),
+      pid_(0) {
+  VLOG(2) << __func__ << ": " << GetDeviceName();
 }
 
 DHCPConfig::~DHCPConfig() {
-  VLOG(2) << "DHCPConfig destroyed.";
+  VLOG(2) << __func__ << ": " << GetDeviceName();
+}
+
+bool DHCPConfig::Request() {
+  VLOG(2) << __func__ << ": " << GetDeviceName();
+  if (!pid_) {
+    return Start();
+  }
+  if (!proxy_.get()) {
+    LOG(ERROR)
+        << "Unable to acquire destination address before receiving request.";
+    return false;
+  }
+  return Renew();
+}
+
+bool DHCPConfig::Renew() {
+  VLOG(2) << __func__ << ": " << GetDeviceName();
+  if (!pid_ || !proxy_.get()) {
+    return false;
+  }
+  proxy_->DoRebind(GetDeviceName());
+  return true;
+}
+
+void DHCPConfig::InitProxy(DBus::Connection *connection, const char *service) {
+  if (!proxy_.get()) {
+    proxy_.reset(new DHCPCDProxy(connection, service));
+  }
+}
+
+bool DHCPConfig::Start() {
+  VLOG(2) << __func__ << ": " << GetDeviceName();
+
+  char *argv[4], *envp[1];
+  argv[0] = const_cast<char *>(kDHCPCDPath);
+  argv[1] = const_cast<char *>("-B");  // foreground
+  argv[2] = const_cast<char *>(GetDeviceName().c_str());
+  argv[3] = NULL;
+
+  envp[0] = NULL;
+
+  GPid pid = 0;
+  if (!g_spawn_async(NULL,
+                     argv,
+                     envp,
+                     G_SPAWN_DO_NOT_REAP_CHILD,
+                     NULL,
+                     NULL,
+                     &pid,
+                     NULL)) {
+    LOG(ERROR) << "Unable to spawn " << kDHCPCDPath;
+    return false;
+  }
+  pid_ = pid;
+  LOG(INFO) << "Spawned " << kDHCPCDPath << " with pid: " << pid_;
+  provider_->BindPID(pid_, DHCPConfigRefPtr(this));
+  // TODO(petkov): Add an exit watch to cleanup w/ g_spawn_close_pid.
+  return true;
 }
 
 }  // namespace shill
diff --git a/dhcp_config.h b/dhcp_config.h
index 735fa9c..f916971 100644
--- a/dhcp_config.h
+++ b/dhcp_config.h
@@ -5,16 +5,47 @@
 #ifndef SHILL_DHCP_CONFIG_
 #define SHILL_DHCP_CONFIG_
 
+#include <base/memory/scoped_ptr.h>
+#include <dbus-c++/connection.h>
+
 #include "shill/ipconfig.h"
 
 namespace shill {
 
+class DHCPConfig;
+class DHCPProvider;
+class DHCPProxyInterface;
+
+typedef scoped_refptr<DHCPConfig> DHCPConfigRefPtr;
+
 class DHCPConfig : public IPConfig {
  public:
-  explicit DHCPConfig(const Device &device);
+  DHCPConfig(DHCPProvider *provider, const Device &device);
   virtual ~DHCPConfig();
 
+  // Inherited from IPConfig.
+  virtual bool Request();
+  virtual bool Renew();
+
+  // If |proxy_| is not initialized already, sets it to a new D-Bus proxy to
+  // |service|.
+  void InitProxy(DBus::Connection *connection, const char *service);
+
  private:
+  static const char kDHCPCDPath[];
+
+  // Starts dhcpcd, returns true on success and false otherwise.
+  bool Start();
+
+  DHCPProvider *provider_;
+
+  // The PID of the spawned DHCP client. May be 0 if no client has been spawned
+  // yet or the client has died.
+  unsigned int pid_;
+
+  // The proxy for communicating with the DHCP client.
+  scoped_ptr<DHCPProxyInterface> proxy_;
+
   DISALLOW_COPY_AND_ASSIGN(DHCPConfig);
 };
 
diff --git a/dhcp_provider.cc b/dhcp_provider.cc
index abb97bd..35f9ca1 100644
--- a/dhcp_provider.cc
+++ b/dhcp_provider.cc
@@ -6,18 +6,49 @@
 
 #include <base/logging.h>
 
+#include "shill/dhcpcd_proxy.h"
+
 namespace shill {
 
 DHCPProvider::DHCPProvider() {
-  VLOG(2) << "DHCPProvider created.";
+  VLOG(2) << __func__;
 }
 
 DHCPProvider::~DHCPProvider() {
-  VLOG(2) << "DHCPProvider destroyed.";
+  VLOG(2) << __func__;
 }
 
 DHCPProvider* DHCPProvider::GetInstance() {
   return Singleton<DHCPProvider>::get();
 }
 
+void DHCPProvider::Init(DBus::Connection *connection) {
+  VLOG(2) << __func__;
+  listener_.reset(new DHCPCDListener(this, connection));
+}
+
+DHCPConfigRefPtr DHCPProvider::CreateConfig(const Device &device) {
+  VLOG(2) << __func__;
+  return DHCPConfigRefPtr(new DHCPConfig(this, device));
+}
+
+DHCPConfigRefPtr DHCPProvider::GetConfig(unsigned int pid) {
+  VLOG(2) << __func__;
+  PIDConfigMap::iterator it = configs_.find(pid);
+  if (it == configs_.end()) {
+    return DHCPConfigRefPtr(NULL);
+  }
+  return it->second;
+}
+
+void DHCPProvider::BindPID(unsigned int pid, DHCPConfigRefPtr config) {
+  VLOG(2) << __func__ << " pid: " << pid;
+  configs_[pid] = config;
+}
+
+void DHCPProvider::UnbindPID(unsigned int pid) {
+  VLOG(2) << __func__ << " pid: " << pid;
+  configs_.erase(pid);
+}
+
 }  // namespace shill
diff --git a/dhcp_provider.h b/dhcp_provider.h
index c77d456..f7ac4f4 100644
--- a/dhcp_provider.h
+++ b/dhcp_provider.h
@@ -5,21 +5,68 @@
 #ifndef SHILL_DHCP_PROVIDER_
 #define SHILL_DHCP_PROVIDER_
 
+#include <map>
+
+#include <base/memory/scoped_ptr.h>
 #include <base/memory/singleton.h>
+#include <dbus-c++/connection.h>
+
+#include "shill/dhcp_config.h"
 
 namespace shill {
 
+class DHCPListenerInterface;
+class Device;
+
+// DHCPProvider is a singleton providing the main DHCP configuration
+// entrypoint. Once the provider is initialized through its Init method, DHCP
+// configurations for devices can be obtained through its CreateConfig
+// method. For example, a single DHCP configuration request can be initiated as:
+//
+// DHCPProvider::GetInstance()->CreateConfig(device)->Request();
 class DHCPProvider {
  public:
   // This is a singleton -- use DHCPProvider::GetInstance()->Foo()
   static DHCPProvider *GetInstance();
 
+  // Initializes the provider singleton. This method hooks up a D-Bus signal
+  // listener to |connection| that catches signals from spawned DHCP clients and
+  // dispatches them to the appropriate DHCP configuration instance.
+  void Init(DBus::Connection *connection);
+
+  // Creates a new DHCPConfig for |device|. The DHCP configuration for the
+  // device can then be initiated through DHCPConfig::Request and
+  // DHCPConfig::Renew.
+  DHCPConfigRefPtr CreateConfig(const Device &device);
+
+  // Returns the DHCP configuration associated with DHCP client |pid|. Return
+  // NULL if |pid| is not bound to a configuration.
+  DHCPConfigRefPtr GetConfig(unsigned int pid);
+
+  // Binds a |pid| to a DHCP |config|. When a DHCP config spawns a new DHCP
+  // client, it binds itself to that client's |pid|.
+  void BindPID(unsigned int pid, DHCPConfigRefPtr config);
+
+  // Unbinds a |pid|. This method is used by a DHCP config to signal the
+  // provider that the DHCP client has been terminated. This may result in
+  // destruction of the DHCP config instance if its reference count goes to 0.
+  void UnbindPID(unsigned int pid);
+
  private:
   friend struct DefaultSingletonTraits<DHCPProvider>;
+  typedef std::map<unsigned int, DHCPConfigRefPtr> PIDConfigMap;
 
+  // Private to ensure that this behaves as a singleton.
   DHCPProvider();
   virtual ~DHCPProvider();
 
+  // A single listener is used to catch signals from all DHCP clients and
+  // dispatch them to the appropriate proxy.
+  scoped_ptr<DHCPListenerInterface> listener_;
+
+  // A map that binds PIDs to DHCP configuration instances.
+  PIDConfigMap configs_;
+
   DISALLOW_COPY_AND_ASSIGN(DHCPProvider);
 };
 
diff --git a/dhcp_proxy_interface.h b/dhcp_proxy_interface.h
index f9ba5bd..9ecc739 100644
--- a/dhcp_proxy_interface.h
+++ b/dhcp_proxy_interface.h
@@ -5,12 +5,16 @@
 #ifndef SHILL_DHCP_PROXY_INTERFACE_
 #define SHILL_DHCP_PROXY_INTERFACE_
 
+#include <string>
+
 namespace shill {
 
 // These are the functions that a DHCP proxy and listener must support.
 class DHCPProxyInterface {
  public:
   virtual ~DHCPProxyInterface() {}
+
+  virtual void DoRebind(const std::string &interface) = 0;
 };
 
 class DHCPListenerInterface {
diff --git a/dhcpcd_proxy.cc b/dhcpcd_proxy.cc
index 3ff6983..9f577c5 100644
--- a/dhcpcd_proxy.cc
+++ b/dhcpcd_proxy.cc
@@ -6,14 +6,20 @@
 
 #include <base/logging.h>
 
+#include "shill/dhcp_provider.h"
+
+using std::string;
+
 namespace shill {
 
 const char DHCPCDProxy::kDBusInterfaceName[] = "org.chromium.dhcpcd";
 const char DHCPCDProxy::kDBusPath[] = "/org/chromium/dhcpcd";
 
-DHCPCDListener::DHCPCDListener(DBus::Connection *connection)
+DHCPCDListener::DHCPCDListener(DHCPProvider *provider,
+                               DBus::Connection *connection)
     : DBus::InterfaceProxy(DHCPCDProxy::kDBusInterfaceName),
-      DBus::ObjectProxy(*connection, DHCPCDProxy::kDBusPath) {
+      DBus::ObjectProxy(*connection, DHCPCDProxy::kDBusPath),
+      provider_(provider) {
   VLOG(2) << __func__;
   connect_signal(DHCPCDListener, Event, EventSignal);
   connect_signal(DHCPCDListener, StatusChanged, StatusChangedSignal);
@@ -25,7 +31,15 @@
   unsigned int pid;
   ri >> pid;
   VLOG(2) << "sender(" << signal.sender() << ") pid(" << pid << ")";
-  // TODO(petkov): Dispatch the signal to the appropriate DHCPCDProxy.
+
+  DHCPConfigRefPtr config = provider_->GetConfig(pid);
+  if (!config.get()) {
+    LOG(ERROR) << "Unknown DHCP client PID " << pid;
+    return;
+  }
+  config->InitProxy(&conn(), signal.sender());
+  // TODO(petkov): Process and dispatch the signal to the appropriate DHCP
+  // configuration.
 }
 
 void DHCPCDListener::StatusChangedSignal(const DBus::SignalMessage &signal) {
@@ -34,15 +48,21 @@
   unsigned int pid;
   ri >> pid;
   VLOG(2) << "sender(" << signal.sender() << ") pid(" << pid << ")";
-  // TODO(petkov): Dispatch the signal to the appropriate DHCPCDProxy.
+
+  // Accept StatusChanged signals just to get the sender address and create an
+  // appropriate proxy for the PID/sender pair.
+  DHCPConfigRefPtr config = provider_->GetConfig(pid);
+  if (!config.get()) {
+    LOG(ERROR) << "Unknown DHCP client PID " << pid;
+    return;
+  }
+  config->InitProxy(&conn(), signal.sender());
 }
 
-DHCPCDProxy::DHCPCDProxy(unsigned int pid,
-                         DBus::Connection *connection,
+DHCPCDProxy::DHCPCDProxy(DBus::Connection *connection,
                          const char *service)
-    : DBus::ObjectProxy(*connection, kDBusPath, service),
-      pid_(pid) {
-  VLOG(2) << "DHCPCDListener(pid=" << pid_ << " service=" << service << ").";
+    : DBus::ObjectProxy(*connection, kDBusPath, service) {
+  VLOG(2) << "DHCPCDProxy(service=" << service << ").";
 
   // Don't catch signals directly in this proxy because they will be dispatched
   // to us by the DHCPCD listener.
@@ -50,18 +70,20 @@
   _signals.erase("StatusChanged");
 }
 
+void DHCPCDProxy::DoRebind(const string &interface) {
+  Rebind(interface);
+}
+
 void DHCPCDProxy::Event(
     const uint32_t& pid,
     const std::string& reason,
     const std::map< std::string, DBus::Variant >& configuration) {
-  VLOG(2) << "Event(pid=" << pid << " reason=\"" << reason << "\")";
-  CHECK_EQ(pid, pid_);
+  NOTREACHED();
 }
 
 void DHCPCDProxy::StatusChanged(const uint32_t& pid,
                                 const std::string& status) {
-  VLOG(2) << "StatusChanged(pid=" << pid << " status=\"" << status << "\")";
-  CHECK_EQ(pid, pid_);
+  NOTREACHED();
 }
 
 }  // namespace shill
diff --git a/dhcpcd_proxy.h b/dhcpcd_proxy.h
index 576c004..39ef8a5 100644
--- a/dhcpcd_proxy.h
+++ b/dhcpcd_proxy.h
@@ -7,11 +7,14 @@
 
 #include <base/basictypes.h>
 
+#include "shill/dhcp_config.h"
 #include "shill/dhcp_proxy_interface.h"
 #include "shill/dhcpcd.h"
 
 namespace shill {
 
+class DHCPProvider;
+
 // The DHCPCD listener is a singleton proxy that listens to signals from all
 // DHCP clients and dispatches them through the DHCP provider to the appropriate
 // client based on the PID.
@@ -19,12 +22,15 @@
                        public DBus::InterfaceProxy,
                        public DBus::ObjectProxy {
  public:
-  explicit DHCPCDListener(DBus::Connection *connection);
+  explicit DHCPCDListener(DHCPProvider *provider,
+                          DBus::Connection *connection);
 
  private:
   void EventSignal(const DBus::SignalMessage &signal);
   void StatusChangedSignal(const DBus::SignalMessage &signal);
 
+  DHCPProvider *provider_;
+
   DISALLOW_COPY_AND_ASSIGN(DHCPCDListener);
 };
 
@@ -37,11 +43,14 @@
   static const char kDBusInterfaceName[];
   static const char kDBusPath[];
 
-  DHCPCDProxy(unsigned int pid,
-              DBus::Connection *connection,
-              const char *service);
+  DHCPCDProxy(DBus::Connection *connection, const char *service);
 
-  // Signal callbacks inherited from dhcpcd_proxy.
+  // Inherited from DHCPProxyInterface.
+  virtual void DoRebind(const std::string &interface);
+
+  // Signal callbacks inherited from dhcpcd_proxy. Note that these callbacks are
+  // unused because signals are dispatched directly to the DHCP configuration
+  // instance by the signal listener.
   virtual void Event(
       const uint32_t& pid,
       const std::string& reason,
@@ -49,10 +58,6 @@
   virtual void StatusChanged(const uint32_t& pid, const std::string& status);
 
  private:
-  // Process ID of the associated dhcpcd process used for verification purposes
-  // only.
-  unsigned int pid_;
-
   DISALLOW_COPY_AND_ASSIGN(DHCPCDProxy);
 };
 
diff --git a/ipconfig.h b/ipconfig.h
index 8debc5c..bd964be 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -24,6 +24,10 @@
 
   const std::string &GetDeviceName() const;
 
+  // Request or renew IP configuration. Return true on success, false otherwise.
+  virtual bool Request() { return false; }
+  virtual bool Renew() { return false; }
+
  private:
   friend class base::RefCounted<IPConfig>;
 
diff --git a/shill_main.cc b/shill_main.cc
index ea45859..da9dc41 100644
--- a/shill_main.cc
+++ b/shill_main.cc
@@ -11,8 +11,9 @@
 #include <base/logging.h>
 #include <chromeos/syslog_logging.h>
 
-#include "shill/shill_daemon.h"
 #include "shill/dbus_control.h"
+#include "shill/dhcp_provider.h"
+#include "shill/shill_daemon.h"
 
 using std::string;
 
@@ -72,9 +73,11 @@
   shill::Config config; /* (config_dir, default_config_dir) */
 
   // TODO(pstew): This should be chosen based on config
-  shill::ControlInterface *control_interface = new shill::DBusControl();
+  shill::DBusControl *dbus_control = new shill::DBusControl();
+  dbus_control->Init();
+  shill::DHCPProvider::GetInstance()->Init(dbus_control->connection());
 
-  shill::Daemon daemon(&config, control_interface);
+  shill::Daemon daemon(&config, dbus_control);
   daemon.Run();
 
   return 0;