shill: Implement DHCPConfig::ReleaseIP

Also, add some unit tests, and some cleanup.

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

Change-Id: I896bce08c6f177c9d5f6c5772c9208e8223c39df
Reviewed-on: http://gerrit.chromium.org/gerrit/2486
Reviewed-by: Chris Masone <cmasone@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/dhcp_config.cc b/dhcp_config.cc
index 49bc8ff..ffe219b 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -49,16 +49,7 @@
   // 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;
-  }
+  // Make sure we don't get any callbacks to the destroyed instance.
   CleanupClientState();
 }
 
@@ -68,16 +59,19 @@
     return Start();
   }
   if (!proxy_.get()) {
-    LOG(ERROR)
-        << "Unable to acquire destination address before receiving request.";
-    return false;
+    LOG(ERROR) << "Unable to request IP before acquiring destination.";
+    return Restart();
   }
   return RenewIP();
 }
 
 bool DHCPConfig::RenewIP() {
   VLOG(2) << __func__ << ": " << GetDeviceName();
-  if (!pid_ || !proxy_.get()) {
+  if (!pid_) {
+    return false;
+  }
+  if (!proxy_.get()) {
+    LOG(ERROR) << "Unable to renew IP before acquiring destination.";
     return false;
   }
   proxy_->DoRebind(GetDeviceName());
@@ -86,7 +80,16 @@
 
 bool DHCPConfig::ReleaseIP() {
   VLOG(2) << __func__ << ": " << GetDeviceName();
-  // TODO(petkov): Implement D-Bus calls to Release and stop the process.
+  if (!pid_) {
+    return true;
+  }
+  if (!proxy_.get()) {
+    LOG(ERROR) << "Unable to release IP before acquiring destination.";
+    return false;
+  }
+  proxy_->DoRelease(GetDeviceName());
+  Stop();
+  return true;
 }
 
 void DHCPConfig::InitProxy(DBus::Connection *connection, const char *service) {
@@ -110,30 +113,30 @@
 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;
+  char *argv[4] = {
+    const_cast<char *>(kDHCPCDPath),
+    const_cast<char *>("-B"),  // foreground
+    const_cast<char *>(GetDeviceName().c_str()),
+    NULL
+  };
+  char *envp[1] = { NULL };
 
-  envp[0] = NULL;
-
-  GPid pid = 0;
+  CHECK(!pid_);
   if (!glib_->SpawnAsync(NULL,
                          argv,
                          envp,
                          G_SPAWN_DO_NOT_REAP_CHILD,
                          NULL,
                          NULL,
-                         &pid,
+                         &pid_,
                          NULL)) {
     LOG(ERROR) << "Unable to spawn " << kDHCPCDPath;
     return false;
   }
-  pid_ = pid;
   LOG(INFO) << "Spawned " << kDHCPCDPath << " with pid: " << pid_;
   provider_->BindPID(pid_, this);
-  child_watch_tag_ = glib_->ChildWatchAdd(pid, ChildWatchCallback, this);
+  CHECK(!child_watch_tag_);
+  child_watch_tag_ = glib_->ChildWatchAdd(pid_, ChildWatchCallback, this);
   return true;
 }
 
@@ -144,6 +147,21 @@
   }
 }
 
+bool DHCPConfig::Restart() {
+  // Check to ensure that this instance doesn't get destroyed in the middle of
+  // this call. If stopping a running client while there's only one reference to
+  // this instance, we will end up destroying it when the PID is unbound from
+  // the Provider. Since the Provider doesn't invoke Restart, this would mean
+  // that Restart was erroneously executed through a bare reference.
+  CHECK(!pid_ || !HasOneRef());
+  Stop();
+  if (pid_) {
+    provider_->UnbindPID(pid_);
+  }
+  CleanupClientState();
+  return Start();
+}
+
 string DHCPConfig::GetIPv4AddressString(unsigned int address) {
   char str[INET_ADDRSTRLEN];
   if (inet_ntop(AF_INET, &address, str, arraysize(str))) {
@@ -226,6 +244,14 @@
 }
 
 void DHCPConfig::CleanupClientState() {
+  if (child_watch_tag_) {
+    glib_->SourceRemove(child_watch_tag_);
+    child_watch_tag_ = 0;
+  }
+  if (pid_) {
+    glib_->SpawnClosePID(pid_);
+    pid_ = 0;
+  }
   file_util::Delete(root_.Append(base::StringPrintf(kDHCPCDPathFormatLease,
                                                     GetDeviceName().c_str())),
                     false);