AU: Many minor cleanup changes

postinstall: Run postinst twice, once for pre-commit (ie, before we
mark the install partition bootable in the partition table), and
post-commit (for after we do). This behavior is needed for specific
types of firmware update.

download action: flush caches, as we found was necessary in
memento_updater.sh

omaha request prep action: update the names of keys we look for in
lsb-release, also get the AU server url from a file, rather than
hard-code it.

set bootable flag action: GPT support.

also, some misc utility functions.

BUG=None
TEST=attached unittests

Review URL: http://codereview.chromium.org/1881001
diff --git a/download_action.cc b/download_action.cc
index 5387821..eee25cc 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -5,10 +5,15 @@
 #include "update_engine/download_action.h"
 #include <errno.h>
 #include <algorithm>
+#include <string>
+#include <vector>
 #include <glib.h>
 #include "update_engine/action_pipe.h"
+#include "update_engine/subprocess.h"
 
 using std::min;
+using std::string;
+using std::vector;
 
 namespace chromeos_update_engine {
 
@@ -84,6 +89,22 @@
   omaha_hash_calculator_.Update(bytes, length);
 }
 
+namespace {
+void FlushLinuxCaches() {
+  vector<string> command;
+  command.push_back("/bin/sync");
+  int rc;
+  LOG(INFO) << "FlushLinuxCaches/sync...";
+  Subprocess::SynchronousExec(command, &rc);
+  LOG(INFO) << "FlushLinuxCaches/drop_caches...";
+
+  const char* const drop_cmd = "3\n";
+  utils::WriteFile("/proc/sys/vm/drop_caches", drop_cmd, strlen(drop_cmd));
+
+  LOG(INFO) << "FlushLinuxCaches done.";
+}
+}
+
 void DownloadAction::TransferComplete(HttpFetcher *fetcher, bool successful) {
   if (writer_) {
     CHECK_EQ(writer_->Close(), 0) << errno;
@@ -99,6 +120,8 @@
       successful = false;
     }
   }
+  
+  FlushLinuxCaches();
 
   // Write the path to the output pipe if we're successful
   if (successful && HasOutputPipe())
diff --git a/main.cc b/main.cc
index a796a98..4725967 100644
--- a/main.cc
+++ b/main.cc
@@ -29,7 +29,9 @@
 gboolean SetupInMainLoop(void* arg) {
   // TODO(adlr): Tell update_attempter to start working.
   // Comment this in for that:
-  //UpdateAttempter* update_attempter = reinterpret_cast<UpdateAttempter*>(arg);
+  UpdateAttempter* update_attempter = reinterpret_cast<UpdateAttempter*>(arg);
+  LOG(INFO) << "Starting update!";
+  update_attempter->Update(false);
 
   return FALSE;  // Don't call this callback function again
 }
@@ -59,7 +61,6 @@
   if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
     g_warning("Got result code %u from requesting name", request_name_ret);
     g_error_free(error);
-    exit(1);
     LOG(FATAL) << "Got result code " << request_name_ret
                << " from requesting name, but expected "
                << DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
diff --git a/omaha_request_prep_action.cc b/omaha_request_prep_action.cc
index 9e144a0..6ee2c24 100644
--- a/omaha_request_prep_action.cc
+++ b/omaha_request_prep_action.cc
@@ -28,9 +28,11 @@
   ScopedActionCompleter completer(processor_, this);
   string machine_id;
   TEST_AND_RETURN(GetMachineId(&machine_id));
-  const string version(GetLsbValue("GOOGLE_RELEASE"));
+  const string version(GetLsbValue("CHROMEOS_RELEASE_VERSION", ""));
   const string sp(version + "_" + GetMachineType());
-  const string track(GetLsbValue("GOOGLE_TRACK"));
+  const string track(GetLsbValue("CHROMEOS_RELEASE_TRACK", ""));
+  const string update_url(GetLsbValue("CHROMEOS_AUSERVER",
+                                      UpdateCheckParams::kUpdateUrl));
 
   UpdateCheckParams out(machine_id,  // machine_id
                         machine_id,  // user_id (use machine_id)
@@ -39,8 +41,9 @@
                         sp,  // e.g. 0.2.3.3_i686
                         UpdateCheckParams::kAppId,
                         version,  // app version (from lsb-release)
-                        "en-US",  //lang
-                        track);  // track
+                        "en-US",  // lang
+                        track,  // track
+                        UpdateCheckParams::kUpdateUrl);
 
   CHECK(HasOutputPipe());
   SetOutputObject(out);
@@ -89,10 +92,13 @@
   return true;
 }
 
