AU: Catch terminate signals and block exit if necessary.

Adds a global Terminator class to manage signals and exit blocking.

BUG=7392
TEST=unit tests, gmerged on device, initctl stop update-engine

Change-Id: I2291d4eb55240a6662b18ff834af161d957bce2f

Review URL: http://codereview.chromium.org/3608015
diff --git a/SConstruct b/SConstruct
index c42b528..1a73466 100644
--- a/SConstruct
+++ b/SConstruct
@@ -264,6 +264,7 @@
                    split_file_writer.cc
                    subprocess.cc
                    tarjan.cc
+                   terminator.cc
                    topological_sort.cc
                    update_attempter.cc
                    update_check_scheduler.cc
diff --git a/delta_performer.cc b/delta_performer.cc
index f807665..10031eb 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -23,6 +23,7 @@
 #include "update_engine/payload_signer.h"
 #include "update_engine/prefs_interface.h"
 #include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
 
 using std::min;
 using std::string;
@@ -209,6 +210,8 @@
             next_operation_num_ - manifest_.install_operations_size());
     if (!CanPerformInstallOperation(op))
       break;
+    ScopedTerminatorExitUnblocker exit_unblocker =
+        ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.
     // Log every thousandth operation, and also the first and last ones
     if ((next_operation_num_ % 1000 == 0) ||
         (next_operation_num_ + 1 == total_operations)) {
@@ -221,6 +224,7 @@
     // that if the operation gets interrupted, we don't try to resume the
     // update.
     if (!IsIdempotentOperation(op)) {
+      Terminator::set_exit_blocked(true);
       ResetUpdateProgress(prefs_);
     }
     if (op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
@@ -574,9 +578,10 @@
 }
 
 bool DeltaPerformer::CheckpointUpdateProgress() {
-  // First reset the progress in case we die in the middle of the state update.
-  ResetUpdateProgress(prefs_);
+  Terminator::set_exit_blocked(true);
   if (last_updated_buffer_offset_ != buffer_offset_) {
+    // Resets the progress in case we die in the middle of the state update.
+    ResetUpdateProgress(prefs_);
     TEST_AND_RETURN_FALSE(
         prefs_->SetString(kPrefsUpdateStateSHA256Context,
                           hash_calculator_.GetContext()));
diff --git a/generate_delta_main.cc b/generate_delta_main.cc
index c66d9d1..8c5d6a9 100644
--- a/generate_delta_main.cc
+++ b/generate_delta_main.cc
@@ -21,6 +21,7 @@
 #include "update_engine/delta_performer.h"
 #include "update_engine/prefs.h"
 #include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
 #include "update_engine/update_metadata.pb.h"
 #include "update_engine/utils.h"
 
@@ -61,6 +62,7 @@
   g_thread_init(NULL);
   google::ParseCommandLineFlags(&argc, &argv, true);
   CommandLine::Init(argc, argv);
+  Terminator::Init();
   Subprocess::Init();
   logging::InitLogging("delta_generator.log",
                        logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
diff --git a/main.cc b/main.cc
index daa5eda..94393c3 100644
--- a/main.cc
+++ b/main.cc
@@ -20,6 +20,7 @@
 #include "update_engine/dbus_service.h"
 #include "update_engine/prefs.h"
 #include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
 #include "update_engine/update_attempter.h"
 #include "update_engine/update_check_scheduler.h"
 
@@ -93,6 +94,7 @@
   g_thread_init(NULL);
   dbus_g_thread_init();
   base::AtExitManager exit_manager;  // Required for base/rand_util.h.
+  chromeos_update_engine::Terminator::Init();
   chromeos_update_engine::Subprocess::Init();
   google::ParseCommandLineFlags(&argc, &argv, true);
   CommandLine::Init(argc, argv);
diff --git a/terminator.cc b/terminator.cc
new file mode 100644
index 0000000..1d33e32
--- /dev/null
+++ b/terminator.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2010 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 "update_engine/terminator.h"
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+volatile sig_atomic_t Terminator::exit_blocked_ = 0;
+volatile sig_atomic_t Terminator::exit_requested_ = 0;
+
+void Terminator::Init() {
+  signal(SIGTERM, HandleSignal);
+}
+
+void Terminator::Exit() {
+  exit(0);
+}
+
+void Terminator::HandleSignal(int signum) {
+  if (exit_blocked_ == 0) {
+    Exit();
+  }
+  exit_requested_ = 1;
+}
+
+ScopedTerminatorExitUnblocker::~ScopedTerminatorExitUnblocker() {
+  Terminator::set_exit_blocked(false);
+  if (Terminator::exit_requested()) {
+    Terminator::Exit();
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/terminator.h b/terminator.h
new file mode 100644
index 0000000..1c014ea
--- /dev/null
+++ b/terminator.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_TERMINATOR_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_TERMINATOR_H__
+
+#include <signal.h>
+
+namespace chromeos_update_engine {
+
+// A class allowing graceful delayed exit.
+class Terminator {
+ public:
+  // Initializes the terminator and sets up signal handlers.
+  static void Init();
+
+  // Terminates the current process.
+  static void Exit();
+
+  // Set to true if the terminator should block termination requests in an
+  // attempt to block exiting.
+  static void set_exit_blocked(bool block) { exit_blocked_ = block ? 1 : 0; }
+
+  // Returns true if the system is trying to terminate the process, false
+  // otherwise. Returns true only if exit was blocked when the termination
+  // request arrived.
+  static bool exit_requested() { return exit_requested_ != 0; }
+
+ private:
+  // The signal handler.
+  static void HandleSignal(int signum);
+
+  static volatile sig_atomic_t exit_blocked_;
+  static volatile sig_atomic_t exit_requested_;
+};
+
+class ScopedTerminatorExitUnblocker {
+ public:
+  ~ScopedTerminatorExitUnblocker();
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_TERMINATOR_H__
diff --git a/testrunner.cc b/testrunner.cc
index 3e40e1f..748dd5f 100644
--- a/testrunner.cc
+++ b/testrunner.cc
@@ -4,21 +4,24 @@
 
 // based on pam_google_testrunner.cc
 
+#include <base/at_exit.h>
+#include <base/command_line.h>
 #include <dbus/dbus-glib.h>
 #include <dbus/dbus-glib-bindings.h>
 #include <dbus/dbus-glib-lowlevel.h>
 #include <glib.h>
 #include <glib-object.h>
 #include <gtest/gtest.h>
-#include "base/at_exit.h"
-#include "base/command_line.h"
+
 #include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
 
 int main(int argc, char **argv) {
   ::g_type_init();
   g_thread_init(NULL);
   dbus_g_thread_init();
   base::AtExitManager exit_manager;
+  chromeos_update_engine::Terminator::Init();
   chromeos_update_engine::Subprocess::Init();
   CommandLine::Init(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);