shill: Almost complete support for terminating dhcpcd.

BUG=chromium-os:16365,chromium-os:16013
TEST=unit tests

Change-Id: I00a46e8364fc4de3cc48ef72c1b0a9a88e95e6a0
Reviewed-on: http://gerrit.chromium.org/gerrit/2435
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Chris Masone <cmasone@chromium.org>
diff --git a/dhcp_config.cc b/dhcp_config.cc
index 13d9325..49bc8ff 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -6,7 +6,9 @@
 
 #include <arpa/inet.h>
 
+#include <base/file_util.h>
 #include <base/logging.h>
+#include <base/stringprintf.h>
 
 #include "shill/dhcpcd_proxy.h"
 #include "shill/dhcp_provider.h"
@@ -26,7 +28,8 @@
 const char DHCPConfig::kConfigurationKeyRouters[] = "Routers";
 const char DHCPConfig::kConfigurationKeySubnetCIDR[] = "SubnetCIDR";
 const char DHCPConfig::kDHCPCDPath[] = "/sbin/dhcpcd";
-
+const char DHCPConfig::kDHCPCDPathFormatLease[] = "var/run/dhcpcd-%s.lease";
+const char DHCPConfig::kDHCPCDPathFormatPID[] = "var/run/dhcpcd-%s.pid";
 
 DHCPConfig::DHCPConfig(DHCPProvider *provider,
                        DeviceConstRefPtr device,
@@ -34,15 +37,32 @@
     : IPConfig(device),
       provider_(provider),
       pid_(0),
+      child_watch_tag_(0),
+      root_("/"),
       glib_(glib) {
   VLOG(2) << __func__ << ": " << GetDeviceName();
 }
 
 DHCPConfig::~DHCPConfig() {
   VLOG(2) << __func__ << ": " << GetDeviceName();
+
+  // Don't leave behind dhcpcd running.
+  Stop();
+
+  // Somehow we got destroyed before the client died, most likely at exit. Make
+  // sure we don't get any callbacks to the destroyed instance.
+  if (child_watch_tag_) {
+    glib_->SourceRemove(child_watch_tag_);
+    child_watch_tag_ = 0;
+  }
+  if (pid_) {
+    glib_->SpawnClosePID(pid_);
+    pid_ = 0;
+  }
+  CleanupClientState();
 }
 
-bool DHCPConfig::Request() {
+bool DHCPConfig::RequestIP() {
   VLOG(2) << __func__ << ": " << GetDeviceName();
   if (!pid_) {
     return Start();
@@ -52,10 +72,10 @@
         << "Unable to acquire destination address before receiving request.";
     return false;
   }
-  return Renew();
+  return RenewIP();
 }
 
-bool DHCPConfig::Renew() {
+bool DHCPConfig::RenewIP() {
   VLOG(2) << __func__ << ": " << GetDeviceName();
   if (!pid_ || !proxy_.get()) {
     return false;
@@ -64,6 +84,11 @@
   return true;
 }
 
+bool DHCPConfig::ReleaseIP() {
+  VLOG(2) << __func__ << ": " << GetDeviceName();
+  // TODO(petkov): Implement D-Bus calls to Release and stop the process.
+}
+
 void DHCPConfig::InitProxy(DBus::Connection *connection, const char *service) {
   if (!proxy_.get()) {
     proxy_.reset(new DHCPCDProxy(connection, service));
@@ -108,10 +133,17 @@
   pid_ = pid;
   LOG(INFO) << "Spawned " << kDHCPCDPath << " with pid: " << pid_;
   provider_->BindPID(pid_, this);
-  // TODO(petkov): Add an exit watch to cleanup w/ g_spawn_close_pid.
+  child_watch_tag_ = glib_->ChildWatchAdd(pid, ChildWatchCallback, this);
   return true;
 }
 
+void DHCPConfig::Stop() {
+  if (pid_) {
+    VLOG(2) << "Terminating " << pid_;
+    PLOG_IF(ERROR, kill(pid_, SIGTERM) < 0);
+  }
+}
+
 string DHCPConfig::GetIPv4AddressString(unsigned int address) {
   char str[INET_ADDRSTRLEN];
   if (inet_ntop(AF_INET, &address, str, arraysize(str))) {
@@ -178,4 +210,28 @@
   return true;
 }
 
+void DHCPConfig::ChildWatchCallback(GPid pid, gint status, gpointer data) {
+  VLOG(2) << "pid " << pid << " exit status " << status;
+  DHCPConfig *config = reinterpret_cast<DHCPConfig *>(data);
+  config->child_watch_tag_ = 0;
+
+  CHECK_EQ(pid, config->pid_);
+  config->glib_->SpawnClosePID(pid);
+  config->pid_ = 0;
+
+  config->CleanupClientState();
+
+  // |config| instance may be destroyed after this call.
+  config->provider_->UnbindPID(pid);
+}
+
+void DHCPConfig::CleanupClientState() {
+  file_util::Delete(root_.Append(base::StringPrintf(kDHCPCDPathFormatLease,
+                                                    GetDeviceName().c_str())),
+                    false);
+  file_util::Delete(root_.Append(base::StringPrintf(kDHCPCDPathFormatPID,
+                                                    GetDeviceName().c_str())),
+                    false);
+}
+
 }  // namespace shill