-std::string OmahaRequestPrepAction::GetLsbValue(const std::string& key) const {
+string OmahaRequestPrepAction::GetLsbValue(
+    const string& key, const string& default_value) const {
   string files[] = {string(utils::kStatefulPartition) + "/etc/lsb-release",
                     "/etc/lsb-release"};
   for (unsigned int i = 0; i < arraysize(files); i++) {
+    // TODO(adlr): make sure files checked are owned as root (and all
+    // their parents are recursively, too).
     string file_data;
     if (!utils::ReadFileToString(root_ + files[i], &file_data))
       continue;
@@ -111,10 +117,10 @@
     return file_data.substr(pos, length);
   }
   // not found
-  return "";
+  return default_value;
 }
 
-std::string OmahaRequestPrepAction::GetMachineType() const {
+string OmahaRequestPrepAction::GetMachineType() const {
   struct utsname buf;
   string ret;
   if (uname(&buf) == 0)
diff --git a/omaha_request_prep_action.h b/omaha_request_prep_action.h
index cb82a9f..d6030f5 100644
--- a/omaha_request_prep_action.h
+++ b/omaha_request_prep_action.h
@@ -54,7 +54,8 @@
   // Fetches the value for a given key from
   // /mnt/stateful_partition/etc/lsb-release if possible. Failing that,
   // it looks for the key in /etc/lsb-release .
-  std::string GetLsbValue(const std::string& key) const;
+  std::string GetLsbValue(const std::string& key,
+                          const std::string& default_value) const;
 
   // Gets the machine type (e.g. "i686")
   std::string GetMachineType() const;
diff --git a/omaha_request_prep_action_unittest.cc b/omaha_request_prep_action_unittest.cc
index d5a95e0..01016c3 100644
--- a/omaha_request_prep_action_unittest.cc
+++ b/omaha_request_prep_action_unittest.cc
@@ -111,7 +111,9 @@
   {
     ASSERT_TRUE(WriteFileString(
         kTestDir + "/etc/lsb-release",
-        "GOOGLE_FOO=bar\nGOOGLE_RELEASE=0.2.2.3\nGOOGLE_TRACK=footrack"));
+        "CHROMEOS_RELEASE_FOO=bar\n"
+        "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+        "CHROMEOS_RELEASE_TRACK=footrack"));
     UpdateCheckParams out;
     EXPECT_TRUE(DoTest(false, &out));
     EXPECT_TRUE(IsValidGuid(out.machine_id)) << "id: " << out.machine_id;
@@ -134,7 +136,9 @@
   {
     ASSERT_TRUE(WriteFileString(
         kTestDir + "/etc/lsb-release",
-        "GOOGLE_FOO=bar\nGOOGLE_RELEASE=0.2.2.3\nGOOGLE_TRXCK=footrack"));
+        "CHROMEOS_RELEASE_FOO=bar\n"
+        "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+        "CHROMEOS_RELEASE_TRXCK=footrack"));
     UpdateCheckParams out;
     EXPECT_TRUE(DoTest(false, &out));
     EXPECT_TRUE(IsValidGuid(out.machine_id));
@@ -157,8 +161,9 @@
   {
     ASSERT_TRUE(WriteFileString(
         kTestDir + "/etc/lsb-release",
-        "GOOGLE_FOO=GOOGLE_RELEASE=1.2.3.4\n"
-        "GOOGLE_RELEASE=0.2.2.3\nGOOGLE_TRXCK=footrack"));
+        "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+        "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+        "CHROMEOS_RELEASE_TRXCK=footrack"));
     UpdateCheckParams out;
     EXPECT_TRUE(DoTest(false, &out));
     EXPECT_TRUE(IsValidGuid(out.machine_id)) << out.machine_id;
@@ -180,8 +185,9 @@
                       utils::kStatefulPartition + "/etc"));
   ASSERT_TRUE(WriteFileString(
       kTestDir + "/etc/lsb-release",
-      "GOOGLE_FOO=GOOGLE_RELEASE=1.2.3.4\n"
-      "GOOGLE_RELEASE=0.2.2.3\nGOOGLE_TRXCK=footrack"));
+      "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRXCK=footrack"));
   UpdateCheckParams out1;
   EXPECT_TRUE(DoTest(false, &out1));
   string machine_id;
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
index 98de1bd..e8f5cc2 100644
--- a/postinstall_runner_action.cc
+++ b/postinstall_runner_action.cc
@@ -5,45 +5,70 @@
 #include "update_engine/postinstall_runner_action.h"
 #include <sys/mount.h>
 #include <stdlib.h>
+#include <vector>
+#include "update_engine/subprocess.h"
 #include "update_engine/utils.h"
 
 namespace chromeos_update_engine {
 
 using std::string;
+using std::vector;
 
 namespace {
-const string kMountPath(string(utils::kStatefulPartition) + "/au_destination");
 const string kPostinstallScript("/postinst");
 }
 
 void PostinstallRunnerAction::PerformAction() {
   CHECK(HasInputObject());
-  const string install_device = GetInputObject();
+  const InstallPlan install_plan = GetInputObject();
+  const string install_device = install_plan.install_path;
+  ScopedActionCompleter completer(processor_, this);
+  
+  utils::BootLoader boot_loader;
+  TEST_AND_RETURN(utils::GetBootloader(&boot_loader));
 
-  int rc = mount(install_device.c_str(), kMountPath.c_str(), "ext3", 0, NULL);
-  if (rc < 0) {
-    LOG(ERROR) << "Unable to mount destination device " << install_device
-               << " onto " << kMountPath;
-    processor_->ActionComplete(this, false);
-    return;
+  bool read_only = (boot_loader == utils::BootLoader_CHROME_FIRMWARE);
+
+  // Make mountpoint
+  string temp_dir;
+  TEST_AND_RETURN(utils::MakeTempDirectory("/tmp/au_postint_mount.XXXXXX",
+                                           &temp_dir));
+  ScopedDirRemover temp_dir_remover(temp_dir);
+
+  {
+    // Scope for the mount
+    unsigned long mountflags = read_only ? MS_RDONLY : 0;
+
+    int rc = mount(install_device.c_str(),
+                   temp_dir.c_str(),
+                   "ext3",
+                   mountflags,
+                   NULL);
+    if (rc < 0) {
+      LOG(ERROR) << "Unable to mount destination device " << install_device
+                 << " onto " << temp_dir;
+      return;
+    }
+    ScopedFilesystemUnmounter unmounter(temp_dir);
+
+    // run postinstall script
+    vector<string> command;
+    command.push_back(temp_dir + kPostinstallScript);
+    command.push_back(install_device);
+    command.push_back(precommit_ ? "" : "--postcommit");
+    rc = 0;
+    TEST_AND_RETURN(Subprocess::SynchronousExec(command, &rc));
+    bool success = (rc == 0);
+    if (!success) {
+      LOG(ERROR) << "Postinst command failed with code: " << rc;
+      return;
+    }
   }
 
-  // run postinstall script
-  rc = system((kMountPath + kPostinstallScript + " " + install_device).c_str());
-  bool success = (rc == 0);
-  if (!success) {
-    LOG(ERROR) << "Postinst command failed with code: " << rc;
+  if (HasOutputPipe()) {
+    SetOutputObject(install_plan);
   }
-
-  rc = umount(kMountPath.c_str());
-  if (rc < 0) {
-    // non-fatal
-    LOG(ERROR) << "Unable to umount destination device";
-  }
-  if (success && HasOutputPipe()) {
-    SetOutputObject(install_device);
-  }
-  processor_->ActionComplete(this, success);
+  completer.set_success(true);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/postinstall_runner_action.h b/postinstall_runner_action.h
index 00478cc..1169af9 100644
--- a/postinstall_runner_action.h
+++ b/postinstall_runner_action.h
@@ -7,6 +7,7 @@
 
 #include <string>
 #include "update_engine/action.h"
+#include "update_engine/install_plan.h"
 
 // The Postinstall Runner Action is responsible for running the postinstall
 // script of a successfully downloaded update.
@@ -20,14 +21,14 @@
 class ActionTraits<PostinstallRunnerAction> {
  public:
   // Takes the device path as input
-  typedef std::string InputObjectType;
+  typedef InstallPlan InputObjectType;
   // Passes the device path as output
-  typedef std::string OutputObjectType;
+  typedef InstallPlan OutputObjectType;
 };
 
 class PostinstallRunnerAction : public Action<PostinstallRunnerAction> {
  public:
-  PostinstallRunnerAction() {}
+  explicit PostinstallRunnerAction(bool precommit) : precommit_(precommit) {}
   typedef ActionTraits<PostinstallRunnerAction>::InputObjectType
       InputObjectType;
   typedef ActionTraits<PostinstallRunnerAction>::OutputObjectType
@@ -43,6 +44,10 @@
   std::string Type() const { return StaticType(); }
 
  private:
+  // If true, this action runs before we've committed to the new update
+  // (by marking it as bootable in the partition table).
+  bool precommit_;
+
   DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
 };
 
diff --git a/postinstall_runner_action_unittest.cc b/postinstall_runner_action_unittest.cc
index 7c1993e..9b19158 100644
--- a/postinstall_runner_action_unittest.cc
+++ b/postinstall_runner_action_unittest.cc
@@ -110,11 +110,13 @@
     ASSERT_EQ(0, System(string("losetup ") + dev + " " + cwd + "/image.dat"));
 
   ActionProcessor processor;
-  ObjectFeederAction<string> feeder_action;
-  feeder_action.set_obj(dev);
-  PostinstallRunnerAction runner_action;
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan;
+  install_plan.install_path = dev;
+  feeder_action.set_obj(install_plan);
+  PostinstallRunnerAction runner_action(true);
   BondActions(&feeder_action, &runner_action);
-  ObjectCollectorAction<string> collector_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
   BondActions(&runner_action, &collector_action);
   PostinstActionProcessorDelegate delegate;
   processor.EnqueueAction(&feeder_action);
@@ -127,9 +129,10 @@
 
   EXPECT_TRUE(delegate.success_set_);
   EXPECT_EQ(do_losetup && !do_err_script, delegate.success_);
-  EXPECT_EQ(do_losetup && !do_err_script, !collector_action.object().empty());
+  EXPECT_EQ(do_losetup && !do_err_script,
+            !collector_action.object().install_path.empty());
   if (do_losetup && !do_err_script) {
-    EXPECT_EQ(dev, collector_action.object());
+    EXPECT_TRUE(install_plan == collector_action.object());
   }
 
   struct stat stbuf;
@@ -148,7 +151,7 @@
 // Death tests don't seem to be working on Hardy
 TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
   ASSERT_EQ(0, getuid());
-  PostinstallRunnerAction runner_action;
+  PostinstallRunnerAction runner_action(true);
   ASSERT_DEATH({ runner_action.TerminateProcessing(); },
                "postinstall_runner_action.h:.*] Check failed");
 }
diff --git a/set_bootable_flag_action.cc b/set_bootable_flag_action.cc
index 8075b2c..ad574cc 100644
--- a/set_bootable_flag_action.cc
+++ b/set_bootable_flag_action.cc
@@ -8,110 +8,70 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string>
+#include <vector>
+#include "update_engine/subprocess.h"
 #include "update_engine/utils.h"
 
 using std::string;
+using std::vector;
 
 namespace chromeos_update_engine {
 
+namespace {
+const char* const kGpt = "/usr/bin/gpt";
+const ssize_t kPmbrLength = 512;
+}
+
 void SetBootableFlagAction::PerformAction() {
   ScopedActionCompleter completer(processor_, this);
 
-  if (!HasInputObject() || GetInputObject().empty()) {
-    LOG(ERROR) << "SetBootableFlagAction: No input object.";
-    return;
+  TEST_AND_RETURN(HasInputObject());
+  const InstallPlan install_plan = GetInputObject();
+  TEST_AND_RETURN(!install_plan.install_path.empty());
+  const string partition = install_plan.install_path;
+  string root_device = utils::RootDevice(partition);
+  string partition_number = utils::PartitionNumber(partition);
+
+  utils::BootLoader boot_loader;
+  TEST_AND_RETURN(utils::GetBootloader(&boot_loader));
+
+  // For now, only support Syslinux bootloader
+  TEST_AND_RETURN(boot_loader == utils::BootLoader_SYSLINUX);
+
+  string temp_file;
+  TEST_AND_RETURN(utils::MakeTempFile("/tmp/pmbr_copy.XXXXXX",
+                                      &temp_file,
+                                      NULL));
+  ScopedPathUnlinker temp_file_unlinker(temp_file);
+
+  // Copy existing PMBR to temp file
+  vector<char> buf(kPmbrLength);
+  {
+    int fd = open(root_device.c_str(), O_RDONLY);
+    TEST_AND_RETURN(fd >= 0);
+    ScopedFdCloser fd_closer(&fd);
+    ssize_t bytes_read;
+    TEST_AND_RETURN(utils::PReadAll(fd, &buf[0], buf.size(), 0, &bytes_read));
   }
-  string device = GetInputObject();
+  TEST_AND_RETURN(utils::WriteFile(temp_file.c_str(), &buf[0], buf.size()));
 
-  if (device.size() < 2) {
-    LOG(ERROR) << "Device name too short: " << device;
-    return;
-  }
+  // Call gpt tool to do the work
+  vector<string> command;
+  command.push_back(kGpt);
+  command.push_back("-S");
+  command.push_back("boot");
+  command.push_back("-i");
+  command.push_back(partition_number);
+  command.push_back("-b");
+  command.push_back(temp_file);
+  command.push_back(root_device);
+  int rc = 0;
+  Subprocess::SynchronousExec(command, &rc);
+  TEST_AND_RETURN(rc == 0);
 
-  // Make sure device is valid.
-  const char last_char = device[device.size() - 1];
-  if ((last_char < '1') || (last_char > '4')) {
-    LOG(ERROR) << "Bad device:" << device;
-    return;
-  }
-
-  // We were passed the partition_number'th partition; indexed from 1, not 0
-  int partition_number = last_char - '0';
-
-  const char second_to_last_char = device[device.size() - 2];
-  if ((second_to_last_char >= '0') && (second_to_last_char <= '9')) {
-    LOG(ERROR) << "Bad device:" << device;
-    return;
-  }
-
-  // Strip trailing 1-4 off end of device
-  device.resize(device.size() - 1);
-
-  char mbr[512];  // MBR is the first 512 bytes of the device
-  if (!ReadMbr(mbr, sizeof(mbr), device.c_str()))
-    return;
-
-  // Check MBR. Verify that last two byes are 0x55aa. This is the MBR signature.
-  if ((mbr[510] != static_cast<char>(0x55)) ||
-      (mbr[511] != static_cast<char>(0xaa))) {
-    LOG(ERROR) << "Bad MBR signature";
-    return;
-  }
-
-  // Mark partition passed in bootable and all other partitions non bootable.
-  // There are 4 partition table entries, each 16 bytes, stored consecutively
-  // beginning at byte 446. Within each entry, the first byte is the
-  // bootable flag. It's set to 0x80 (bootable) or 0x00 (not bootable).
-  for (int i = 0; i < 4; i++) {
-    int offset = 446 + 16 * i;
-
-    // partition_number is indexed from 1, not 0
-    if ((i + 1) == partition_number)
-      mbr[offset] = 0x80;
-    else
-      mbr[offset] = '\0';
-  }
-
-  // Write MBR back to disk
-  bool success = WriteMbr(mbr, sizeof(mbr), device.c_str());
-  if (success) {
-    completer.set_success(true);
-    if (HasOutputPipe()) {
-      SetOutputObject(GetInputObject());
-    }
-  }
+  completer.set_success(true);
+  if (HasOutputPipe())
+    SetOutputObject(GetInputObject());
 }
 
-bool SetBootableFlagAction::ReadMbr(char* buffer,
-                                    int buffer_len,
-                                    const char* device) {
-  int fd = open(device, O_RDONLY, 0);
-  TEST_AND_RETURN_FALSE(fd >= 0);
-
-  ssize_t r = read(fd, buffer, buffer_len);
-  close(fd);
-  TEST_AND_RETURN_FALSE(r == buffer_len);
-
-  return true;
-}
-
-bool SetBootableFlagAction::WriteMbr(const char* buffer,
-                                     int buffer_len,
-                                     const char* device) {
-  int fd = open(device, O_WRONLY, 0666);
-  TEST_AND_RETURN_FALSE(fd >= 0);
-  ScopedFdCloser fd_closer(&fd);
-
-  ssize_t bytes_written = 0;
-  while (bytes_written < buffer_len) {
-    ssize_t r = write(fd, buffer + bytes_written, buffer_len - bytes_written);
-    TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
-    bytes_written += r;
-  }
-  TEST_AND_RETURN_FALSE(bytes_written == buffer_len);
-
-  return true;
-}
-
-
 }  // namespace chromeos_update_engine
diff --git a/set_bootable_flag_action.h b/set_bootable_flag_action.h
index 44e9555..2b45df3 100644
--- a/set_bootable_flag_action.h
+++ b/set_bootable_flag_action.h
@@ -7,6 +7,7 @@
 
 #include <string>
 #include "update_engine/action.h"
+#include "update_engine/install_plan.h"
 
 // This class takes in a device via the input pipe.  The device is the
 // partition (e.g. /dev/sda1), not the full device (e.g. /dev/sda).
@@ -22,9 +23,9 @@
 class ActionTraits<SetBootableFlagAction> {
  public:
   // Takes the device path as input.
-  typedef std::string InputObjectType;
+  typedef InstallPlan InputObjectType;
   // Passes the device path as output
-  typedef std::string OutputObjectType;
+  typedef InstallPlan OutputObjectType;
 };
 
 class SetBootableFlagAction : public Action<SetBootableFlagAction> {
@@ -45,11 +46,6 @@
   std::string Type() const { return StaticType(); }
 
  private:
-  // Returns true on success
-  bool ReadMbr(char* buffer, int buffer_len, const char* device);
-
-  // Returns true on success
-  bool WriteMbr(const char* buffer, int buffer_len, const char* device);
 
   DISALLOW_COPY_AND_ASSIGN(SetBootableFlagAction);
 };
diff --git a/set_bootable_flag_action_unittest.cc b/set_bootable_flag_action_unittest.cc
index 023c7bd..d2a211b 100644
--- a/set_bootable_flag_action_unittest.cc
+++ b/set_bootable_flag_action_unittest.cc
@@ -17,137 +17,17 @@
 
 namespace chromeos_update_engine {
 
-class SetBootableFlagActionProcessorDelegate : public ActionProcessorDelegate {
- public:
-  SetBootableFlagActionProcessorDelegate()
-      : success_(false), success_set_(false) {}
-  void ActionCompleted(ActionProcessor* processor,
-                       AbstractAction* action,
-                       bool success) {
-    if (action->Type() == SetBootableFlagAction::StaticType()) {
-      success_ = success;
-      success_set_ = true;
-    }
-  }
-  bool success_;
-  bool success_set_;
-};
+class SetBootableFlagActionTest : public ::testing::Test {};
 
-class SetBootableFlagActionTest : public ::testing::Test {
- public:
-  SetBootableFlagActionTest();
- protected:
-  enum TestFlags {
-    EXPECT_SUCCESS = 0x01,
-    WRITE_FILE = 0x02,
-    SKIP_INPUT_OBJECT = 0x04
-  };
-  static const char* kTestDir;
-  virtual void SetUp() {
-    EXPECT_EQ(0, mkdir(kTestDir, 0755));
-  }
-  virtual void TearDown() {
-    EXPECT_EQ(0, System(string("rm -rf ") + kTestDir));
-  }
-  vector<char> DoTest(vector<char> mbr_in,
-                      const string& filename,
-                      uint32_t test_flags);
-  // first partition bootable, no others bootable
-  const vector<char> sample_mbr_;
-};
-
-const char* SetBootableFlagActionTest::kTestDir =
-    "SetBootableFlagActionTestDir";
-
-SetBootableFlagActionTest::SetBootableFlagActionTest()
-    : sample_mbr_(GenerateSampleMbr()) {}
-
-vector<char> SetBootableFlagActionTest::DoTest(vector<char> mbr_in,
-                                               const string& filename,
-                                               uint32_t flags) {
-  CHECK(!filename.empty());
-  const string root_filename(filename.begin(), --filename.end());
-  if (flags & WRITE_FILE)
-    EXPECT_TRUE(WriteFileVector(root_filename, mbr_in));
-
-  ActionProcessor processor;
-  SetBootableFlagActionProcessorDelegate delegate;
-  processor.set_delegate(&delegate);
-
-  ObjectFeederAction<string> feeder_action;
-  feeder_action.set_obj(filename);
-  SetBootableFlagAction set_bootable_action;
-  if (!(flags & SKIP_INPUT_OBJECT))
-    BondActions(&feeder_action, &set_bootable_action);
-  ObjectCollectorAction<string> collector_action;
-  BondActions(&set_bootable_action, &collector_action);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&set_bootable_action);
-  processor.EnqueueAction(&collector_action);
-  processor.StartProcessing();
-  EXPECT_TRUE(!processor.IsRunning())
-      << "Update test to handle non-asynch actions";
-
-  EXPECT_TRUE(delegate.success_set_);
-  EXPECT_EQ(flags & EXPECT_SUCCESS, delegate.success_);
-
-  vector<char> new_mbr;
-  if (flags & WRITE_FILE)
-    utils::ReadFile(root_filename, &new_mbr);
-
-  unlink(root_filename.c_str());
-  return new_mbr;
+// These disabled tests are a reminder this needs to change.
+TEST_F(SetBootableFlagActionTest, DISABLED_SimpleTest) {
+  // TODO(adlr): find a way to test this object.
 }
 
-TEST_F(SetBootableFlagActionTest, SimpleTest) {
-  for (int i = 0; i < 4; i++) {
-    vector<char> expected_new_mbr = sample_mbr_;
-    for (int j = 0; j < 4; j++)
-      expected_new_mbr[446 + 16 * j] = '\0';  // mark non-bootable
-    expected_new_mbr[446 + 16 * i] = 0x80;  // mark bootable
-
-    string filename(string(kTestDir) + "/SetBootableFlagActionTest.devX");
-    filename[filename.size() - 1] = '1' + i;
-
-    vector<char> actual_new_mbr = DoTest(expected_new_mbr, filename,
-                                         EXPECT_SUCCESS | WRITE_FILE);
-
-    ExpectVectorsEq(expected_new_mbr, actual_new_mbr);
-  }
+TEST_F(SetBootableFlagActionTest, DISABLED_BadDeviceTest) {
 }
 
-TEST_F(SetBootableFlagActionTest, BadDeviceTest) {
-  vector<char> actual_new_mbr = DoTest(sample_mbr_,
-                                       string(kTestDir) +
-                                       "SetBootableFlagActionTest.dev5",
-                                       WRITE_FILE);
-  ExpectVectorsEq(sample_mbr_, actual_new_mbr);  // no change
-
-  actual_new_mbr = DoTest(sample_mbr_,
-                          string(kTestDir) + "SetBootableFlagActionTest.dev13",
-                          WRITE_FILE);
-  ExpectVectorsEq(sample_mbr_, actual_new_mbr);  // no change
-
-  actual_new_mbr = DoTest(sample_mbr_,
-                          "/some/nonexistent/file3",
-                          0);
-  EXPECT_TRUE(actual_new_mbr.empty());
-
-  vector<char> bad_mbr = sample_mbr_;
-  bad_mbr[510] = 'x';  // makes signature invalid
-
-  actual_new_mbr = DoTest(bad_mbr,
-                          string(kTestDir) + "SetBootableFlagActionTest.dev2",
-                          WRITE_FILE);
-  ExpectVectorsEq(bad_mbr, actual_new_mbr);  // no change
-}
-
-TEST_F(SetBootableFlagActionTest, NoInputObjectTest) {
-  vector<char> actual_new_mbr = DoTest(sample_mbr_,
-                                       string(kTestDir) +
-                                       "SetBootableFlagActionTest.dev5",
-                                       WRITE_FILE | SKIP_INPUT_OBJECT);
-  ExpectVectorsEq(sample_mbr_, actual_new_mbr);  // no change
+TEST_F(SetBootableFlagActionTest, DISABLED_NoInputObjectTest) {
 }
 
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.cc b/update_attempter.cc
index 84410ad..e12fed9 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -36,14 +36,16 @@
       new OmahaResponseHandlerAction);
   shared_ptr<FilesystemCopierAction> filesystem_copier_action(
       new FilesystemCopierAction(false));
-  shared_ptr<FilesystemCopierAction> filesystem_copier_action_kernel(
+  shared_ptr<FilesystemCopierAction> kernel_filesystem_copier_action(
       new FilesystemCopierAction(true));
   shared_ptr<DownloadAction> download_action(
       new DownloadAction(new LibcurlHttpFetcher));
-  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
-      new PostinstallRunnerAction);
+  shared_ptr<PostinstallRunnerAction> postinstall_runner_action_precommit(
+      new PostinstallRunnerAction(true));
   shared_ptr<SetBootableFlagAction> set_bootable_flag_action(
       new SetBootableFlagAction);
+  shared_ptr<PostinstallRunnerAction> postinstall_runner_action_postcommit(
+      new PostinstallRunnerAction(false));
       
   response_handler_action_ = response_handler_action;
 
@@ -52,10 +54,13 @@
   actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
   actions_.push_back(shared_ptr<AbstractAction>(filesystem_copier_action));
   actions_.push_back(shared_ptr<AbstractAction>(
-      filesystem_copier_action_kernel));
+      kernel_filesystem_copier_action));
   actions_.push_back(shared_ptr<AbstractAction>(download_action));
-  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      postinstall_runner_action_precommit));
   actions_.push_back(shared_ptr<AbstractAction>(set_bootable_flag_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      postinstall_runner_action_postcommit));
   
   // Enqueue the actions
   for (vector<shared_ptr<AbstractAction> >::iterator it = actions_.begin();
@@ -69,13 +74,14 @@
   BondActions(update_check_action.get(), response_handler_action.get());
   BondActions(response_handler_action.get(), filesystem_copier_action.get());
   BondActions(response_handler_action.get(),
-              filesystem_copier_action_kernel.get());
-  BondActions(filesystem_copier_action_kernel.get(),
+              kernel_filesystem_copier_action.get());
+  BondActions(kernel_filesystem_copier_action.get(),
               download_action.get());
-  // TODO(adlr): Bond these actions together properly
-  // BondActions(download_action.get(), install_action.get());
-  // BondActions(install_action.get(), postinstall_runner_action.get());
-  BondActions(postinstall_runner_action.get(), set_bootable_flag_action.get());
+  BondActions(download_action.get(), postinstall_runner_action_precommit.get());
+  BondActions(postinstall_runner_action_precommit.get(),
+              set_bootable_flag_action.get());
+  BondActions(set_bootable_flag_action.get(),
+              postinstall_runner_action_postcommit.get());
 
   processor_.StartProcessing();
 }
diff --git a/update_check_action.cc b/update_check_action.cc
index b92e788..106400b 100644
--- a/update_check_action.cc
+++ b/update_check_action.cc
@@ -22,6 +22,8 @@
     "{87efface-864d-49a5-9bb3-4b050a7c227a}");
 const char* const UpdateCheckParams::kOsPlatform("Chrome OS");
 const char* const UpdateCheckParams::kOsVersion("Indy");
+const char* const UpdateCheckParams::kUpdateUrl(
+    "https://tools.google.com/service/update2");
 
 namespace {
 
@@ -105,7 +107,7 @@
   http_fetcher_->set_delegate(this);
   string request_post(FormatRequest(params_));
   http_fetcher_->SetPostData(request_post.data(), request_post.size());
-  http_fetcher_->BeginTransfer("https://tools.google.com/service/update2");
+  http_fetcher_->BeginTransfer(params_.update_url);
 }
 
 void UpdateCheckAction::TerminateProcessing() {
diff --git a/update_check_action.h b/update_check_action.h
index da48aba..831039d 100644
--- a/update_check_action.h
+++ b/update_check_action.h
@@ -39,7 +39,8 @@
                     const std::string& in_app_id,
                     const std::string& in_app_version,
                     const std::string& in_app_lang,
-                    const std::string& in_app_track)
+                    const std::string& in_app_track,
+                    const std::string& in_update_url)
       : machine_id(in_machine_id),
         user_id(in_user_id),
         os_platform(in_os_platform),
@@ -48,22 +49,26 @@
         app_id(in_app_id),
         app_version(in_app_version),
         app_lang(in_app_lang),
-        app_track(in_app_track) {}
+        app_track(in_app_track),
+        update_url(in_update_url) {}
 
-    std::string machine_id;
-    std::string user_id;
-    std::string os_platform;
-    std::string os_version;
-    std::string os_sp;
-    std::string app_id;
-    std::string app_version;
-    std::string app_lang;
-    std::string app_track;
+  std::string machine_id;
+  std::string user_id;
+  std::string os_platform;
+  std::string os_version;
+  std::string os_sp;
+  std::string app_id;
+  std::string app_version;
+  std::string app_lang;
+  std::string app_track;
+  
+  std::string update_url;
 
   // Suggested defaults
   static const char* const kAppId;
   static const char* const kOsPlatform;
   static const char* const kOsVersion;
+  static const char* const kUpdateUrl;
 };
 
 // This struct encapsulates the data Omaha returns for the update check.
diff --git a/update_check_action_unittest.cc b/update_check_action_unittest.cc
index c57e2cd..c730b40 100644
--- a/update_check_action_unittest.cc
+++ b/update_check_action_unittest.cc
@@ -162,7 +162,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest");
+                           "unittest",
+                           "");  // url
   UpdateCheckResponse response;
   ASSERT_TRUE(
       TestUpdateCheckAction(params,
@@ -182,7 +183,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "");  // url
   UpdateCheckResponse response;
   ASSERT_TRUE(
       TestUpdateCheckAction(params,
@@ -216,7 +218,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest");
+                           "unittest",
+                           "");  // url
   const string http_response(GetNoUpdateResponse(UpdateCheckParams::kAppId));
 
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
@@ -248,7 +251,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_FALSE(
       TestUpdateCheckAction(params,
@@ -268,7 +272,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_FALSE(TestUpdateCheckAction(
       params,
@@ -291,7 +296,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_FALSE(TestUpdateCheckAction(
       params,
@@ -314,7 +320,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_FALSE(TestUpdateCheckAction(
       params,
@@ -337,7 +344,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_TRUE(TestUpdateCheckAction(params,
                                     string("<?xml version=\"1.0\" "
@@ -395,7 +403,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest");
+                           "unittest",
+                           "http://url");
   string http_response("doesn't matter");
   GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
 
@@ -434,7 +443,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_FALSE(
       TestUpdateCheckAction(params,
@@ -461,7 +471,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_TRUE(
       TestUpdateCheckAction(params,
@@ -490,7 +501,8 @@
                            UpdateCheckParams::kAppId,
                            "0.1.0.0",
                            "en-US",
-                           "unittest_track");
+                           "unittest_track",
+                           "http://url");
   UpdateCheckResponse response;
   ASSERT_TRUE(
       TestUpdateCheckAction(params,
diff --git a/utils.cc b/utils.cc
index 83096d2..b19e6fc 100644
--- a/utils.cc
+++ b/utils.cc
@@ -195,6 +195,30 @@
   return true;
 }
 
+string RootDevice(const string& partition_device) {
+  CHECK(!partition_device.empty());
+  string::const_iterator it = --partition_device.end();
+  for (; it >= partition_device.begin(); --it) {
+    if (!isdigit(*it))
+      break;
+  }
+  // Some devices contain a p before the partitions. For example:
+  // /dev/mmc0p4 should be shortened to /dev/mmc0.
+  if (*it == 'p')
+    --it;
+  return string(partition_device.begin(), it + 1);
+}
+
+string PartitionNumber(const string& partition_device) {
+  CHECK(!partition_device.empty());
+  string::const_iterator it = --partition_device.end();
+  for (; it >= partition_device.begin(); --it) {
+    if (!isdigit(*it))
+      break;
+  }
+  return string(it + 1, partition_device.end());
+}
+
 std::string ErrnoNumberAsString(int err) {
   char buf[100];
   buf[0] = '\0';
@@ -357,6 +381,12 @@
   return true;
 }
 
+bool GetBootloader(BootLoader* out_bootloader) {
+  // For now, hardcode to syslinux.
+  *out_bootloader = BootLoader_SYSLINUX;
+  return true;
+}
+
 const char* GetGErrorMessage(const GError* error) {
   if (!error)
     return "Unknown error.";
diff --git a/utils.h b/utils.h
index e5d099f..c6edb7d 100644
--- a/utils.h
+++ b/utils.h
@@ -77,12 +77,28 @@
 // This WILL cross filesystem boundaries.
 bool RecursiveUnlinkDir(const std::string& path);
 
+// Returns the root device for a partition. For example,
+// RootDevice("/dev/sda3") returns "/dev/sda".
+std::string RootDevice(const std::string& partition_device);
+
+// Returns the partition number, as a string, of partition_device. For example,
+// PartitionNumber("/dev/sda3") return "3".
+std::string PartitionNumber(const std::string& partition_device);
+
 // Synchronously mount or unmount a filesystem. Return true on success.
 // Mounts as ext3 with default options.
 bool MountFilesystem(const std::string& device, const std::string& mountpoint,
                      unsigned long flags);
 bool UnmountFilesystem(const std::string& mountpoint);
 
+enum BootLoader {
+  BootLoader_SYSLINUX = 0,
+  BootLoader_CHROME_FIRMWARE = 1
+};
+// Detects which bootloader this system uses and returns it via the out
+// param. Returns true on success.
+bool GetBootloader(BootLoader* out_bootloader);
+
 // Returns the error message, if any, from a GError pointer.
 const char* GetGErrorMessage(const GError* error);
 
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 5ab78cd..a66e739 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -125,4 +125,14 @@
   EXPECT_FALSE(utils::StringHasSuffix(result, "XXXXXX"));
 }
 
+TEST(UtilsTest, RootDeviceTest) {
+  EXPECT_EQ("/dev/sda", utils::RootDevice("/dev/sda3"));
+  EXPECT_EQ("/dev/mmc0", utils::RootDevice("/dev/mmc0p3"));
+}
+
+TEST(UtilsTest, PartitionNumberTest) {
+  EXPECT_EQ("3", utils::PartitionNumber("/dev/sda3"));
+  EXPECT_EQ("3", utils::PartitionNumber("/dev/mmc0p3"));
+}
+
 }  // namespace chromeos_update_engine