Missed new files in last commit

Review URL: http://codereview.chromium.org/465067


git-svn-id: svn://chrome-svn/chromeos/trunk@336 06c00378-0e64-4dae-be16-12b19f9950a1
diff --git a/delta_diff_generator.cc b/delta_diff_generator.cc
new file mode 100644
index 0000000..6f352c7
--- /dev/null
+++ b/delta_diff_generator.cc
@@ -0,0 +1,423 @@
+// Copyright (c) 2009 The Chromium 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/delta_diff_generator.h"
+#include <dirent.h>
+#include <endian.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <algorithm>
+#include <vector>
+#include <tr1/memory>
+#include <zlib.h>
+#include "chromeos/obsolete_logging.h"
+#include "base/scoped_ptr.h"
+#include "update_engine/delta_diff_parser.h"
+#include "update_engine/gzip.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+using std::vector;
+using std::tr1::shared_ptr;
+using chromeos_update_engine::DeltaArchiveManifest;
+
+namespace chromeos_update_engine {
+
+namespace {
+// These structs and methods are helpers for EncodeDataToDeltaFile()
+
+// Before moving the data into a proto buffer, the data is stored in
+// memory in these Node and Child structures.
+
+// Each Node struct represents a file on disk (which can be regular file,
+// directory, fifo, socket, symlink, etc). Nodes that contain children
+// (just directories) will have a vector of Child objects. Each child
+// object has a filename and a pointer to the associated Node. Thus,
+// filenames for files are stored with their parents, not as part of
+// the file itself.
+
+// These structures are easier to work with than the protobuf format
+// when adding files. When generating a delta file, we add an entry
+// for each file to a root Node object. Then, we sort each Node's
+// children vector so the children are stored alphabetically. Then,
+// we assign an index value to the idx field of each Node by a preorder
+// tree traversal. The index value assigned to a Node is the index it
+// will have in the DeltaArchiveManifest protobuf.
+// Finally, we add each Node to a DeltaArchiveManifest protobuf.
+
+struct Node;
+
+struct Child {
+  Child(const string& the_name,
+        Node* the_node)
+      : name(the_name),
+        node(the_node) {}
+  string name;
+  // Use shared_ptr here rather than scoped_ptr b/c this struct will be copied
+  // in stl containers
+  scoped_ptr<Node> node;
+};
+
+// For the C++ sort() function.
+struct ChildLessThan {
+  bool operator()(const shared_ptr<Child>& a, const shared_ptr<Child>& b) {
+    return a->name < b->name;
+  }
+};
+
+struct Node {
+  Node()
+      : mode(0),
+        uid(0),
+        gid(0),
+        compressed(false),
+        offset(-1),
+        length(0),
+        idx(0) {}
+
+  mode_t mode;
+  uid_t uid;
+  gid_t gid;
+
+  // data
+  bool compressed;
+  int offset;  // -1 means no data
+  int length;
+
+  vector<shared_ptr<Child> > children;
+  int idx;
+};
+
+// This function sets *node's variables to match what's at path.
+// This includes calling this function recursively on all children. Children
+// not on the same device as the original node will not be considered.
+// Returns true on success.
+bool UpdateNodeFromPath(const string& path, Node* node) {
+  // Set metadata
+  struct stat stbuf;
+  TEST_AND_RETURN_FALSE_ERRNO(lstat(path.c_str(), &stbuf) == 0);
+  const dev_t dev = stbuf.st_dev;
+  node->mode = stbuf.st_mode;
+  node->uid = stbuf.st_uid;
+  node->gid = stbuf.st_gid;
+  if (!S_ISDIR(node->mode)) {
+    return true;
+  }
+
+  DIR* dir = opendir(path.c_str());
+  TEST_AND_RETURN_FALSE(dir);
+
+  struct dirent entry;
+  struct dirent* dir_entry;
+
+  for (;;) {
+    TEST_AND_RETURN_FALSE_ERRNO(readdir_r(dir, &entry, &dir_entry) == 0);
+    if (!dir_entry) {
+      // done
+      break;
+    }
+    if (!strcmp(".", dir_entry->d_name))
+      continue;
+    if (!strcmp("..", dir_entry->d_name))
+      continue;
+
+    string child_path = path + "/" + dir_entry->d_name;
+    struct stat child_stbuf;
+    TEST_AND_RETURN_FALSE_ERRNO(lstat(child_path.c_str(), &child_stbuf) == 0);
+    // make sure it's on the same dev
+    if (child_stbuf.st_dev != dev)
+      continue;
+    shared_ptr<Child> child(new Child(dir_entry->d_name, new Node));
+    node->children.push_back(child);
+    TEST_AND_RETURN_FALSE(UpdateNodeFromPath(path + "/" + child->name,
+                                             child->node.get()));
+  }
+  TEST_AND_RETURN_FALSE_ERRNO(closedir(dir) == 0);
+  // Done with all subdirs. sort children.
+  sort(node->children.begin(), node->children.end(), ChildLessThan());
+  return true;
+}
+
+// We go through n setting the index value of each Node to
+// *next_index_value, then increment next_index_value.
+// We then recursively assign index values to children.
+// The first caller should call this with *next_index_value == 0 and
+// the root Node, thus setting the root Node's index to 0.
+void PopulateChildIndexes(Node* n, int* next_index_value) {
+  n->idx = (*next_index_value)++;
+  for (unsigned int i = 0; i < n->children.size(); i++) {
+    PopulateChildIndexes(n->children[i]->node.get(), next_index_value);
+  }
+}
+
+// This converts a Node tree rooted at n into a DeltaArchiveManifest.
+void NodeToDeltaArchiveManifest(Node* n, DeltaArchiveManifest* archive) {
+  DeltaArchiveManifest_File *f = archive->add_files();
+  f->set_mode(n->mode);
+  f->set_uid(n->uid);
+  f->set_gid(n->gid);
+  if (!S_ISDIR(n->mode))
+    return;
+  for (unsigned int i = 0; i < n->children.size(); i++) {
+    DeltaArchiveManifest_File_Child* child = f->add_children();
+    child->set_name(n->children[i]->name);
+    child->set_index(n->children[i]->node->idx);
+  }
+  for (unsigned int i = 0; i < n->children.size(); i++) {
+    NodeToDeltaArchiveManifest(n->children[i]->node.get(), archive);
+  }
+}
+
+}  // namespace {}
+
+// For each file in archive, write a delta for it into out_file
+// and update 'file' to refer to the delta.
+// This is a recursive function. Returns true on success.
+bool DeltaDiffGenerator::WriteFileDiffsToDeltaFile(
+    DeltaArchiveManifest* archive,
+    DeltaArchiveManifest_File* file,
+    const std::string& file_name,
+    const std::string& old_path,
+    const std::string& new_path,
+    FileWriter* out_file_writer,
+    int* out_file_length) {
+  TEST_AND_RETURN_FALSE(file->has_mode());
+
+  // Stat the actual file, too
+  struct stat stbuf;
+  TEST_AND_RETURN_FALSE_ERRNO(lstat((new_path + "/" + file_name).c_str(),
+                                    &stbuf) == 0);
+  TEST_AND_RETURN_FALSE(stbuf.st_mode == file->mode());
+
+  // See if we're a directory or not
+  if (S_ISDIR(file->mode())) {
+    for (int i = 0; i < file->children_size(); i++) {
+      DeltaArchiveManifest_File_Child* child = file->mutable_children(i);
+      DeltaArchiveManifest_File* child_file =
+          archive->mutable_files(child->index());
+      TEST_AND_RETURN_FALSE(WriteFileDiffsToDeltaFile(
+          archive,
+          child_file,
+          child->name(),
+          old_path + "/" + file_name,
+          new_path + "/" + file_name,
+          out_file_writer,
+          out_file_length));
+    }
+    return true;
+  }
+
+  if (S_ISFIFO(file->mode()) || S_ISSOCK(file->mode())) {
+    // These don't store any additional data
+    return true;
+  }
+
+  vector<char> data;
+  bool should_compress = true;
+  bool format_set = false;
+  DeltaArchiveManifest_File_DataFormat format;
+  if (S_ISLNK(file->mode())) {
+    LOG(INFO) << "link";
+    TEST_AND_RETURN_FALSE(EncodeLink(new_path + "/" + file_name, &data));
+  } else if (S_ISCHR(file->mode()) || S_ISBLK(file->mode())) {
+    LOG(INFO) << "dev";
+    TEST_AND_RETURN_FALSE(EncodeDev(stbuf, &data));
+  } else if (S_ISREG(file->mode())) {
+    LOG(INFO) << "reg";
+    // regular file. We may use a delta here.
+    TEST_AND_RETURN_FALSE(EncodeFile(old_path, new_path, file_name, &format,
+                                     &data));
+    should_compress = false;
+    format_set = true;
+    if ((format == DeltaArchiveManifest_File_DataFormat_BSDIFF) ||
+        (format == DeltaArchiveManifest_File_DataFormat_FULL_GZ))
+      TEST_AND_RETURN_FALSE(!data.empty());
+  } else {
+    // Should never get here; unhandled mode type.
+    LOG(ERROR) << "Unhandled mode type: " << file->mode();
+    return false;
+  }
+  LOG(INFO) << "data len: " << data.size();
+  if (!format_set) {
+    // Pick a format now
+    vector<char> compressed_data;
+    TEST_AND_RETURN_FALSE(GzipCompress(data, &compressed_data));
+    if (compressed_data.size() < data.size()) {
+      format = DeltaArchiveManifest_File_DataFormat_FULL_GZ;
+      data.swap(compressed_data);
+    } else {
+      format = DeltaArchiveManifest_File_DataFormat_FULL;
+    }
+    format_set = true;
+  }
+
+  if (!data.empty()) {
+    TEST_AND_RETURN_FALSE(format_set);
+    file->set_data_format(format);
+    file->set_data_offset(*out_file_length);
+    TEST_AND_RETURN_FALSE(static_cast<ssize_t>(data.size()) ==
+                          out_file_writer->Write(&data[0], data.size()));
+    file->set_data_length(data.size());
+    *out_file_length += data.size();
+  }
+  return true;
+}
+
+bool DeltaDiffGenerator::EncodeLink(const std::string& path,
+                                    std::vector<char>* out) {
+  // Store symlink path as file data
+  vector<char> link_data(4096);
+  int rc = readlink(path.c_str(), &link_data[0], link_data.size());
+  TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+  link_data.resize(rc);
+  out->swap(link_data);
+  return true;
+}
+
+bool DeltaDiffGenerator::EncodeDev(const struct stat& stbuf,
+                                   std::vector<char>* out) {
+  LinuxDevice dev;
+  dev.set_major(major(stbuf.st_rdev));
+  dev.set_minor(minor(stbuf.st_rdev));
+  out->resize(dev.ByteSize());
+  TEST_AND_RETURN_FALSE(dev.SerializeToArray(&(*out)[0], out->size()));
+  return true;
+}
+
+// Encode the file at new_path + "/" + file_name. It may be a binary diff
+// based on old_path + "/" + file_name. out_data_format will be set to
+// the format used. out_data_format may not be NULL.
+bool DeltaDiffGenerator::EncodeFile(
+    const string& old_dir,
+    const string& new_dir,
+    const string& file_name,
+    DeltaArchiveManifest_File_DataFormat* out_data_format,
+    vector<char>* out) {
+  TEST_AND_RETURN_FALSE(out_data_format);
+  // First, see the full length:
+  vector<char> full_data;
+  TEST_AND_RETURN_FALSE(utils::ReadFile(new_dir + "/" + file_name, &full_data));
+  vector<char> gz_data;
+  if (!full_data.empty()) {
+    TEST_AND_RETURN_FALSE(GzipCompress(full_data, &gz_data));
+  }
+  vector<char> *ret = NULL;
+
+  if (gz_data.size() < full_data.size()) {
+    *out_data_format = DeltaArchiveManifest_File_DataFormat_FULL_GZ;
+    ret = &gz_data;
+  } else {
+    *out_data_format = DeltaArchiveManifest_File_DataFormat_FULL;
+    ret = &full_data;
+  }
+
+  struct stat old_stbuf;
+  if ((stat((old_dir + "/" + file_name).c_str(), &old_stbuf) < 0) ||
+      (!S_ISREG(old_stbuf.st_mode))) {
+    // stat() failed or old file is not a regular file. Just send back the full
+    // contents
+    *out = *ret;
+    return true;
+  }
+  // We have an old file. Do a binary diff. For now use bsdiff.
+  const string kPatchFile = "/tmp/delta.patch";
+
+  vector<string> cmd;
+  cmd.push_back("/usr/bin/bsdiff");
+  cmd.push_back(old_dir + "/" + file_name);
+  cmd.push_back(new_dir + "/" + file_name);
+  cmd.push_back(kPatchFile);
+
+  int rc = 1;
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc));
+  TEST_AND_RETURN_FALSE(rc == 0);
+  vector<char> patch_file;
+  TEST_AND_RETURN_FALSE(utils::ReadFile(kPatchFile, &patch_file));
+  unlink(kPatchFile.c_str());
+
+  if (patch_file.size() < ret->size()) {
+    *out_data_format = DeltaArchiveManifest_File_DataFormat_BSDIFF;
+    ret = &patch_file;
+  }
+
+  *out = *ret;
+  return true;
+}
+
+DeltaArchiveManifest* DeltaDiffGenerator::EncodeMetadataToProtoBuffer(
+    const char* new_path) {
+  Node node;
+  if (!UpdateNodeFromPath(new_path, &node))
+    return NULL;
+  int index = 0;
+  PopulateChildIndexes(&node, &index);
+  DeltaArchiveManifest *ret = new DeltaArchiveManifest;
+  NodeToDeltaArchiveManifest(&node, ret);
+  return ret;
+}
+
+bool DeltaDiffGenerator::EncodeDataToDeltaFile(DeltaArchiveManifest* archive,
+                                               const std::string& old_path,
+                                               const std::string& new_path,
+                                               const std::string& out_file) {
+  DirectFileWriter out_writer;
+  int r = out_writer.Open(out_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+  TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
+  ScopedFileWriterCloser closer(&out_writer);
+  TEST_AND_RETURN_FALSE(out_writer.Write(DeltaDiffParser::kFileMagic,
+                                         strlen(DeltaDiffParser::kFileMagic))
+                        == static_cast<ssize_t>(
+                            strlen(DeltaDiffParser::kFileMagic)));
+  // Write 8 null bytes. This will be filled in w/ the offset of
+  // the protobuf.
+  TEST_AND_RETURN_FALSE(out_writer.Write("\0\0\0\0\0\0\0\0", 8) == 8);
+  // 8 more bytes will be filled w/ the protobuf length.
+  TEST_AND_RETURN_FALSE(out_writer.Write("\0\0\0\0\0\0\0\0", 8) == 8);
+  int out_file_length = strlen(DeltaDiffParser::kFileMagic) + 16;
+
+  TEST_AND_RETURN_FALSE(archive->files_size() > 0);
+  DeltaArchiveManifest_File* file = archive->mutable_files(0);
+
+  TEST_AND_RETURN_FALSE(WriteFileDiffsToDeltaFile(archive,
+                                                  file,
+                                                  "",
+                                                  old_path,
+                                                  new_path,
+                                                  &out_writer,
+                                                  &out_file_length));
+
+  // Finally, write the protobuf to the end of the file
+  string encoded_archive;
+  TEST_AND_RETURN_FALSE(archive->SerializeToString(&encoded_archive));
+
+  // Compress the protobuf (which contains filenames)
+  vector<char> compressed_encoded_archive;
+  TEST_AND_RETURN_FALSE(GzipCompressString(encoded_archive,
+                                           &compressed_encoded_archive));
+
+  TEST_AND_RETURN_FALSE(out_writer.Write(compressed_encoded_archive.data(),
+                                         compressed_encoded_archive.size()) ==
+                        static_cast<ssize_t>(
+                            compressed_encoded_archive.size()));
+
+  // write offset of protobut to just after the file magic
+  int64 big_endian_protobuf_offset = htobe64(out_file_length);
+  TEST_AND_RETURN_FALSE(pwrite(out_writer.fd(),
+                               &big_endian_protobuf_offset,
+                               sizeof(big_endian_protobuf_offset),
+                               strlen(DeltaDiffParser::kFileMagic)) ==
+                        sizeof(big_endian_protobuf_offset));
+  // Write the size just after the offset
+  int64 pb_length = htobe64(compressed_encoded_archive.size());
+  TEST_AND_RETURN_FALSE(pwrite(out_writer.fd(),
+                               &pb_length,
+                               sizeof(pb_length),
+                               strlen(DeltaDiffParser::kFileMagic) +
+                               sizeof(big_endian_protobuf_offset)) ==
+                        sizeof(pb_length));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/delta_diff_generator.h b/delta_diff_generator.h
new file mode 100644
index 0000000..0f73d06
--- /dev/null
+++ b/delta_diff_generator.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2009 The Chromium 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_DELTA_DIFF_GENERATOR_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_DELTA_DIFF_GENERATOR_H__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "update_engine/file_writer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class DeltaDiffGenerator {
+ public:
+  // Encodes the metadata at new_path recursively into a DeltaArchiveManifest
+  // protobuf object. This will only read the filesystem. Children will
+  // be recorded recursively iff they are on the same device as their
+  // parent.
+  // This will set all fields in the DeltaArchiveManifest except for
+  // DeltaArchiveManifest_File_data_* as those are set only when writing
+  // the actual delta file to disk.
+  // Caller is responsible for freeing the returned value.
+  // Returns NULL on failure.
+  static DeltaArchiveManifest* EncodeMetadataToProtoBuffer(
+      const char* new_path);
+
+  // Takes a DeltaArchiveManifest as given from EncodeMetadataToProtoBuffer(),
+  // fill in the missing fields (DeltaArchiveManifest_File_data_*), and
+  // write the full delta out to the output file.
+  // Returns true on success.
+  static bool EncodeDataToDeltaFile(DeltaArchiveManifest* archive,
+                                    const std::string& old_path,
+                                    const std::string& new_path,
+                                    const std::string& out_file);
+ private:
+  // These functions encode all the data about a file that's not already
+  // stored in the DeltaArchiveManifest message into the vector 'out'.
+  // They all return true on success.
+
+  // EncodeLink stores the path the symlink points to.
+  static bool EncodeLink(const std::string& path, std::vector<char>* out);
+  // EncodeDev stores the major and minor device numbers.
+  // Specifically it writes a LinuxDevice message.
+  static bool EncodeDev(const struct stat& stbuf, std::vector<char>* out);
+  // EncodeFile stores the full data, gzipped data, or a binary diff from
+  // the old data. out_data_format will be set to the method used.
+  static bool EncodeFile(const std::string& old_dir,
+                         const std::string& new_dir,
+                         const std::string& file_name,
+                         DeltaArchiveManifest_File_DataFormat* out_data_format,
+                         std::vector<char>* out);
+
+  static bool WriteFileDiffsToDeltaFile(DeltaArchiveManifest* archive,
+                                        DeltaArchiveManifest_File* file,
+                                        const std::string& file_name,
+                                        const std::string& old_path,
+                                        const std::string& new_path,
+                                        FileWriter* out_file_writer,
+                                        int* out_file_length);
+
+  // This should never be constructed
+  DISALLOW_IMPLICIT_CONSTRUCTORS(DeltaDiffGenerator);
+};
+
+};  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_DELTA_DIFF_GENERATOR_H__
diff --git a/delta_diff_generator_unittest.cc b/delta_diff_generator_unittest.cc
new file mode 100644
index 0000000..495bc3e
--- /dev/null
+++ b/delta_diff_generator_unittest.cc
@@ -0,0 +1,814 @@
+// Copyright (c) 2009 The Chromium 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include "base/string_util.h"
+#include <gtest/gtest.h>
+#include "chromeos/obsolete_logging.h"
+#include "update_engine/decompressing_file_writer.h"
+#include "update_engine/delta_diff_generator.h"
+#include "update_engine/delta_diff_parser.h"
+#include "update_engine/gzip.h"
+#include "update_engine/mock_file_writer.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+class DeltaDiffGeneratorTest : public ::testing::Test {};
+
+namespace {
+void DumpProto(const DeltaArchiveManifest* archive) {
+  for (int i = 0; i < archive->files_size(); i++) {
+    printf("Node %d\n", i);
+    const DeltaArchiveManifest_File& file = archive->files(i);
+    for (int j = 0; j < file.children_size(); j++) {
+      const DeltaArchiveManifest_File_Child& child = file.children(j);
+      printf("  %d %s\n", child.index(), child.name().c_str());
+    }
+  }
+}
+
+// The following files are generated at the path 'base':
+// /
+//  cdev (c 2 1)
+//  dir/
+//      bdev (b 3 1)
+//      emptydir/ (owner:group = 501:503)
+//      hello ("hello")
+//      newempty ("")
+//      subdir/
+//             fifo
+//             link -> /target
+//  encoding/
+//           long_new
+//           long_small_change
+//           nochange
+//           onebyte
+//  hi ("hi")
+void GenerateFilesAtPath(const string& base) {
+  const char* base_c = base.c_str();
+  EXPECT_EQ(0, System(StringPrintf("echo hi > '%s/hi'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p '%s/dir'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("rm -f '%s/dir/bdev'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("mknod '%s/dir/bdev' b 3 1", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("rm -f '%s/cdev'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("mknod '%s/cdev' c 2 1", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p '%s/dir/subdir'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p '%s/dir/emptydir'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("chown 501:503 '%s/dir/emptydir'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("rm -f '%s/dir/subdir/fifo'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("mkfifo '%s/dir/subdir/fifo'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("ln -f -s /target '%s/dir/subdir/link'",
+                                   base_c)));
+
+  // Things that will encode differently:
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p '%s/encoding'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("echo nochange > '%s/encoding/nochange'",
+                                   base_c)));
+  EXPECT_EQ(0, System(StringPrintf("echo -n > '%s/encoding/onebyte'", base_c)));
+  EXPECT_EQ(0, System(StringPrintf("echo -n > '%s/encoding/long_new'",
+                                   base_c)));
+  // Random 1 MiB byte length file
+  EXPECT_TRUE(WriteFile((base +
+                         "/encoding/long_small_change").c_str(),
+                        reinterpret_cast<const char*>(kRandomString),
+                        sizeof(kRandomString)));
+}
+// base points to a folder that was passed to GenerateFilesAtPath().
+// This edits some, so that one can make a diff from the original data
+// and the edited data.
+void EditFilesAtPath(const string& base) {
+  CHECK_EQ(0, System(string("echo hello > ") + base + "/dir/hello"));
+  CHECK_EQ(0, System(string("echo -n > ") + base + "/dir/newempty"));
+  CHECK_EQ(0, System(string("echo newhi > ") + base + "/hi"));
+  CHECK_EQ(0, System(string("echo -n h >> ") + base +
+                     "/encoding/onebyte"));
+  CHECK_EQ(0, System(string("echo -n h >> ") + base +
+                     "/encoding/long_small_change"));
+  CHECK_EQ(0, System(string("echo -n This is a pice of text that should "
+                            "compress well since it is just ascii and it "
+                            "has repetition xxxxxxxxxxxxxxxxxxxxx"
+                            "xxxxxxxxxxxxxxxxxxxx > ") + base +
+                     "/encoding/long_new"));
+}
+
+}  // namespace {}
+
+TEST_F(DeltaDiffGeneratorTest, FakerootEncodeMetadataToProtoBufferTest) {
+  char cwd[1000];
+  ASSERT_EQ(cwd, getcwd(cwd, sizeof(cwd))) << "cwd buf possibly too small";
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test/old"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test/new"));
+  GenerateFilesAtPath(string(cwd) + "/diff-gen-test/old");
+  GenerateFilesAtPath(string(cwd) + "/diff-gen-test/new");
+  EditFilesAtPath(string(cwd) + "/diff-gen-test/new");
+
+  DeltaArchiveManifest* archive =
+      DeltaDiffGenerator::EncodeMetadataToProtoBuffer(
+          (string(cwd) + "/diff-gen-test/new").c_str());
+  EXPECT_TRUE(NULL != archive);
+
+  EXPECT_EQ(16, archive->files_size());
+  //DumpProto(archive);
+  const DeltaArchiveManifest_File& root = archive->files(0);
+  EXPECT_TRUE(S_ISDIR(root.mode()));
+  EXPECT_EQ(0, root.uid());
+  EXPECT_EQ(0, root.gid());
+  ASSERT_EQ(4, root.children_size());
+  EXPECT_EQ("cdev", root.children(0).name());
+  EXPECT_EQ("dir", root.children(1).name());
+  EXPECT_EQ("encoding", root.children(2).name());
+  EXPECT_EQ("hi", root.children(3).name());
+  EXPECT_FALSE(root.has_data_format());
+  EXPECT_FALSE(root.has_data_offset());
+  EXPECT_FALSE(root.has_data_length());
+
+  const DeltaArchiveManifest_File& cdev =
+      archive->files(root.children(0).index());
+  EXPECT_EQ(0, cdev.children_size());
+  EXPECT_TRUE(S_ISCHR(cdev.mode()));
+  EXPECT_EQ(0, cdev.uid());
+  EXPECT_EQ(0, cdev.gid());
+  EXPECT_FALSE(cdev.has_data_format());
+  EXPECT_FALSE(cdev.has_data_offset());
+  EXPECT_FALSE(cdev.has_data_length());
+
+  const DeltaArchiveManifest_File& hi =
+      archive->files(root.children(3).index());
+  EXPECT_EQ(0, hi.children_size());
+  EXPECT_TRUE(S_ISREG(hi.mode()));
+  EXPECT_EQ(0, hi.uid());
+  EXPECT_EQ(0, hi.gid());
+  EXPECT_FALSE(hi.has_data_format());
+  EXPECT_FALSE(hi.has_data_offset());
+  EXPECT_FALSE(hi.has_data_length());
+
+  const DeltaArchiveManifest_File& encoding =
+      archive->files(root.children(2).index());
+  EXPECT_TRUE(S_ISDIR(encoding.mode()));
+  EXPECT_EQ(0, encoding.uid());
+  EXPECT_EQ(0, encoding.gid());
+  EXPECT_EQ(4, encoding.children_size());
+  EXPECT_EQ("long_new", encoding.children(0).name());
+  EXPECT_EQ("long_small_change", encoding.children(1).name());
+  EXPECT_EQ("nochange", encoding.children(2).name());
+  EXPECT_EQ("onebyte", encoding.children(3).name());
+  EXPECT_FALSE(encoding.has_data_format());
+  EXPECT_FALSE(encoding.has_data_offset());
+  EXPECT_FALSE(encoding.has_data_length());
+
+  const DeltaArchiveManifest_File& long_new =
+      archive->files(encoding.children(0).index());
+  EXPECT_EQ(0, long_new.children_size());
+  EXPECT_TRUE(S_ISREG(long_new.mode()));
+  EXPECT_EQ(0, long_new.uid());
+  EXPECT_EQ(0, long_new.gid());
+  EXPECT_FALSE(long_new.has_data_format());
+  EXPECT_FALSE(long_new.has_data_offset());
+  EXPECT_FALSE(long_new.has_data_length());
+
+  const DeltaArchiveManifest_File& long_small_change =
+      archive->files(encoding.children(1).index());
+  EXPECT_EQ(0, long_small_change.children_size());
+  EXPECT_TRUE(S_ISREG(long_small_change.mode()));
+  EXPECT_EQ(0, long_small_change.uid());
+  EXPECT_EQ(0, long_small_change.gid());
+  EXPECT_FALSE(long_small_change.has_data_format());
+  EXPECT_FALSE(long_small_change.has_data_offset());
+  EXPECT_FALSE(long_small_change.has_data_length());
+
+  const DeltaArchiveManifest_File& nochange =
+      archive->files(encoding.children(2).index());
+  EXPECT_EQ(0, nochange.children_size());
+  EXPECT_TRUE(S_ISREG(nochange.mode()));
+  EXPECT_EQ(0, nochange.uid());
+  EXPECT_EQ(0, nochange.gid());
+  EXPECT_FALSE(nochange.has_data_format());
+  EXPECT_FALSE(nochange.has_data_offset());
+  EXPECT_FALSE(nochange.has_data_length());
+
+  const DeltaArchiveManifest_File& onebyte =
+      archive->files(encoding.children(3).index());
+  EXPECT_EQ(0, onebyte.children_size());
+  EXPECT_TRUE(S_ISREG(onebyte.mode()));
+  EXPECT_EQ(0, onebyte.uid());
+  EXPECT_EQ(0, onebyte.gid());
+  EXPECT_FALSE(onebyte.has_data_format());
+  EXPECT_FALSE(onebyte.has_data_offset());
+  EXPECT_FALSE(onebyte.has_data_length());
+
+  const DeltaArchiveManifest_File& dir =
+      archive->files(root.children(1).index());
+  EXPECT_TRUE(S_ISDIR(dir.mode()));
+  EXPECT_EQ(0, dir.uid());
+  EXPECT_EQ(0, dir.gid());
+  ASSERT_EQ(5, dir.children_size());
+  EXPECT_EQ("bdev", dir.children(0).name());
+  EXPECT_EQ("emptydir", dir.children(1).name());
+  EXPECT_EQ("hello", dir.children(2).name());
+  EXPECT_EQ("newempty", dir.children(3).name());
+  EXPECT_EQ("subdir", dir.children(4).name());
+  EXPECT_FALSE(dir.has_data_format());
+  EXPECT_FALSE(dir.has_data_offset());
+  EXPECT_FALSE(dir.has_data_length());
+
+  const DeltaArchiveManifest_File& bdev =
+      archive->files(dir.children(0).index());
+  EXPECT_EQ(0, bdev.children_size());
+  EXPECT_TRUE(S_ISBLK(bdev.mode()));
+  EXPECT_EQ(0, bdev.uid());
+  EXPECT_EQ(0, bdev.gid());
+  EXPECT_FALSE(bdev.has_data_format());
+  EXPECT_FALSE(bdev.has_data_offset());
+  EXPECT_FALSE(bdev.has_data_length());
+
+  const DeltaArchiveManifest_File& emptydir =
+      archive->files(dir.children(1).index());
+  EXPECT_EQ(0, emptydir.children_size());
+  EXPECT_TRUE(S_ISDIR(emptydir.mode()));
+  EXPECT_EQ(501, emptydir.uid());
+  EXPECT_EQ(503, emptydir.gid());
+  EXPECT_FALSE(emptydir.has_data_format());
+  EXPECT_FALSE(emptydir.has_data_offset());
+  EXPECT_FALSE(emptydir.has_data_length());
+
+  const DeltaArchiveManifest_File& hello =
+      archive->files(dir.children(2).index());
+  EXPECT_EQ(0, hello.children_size());
+  EXPECT_TRUE(S_ISREG(hello.mode()));
+  EXPECT_EQ(0, hello.uid());
+  EXPECT_EQ(0, hello.gid());
+  EXPECT_FALSE(hello.has_data_format());
+  EXPECT_FALSE(hello.has_data_offset());
+  EXPECT_FALSE(hello.has_data_length());
+
+  const DeltaArchiveManifest_File& newempty =
+      archive->files(dir.children(3).index());
+  EXPECT_EQ(0, newempty.children_size());
+  EXPECT_TRUE(S_ISREG(newempty.mode()));
+  EXPECT_EQ(0, newempty.uid());
+  EXPECT_EQ(0, newempty.gid());
+  EXPECT_FALSE(newempty.has_data_format());
+  EXPECT_FALSE(newempty.has_data_offset());
+  EXPECT_FALSE(newempty.has_data_length());
+
+  const DeltaArchiveManifest_File& subdir =
+      archive->files(dir.children(4).index());
+  EXPECT_EQ(2, subdir.children_size());
+  EXPECT_EQ("fifo", subdir.children(0).name());
+  EXPECT_EQ("link", subdir.children(1).name());
+  EXPECT_TRUE(S_ISDIR(subdir.mode()));
+  EXPECT_EQ(0, subdir.uid());
+  EXPECT_EQ(0, subdir.gid());
+  EXPECT_FALSE(subdir.has_data_format());
+  EXPECT_FALSE(subdir.has_data_offset());
+  EXPECT_FALSE(subdir.has_data_length());
+
+  const DeltaArchiveManifest_File& fifo =
+      archive->files(subdir.children(0).index());
+  EXPECT_EQ(0, fifo.children_size());
+  EXPECT_TRUE(S_ISFIFO(fifo.mode()));
+  EXPECT_EQ(0, fifo.uid());
+  EXPECT_EQ(0, fifo.gid());
+  EXPECT_FALSE(fifo.has_data_format());
+  EXPECT_FALSE(fifo.has_data_offset());
+  EXPECT_FALSE(fifo.has_data_length());
+
+  const DeltaArchiveManifest_File& link =
+      archive->files(subdir.children(1).index());
+  EXPECT_EQ(0, link.children_size());
+  EXPECT_TRUE(S_ISLNK(link.mode()));
+  EXPECT_EQ(0, link.uid());
+  EXPECT_EQ(0, link.gid());
+  EXPECT_FALSE(link.has_data_format());
+  EXPECT_FALSE(link.has_data_offset());
+  EXPECT_FALSE(link.has_data_length());
+}
+
+TEST_F(DeltaDiffGeneratorTest, FakerootEncodeDataToDeltaFileTest) {
+  char cwd[1000];
+  ASSERT_EQ(cwd, getcwd(cwd, sizeof(cwd))) << "cwd buf possibly too small";
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test/old"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test/new"));
+  GenerateFilesAtPath(string(cwd) + "/diff-gen-test/old");
+  GenerateFilesAtPath(string(cwd) + "/diff-gen-test/new");
+  EditFilesAtPath(string(cwd) + "/diff-gen-test/new");
+
+  DeltaArchiveManifest* archive =
+      DeltaDiffGenerator::EncodeMetadataToProtoBuffer(
+          (string(cwd) + "/diff-gen-test/new").c_str());
+  EXPECT_TRUE(NULL != archive);
+
+  EXPECT_TRUE(DeltaDiffGenerator::EncodeDataToDeltaFile(
+      archive,
+      string(cwd) + "/diff-gen-test/old",
+      string(cwd) + "/diff-gen-test/new",
+      string(cwd) + "/diff-gen-test/out.dat"));
+
+  EXPECT_EQ(16, archive->files_size());
+
+  const DeltaArchiveManifest_File& root = archive->files(0);
+  EXPECT_TRUE(S_ISDIR(root.mode()));
+  EXPECT_EQ(0, root.uid());
+  EXPECT_EQ(0, root.gid());
+  ASSERT_EQ(4, root.children_size());
+  EXPECT_EQ("cdev", root.children(0).name());
+  EXPECT_EQ("dir", root.children(1).name());
+  EXPECT_EQ("encoding", root.children(2).name());
+  EXPECT_EQ("hi", root.children(3).name());
+  EXPECT_FALSE(root.has_data_format());
+  EXPECT_FALSE(root.has_data_offset());
+  EXPECT_FALSE(root.has_data_length());
+
+  const DeltaArchiveManifest_File& cdev =
+      archive->files(root.children(0).index());
+  EXPECT_EQ(0, cdev.children_size());
+  EXPECT_TRUE(S_ISCHR(cdev.mode()));
+  EXPECT_EQ(0, cdev.uid());
+  EXPECT_EQ(0, cdev.gid());
+  ASSERT_TRUE(cdev.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, cdev.data_format());
+  EXPECT_TRUE(cdev.has_data_offset());
+  ASSERT_TRUE(cdev.has_data_length());
+  EXPECT_GT(cdev.data_length(), 0);
+
+  const DeltaArchiveManifest_File& hi =
+      archive->files(root.children(3).index());
+  EXPECT_EQ(0, hi.children_size());
+  EXPECT_TRUE(S_ISREG(hi.mode()));
+  EXPECT_EQ(0, hi.uid());
+  EXPECT_EQ(0, hi.gid());
+  ASSERT_TRUE(hi.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, hi.data_format());
+  EXPECT_TRUE(hi.has_data_offset());
+  ASSERT_TRUE(hi.has_data_length());
+  EXPECT_GT(hi.data_length(), 0);
+
+  const DeltaArchiveManifest_File& encoding =
+      archive->files(root.children(2).index());
+  EXPECT_TRUE(S_ISDIR(encoding.mode()));
+  EXPECT_EQ(0, encoding.uid());
+  EXPECT_EQ(0, encoding.gid());
+  EXPECT_EQ(4, encoding.children_size());
+  EXPECT_EQ("long_new", encoding.children(0).name());
+  EXPECT_EQ("long_small_change", encoding.children(1).name());
+  EXPECT_EQ("nochange", encoding.children(2).name());
+  EXPECT_EQ("onebyte", encoding.children(3).name());
+  EXPECT_FALSE(encoding.has_data_format());
+  EXPECT_FALSE(encoding.has_data_offset());
+  EXPECT_FALSE(encoding.has_data_length());
+
+  const DeltaArchiveManifest_File& long_new =
+      archive->files(encoding.children(0).index());
+  EXPECT_EQ(0, long_new.children_size());
+  EXPECT_TRUE(S_ISREG(long_new.mode()));
+  EXPECT_EQ(0, long_new.uid());
+  EXPECT_EQ(0, long_new.gid());
+  EXPECT_TRUE(long_new.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL_GZ,
+            long_new.data_format());
+  EXPECT_TRUE(long_new.has_data_offset());
+  EXPECT_TRUE(long_new.has_data_length());
+
+  const DeltaArchiveManifest_File& long_small_change =
+      archive->files(encoding.children(1).index());
+  EXPECT_EQ(0, long_small_change.children_size());
+  EXPECT_TRUE(S_ISREG(long_small_change.mode()));
+  EXPECT_EQ(0, long_small_change.uid());
+  EXPECT_EQ(0, long_small_change.gid());
+  EXPECT_TRUE(long_small_change.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_BSDIFF,
+            long_small_change.data_format());
+  EXPECT_TRUE(long_small_change.has_data_offset());
+  EXPECT_TRUE(long_small_change.has_data_length());
+
+  const DeltaArchiveManifest_File& nochange =
+      archive->files(encoding.children(2).index());
+  EXPECT_EQ(0, nochange.children_size());
+  EXPECT_TRUE(S_ISREG(nochange.mode()));
+  EXPECT_EQ(0, nochange.uid());
+  EXPECT_EQ(0, nochange.gid());
+  EXPECT_TRUE(nochange.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, nochange.data_format());
+  EXPECT_TRUE(nochange.has_data_offset());
+  EXPECT_TRUE(nochange.has_data_length());
+
+  const DeltaArchiveManifest_File& onebyte =
+      archive->files(encoding.children(3).index());
+  EXPECT_EQ(0, onebyte.children_size());
+  EXPECT_TRUE(S_ISREG(onebyte.mode()));
+  EXPECT_EQ(0, onebyte.uid());
+  EXPECT_EQ(0, onebyte.gid());
+  EXPECT_TRUE(onebyte.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, onebyte.data_format());
+  EXPECT_TRUE(onebyte.has_data_offset());
+  EXPECT_TRUE(onebyte.has_data_length());
+  EXPECT_EQ(1, onebyte.data_length());
+
+  const DeltaArchiveManifest_File& dir =
+      archive->files(root.children(1).index());
+  EXPECT_TRUE(S_ISDIR(dir.mode()));
+  EXPECT_EQ(0, dir.uid());
+  EXPECT_EQ(0, dir.gid());
+  ASSERT_EQ(5, dir.children_size());
+  EXPECT_EQ("bdev", dir.children(0).name());
+  EXPECT_EQ("emptydir", dir.children(1).name());
+  EXPECT_EQ("hello", dir.children(2).name());
+  EXPECT_EQ("newempty", dir.children(3).name());
+  EXPECT_EQ("subdir", dir.children(4).name());
+  EXPECT_FALSE(dir.has_data_format());
+  EXPECT_FALSE(dir.has_data_offset());
+  EXPECT_FALSE(dir.has_data_length());
+
+  const DeltaArchiveManifest_File& bdev =
+      archive->files(dir.children(0).index());
+  EXPECT_EQ(0, bdev.children_size());
+  EXPECT_TRUE(S_ISBLK(bdev.mode()));
+  EXPECT_EQ(0, bdev.uid());
+  EXPECT_EQ(0, bdev.gid());
+  ASSERT_TRUE(bdev.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, bdev.data_format());
+  EXPECT_TRUE(bdev.has_data_offset());
+  ASSERT_TRUE(bdev.has_data_length());
+  EXPECT_GT(bdev.data_length(), 0);
+
+  const DeltaArchiveManifest_File& emptydir =
+      archive->files(dir.children(1).index());
+  EXPECT_EQ(0, emptydir.children_size());
+  EXPECT_TRUE(S_ISDIR(emptydir.mode()));
+  EXPECT_EQ(501, emptydir.uid());
+  EXPECT_EQ(503, emptydir.gid());
+  EXPECT_FALSE(emptydir.has_data_format());
+  EXPECT_FALSE(emptydir.has_data_offset());
+  EXPECT_FALSE(emptydir.has_data_length());
+
+  const DeltaArchiveManifest_File& hello =
+      archive->files(dir.children(2).index());
+  EXPECT_EQ(0, hello.children_size());
+  EXPECT_TRUE(S_ISREG(hello.mode()));
+  EXPECT_EQ(0, hello.uid());
+  EXPECT_EQ(0, hello.gid());
+  ASSERT_TRUE(hello.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, hello.data_format());
+  EXPECT_TRUE(hello.has_data_offset());
+  ASSERT_TRUE(hello.has_data_length());
+  EXPECT_GT(hello.data_length(), 0);
+
+  const DeltaArchiveManifest_File& newempty =
+      archive->files(dir.children(3).index());
+  EXPECT_EQ(0, newempty.children_size());
+  EXPECT_TRUE(S_ISREG(newempty.mode()));
+  EXPECT_EQ(0, newempty.uid());
+  EXPECT_EQ(0, newempty.gid());
+  EXPECT_FALSE(newempty.has_data_format());
+  EXPECT_FALSE(newempty.has_data_offset());
+  EXPECT_FALSE(newempty.has_data_length());
+
+  const DeltaArchiveManifest_File& subdir =
+      archive->files(dir.children(4).index());
+  EXPECT_EQ(2, subdir.children_size());
+  EXPECT_EQ("fifo", subdir.children(0).name());
+  EXPECT_EQ("link", subdir.children(1).name());
+  EXPECT_TRUE(S_ISDIR(subdir.mode()));
+  EXPECT_EQ(0, subdir.uid());
+  EXPECT_EQ(0, subdir.gid());
+  EXPECT_FALSE(subdir.has_data_format());
+  EXPECT_FALSE(subdir.has_data_offset());
+  EXPECT_FALSE(subdir.has_data_length());
+
+  const DeltaArchiveManifest_File& fifo =
+      archive->files(subdir.children(0).index());
+  EXPECT_EQ(0, fifo.children_size());
+  EXPECT_TRUE(S_ISFIFO(fifo.mode()));
+  EXPECT_EQ(0, fifo.uid());
+  EXPECT_EQ(0, fifo.gid());
+  EXPECT_FALSE(fifo.has_data_format());
+  EXPECT_FALSE(fifo.has_data_offset());
+  EXPECT_FALSE(fifo.has_data_length());
+
+  const DeltaArchiveManifest_File& link =
+      archive->files(subdir.children(1).index());
+  EXPECT_EQ(0, link.children_size());
+  EXPECT_TRUE(S_ISLNK(link.mode()));
+  EXPECT_EQ(0, link.uid());
+  EXPECT_EQ(0, link.gid());
+  ASSERT_TRUE(link.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, link.data_format());
+  EXPECT_TRUE(link.has_data_offset());
+  ASSERT_TRUE(link.has_data_length());
+  EXPECT_GT(link.data_length(), 0);
+}
+
+class DeltaDiffParserTest : public ::testing::Test {
+  virtual void TearDown() {
+    EXPECT_EQ(0, system("rm -rf diff-gen-test"));
+  }
+};
+
+namespace {
+// Reads part of a file into memory
+vector<char> ReadFilePart(const string& path, off_t start, off_t size) {
+  vector<char> ret;
+  int fd = open(path.c_str(), O_RDONLY, 0);
+  if (fd < 0)
+    return ret;
+  ret.resize(size);
+  EXPECT_EQ(size, pread(fd, &ret[0], size, start));
+  close(fd);
+  return ret;
+}
+
+string ReadFilePartToString(const string& path, off_t start, off_t size) {
+  vector<char> bytes = ReadFilePart(path, start, size);
+  string ret;
+  ret.append(&bytes[0], bytes.size());
+  return ret;
+}
+
+string StringFromVectorChar(const vector<char>& in) {
+  return string(&in[0], in.size());
+}
+
+string GzipDecompressToString(const vector<char>& in) {
+  vector<char> out;
+  EXPECT_TRUE(GzipDecompress(in, &out));
+  return StringFromVectorChar(out);
+}
+
+}
+
+TEST_F(DeltaDiffParserTest, FakerootDecodeDataFromDeltaFileTest) {
+  char cwd[1000];
+  ASSERT_EQ(cwd, getcwd(cwd, sizeof(cwd))) << "cwd buf possibly too small";
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test/old"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + cwd + "/diff-gen-test/new"));
+  GenerateFilesAtPath(string(cwd) + "/diff-gen-test/old");
+  GenerateFilesAtPath(string(cwd) + "/diff-gen-test/new");
+  EditFilesAtPath(string(cwd) + "/diff-gen-test/new");
+
+  DeltaArchiveManifest* archive =
+      DeltaDiffGenerator::EncodeMetadataToProtoBuffer(
+          (string(cwd) + "/diff-gen-test/new").c_str());
+  EXPECT_TRUE(NULL != archive);
+
+  EXPECT_TRUE(DeltaDiffGenerator::EncodeDataToDeltaFile(
+      archive,
+      string(cwd) + "/diff-gen-test/old",
+      string(cwd) + "/diff-gen-test/new",
+      string(cwd) + "/diff-gen-test/out.dat"));
+  // parse the file
+
+  DeltaDiffParser parser(string(cwd) + "/diff-gen-test/out.dat");
+  ASSERT_TRUE(parser.valid());
+  DeltaDiffParser::Iterator it = parser.Begin();
+  string expected_paths[] = {
+    "",
+    "/cdev",
+    "/dir",
+    "/dir/bdev",
+    "/dir/emptydir",
+    "/dir/hello",
+    "/dir/newempty",
+    "/dir/subdir",
+    "/dir/subdir/fifo",
+    "/dir/subdir/link",
+    "/encoding",
+    "/encoding/long_new",
+    "/encoding/long_small_change",
+    "/encoding/nochange",
+    "/encoding/onebyte",
+    "/hi"
+  };
+  for (unsigned int i = 0;
+       i < (sizeof(expected_paths)/sizeof(expected_paths[0])); i++) {
+    ASSERT_TRUE(it != parser.End());
+    ASSERT_TRUE(parser.ContainsPath(expected_paths[i]));
+    EXPECT_EQ(expected_paths[i], it.path());
+    EXPECT_EQ(expected_paths[i].substr(expected_paths[i].find_last_of('/') + 1),
+              it.GetName());
+    DeltaArchiveManifest_File f1 = parser.GetFileAtPath(expected_paths[i]);
+    DeltaArchiveManifest_File f2 = it.GetFile();
+    EXPECT_EQ(f1.mode(), f2.mode()) << it.path();
+    EXPECT_EQ(f1.uid(), f2.uid());
+    EXPECT_EQ(f1.gid(), f2.gid());
+    EXPECT_EQ(f1.has_data_format(), f2.has_data_format());
+    if (f1.has_data_format()) {
+      EXPECT_EQ(f1.data_format(), f2.data_format());
+      EXPECT_TRUE(f1.has_data_offset());
+      EXPECT_TRUE(f2.has_data_offset());
+      EXPECT_EQ(f1.data_offset(), f2.data_offset());
+    } else {
+      EXPECT_FALSE(f2.has_data_format());
+      EXPECT_FALSE(f1.has_data_offset());
+      EXPECT_FALSE(f2.has_data_offset());
+    }
+    EXPECT_EQ(f1.children_size(), f2.children_size());
+    for (int j = 0; j < f1.children_size(); j++) {
+      EXPECT_EQ(f1.children(j).name(), f2.children(j).name());
+      EXPECT_EQ(f1.children(j).index(), f2.children(j).index());
+    }
+    it.Increment();
+  }
+  EXPECT_TRUE(it == parser.End());
+  EXPECT_FALSE(parser.ContainsPath("/cdew"));
+  EXPECT_FALSE(parser.ContainsPath("/hi/hi"));
+  EXPECT_FALSE(parser.ContainsPath("/dir/newempty/hi"));
+  EXPECT_TRUE(parser.ContainsPath("/dir/"));
+
+  // Check the data
+  // root
+  DeltaArchiveManifest_File file = parser.GetFileAtPath("");
+  EXPECT_TRUE(S_ISDIR(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // cdev
+  file = parser.GetFileAtPath("/cdev");
+  EXPECT_TRUE(S_ISCHR(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  vector<char> data = ReadFilePart(string(cwd) + "/diff-gen-test/out.dat",
+                                   file.data_offset(), file.data_length());
+  LinuxDevice linux_device;
+  linux_device.ParseFromArray(&data[0], data.size());
+  EXPECT_EQ(linux_device.major(), 2);
+  EXPECT_EQ(linux_device.minor(), 1);
+
+  // dir
+  file = parser.GetFileAtPath("/dir");
+  EXPECT_TRUE(S_ISDIR(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // bdev
+  file = parser.GetFileAtPath("/dir/bdev");
+  EXPECT_TRUE(S_ISBLK(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  data = ReadFilePart(string(cwd) + "/diff-gen-test/out.dat",
+                      file.data_offset(), file.data_length());
+  linux_device.ParseFromArray(&data[0], data.size());
+  EXPECT_EQ(linux_device.major(), 3);
+  EXPECT_EQ(linux_device.minor(), 1);
+
+  // emptydir
+  file = parser.GetFileAtPath("/dir/emptydir");
+  EXPECT_TRUE(S_ISDIR(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // hello
+  file = parser.GetFileAtPath("/dir/hello");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, file.data_format());
+  EXPECT_EQ("hello\n", ReadFilePartToString(string(cwd) +
+                                            "/diff-gen-test/out.dat",
+                                            file.data_offset(),
+                                            file.data_length()));
+
+  // newempty
+  file = parser.GetFileAtPath("/dir/newempty");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // subdir
+  file = parser.GetFileAtPath("/dir/subdir");
+  EXPECT_TRUE(S_ISDIR(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // fifo
+  file = parser.GetFileAtPath("/dir/subdir/fifo");
+  EXPECT_TRUE(S_ISFIFO(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // link
+  file = parser.GetFileAtPath("/dir/subdir/link");
+  EXPECT_TRUE(S_ISLNK(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, file.data_format());
+  EXPECT_EQ("/target", ReadFilePartToString(string(cwd) +
+                                            "/diff-gen-test/out.dat",
+                                            file.data_offset(),
+                                            file.data_length()));
+
+  // encoding
+  file = parser.GetFileAtPath("/encoding");
+  EXPECT_TRUE(S_ISDIR(file.mode()));
+  EXPECT_FALSE(file.has_data_format());
+
+  // long_new
+  file = parser.GetFileAtPath("/encoding/long_new");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL_GZ, file.data_format());
+  EXPECT_EQ("This is a pice of text that should "
+            "compress well since it is just ascii and it "
+            "has repetition xxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxx",
+            GzipDecompressToString(ReadFilePart(string(cwd) +
+                                                "/diff-gen-test/out.dat",
+                                                file.data_offset(),
+                                                file.data_length())));
+
+  // long_small_change
+  file = parser.GetFileAtPath("/encoding/long_small_change");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_BSDIFF, file.data_format());
+  data = ReadFilePart(string(cwd) + "/diff-gen-test/out.dat",
+                      file.data_offset(), file.data_length());
+  WriteFileVector(string(cwd) + "/diff-gen-test/patch", data);
+  int rc = 1;
+  vector<string> cmd;
+  cmd.push_back("/usr/bin/bspatch");
+  cmd.push_back(string(cwd) + "/diff-gen-test/old/encoding/long_small_change");
+  cmd.push_back(string(cwd) + "/diff-gen-test/patch_result");
+  cmd.push_back(string(cwd) + "/diff-gen-test/patch");
+  Subprocess::SynchronousExec(cmd, &rc);
+  EXPECT_EQ(0, rc);
+  vector<char> patch_result;
+  EXPECT_TRUE(utils::ReadFile(string(cwd) + "/diff-gen-test/patch_result",
+                              &patch_result));
+  vector<char> expected_data(sizeof(kRandomString) + 1);
+  memcpy(&expected_data[0], kRandomString, sizeof(kRandomString));
+  expected_data[expected_data.size() - 1] = 'h';
+  ExpectVectorsEq(expected_data, patch_result);
+
+  // nochange
+  file = parser.GetFileAtPath("/encoding/nochange");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, file.data_format());
+  EXPECT_EQ("nochange\n", ReadFilePartToString(string(cwd) +
+                                               "/diff-gen-test/out.dat",
+                                               file.data_offset(),
+                                               file.data_length()));
+
+  // onebyte
+  file = parser.GetFileAtPath("/encoding/onebyte");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, file.data_format());
+  EXPECT_EQ("h", ReadFilePartToString(string(cwd) +
+                                      "/diff-gen-test/out.dat",
+                                      file.data_offset(),
+                                      file.data_length()));
+
+  // hi
+  file = parser.GetFileAtPath("/hi");
+  EXPECT_TRUE(S_ISREG(file.mode()));
+  EXPECT_TRUE(file.has_data_format());
+  EXPECT_EQ(DeltaArchiveManifest_File_DataFormat_FULL, file.data_format());
+  EXPECT_EQ("newhi\n", ReadFilePartToString(string(cwd) +
+                                            "/diff-gen-test/out.dat",
+                                            file.data_offset(),
+                                            file.data_length()));
+}
+
+TEST_F(DeltaDiffParserTest, FakerootInvalidTest) {
+  ASSERT_EQ(0, mkdir("diff-gen-test", 0777));
+  {
+    DeltaDiffParser parser("/no/such/file");
+    EXPECT_FALSE(parser.valid());
+  }
+  {
+    vector<char> data(3);
+    memcpy(&data[0], "CrA", 3);
+    WriteFileVector("diff-gen-test/baddelta", data);
+    DeltaDiffParser parser("diff-gen-test/baddelta");
+    EXPECT_FALSE(parser.valid());
+  }
+  {
+    vector<char> data(5);
+    memcpy(&data[0], "CrAPx", 5);
+    WriteFileVector("diff-gen-test/baddelta", data);
+    DeltaDiffParser parser("diff-gen-test/baddelta");
+    EXPECT_FALSE(parser.valid());
+  }
+  {
+    vector<char> data(5);
+    memcpy(&data[0], "CrAU\0", 5);
+    WriteFileVector("diff-gen-test/baddelta", data);
+    DeltaDiffParser parser("diff-gen-test/baddelta");
+    EXPECT_FALSE(parser.valid());
+  }
+  {
+    vector<char> data(14);
+    memcpy(&data[0], "CrAU\0\0\0\0\0\0\0\x0cxx", 12);
+    WriteFileVector("diff-gen-test/baddelta", data);
+    DeltaDiffParser parser("diff-gen-test/baddelta");
+    EXPECT_FALSE(parser.valid());
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/delta_diff_parser.cc b/delta_diff_parser.cc
new file mode 100644
index 0000000..47c763f
--- /dev/null
+++ b/delta_diff_parser.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2009 The Chromium 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/delta_diff_parser.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include "base/scoped_ptr.h"
+#include "update_engine/decompressing_file_writer.h"
+#include "update_engine/gzip.h"
+#include "update_engine/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const int kCopyFileBufferSize = 4096;
+}
+
+const char* const DeltaDiffParser::kFileMagic("CrAU");
+
+// The iterator returns a directory before returning its children.
+// Steps taken in Increment():
+//   - See if the current item has children. If so, the child becomes
+//     the new current item and we return.
+//   - If current item has no children, we loop. Each loop iteration
+//     considers an item (first the current item, then its parent,
+//     then grand parent, and so on). Each loop iteration, we see if there
+//     are any siblings we haven't iterated on yet. If so, we're done.
+//     If not, keep looping to parents.
+void DeltaDiffParserIterator::Increment() {
+  // See if we have any children.
+  const DeltaArchiveManifest_File& file = GetFile();
+  if (file.children_size() > 0) {
+    path_indices_.push_back(file.children(0).index());
+    path_ += "/";
+    path_ += file.children(0).name();
+    child_indices_.push_back(0);
+    return;
+  }
+  // Look in my parent for the next child, then try grandparent, etc.
+
+  path_indices_.pop_back();
+  path_.resize(path_.rfind('/'));
+
+  while (!child_indices_.empty()) {
+    // Try to bump the last entry
+    CHECK_EQ(path_indices_.size(), child_indices_.size());
+    child_indices_.back()++;
+    const DeltaArchiveManifest_File& parent =
+        archive_->files(path_indices_.back());
+    if (parent.children_size() > child_indices_.back()) {
+      // we found a new child!
+      path_indices_.push_back(parent.children(child_indices_.back()).index());
+      path_ += "/";
+      path_ += parent.children(child_indices_.back()).name();
+      return;
+    }
+    path_indices_.pop_back();
+    child_indices_.pop_back();
+    if (!path_.empty())
+      path_.resize(path_.rfind('/'));
+  }
+}
+
+const string DeltaDiffParserIterator::GetName() const {
+  if (path_.empty())
+    return "";
+  CHECK_NE(path_.rfind('/'), string::npos);
+  return string(path_, path_.rfind('/') + 1);
+}
+
+const DeltaArchiveManifest_File& DeltaDiffParserIterator::GetFile() const {
+  CHECK(!path_indices_.empty());
+  return archive_->files(path_indices_.back());
+}
+
+
+DeltaDiffParser::DeltaDiffParser(const string& delta_file)
+    : fd_(-1),
+      valid_(false) {
+  fd_ = open(delta_file.c_str(), O_RDONLY, 0);
+  if (fd_ < 0) {
+    LOG(ERROR) << "Unable to open delta file: " << delta_file;
+    return;
+  }
+  ScopedFdCloser fd_closer(&fd_);
+  scoped_array<char> magic(new char[strlen(kFileMagic)]);
+  if (strlen(kFileMagic) != read(fd_, magic.get(), strlen(kFileMagic))) {
+    LOG(ERROR) << "delta file too short";
+    return;
+  }
+  if (strncmp(magic.get(), kFileMagic, strlen(kFileMagic))) {
+    LOG(ERROR) << "Incorrect magic at beginning of delta file";
+    return;
+  }
+
+  int64 proto_offset = 0;
+  COMPILE_ASSERT(sizeof(proto_offset) == sizeof(off_t), off_t_wrong_size);
+  if (sizeof(proto_offset) != read(fd_, &proto_offset, sizeof(proto_offset))) {
+    LOG(ERROR) << "delta file too short";
+    return;
+  }
+  proto_offset = be64toh(proto_offset);  // switch from big-endian to host
+
+  int64 proto_length = 0;
+  if (sizeof(proto_length) != read(fd_, &proto_length, sizeof(proto_length))) {
+    LOG(ERROR) << "delta file too short";
+    return;
+  }
+  proto_length = be64toh(proto_length);  // switch from big-endian to host
+
+  vector<char> proto(proto_length);
+  size_t bytes_read = 0;
+  while (bytes_read < proto_length) {
+    ssize_t r = pread(fd_, &proto[bytes_read], proto_length - bytes_read,
+                      proto_offset + bytes_read);
+    TEST_AND_RETURN(r >= 0);
+    bytes_read += r;
+  }
+  {
+    vector<char> decompressed_proto;
+    TEST_AND_RETURN(GzipDecompress(proto, &decompressed_proto));
+    proto.swap(decompressed_proto);
+  }
+
+  valid_ = archive_.ParseFromArray(&proto[0], proto.size());
+  if (valid_) {
+    fd_closer.set_should_close(false);
+  } else {
+    LOG(ERROR) << "load from file failed";
+  }
+}
+
+DeltaDiffParser::~DeltaDiffParser() {
+  if (fd_ >= 0) {
+    close(fd_);
+    fd_ = -1;
+  }
+}
+
+bool DeltaDiffParser::ContainsPath(const string& path) const {
+  return GetIndexForPath(path) >= 0;
+}
+
+const DeltaArchiveManifest_File& DeltaDiffParser::GetFileAtPath(
+    const string& path) const {
+  int idx = GetIndexForPath(path);
+  CHECK_GE(idx, 0) << path;
+  return archive_.files(idx);
+}
+
+// Returns -1 if not found.
+int DeltaDiffParser::GetIndexOfFileChild(
+    const DeltaArchiveManifest_File& file, const string& child_name) const {
+  if (file.children_size() == 0)
+    return -1;
+  int begin = 0;
+  int end = file.children_size();
+  while (begin < end) {
+    int middle = (begin + end) / 2;
+    const string& middle_name = file.children(middle).name();
+    int cmp_result = strcmp(middle_name.c_str(), child_name.c_str());
+    if (cmp_result == 0)
+      return file.children(middle).index();
+    if (cmp_result < 0)
+      begin = middle + 1;
+    else
+      end = middle;
+  }
+  return -1;
+}
+
+// Converts a path to an index in archive_. It does this by separating
+// the path components and going from root to leaf, finding the
+// File message for each component. Index values for children are
+// stored in File messages.
+int DeltaDiffParser::GetIndexForPath(const string& path) const {
+  string cleaned_path = utils::NormalizePath(path, true);
+  // strip leading slash
+  if (cleaned_path[0] == '/')
+    cleaned_path = cleaned_path.c_str() + 1;
+  if (cleaned_path.empty())
+    return 0;
+  string::size_type begin = 0;
+  string::size_type end = cleaned_path.find_first_of('/', begin + 1);
+  const DeltaArchiveManifest_File* file = &archive_.files(0);
+  int file_idx = -1;
+  for (;;) {
+    string component = cleaned_path.substr(begin, end - begin);
+    if (component.empty())
+      break;
+    // search for component in 'file'
+    file_idx = GetIndexOfFileChild(*file, component);
+    if (file_idx < 0)
+      return file_idx;
+    file = &archive_.files(file_idx);
+    if (end == string::npos)
+      break;
+    begin = end + 1;
+    end = cleaned_path.find_first_of('/', begin + 1);
+  }
+  return file_idx;
+}
+
+bool DeltaDiffParser::ReadDataVector(off_t offset, off_t length,
+                                     std::vector<char>* out) const {
+  out->resize(static_cast<vector<char>::size_type>(length));
+  int r = pread(fd_, &((*out)[0]), length, offset);
+  TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
+  return true;
+}
+
+bool DeltaDiffParser::CopyDataToFile(off_t offset, off_t length,
+                                     bool should_decompress,
+                                     const std::string& path) const {
+  DirectFileWriter direct_writer;
+  GzipDecompressingFileWriter decompressing_writer(&direct_writer);
+  FileWriter* writer = NULL;  // will point to one of the two writers above
+
+  writer = (should_decompress ?
+            static_cast<FileWriter*>(&decompressing_writer) :
+            static_cast<FileWriter*>(&direct_writer));
+  ScopedFileWriterCloser closer(writer);
+
+  int r = writer->Open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0644);
+  TEST_AND_RETURN_FALSE(r == 0);
+
+  off_t bytes_transferred = 0;
+
+  while (bytes_transferred < length) {
+    char buf[kCopyFileBufferSize];
+    size_t bytes_to_read = min(length - bytes_transferred,
+                               static_cast<off_t>(sizeof(buf)));
+    ssize_t bytes_read = pread(fd_, buf, bytes_to_read,
+                               offset + bytes_transferred);
+    if (bytes_read == 0)
+      break;  // EOF
+    TEST_AND_RETURN_FALSE_ERRNO(bytes_read > 0);
+    int bytes_written = writer->Write(buf, bytes_read);
+    TEST_AND_RETURN_FALSE(bytes_written == bytes_read);
+    bytes_transferred += bytes_written;
+  }
+  TEST_AND_RETURN_FALSE(bytes_transferred == length);
+  LOG_IF(ERROR, bytes_transferred > length) << "Wrote too many bytes(?)";
+  return true;
+}
+
+
+const DeltaDiffParser::Iterator DeltaDiffParser::Begin() {
+  DeltaDiffParserIterator ret(&archive_);
+  ret.path_indices_.push_back(0);
+  return ret;
+}
+
+const DeltaDiffParser::Iterator DeltaDiffParser::End() {
+  return DeltaDiffParserIterator(&archive_);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/delta_diff_parser.h b/delta_diff_parser.h
new file mode 100644
index 0000000..5c6d664
--- /dev/null
+++ b/delta_diff_parser.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2009 The Chromium 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_DELTA_DIFF_PARSER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_DELTA_DIFF_PARSER_H__
+
+#include <string>
+#include <vector>
+#include "chromeos/obsolete_logging.h"
+#include "base/basictypes.h"
+#include "update_engine/update_metadata.pb.h"
+
+// The DeltaDiffParser class is used to parse a delta file on disk. It will
+// copy the metadata into memory, but not the file data. This class can
+// also be used to copy file data out to disk.
+
+// The DeltaDiffParserIterator class is used to iterate through the
+// metadata of a delta file. It will return directories before their
+// children.
+
+namespace chromeos_update_engine {
+
+class DeltaDiffParser;
+
+class DeltaDiffParserIterator {
+  friend class DeltaDiffParser;
+ public:
+  void Increment();
+
+  // Returns the full path for the current file, e.g. "/bin/bash".
+  // Returns empty string for root.
+  const std::string& path() const {
+    return path_;
+  }
+
+  // Returns the basename for the current file. If path() returns
+  // "/bin/bash", then GetName() returns "bash".
+  // Returns empty string for root
+  const std::string GetName() const;
+
+  const DeltaArchiveManifest_File& GetFile() const;
+  bool operator==(const DeltaDiffParserIterator& that) const {
+    return path_indices_ == that.path_indices_ &&
+        child_indices_ == that.child_indices_ &&
+        path_ == that.path_ &&
+        archive_ == that.archive_;
+  }
+  bool operator!=(const DeltaDiffParserIterator& that) const {
+    return !(*this == that);
+  }
+ private:
+  // Container of all the File messages. Each File message has an index
+  // in archive_. The root directory is always stored at index 0.
+  const DeltaArchiveManifest* archive_;
+  
+  // These variables are used to implement the common recursive depth-first
+  // search algorithm (which we can't use here, since we need to walk the
+  // tree incrementally).
+  
+  // Indices into 'archive_' of the current path components.  For example, if
+  // the current path is "/bin/bash", 'path_stack_' will contain the archive
+  // indices for "/", "/bin", and "/bin/bash", in that order.  This is
+  // analogous to the call stack of the recursive algorithm.
+  std::vector<int> path_indices_;
+
+  // For each component in 'path_stack_', the currently-selected child in its
+  // child vector.  In the previous example, if "/" has "abc" and "bin"
+  // subdirectories and "/bin" contains only "bash", this will contain
+  // [0, 1, 0], since we are using the 0th child at the root directory level
+  // (there's only one child there), the first of the root dir's children
+  // ("bin"), and the 0th child of /bin ("bash").  This is analogous to the
+  // state of each function (in terms of which child it's currently
+  // handling) in the call stack of the recursive algorithm.
+  std::vector<int> child_indices_;
+
+  std::string path_;
+  // Instantiated by friend class DeltaDiffParser
+  explicit DeltaDiffParserIterator(const DeltaArchiveManifest* archive)
+      : archive_(archive) {}
+  DeltaDiffParserIterator() {
+    CHECK(false);  // Should never be called.
+  }
+};
+
+class DeltaDiffParser {
+ public:
+  DeltaDiffParser(const std::string& delta_file);
+  ~DeltaDiffParser();
+  bool valid() const { return valid_; }
+  bool ContainsPath(const std::string& path) const;
+  const DeltaArchiveManifest_File& GetFileAtPath(const std::string& path) const;
+
+  // Reads length bytes at offset of the delta file into the out string
+  // or vector. Be careful not to call this with large length values,
+  // since that much memory will have to be allocated to store the output.
+  // Returns true on success.
+  bool ReadDataVector(off_t offset, off_t length, std::vector<char>* out) const;
+
+  // Copies length bytes of data from offset into a new file at path specified.
+  // If should_decompress is true, will gzip decompress while writing to the
+  // file. Returns true on success.
+  bool CopyDataToFile(off_t offset, off_t length, bool should_decompress,
+                      const std::string& path) const;
+
+  typedef DeltaDiffParserIterator Iterator;
+  const Iterator Begin();
+  const Iterator End();
+
+  // The identifier we expect at the beginning of a delta file.
+  static const char* const kFileMagic;
+
+ private:
+  // (Binary) Searches the children of 'file' for one named child_name.
+  // If found, returns the index into the archive. If not found, returns -1.
+  int GetIndexOfFileChild(const DeltaArchiveManifest_File& file,
+                            const std::string& child_name) const;
+
+  // Returns -1 if not found, 0 for root
+  int GetIndexForPath(const std::string& path) const;
+
+  // We keep a filedescriptor open to the delta file.
+  int fd_;
+
+  DeltaArchiveManifest archive_;
+  bool valid_;
+  DISALLOW_COPY_AND_ASSIGN(DeltaDiffParser);
+};
+
+};  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_DELTA_DIFF_PARSER_H__
diff --git a/delta_diff_parser_unittest.cc b/delta_diff_parser_unittest.cc
new file mode 100644
index 0000000..241a834
--- /dev/null
+++ b/delta_diff_parser_unittest.cc
@@ -0,0 +1,2 @@
+// Due to shared code, DeltaDiffParser is tested in
+// delta_diff_generator_unittest.cc
diff --git a/file_writer.cc b/file_writer.cc
new file mode 100644
index 0000000..07bdb46
--- /dev/null
+++ b/file_writer.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2009 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/file_writer.h"
+#include <errno.h>
+
+namespace chromeos_update_engine {
+
+int DirectFileWriter::Open(const char* path, int flags, mode_t mode) {
+  CHECK_EQ(fd_, -1);
+  fd_ = open(path, flags, mode);
+  if (fd_ < 0)
+    return -errno;
+  return 0;
+}
+
+int DirectFileWriter::Write(const void* bytes, size_t count) {
+  CHECK_GE(fd_, 0);
+  const char* char_bytes = reinterpret_cast<const char*>(bytes);
+
+  size_t bytes_written = 0;
+  while (bytes_written < count) {
+    ssize_t rc = write(fd_, char_bytes + bytes_written,
+                       count - bytes_written);
+    if (rc < 0)
+      return -errno;
+    bytes_written += rc;
+  }
+  CHECK_EQ(bytes_written, count);
+  return bytes_written;
+}
+
+int DirectFileWriter::Close() {
+  CHECK_GE(fd_, 0);
+  int rc = close(fd_);
+
+  // This can be any negative number that's not -1. This way, this FileWriter
+  // won't be used again for another file.
+  fd_ = -2;
+
+  if (rc < 0)
+    return -errno;
+  return rc;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
new file mode 100644
index 0000000..875df95
--- /dev/null
+++ b/filesystem_copier_action.cc
@@ -0,0 +1,305 @@
+// Copyright (c) 2009 The Chromium 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/filesystem_copier_action.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include "update_engine/filesystem_iterator.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char* kMountpointTemplate = "/tmp/au_dest_mnt.XXXXXX";
+const off_t kCopyFileBufferSize = 4 * 1024 * 1024;
+const char* kCopyExclusionPrefix = "/lost+found";
+}  // namespace {}
+
+void FilesystemCopierAction::PerformAction() {
+  if (!HasInputObject()) {
+    LOG(ERROR) << "No input object. Aborting.";
+    processor_->ActionComplete(this, false);
+    return;
+  }
+  install_plan_ = GetInputObject();
+
+  if (install_plan_.is_full_update) {
+    // No copy needed.
+    processor_->ActionComplete(this, true);
+    return;
+  }
+
+  {
+    // Set up dest_path_
+    char *dest_path_temp = strdup(kMountpointTemplate);
+    CHECK(dest_path_temp);
+    CHECK_EQ(mkdtemp(dest_path_temp), dest_path_temp);
+    CHECK_NE(dest_path_temp[0], '\0');
+    dest_path_ = dest_path_temp;
+    free(dest_path_temp);
+  }
+
+  // Make sure we're properly mounted
+  if (Mount(install_plan_.install_path, dest_path_)) {
+    bool done_early = false;
+    if (utils::FileExists(
+            (dest_path_ +
+             FilesystemCopierAction::kCompleteFilesystemMarker).c_str())) {
+      // We're done!
+      done_early = true;
+      skipped_copy_ = true;
+      if (HasOutputPipe())
+        SetOutputObject(install_plan_);
+    }
+    if (!Unmount(dest_path_)) {
+      LOG(ERROR) << "Unmount failed. Aborting.";
+      processor_->ActionComplete(this, false);
+      return;
+    }
+    if (done_early) {
+      CHECK(!is_mounted_);
+      if (rmdir(dest_path_.c_str()) != 0)
+        LOG(ERROR) << "Unable to remove " << dest_path_;
+      processor_->ActionComplete(this, true);
+      return;
+    }
+  }
+  LOG(ERROR) << "not mounted; spawning thread";
+  // If we get here, mount failed or we're not done yet. Reformat and copy.
+  CHECK_EQ(pthread_create(&helper_thread_, NULL, HelperThreadMainStatic, this),
+           0);
+}
+
+void FilesystemCopierAction::TerminateProcessing() {
+  if (is_mounted_) {
+    LOG(ERROR) << "Aborted processing, but left a filesystem mounted.";
+  }
+}
+
+bool FilesystemCopierAction::Mount(const string& device,
+                                   const string& mountpoint) {
+  CHECK(!is_mounted_);
+  if(utils::MountFilesystem(device, mountpoint))
+    is_mounted_ = true;
+  return is_mounted_;
+}
+
+bool FilesystemCopierAction::Unmount(const string& mountpoint) {
+  CHECK(is_mounted_);
+  if (utils::UnmountFilesystem(mountpoint))
+    is_mounted_ = false;
+  return !is_mounted_;
+}
+
+void* FilesystemCopierAction::HelperThreadMain() {
+  // First, format the drive
+  vector<string> cmd;
+  cmd.push_back("/sbin/mkfs.ext3");
+  cmd.push_back("-F");
+  cmd.push_back(install_plan_.install_path);
+  int return_code = 1;
+  bool success = Subprocess::SynchronousExec(cmd, &return_code);
+  if (return_code != 0) {
+    LOG(INFO) << "Format of " << install_plan_.install_path
+              << " failed. Exit code: " << return_code;
+    success = false;
+  }
+  if (success) {
+    if (!Mount(install_plan_.install_path, dest_path_)) {
+      LOG(ERROR) << "Mount failed. Aborting";
+      success = false;
+    }
+  }
+  if (success) {
+    success = CopySynchronously();
+  }
+  if (success) {
+    // Place our marker to avoid copies again in the future
+    int r = open((dest_path_ +
+                  FilesystemCopierAction::kCompleteFilesystemMarker).c_str(),
+                 O_CREAT | O_WRONLY, 0644);
+    if (r >= 0)
+      close(r);
+  }
+  // Unmount
+  if (!Unmount(dest_path_)) {
+    LOG(ERROR) << "Unmount failed. Aborting";
+    success = false;
+  }
+  if (HasOutputPipe())
+    SetOutputObject(install_plan_);
+
+  // Tell main thread that we're done
+  g_timeout_add(0, CollectThreadStatic, this);
+  return reinterpret_cast<void*>(success ? 0 : 1);
+}
+
+void FilesystemCopierAction::CollectThread() {
+  void *thread_ret_value = NULL;
+  CHECK_EQ(pthread_join(helper_thread_, &thread_ret_value), 0);
+  bool success = (thread_ret_value == 0);
+  CHECK(!is_mounted_);
+  if (rmdir(dest_path_.c_str()) != 0)
+    LOG(INFO) << "Unable to remove " << dest_path_;
+  LOG(INFO) << "FilesystemCopierAction done";
+  processor_->ActionComplete(this, success);
+}
+
+bool FilesystemCopierAction::CreateDirSynchronously(const std::string& new_path,
+                                                    const struct stat& stbuf) {
+  int r = mkdir(new_path.c_str(), stbuf.st_mode);
+  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+  return true;
+}
+
+bool FilesystemCopierAction::CopyFileSynchronously(const std::string& old_path,
+                                                   const std::string& new_path,
+                                                   const struct stat& stbuf) {
+  int fd_out = open(new_path.c_str(), O_CREAT | O_EXCL | O_WRONLY,
+                    stbuf.st_mode);
+  TEST_AND_RETURN_FALSE_ERRNO(fd_out >= 0);
+  ScopedFdCloser fd_out_closer(&fd_out);
+  int fd_in = open(old_path.c_str(), O_RDONLY, 0);
+  TEST_AND_RETURN_FALSE_ERRNO(fd_in >= 0);
+  ScopedFdCloser fd_in_closer(&fd_in);
+
+  vector<char> buf(min(kCopyFileBufferSize, stbuf.st_size));
+  off_t bytes_written = 0;
+  while (true) {
+    // Make sure we don't need to abort early:
+    TEST_AND_RETURN_FALSE(!g_atomic_int_get(&thread_should_exit_));
+
+    ssize_t read_size = read(fd_in, &buf[0], buf.size());
+    TEST_AND_RETURN_FALSE_ERRNO(read_size >= 0);
+    if (0 == read_size)  // EOF
+      break;
+
+    ssize_t write_size = 0;
+    while (write_size < read_size) {
+      ssize_t r = write(fd_out, &buf[write_size], read_size - write_size);
+      TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
+      write_size += r;
+    }
+    CHECK_EQ(write_size, read_size);
+    bytes_written += write_size;
+    CHECK_LE(bytes_written, stbuf.st_size);
+    if (bytes_written == stbuf.st_size)
+      break;
+  }
+  CHECK_EQ(bytes_written, stbuf.st_size);
+  return true;
+}
+
+bool FilesystemCopierAction::CreateHardLinkSynchronously(
+    const std::string& old_path,
+    const std::string& new_path) {
+  int r = link(old_path.c_str(), new_path.c_str());
+  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+  return true;
+}
+
+bool FilesystemCopierAction::CopySymlinkSynchronously(
+    const std::string& old_path,
+    const std::string& new_path,
+    const struct stat& stbuf) {
+  vector<char> buf(PATH_MAX + 1);
+  ssize_t r = readlink(old_path.c_str(), &buf[0], buf.size());
+  TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
+  // Make sure we got the entire link
+  TEST_AND_RETURN_FALSE(static_cast<unsigned>(r) < buf.size());
+  buf[r] = '\0';
+  int rc = symlink(&buf[0], new_path.c_str());
+  TEST_AND_RETURN_FALSE_ERRNO(rc == 0);
+  return true;
+}
+
+bool FilesystemCopierAction::CreateNodeSynchronously(
+    const std::string& new_path,
+    const struct stat& stbuf) {
+  int r = mknod(new_path.c_str(), stbuf.st_mode, stbuf.st_rdev);
+  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+  return true;
+}
+
+// Returns true on success
+bool FilesystemCopierAction::CopySynchronously() {
+  // This map is a map from inode # to new_path.
+  map<ino_t, string> hard_links;
+  FilesystemIterator iter(copy_source_,
+                          utils::SetWithValue<string>(kCopyExclusionPrefix));
+  bool success = true;
+  for (; !g_atomic_int_get(&thread_should_exit_) &&
+           !iter.IsEnd(); iter.Increment()) {
+    const string old_path = iter.GetFullPath();
+    const string new_path = dest_path_ + iter.GetPartialPath();
+    LOG(INFO) << "copying " << old_path << " to " << new_path;
+    const struct stat stbuf = iter.GetStat();
+    success = false;
+
+    // Skip lost+found
+    CHECK_NE(kCopyExclusionPrefix, iter.GetPartialPath());
+
+    // Directories can't be hard-linked, so check for directories first
+    if (iter.GetPartialPath().empty()) {
+      // Root has an empty path.
+      // We don't need to create anything for the root, which is the first
+      // thing we get from the iterator.
+      success = true;
+    } else if (S_ISDIR(stbuf.st_mode)) {
+      success = CreateDirSynchronously(new_path, stbuf);
+    } else {
+      if (stbuf.st_nlink > 1 &&
+          utils::MapContainsKey(hard_links, stbuf.st_ino)) {
+        success = CreateHardLinkSynchronously(hard_links[stbuf.st_ino],
+                                              new_path);
+      } else {
+        if (stbuf.st_nlink > 1)
+          hard_links[stbuf.st_ino] = new_path;
+        if (S_ISREG(stbuf.st_mode)) {
+          success = CopyFileSynchronously(old_path, new_path, stbuf);
+        } else if (S_ISLNK(stbuf.st_mode)) {
+          success = CopySymlinkSynchronously(old_path, new_path, stbuf);
+        } else if (S_ISFIFO(stbuf.st_mode) ||
+                   S_ISCHR(stbuf.st_mode) ||
+                   S_ISBLK(stbuf.st_mode) ||
+                   S_ISSOCK(stbuf.st_mode)) {
+          success = CreateNodeSynchronously(new_path, stbuf);
+        } else {
+          CHECK(false) << "Unable to copy file " << old_path << " with mode "
+                       << stbuf.st_mode;
+        }
+      }
+    }
+    TEST_AND_RETURN_FALSE(success);
+
+    // chmod new file
+    if (!S_ISLNK(stbuf.st_mode)) {
+      int r = chmod(new_path.c_str(), stbuf.st_mode);
+      TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+    }
+
+    // Set uid/gid.
+    int r = lchown(new_path.c_str(), stbuf.st_uid, stbuf.st_gid);
+    TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+  }
+  TEST_AND_RETURN_FALSE(!iter.IsErr());
+  // Success!
+  return true;
+}
+
+const char* FilesystemCopierAction::kCompleteFilesystemMarker(
+    "/update_engine_copy_success");
+
+}  // namespace chromeos_update_engine
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
new file mode 100644
index 0000000..8f0dc06
--- /dev/null
+++ b/filesystem_copier_action.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2009 The Chromium 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_FILESYSTEM_COPIER_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_FILESYSTEM_COPIER_ACTION_H__
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string>
+#include <glib.h>
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+
+// This action will only do real work if it's a delta update. It will
+// format the install partition as ext3/4, copy the root filesystem into it,
+// and then terminate.
+
+// Implementation notes: This action uses a helper thread, which seems to
+// violate the design decision to only have a single thread and use
+// asynchronous i/o. The issue is that (to the best of my knowledge),
+// there are no linux APIs to crawl a filesystem's metadata asynchronously.
+// The suggested way seems to be to open the raw device and parse the ext
+// filesystem. That's not a good approach for a number of reasons:
+// - ties us to ext filesystem
+// - although this wouldn't happen at the time of writing, it may not handle
+//   changes to the source fs during the copy as gracefully.
+// - requires us to have read-access to the source filesystem device, which
+//   may be a security issue.
+//
+// Having said this, using a helper thread is not ideal, but it's acceptable:
+// we still honor the Action API. That is, all interaction between the action
+// and other objects in the system (e.g. the ActionProcessor) happens on the
+// main thread. The helper thread is fully encapsulated by the action.
+
+namespace chromeos_update_engine {
+
+class FilesystemCopierAction;
+
+template<>
+class ActionTraits<FilesystemCopierAction> {
+ public:
+  // Takes the install plan as input
+  typedef InstallPlan InputObjectType;
+  // Passes the install plan as output
+  typedef InstallPlan OutputObjectType;
+};
+
+class FilesystemCopierAction : public Action<FilesystemCopierAction> {
+ public:
+  FilesystemCopierAction()
+      : thread_should_exit_(0),
+        is_mounted_(false),
+        copy_source_("/"),
+        skipped_copy_(false) {}
+  typedef ActionTraits<FilesystemCopierAction>::InputObjectType
+  InputObjectType;
+  typedef ActionTraits<FilesystemCopierAction>::OutputObjectType
+  OutputObjectType;
+  void PerformAction();
+  void TerminateProcessing();
+
+  // Used for testing, so we can copy from somewhere other than root
+  void set_copy_source(const string& path) {
+    copy_source_ = path;
+  }
+  // Returns true if we detected that a copy was unneeded and thus skipped it.
+  bool skipped_copy() { return skipped_copy_; }
+
+  // Debugging/logging
+  static std::string StaticType() { return "FilesystemCopierAction"; }
+  std::string Type() const { return StaticType(); }
+
+ private:
+  // These synchronously mount or unmount the given mountpoint
+  bool Mount(const string& device, const string& mountpoint);
+  bool Unmount(const string& mountpoint);
+
+  // Performs a recursive file/directory copy from copy_source_ to dest_path_.
+  // Doesn't return until the copy has completed. Returns true on success
+  // or false on error.
+  bool CopySynchronously();
+
+  // There are helper functions for CopySynchronously. They handle creating
+  // various types of files. They return true on success.
+  bool CreateDirSynchronously(const std::string& new_path,
+                              const struct stat& stbuf);
+  bool CopyFileSynchronously(const std::string& old_path,
+                             const std::string& new_path,
+                             const struct stat& stbuf);
+  bool CreateHardLinkSynchronously(const std::string& old_path,
+                                   const std::string& new_path);
+  // Note: Here, old_path is an existing symlink that will be copied to
+  // new_path. Thus, old_path is *not* the same as the old_path from
+  // the symlink() syscall.
+  bool CopySymlinkSynchronously(const std::string& old_path,
+                                const std::string& new_path,
+                                const struct stat& stbuf);
+  bool CreateNodeSynchronously(const std::string& new_path,
+                               const struct stat& stbuf);
+
+  // Returns NULL on success
+  void* HelperThreadMain();
+  static void* HelperThreadMainStatic(void* data) {
+    FilesystemCopierAction* self =
+        reinterpret_cast<FilesystemCopierAction*>(data);
+    return self->HelperThreadMain();
+  }
+
+  // Joins the thread and tells the processor that we're done
+  void CollectThread();
+  // GMainLoop callback function:
+  static gboolean CollectThreadStatic(gpointer data) {
+    FilesystemCopierAction* self =
+        reinterpret_cast<FilesystemCopierAction*>(data);
+    self->CollectThread();
+    return FALSE;
+  }
+
+  pthread_t helper_thread_;
+
+  volatile gint thread_should_exit_;
+
+  static const char* kCompleteFilesystemMarker;
+
+  // Whether or not the destination device is currently mounted.
+  bool is_mounted_;
+
+  // Where the destination device is mounted.
+  string dest_path_;
+
+  // The path to copy from. Usually left as the default "/", but tests can
+  // change it.
+  string copy_source_;
+
+  // The install plan we're passed in via the input pipe.
+  InstallPlan install_plan_;
+
+  // Set to true if we detected the copy was unneeded and thus we skipped it.
+  bool skipped_copy_;
+
+  DISALLOW_COPY_AND_ASSIGN(FilesystemCopierAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_FILESYSTEM_COPIER_ACTION_H__
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
new file mode 100644
index 0000000..46c13a3
--- /dev/null
+++ b/filesystem_copier_action_unittest.cc
@@ -0,0 +1,273 @@
+// Copyright (c) 2009 The Chromium 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 <glib.h>
+#include <set>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/filesystem_copier_action.h"
+#include "update_engine/filesystem_iterator.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemCopierActionTest : public ::testing::Test {
+ protected:
+  void DoTest(bool double_copy, bool run_out_of_space);
+  string TestDir() { return "./FilesystemCopierActionTestDir"; }
+  void SetUp() {
+    System(string("mkdir -p ") + TestDir());
+  }
+  void TearDown() {
+    System(string("rm -rf ") + TestDir());
+  }
+};
+
+class FilesystemCopierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+  FilesystemCopierActionTestDelegate() : ran_(false), success_(false) {}
+  void ProcessingDone(const ActionProcessor* processor) {
+    g_main_loop_quit(loop_);
+  }
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       bool success) {
+    if (action->Type() == FilesystemCopierAction::StaticType()) {
+      ran_ = true;
+      success_ = success;
+    }
+  }
+  void set_loop(GMainLoop* loop) {
+    loop_ = loop;
+  }
+  bool ran() { return ran_; }
+  bool success() { return success_; }
+ private:
+  GMainLoop* loop_;
+  bool ran_;
+  bool success_;
+};
+
+gboolean StartProcessorInRunLoop(gpointer data) {
+  ActionProcessor* processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  return FALSE;
+}
+
+TEST_F(FilesystemCopierActionTest, RunAsRootSimpleTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(false, false);
+}
+void FilesystemCopierActionTest::DoTest(bool double_copy,
+                                        bool run_out_of_space) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  // make two populated ext images, mount them both (one in the other),
+  // and copy to a loop device setup to correspond to another file.
+
+  const string a_image(TestDir() + "/a_image");
+  const string b_image(TestDir() + "/b_image");
+  const string out_image(TestDir() + "/out_image");
+
+  vector<string> expected_paths_vector;
+  CreateExtImageAtPath(a_image, &expected_paths_vector);
+  CreateExtImageAtPath(b_image, NULL);
+
+  // create 5 MiB file
+  ASSERT_EQ(0, System(string("dd if=/dev/zero of=") + out_image
+                      + " seek=5242879 bs=1 count=1"));
+
+  // mount them both
+  System(("mkdir -p " + TestDir() + "/mnt").c_str());
+  ASSERT_EQ(0, System(string("mount -o loop ") + a_image + " " +
+                      TestDir() + "/mnt"));
+  ASSERT_EQ(0,
+            System(string("mount -o loop ") + b_image + " " +
+                   TestDir() + "/mnt/some_dir/mnt"));
+
+  if (run_out_of_space)
+    ASSERT_EQ(0, System(string("dd if=/dev/zero of=") +
+                        TestDir() + "/mnt/big_zero bs=5M count=1"));
+
+  string dev = GetUnusedLoopDevice();
+
+  EXPECT_EQ(0, System(string("losetup ") + dev + " " + out_image));
+
+  InstallPlan install_plan;
+  install_plan.is_full_update = false;
+  install_plan.install_path = dev;
+
+  ActionProcessor processor;
+  FilesystemCopierActionTestDelegate delegate;
+  delegate.set_loop(loop);
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  FilesystemCopierAction copier_action;
+  FilesystemCopierAction copier_action2;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&feeder_action, &copier_action);
+  if (double_copy) {
+    BondActions(&copier_action, &copier_action2);
+    BondActions(&copier_action2, &collector_action);
+  } else {
+    BondActions(&copier_action, &collector_action);
+  }
+
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&copier_action);
+  if (double_copy)
+    processor.EnqueueAction(&copier_action2);
+  processor.EnqueueAction(&collector_action);
+
+  copier_action.set_copy_source(TestDir() + "/mnt");
+  feeder_action.set_obj(install_plan);
+
+  g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+
+  EXPECT_EQ(0, System(string("losetup -d ") + dev));
+  EXPECT_EQ(0, System(string("umount ") + TestDir() + "/mnt/some_dir/mnt"));
+  EXPECT_EQ(0, System(string("umount ") + TestDir() + "/mnt"));
+  EXPECT_EQ(0, unlink(a_image.c_str()));
+  EXPECT_EQ(0, unlink(b_image.c_str()));
+
+  EXPECT_TRUE(delegate.ran());
+  if (run_out_of_space) {
+    EXPECT_FALSE(delegate.success());
+    EXPECT_EQ(0, unlink(out_image.c_str()));
+    EXPECT_EQ(0, rmdir((TestDir() + "/mnt").c_str()));
+    return;
+  }
+  EXPECT_TRUE(delegate.success());
+
+  EXPECT_EQ(0, System(string("mount -o loop ") + out_image + " " +
+                      TestDir() + "/mnt"));
+  // Make sure everything in the out_image is there
+  expected_paths_vector.push_back("/update_engine_copy_success");
+  for (vector<string>::iterator it = expected_paths_vector.begin();
+       it != expected_paths_vector.end(); ++it) {
+    *it = TestDir() + "/mnt" + *it;
+  }
+  set<string> expected_paths(expected_paths_vector.begin(),
+                             expected_paths_vector.end());
+  VerifyAllPaths(TestDir() + "/mnt", expected_paths);
+  string file_data;
+  EXPECT_TRUE(utils::ReadFileToString(TestDir() + "/mnt/hi", &file_data));
+  EXPECT_EQ("hi\n", file_data);
+  EXPECT_TRUE(utils::ReadFileToString(TestDir() + "/mnt/hello", &file_data));
+  EXPECT_EQ("hello\n", file_data);
+  EXPECT_EQ("/some/target", Readlink(TestDir() + "/mnt/sym"));
+  EXPECT_EQ(0, System(string("umount ") + TestDir() + "/mnt"));
+
+  EXPECT_EQ(0, unlink(out_image.c_str()));
+  EXPECT_EQ(0, rmdir((TestDir() + "/mnt").c_str()));
+
+  EXPECT_FALSE(copier_action.skipped_copy());
+  LOG(INFO) << "collected plan:";
+  collector_action.object().Dump();
+  LOG(INFO) << "expected plan:";
+  install_plan.Dump();
+  EXPECT_TRUE(collector_action.object() == install_plan);
+}
+
+class FilesystemCopierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       bool success) {
+    if (action->Type() == FilesystemCopierAction::StaticType()) {
+      ran_ = true;
+      success_ = success;
+    }
+  }
+  GMainLoop *loop_;
+  bool ran_;
+  bool success_;
+};
+
+TEST_F(FilesystemCopierActionTest, MissingInputObjectTest) {
+  ActionProcessor processor;
+  FilesystemCopierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  FilesystemCopierAction copier_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&copier_action, &collector_action);
+
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_FALSE(delegate.success_);
+}
+
+TEST_F(FilesystemCopierActionTest, FullUpdateTest) {
+  ActionProcessor processor;
+  FilesystemCopierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan(true, "", "", "", "");
+  feeder_action.set_obj(install_plan);
+  FilesystemCopierAction copier_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&feeder_action, &copier_action);
+  BondActions(&copier_action, &collector_action);
+
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_TRUE(delegate.success_);
+}
+
+TEST_F(FilesystemCopierActionTest, NonExistentDriveTest) {
+  ActionProcessor processor;
+  FilesystemCopierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan(false, "", "", "", "/some/missing/file/path");
+  feeder_action.set_obj(install_plan);
+  FilesystemCopierAction copier_action;
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&copier_action, &collector_action);
+
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_FALSE(delegate.success_);
+}
+
+TEST_F(FilesystemCopierActionTest, RunAsRootSkipUpdateTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(true, false);
+}
+
+TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(false, true);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/filesystem_iterator.cc b/filesystem_iterator.cc
new file mode 100644
index 0000000..9d98dea
--- /dev/null
+++ b/filesystem_iterator.cc
@@ -0,0 +1,147 @@
+// Copyright (c) 2009 The Chromium 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/filesystem_iterator.h"
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <set>
+#include <string>
+#include <vector>
+#include "chromeos/obsolete_logging.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// We use a macro here for two reasons:
+// 1. We want to be able to return from the caller's function.
+// 2. We can use the #macro_arg ism to get a string of the calling statement,
+//    which we can log.
+
+#define RETURN_ERROR_IF_FALSE(_statement)                               \
+  do {                                                                  \
+    bool _result = (_statement);                                        \
+    if (!_result) {                                                     \
+      string _message = utils::ErrnoNumberAsString(errno);              \
+      LOG(INFO) << #_statement << " failed: " << _message << ". Aborting"; \
+      is_end_ = true;                                                   \
+      is_err_ = true;                                                   \
+      return;                                                           \
+    }                                                                   \
+  } while (0)
+
+FilesystemIterator::FilesystemIterator(
+    const std::string& path,
+    const std::set<std::string>& excl_prefixes)
+    : excl_prefixes_(excl_prefixes),
+      is_end_(false),
+      is_err_(false) {
+  root_path_ = utils::NormalizePath(path, true);
+  RETURN_ERROR_IF_FALSE(lstat(root_path_.c_str(), &stbuf_) == 0);
+  root_dev_ = stbuf_.st_dev;
+}
+
+FilesystemIterator::~FilesystemIterator() {
+  for (vector<DIR*>::iterator it = dirs_.begin(); it != dirs_.end(); ++it) {
+    LOG_IF(ERROR, closedir(*it) != 0) << "closedir failed";
+  }
+}
+
+// Returns full path for current file
+std::string FilesystemIterator::GetFullPath() const {
+  return root_path_ + GetPartialPath();
+}
+
+std::string FilesystemIterator::GetPartialPath() const {
+  std::string ret;
+  for (vector<string>::const_iterator it = names_.begin();
+       it != names_.end(); ++it) {
+    ret += "/";
+    ret += *it;
+  }
+  return ret;
+}
+
+// Increments to the next file
+void FilesystemIterator::Increment() {
+  // If we're currently on a dir, descend into children, but only if
+  // we're on the same device as the root device
+
+  bool entering_dir = false;  // true if we're entering into a new dir
+  if (S_ISDIR(stbuf_.st_mode) && (stbuf_.st_dev == root_dev_)) {
+    DIR* dir = opendir(GetFullPath().c_str());
+    if ((!dir) && ((errno == ENOTDIR) || (errno == ENOENT))) {
+      // opendir failed b/c either it's not a dir or it doesn't exist.
+      // that's fine. let's just skip over this.
+      LOG(ERROR) << "Can't descend into " << GetFullPath();
+    } else {
+      RETURN_ERROR_IF_FALSE(dir);
+      entering_dir = true;
+      dirs_.push_back(dir);
+    }
+  }
+
+  if (!entering_dir && names_.empty()) {
+    // root disappeared while we tried to descend into it
+    is_end_ = true;
+    return;
+  }
+
+  if (!entering_dir)
+    names_.pop_back();
+
+  IncrementInternal();
+  for (set<string>::const_iterator it = excl_prefixes_.begin();
+       it != excl_prefixes_.end(); ++it) {
+    if (utils::StringHasPrefix(GetPartialPath(), *it)) {
+      Increment();
+      break;
+    }
+  }
+  return;
+}
+
+// Assumes that we need to find the next child of dirs_.back(), or if
+// there are none more, go up the chain
+void FilesystemIterator::IncrementInternal() {
+  CHECK_EQ(dirs_.size(), names_.size() + 1);
+  for (;;) {
+    struct dirent dir_entry;
+    struct dirent* dir_entry_pointer;
+    int r;
+    RETURN_ERROR_IF_FALSE(
+        (r = readdir_r(dirs_.back(), &dir_entry, &dir_entry_pointer)) == 0);
+    if (dir_entry_pointer) {
+      // Found an entry
+      names_.push_back(dir_entry_pointer->d_name);
+      // Validate
+      RETURN_ERROR_IF_FALSE(lstat(GetFullPath().c_str(), &stbuf_) == 0);
+      if (strcmp(dir_entry_pointer->d_name, ".") &&
+          strcmp(dir_entry_pointer->d_name, "..")) {
+        // Done
+        return;
+      }
+      // Child didn't work out. Try again
+      names_.pop_back();
+    } else {
+      // No more children in this dir. Pop it and try again
+      RETURN_ERROR_IF_FALSE(closedir(dirs_.back()) == 0);
+      dirs_.pop_back();
+      if (dirs_.empty()) {
+        CHECK(names_.empty());
+        // Done with the entire iteration
+        is_end_ = true;
+        return;
+      }
+      CHECK(!names_.empty());
+      names_.pop_back();
+    }
+  }
+}
+
+}  //   namespace chromeos_update_engine
diff --git a/filesystem_iterator.h b/filesystem_iterator.h
new file mode 100644
index 0000000..a1ba8af
--- /dev/null
+++ b/filesystem_iterator.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2009 The Chromium 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_FILESYSTEM_ITERATOR_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_FILESYSTEM_ITERATOR_H__
+
+// This class is used to walk a filesystem. It will iterate over every file
+// on the same device as the file passed in the ctor. Directories will be
+// visited before their children. Children will be visited in no particular
+// order.
+
+// The iterator is a forward iterator. It's not random access nor can it be
+// decremented.
+
+// Note: If the iterator comes across a mount point where another filesystem
+// is mounted, that mount point will be present, but none of its children
+// will be. Technically the mount point is on the other filesystem (and
+// the Stat() call will verify that), but we return it anyway since:
+// 1. Such a folder must exist in the first filesystem if it got used
+//    as a mount point.
+// 2. You probably want to copy if it you're using the iterator to do a
+//    filesystem copy
+// 3. If you don't want that, you can just check Stat().st_dev and skip
+//    foreign filesystems manually.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <string>
+#include <set>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class FilesystemIterator {
+ public:
+  FilesystemIterator(const std::string& path,
+                     const std::set<std::string>& excl_prefixes);
+
+  ~FilesystemIterator();
+
+  // Returns stat struct for the current file.
+  struct stat GetStat() const {
+    return stbuf_;
+  }
+
+  // Returns full path for current file.
+  std::string GetFullPath() const;
+
+  // Returns the path that's part of the iterator. For example, if
+  // the object were constructed by passing in "/foo/bar" and Path()
+  // returns "/foo/bar/baz/bat.txt", IterPath would return
+  // "/baz/bat.txt". When this object is on root (ie, the very first
+  // path), IterPath will return "", otherwise the first character of
+  // IterPath will be "/".
+  std::string GetPartialPath() const;
+
+  // Returns name for current file.
+  std::string GetBasename() const {
+    return names_.back();
+  }
+
+  // Increments to the next file.
+  void Increment();
+
+  // If we're at the end. If at the end, do not call Stat(), Path(), etc.,
+  // since this iterator currently isn't pointing to any file at all.
+  bool IsEnd() const {
+    return is_end_;
+  }
+
+  // Returns true if the iterator is in an error state.
+  bool IsErr() const {
+    return is_err_;
+  }
+ private:
+  // Helper for Increment.
+  void IncrementInternal();
+
+  // Returns true if path exists and it's a directory.
+  bool DirectoryExists(const std::string& path);
+
+  // In general (i.e., not midway through a call to Increment()), there is a
+  // relationship between dirs_ and names_: dirs[i] == names_[i - 1].
+  // For example, say we are asked to iterate "/usr/local" and we're currently
+  // at /usr/local/share/dict/words. dirs_ contains DIR* variables for the
+  // dirs at: {"/usr/local", ".../share", ".../dict"} and names_ contains:
+  // {"share", "dict", "words"}. root_path_ contains "/usr/local".
+  // root_dev_ would be the dev for root_path_
+  // (and /usr/local/share/dict/words). stbuf_ would be the stbuf for
+  // /usr/local/share/dict/words.
+
+  // All opened directories. If this is empty, we're currently on the root,
+  // but not descended into the root.
+  // This will always contain the current directory and all it's ancestors
+  // in root-to-leaf order. For more details, see comment above.
+  std::vector<DIR*> dirs_;
+
+  // The list of all filenames for the current path that we've descended into.
+  std::vector<std::string> names_;
+
+  // The device of the root path we've been asked to iterate.
+  dev_t root_dev_;
+
+  // The root path we've been asked to iteratate.
+  std::string root_path_;
+
+  // Exclude items w/ this prefix.
+  std::set<std::string> excl_prefixes_;
+
+  // The struct stat of the current file we're at.
+  struct stat stbuf_;
+
+  // Generally false; set to true when we reach the end of files to iterate
+  // or error occurs.
+  bool is_end_;
+
+  // Generally false; set to true if an error occurrs.
+  bool is_err_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_FILESYSTEM_ITERATOR_H__
diff --git a/filesystem_iterator_unittest.cc b/filesystem_iterator_unittest.cc
new file mode 100644
index 0000000..82b8d3f
--- /dev/null
+++ b/filesystem_iterator_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2009 The Chromium 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <set>
+#include <string>
+#include <vector>
+#include "base/string_util.h"
+#include <gtest/gtest.h>
+#include "update_engine/filesystem_iterator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char* TestDir() { return "./FilesystemIteratorTest-dir"; }
+}  // namespace {}
+
+class FilesystemIteratorTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    LOG(INFO) << "SetUp() mkdir";
+    EXPECT_EQ(0, System(StringPrintf("rm -rf %s", TestDir())));
+    EXPECT_EQ(0, System(StringPrintf("mkdir -p %s", TestDir())));
+  }
+  virtual void TearDown() {
+    LOG(INFO) << "TearDown() rmdir";
+    EXPECT_EQ(0, System(StringPrintf("rm -rf %s", TestDir())));
+  }
+};
+
+TEST_F(FilesystemIteratorTest, RunAsRootSuccessTest) {
+  ASSERT_EQ(0, getuid());
+  string first_image("FilesystemIteratorTest.image1");
+  string sub_image("FilesystemIteratorTest.image2");
+
+  ASSERT_EQ(0, System(string("rm -f ") + first_image + " " + sub_image));
+  vector<string> expected_paths_vector;
+  CreateExtImageAtPath(first_image, &expected_paths_vector);
+  CreateExtImageAtPath(sub_image, NULL);
+  ASSERT_EQ(0, System(string("mount -o loop ") + first_image + " " +
+                      kMountPath));
+  ASSERT_EQ(0, System(string("mount -o loop ") + sub_image + " " +
+                      kMountPath + "/some_dir/mnt"));
+  for (vector<string>::iterator it = expected_paths_vector.begin();
+       it != expected_paths_vector.end(); ++it)
+    *it = kMountPath + *it;
+  set<string> expected_paths(expected_paths_vector.begin(),
+                             expected_paths_vector.end());
+  VerifyAllPaths(kMountPath, expected_paths);
+  
+  EXPECT_EQ(0, System(string("umount ") + kMountPath + "/some_dir/mnt"));
+  EXPECT_EQ(0, System(string("umount ") + kMountPath));
+  EXPECT_EQ(0, System(string("rm -f ") + first_image + " " + sub_image));
+}
+
+TEST_F(FilesystemIteratorTest, NegativeTest) {
+  {
+    FilesystemIterator iter("/non/existent/path", set<string>());
+    EXPECT_TRUE(iter.IsEnd());
+    EXPECT_TRUE(iter.IsErr());
+  }
+
+  {
+    FilesystemIterator iter(TestDir(), set<string>());
+    EXPECT_FALSE(iter.IsEnd());
+    EXPECT_FALSE(iter.IsErr());
+    // Here I'm deleting the exact directory that iterator is point at,
+    // then incrementing (which normally would descend into that directory).
+    EXPECT_EQ(0, rmdir(TestDir()));
+    iter.Increment();
+    EXPECT_TRUE(iter.IsEnd());
+    EXPECT_FALSE(iter.IsErr());
+  }
+}
+
+TEST_F(FilesystemIteratorTest, DeleteWhileTraverseTest) {
+  ASSERT_EQ(0, mkdir("DeleteWhileTraverseTest", 0755));
+  ASSERT_EQ(0, mkdir("DeleteWhileTraverseTest/a", 0755));
+  ASSERT_EQ(0, mkdir("DeleteWhileTraverseTest/a/b", 0755));
+  ASSERT_EQ(0, mkdir("DeleteWhileTraverseTest/b", 0755));
+  ASSERT_EQ(0, mkdir("DeleteWhileTraverseTest/c", 0755));
+
+  string expected_paths_arr[] = {
+    "",
+    "/a",
+    "/b",
+    "/c"
+  };
+  set<string> expected_paths(expected_paths_arr,
+                             expected_paths_arr +
+                             arraysize(expected_paths_arr));
+
+  FilesystemIterator iter("DeleteWhileTraverseTest", set<string>());
+  while (!iter.IsEnd()) {
+    string path = iter.GetPartialPath();
+    EXPECT_TRUE(expected_paths.find(path) != expected_paths.end());
+    if (expected_paths.find(path) != expected_paths.end()) {
+      expected_paths.erase(path);
+    }
+    if (path == "/a") {
+      EXPECT_EQ(0, rmdir("DeleteWhileTraverseTest/a/b"));
+      EXPECT_EQ(0, rmdir("DeleteWhileTraverseTest/a"));
+    }
+    iter.Increment();
+  }
+  EXPECT_FALSE(iter.IsErr());
+  EXPECT_TRUE(expected_paths.empty());
+  EXPECT_EQ(0, system("rm -rf DeleteWhileTraverseTest"));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/generate_delta_main.cc b/generate_delta_main.cc
new file mode 100644
index 0000000..14af193
--- /dev/null
+++ b/generate_delta_main.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 The Chromium 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 <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <tr1/memory>
+
+#include <glib.h>
+
+#include "chromeos/obsolete_logging.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::sort;
+using std::string;
+using std::vector;
+using std::tr1::shared_ptr;
+
+// This file contains a simple program that takes an old path, a new path,
+// and an output file as arguments and the path to an output file and
+// generates a delta that can be sent to Chrome OS clients.
+
+namespace chromeos_update_engine {
+
+int main(int argc, char** argv) {
+  g_thread_init(NULL);
+  Subprocess::Init();
+  if (argc != 4) {
+    usage(argv[0]);
+  }
+  const char* old_dir = argv[1];
+  const char* new_dir = argv[2];
+  if ((!IsDir(old_dir)) || (!IsDir(new_dir))) {
+    usage(argv[0]);
+  }
+  // TODO(adlr): implement using DeltaDiffGenerator
+  return 0;
+}
\ No newline at end of file
diff --git a/gzip.cc b/gzip.cc
new file mode 100644
index 0000000..9643724
--- /dev/null
+++ b/gzip.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2009 The Chromium 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/gzip.h"
+#include <stdlib.h>
+#include <algorithm>
+#include <zlib.h>
+#include "chromeos/obsolete_logging.h"
+#include "update_engine/utils.h"
+
+using std::max;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool GzipDecompressData(const char* const in, const size_t in_size,
+                        char** out, size_t* out_size) {
+  if (in_size == 0) {
+    // malloc(0) may legally return NULL, so do malloc(1)
+    *out = reinterpret_cast<char*>(malloc(1));
+    *out_size = 0;
+    return true;
+  }
+  TEST_AND_RETURN_FALSE(out);
+  TEST_AND_RETURN_FALSE(out_size);
+  z_stream stream;
+  memset(&stream, 0, sizeof(stream));
+  TEST_AND_RETURN_FALSE(inflateInit2(&stream, 16 + MAX_WBITS) == Z_OK);
+
+  // guess that output will be roughly double the input size
+  *out_size = in_size * 2;
+  *out = reinterpret_cast<char*>(malloc(*out_size));
+  TEST_AND_RETURN_FALSE(*out);
+
+  // TODO(adlr): ensure that this const_cast is safe.
+  stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(in));
+  stream.avail_in = in_size;
+  stream.next_out = reinterpret_cast<Bytef*>(*out);
+  stream.avail_out = *out_size;
+  for (;;) {
+    int rc = inflate(&stream, Z_FINISH);
+    switch (rc) {
+      case Z_STREAM_END: {
+        *out_size = reinterpret_cast<char*>(stream.next_out) - (*out);
+        TEST_AND_RETURN_FALSE(inflateEnd(&stream) == Z_OK);
+        return true;
+      }
+      case Z_OK:  // fall through
+      case Z_BUF_ERROR: {
+        // allocate more space
+        ptrdiff_t out_length =
+            reinterpret_cast<char*>(stream.next_out) - (*out);
+        *out_size *= 2;
+        char* new_out = reinterpret_cast<char*>(realloc(*out, *out_size));
+        if (!new_out) {
+          free(*out);
+          return false;
+        }
+        *out = new_out;
+        stream.next_out = reinterpret_cast<Bytef*>((*out) + out_length);
+        stream.avail_out = (*out_size) - out_length;
+        break;
+      }
+      default:
+        LOG(INFO) << "Unknown inflate() return value: " << rc;
+        if (stream.msg)
+          LOG(INFO) << " message: " << stream.msg;
+        free(*out);
+        return false;
+    }
+  }
+}
+
+bool GzipCompressData(const char* const in, const size_t in_size,
+                      char** out, size_t* out_size) {
+  if (in_size == 0) {
+    // malloc(0) may legally return NULL, so do malloc(1)
+    *out = reinterpret_cast<char*>(malloc(1));
+    *out_size = 0;
+    return true;
+  }
+  TEST_AND_RETURN_FALSE(out);
+  TEST_AND_RETURN_FALSE(out_size);
+  z_stream stream;
+  memset(&stream, 0, sizeof(stream));
+  TEST_AND_RETURN_FALSE(deflateInit2(&stream,
+                                      Z_BEST_COMPRESSION,
+                                      Z_DEFLATED,
+                                      16 + MAX_WBITS,
+                                      9,  // most memory used/best compression
+                                      Z_DEFAULT_STRATEGY) == Z_OK);
+
+  // guess that output will be roughly half the input size
+  *out_size = max(1U, in_size / 2);
+  *out = reinterpret_cast<char*>(malloc(*out_size));
+  TEST_AND_RETURN_FALSE(*out);
+
+  // TODO(adlr): ensure that this const_cast is safe.
+  stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(in));
+  stream.avail_in = in_size;
+  stream.next_out = reinterpret_cast<Bytef*>(*out);
+  stream.avail_out = *out_size;
+  for (;;) {
+    int rc = deflate(&stream, Z_FINISH);
+    switch (rc) {
+      case Z_STREAM_END: {
+        *out_size = reinterpret_cast<char*>(stream.next_out) - (*out);
+        TEST_AND_RETURN_FALSE(deflateEnd(&stream) == Z_OK);
+        return true;
+      }
+      case Z_OK:  // fall through
+      case Z_BUF_ERROR: {
+        // allocate more space
+        ptrdiff_t out_length =
+            reinterpret_cast<char*>(stream.next_out) - (*out);
+        *out_size *= 2;
+        char* new_out = reinterpret_cast<char*>(realloc(*out, *out_size));
+        if (!new_out) {
+          free(*out);
+          return false;
+        }
+        *out = new_out;
+        stream.next_out = reinterpret_cast<Bytef*>((*out) + out_length);
+        stream.avail_out = (*out_size) - out_length;
+        break;
+      }
+      default:
+        LOG(INFO) << "Unknown defalate() return value: " << rc;
+        if (stream.msg)
+          LOG(INFO) << " message: " << stream.msg;
+        free(*out);
+        return false;
+    }
+  }
+}
+
+bool GzipDecompress(const std::vector<char>& in, std::vector<char>* out) {
+  TEST_AND_RETURN_FALSE(out);
+  char* out_buf;
+  size_t out_size;
+  TEST_AND_RETURN_FALSE(GzipDecompressData(&in[0], in.size(),
+                                            &out_buf, &out_size));
+  out->insert(out->end(), out_buf, out_buf + out_size);
+  free(out_buf);
+  return true;
+}
+
+bool GzipCompress(const std::vector<char>& in, std::vector<char>* out) {
+  TEST_AND_RETURN_FALSE(out);
+  char* out_buf;
+  size_t out_size;
+  TEST_AND_RETURN_FALSE(GzipCompressData(&in[0], in.size(),
+                                          &out_buf, &out_size));
+  out->insert(out->end(), out_buf, out_buf + out_size);
+  free(out_buf);
+  return true;
+}
+
+bool GzipCompressString(const std::string& str,
+                        std::vector<char>* out) {
+  TEST_AND_RETURN_FALSE(out);
+  char* out_buf;
+  size_t out_size;
+  TEST_AND_RETURN_FALSE(GzipCompressData(str.data(), str.size(),
+                                          &out_buf, &out_size));
+  out->insert(out->end(), out_buf, out_buf + out_size);
+  free(out_buf);
+  return true;
+}
+
+bool GzipDecompressString(const std::string& str,
+                          std::vector<char>* out) {
+  TEST_AND_RETURN_FALSE(out);
+  char* out_buf;
+  size_t out_size;
+  TEST_AND_RETURN_FALSE(GzipDecompressData(str.data(), str.size(),
+                                            &out_buf, &out_size));
+  out->insert(out->end(), out_buf, out_buf + out_size);
+  free(out_buf);
+  return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/gzip.h b/gzip.h
new file mode 100644
index 0000000..d1f4aff
--- /dev/null
+++ b/gzip.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2009 The Chromium 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 <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+// Gzip compresses or decompresses the input to the output.
+// Returns true on success. If true, *out will point to a malloc()ed
+// buffer, which must be free()d by the caller.
+bool GzipCompressData(const char* const in, const size_t in_size,
+                      char** out, size_t* out_size);
+bool GzipDecompressData(const char* const in, const size_t in_size,
+                        char** out, size_t* out_size);
+
+// Helper functions:
+bool GzipDecompress(const std::vector<char>& in, std::vector<char>* out);
+bool GzipCompress(const std::vector<char>& in, std::vector<char>* out);
+bool GzipCompressString(const std::string& str, std::vector<char>* out);
+bool GzipDecompressString(const std::string& str, std::vector<char>* out);
+
+}  // namespace chromeos_update_engine {
diff --git a/gzip_unittest.cc b/gzip_unittest.cc
new file mode 100644
index 0000000..298ca9d
--- /dev/null
+++ b/gzip_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2009 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 <string.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/gzip.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class GzipTest : public ::testing::Test { };
+
+TEST(GzipTest, SimpleTest) {
+  string in("this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+  vector<char> out;
+  EXPECT_TRUE(GzipCompressString(in, &out));
+  EXPECT_LT(out.size(), in.size());
+  EXPECT_GT(out.size(), 0);
+  vector<char> decompressed;
+  EXPECT_TRUE(GzipDecompress(out, &decompressed));
+  EXPECT_EQ(in.size(), decompressed.size());
+  EXPECT_TRUE(!memcmp(in.data(), &decompressed[0], in.size()));
+}
+
+TEST(GzipTest, PoorCompressionTest) {
+  string in(reinterpret_cast<const char*>(kRandomString),
+            sizeof(kRandomString));
+  vector<char> out;
+  EXPECT_TRUE(GzipCompressString(in, &out));
+  EXPECT_GT(out.size(), in.size());
+  string out_string(&out[0], out.size());
+  vector<char> decompressed;
+  EXPECT_TRUE(GzipDecompressString(out_string, &decompressed));
+  EXPECT_EQ(in.size(), decompressed.size());
+  EXPECT_TRUE(!memcmp(in.data(), &decompressed[0], in.size()));
+}
+
+TEST(GzipTest, MalformedGzipTest) {
+  string in(reinterpret_cast<const char*>(kRandomString),
+            sizeof(kRandomString));
+  vector<char> out;
+  EXPECT_FALSE(GzipDecompressString(in, &out));
+}
+
+TEST(GzipTest, EmptyInputsTest) {
+  string in;
+  vector<char> out;
+  EXPECT_TRUE(GzipDecompressString(in, &out));
+  EXPECT_EQ(0, out.size());
+
+  EXPECT_TRUE(GzipCompressString(in, &out));
+  EXPECT_EQ(0, out.size());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/install_action.cc b/install_action.cc
new file mode 100644
index 0000000..9c644df
--- /dev/null
+++ b/install_action.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2009 The Chromium 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/install_action.h"
+#include <errno.h>
+#include <vector>
+#include <gflags/gflags.h>
+#include "update_engine/filesystem_iterator.h"
+#include "update_engine/gzip.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+DEFINE_string(mount_install_path, "",
+              "If set, the path to use when mounting the "
+              "destination device during install");
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const string kBspatchPath = "/usr/bin/bspatch";
+}
+
+void InstallAction::PerformAction() {
+  ScopedActionCompleter completer(processor_, this);
+  // For now, do nothing other than pass what we need to to the output pipe
+  CHECK(HasInputObject());
+  const InstallPlan install_plan = GetInputObject();
+  if (HasOutputPipe())
+    SetOutputObject(install_plan.install_path);
+  if (install_plan.is_full_update) {
+    // No need to perform an install
+    completer.set_success(true);
+    return;
+  }
+  // We have a delta update.
+
+  // Open delta file
+  DeltaDiffParser parser(install_plan.download_path);
+  if (!parser.valid()) {
+    LOG(ERROR) << "Unable to open delta file";
+    return;
+  }
+
+  // Mount install fs
+  string mountpoint = FLAGS_mount_install_path;
+  if (mountpoint.empty()) {
+    // Set up dest_path_
+    char *mountpoint_temp = strdup("/tmp/install_mnt.XXXXXX");
+    CHECK(mountpoint_temp);
+    CHECK_EQ(mountpoint_temp, mkdtemp(mountpoint_temp));
+    CHECK_NE('\0', mountpoint_temp[0]);
+    mountpoint = mountpoint_temp;
+    free(mountpoint_temp);
+  }
+
+  TEST_AND_RETURN(utils::MountFilesystem(install_plan.install_path,
+                                         mountpoint));
+
+  // Automatically unmount the fs when this goes out of scope:
+  ScopedFilesystemUnmounter filesystem_unmounter(mountpoint);
+
+  {
+    // iterate through existing fs, deleting unneeded files
+    FilesystemIterator iter(mountpoint,
+                            utils::SetWithValue<string>("/lost+found"));
+    for (; !iter.IsEnd(); iter.Increment()) {
+      if (!parser.ContainsPath(iter.GetPartialPath())) {
+        VLOG(1) << "install removing local path: " << iter.GetFullPath();
+        TEST_AND_RETURN(utils::RecursiveUnlinkDir(iter.GetFullPath()));
+      }
+    }
+    TEST_AND_RETURN(!iter.IsErr());
+  }
+
+  // iterate through delta metadata, writing files
+  DeltaDiffParserIterator iter = parser.Begin();
+  for (; iter != parser.End(); iter.Increment()) {
+    const DeltaArchiveManifest_File& file = iter.GetFile();
+    VLOG(1) << "Installing file: " << iter.path();
+    TEST_AND_RETURN(InstallFile(mountpoint, file, iter.path(), parser));
+  }
+
+  completer.set_success(true);
+}
+
+bool InstallAction::InstallFile(const std::string& mountpoint,
+                                const DeltaArchiveManifest_File& file,
+                                const std::string& path,
+                                const DeltaDiffParser& parser) const {
+  // See what's already there
+  struct stat existing_stbuf;
+  int result = lstat((mountpoint + path).c_str(), &existing_stbuf);
+  TEST_AND_RETURN_FALSE_ERRNO((result == 0) || (errno == ENOENT));
+  bool exists = (result == 0);
+  // Create the proper file
+  if (S_ISDIR(file.mode())) {
+    if (!exists) {
+      TEST_AND_RETURN_FALSE_ERRNO(
+          (mkdir((mountpoint + path).c_str(), file.mode())) == 0);
+    }
+  } else if (S_ISLNK(file.mode())) {
+    InstallFileSymlink(mountpoint, file, path, parser, exists);
+  } else if (S_ISCHR(file.mode()) ||
+             S_ISBLK(file.mode()) ||
+             S_ISFIFO(file.mode()) ||
+             S_ISSOCK(file.mode())) {
+    InstallFileSpecialFile(mountpoint, file, path, parser, exists);
+  } else if (S_ISREG(file.mode())) {
+    InstallFileRegularFile(mountpoint, file, path, parser, exists);
+  } else {
+    // unknown mode type
+    TEST_AND_RETURN_FALSE(false);
+  }
+
+  // chmod/chown new file
+  if (!S_ISLNK(file.mode()))
+    TEST_AND_RETURN_FALSE_ERRNO(chmod((mountpoint + path).c_str(), file.mode())
+                                == 0);
+  TEST_AND_RETURN_FALSE(file.has_uid() && file.has_gid());
+  TEST_AND_RETURN_FALSE_ERRNO(lchown((mountpoint + path).c_str(),
+                                     file.uid(), file.gid()) == 0);
+  return true;
+}
+
+bool InstallAction::InstallFileRegularFile(
+    const std::string& mountpoint,
+    const DeltaArchiveManifest_File& file,
+    const std::string& path,
+    const DeltaDiffParser& parser,
+    const bool exists) const {
+  if (!file.has_data_format())
+    return true;
+  TEST_AND_RETURN_FALSE(file.has_data_offset() && file.has_data_length());
+  if (file.data_format() == DeltaArchiveManifest_File_DataFormat_BSDIFF) {
+    // Expand with bspatch
+    string patch_path = utils::TempFilename(mountpoint + path + ".XXXXXX");
+    TEST_AND_RETURN_FALSE(file.has_data_length());
+    TEST_AND_RETURN_FALSE(parser.CopyDataToFile(
+        file.data_offset(),
+        static_cast<off_t>(file.data_length()), false,
+        patch_path));
+    string output_path = utils::TempFilename(mountpoint + path + ".XXXXXX");
+    int rc = 1;
+    vector<string> cmd;
+    cmd.push_back(kBspatchPath);
+    cmd.push_back(mountpoint + path);
+    cmd.push_back(output_path);
+    cmd.push_back(patch_path);
+    TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc));
+    TEST_AND_RETURN_FALSE(rc == 0);
+    TEST_AND_RETURN_FALSE_ERRNO(rename(output_path.c_str(),
+                                       (mountpoint + path).c_str()) == 0);
+    TEST_AND_RETURN_FALSE_ERRNO(unlink(patch_path.c_str()) == 0);
+  } else {
+    // Expand full data, decompressing if necessary
+    TEST_AND_RETURN_FALSE((file.data_format() ==
+                           DeltaArchiveManifest_File_DataFormat_FULL) ||
+                          (file.data_format() ==
+                           DeltaArchiveManifest_File_DataFormat_FULL_GZ));
+    if (exists)
+      TEST_AND_RETURN_FALSE_ERRNO(unlink((mountpoint + path).c_str()) == 0);
+    TEST_AND_RETURN_FALSE(file.has_data_length());
+    const bool gzipped = file.data_format() ==
+        DeltaArchiveManifest_File_DataFormat_FULL_GZ;
+    bool success =
+        parser.CopyDataToFile(file.data_offset(), file.data_length(),
+                              gzipped,
+                              mountpoint + path);
+    TEST_AND_RETURN_FALSE(success);
+  }
+  return true;
+}
+
+// char/block devices, fifos, and sockets:
+bool InstallAction::InstallFileSpecialFile(
+    const std::string& mountpoint,
+    const DeltaArchiveManifest_File& file,
+    const std::string& path,
+    const DeltaDiffParser& parser,
+    const bool exists) const {
+  if (exists)
+    TEST_AND_RETURN_FALSE(unlink((mountpoint + path).c_str()) == 0);
+  dev_t dev = 0;
+  if (S_ISCHR(file.mode()) || S_ISBLK(file.mode())) {
+    vector<char> dev_proto;
+    TEST_AND_RETURN_FALSE(parser.ReadDataVector(file.data_offset(),
+                                                file.data_length(),
+                                                &dev_proto));
+    if (file.data_format() == DeltaArchiveManifest_File_DataFormat_FULL_GZ) {
+      TEST_AND_RETURN_FALSE(file.has_data_length());
+      {
+        vector<char> decompressed_dev_proto;
+        TEST_AND_RETURN_FALSE(GzipDecompress(dev_proto,
+                                             &decompressed_dev_proto));
+        dev_proto = decompressed_dev_proto;
+      }
+    } else {
+      TEST_AND_RETURN_FALSE(file.data_format() ==
+                            DeltaArchiveManifest_File_DataFormat_FULL);
+    }
+    LinuxDevice linux_device;
+    utils::HexDumpVector(dev_proto);
+    TEST_AND_RETURN_FALSE(linux_device.ParseFromArray(&dev_proto[0],
+                                                      dev_proto.size()));
+    dev = makedev(linux_device.major(), linux_device.minor());
+  }
+  TEST_AND_RETURN_FALSE_ERRNO(mknod((mountpoint + path).c_str(),
+                                    file.mode(), dev) == 0);
+  return true;
+}
+// symlinks:
+bool InstallAction::InstallFileSymlink(const std::string& mountpoint,
+                                       const DeltaArchiveManifest_File& file,
+                                       const std::string& path,
+                                       const DeltaDiffParser& parser,
+                                       const bool exists) const {
+  // If there's no data, we leave the symlink as is
+  if (!file.has_data_format())
+    return true;  // No changes needed
+  TEST_AND_RETURN_FALSE((file.data_format() ==
+                         DeltaArchiveManifest_File_DataFormat_FULL) ||
+                        (file.data_format() ==
+                         DeltaArchiveManifest_File_DataFormat_FULL_GZ));
+  TEST_AND_RETURN_FALSE(file.has_data_offset() && file.has_data_length());
+  // We have data, and thus use it to create a symlink.
+  // First delete any existing symlink:
+  if (exists)
+    TEST_AND_RETURN_FALSE_ERRNO(unlink((mountpoint + path).c_str()) == 0);
+  vector<char> symlink_data;
+  TEST_AND_RETURN_FALSE(parser.ReadDataVector(file.data_offset(),
+                                              file.data_length(),
+                                              &symlink_data));
+  if (file.data_format() == DeltaArchiveManifest_File_DataFormat_FULL_GZ) {
+    vector<char> decompressed_symlink_data;
+    TEST_AND_RETURN_FALSE(GzipDecompress(symlink_data,
+                                         &decompressed_symlink_data));
+    symlink_data = decompressed_symlink_data;
+  }
+  symlink_data.push_back('\0');
+  TEST_AND_RETURN_FALSE_ERRNO(symlink(&symlink_data[0],
+                                      (mountpoint + path).c_str()) == 0);
+  return true;
+}
+
+
+}  //   namespace chromeos_update_engine
diff --git a/install_action.h b/install_action.h
new file mode 100644
index 0000000..8bed632
--- /dev/null
+++ b/install_action.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2009 The Chromium 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_INSTALL_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_INSTALL_ACTION_H__
+
+#include "base/scoped_ptr.h"
+#include "update_engine/action.h"
+#include "update_engine/delta_diff_parser.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/update_metadata.pb.h"
+
+// The Install Action is responsible for ensuring the update that's been
+// downloaded has been installed. This may be a no-op in the case of a full
+// update, since those will be downloaded directly into the destination
+// partition. However, for a delta update some work is required.
+
+// An InstallPlan struct must be passed to this action before PerformAction()
+// is called so that this action knows if it's a delta update, and if so,
+// what the paths are.
+
+// TODO(adlr): At the moment, InstallAction is synchronous. It should be
+// updated to be asynchronous at some point.
+
+namespace chromeos_update_engine {
+
+class InstallAction;
+class NoneType;
+
+template<>
+class ActionTraits<InstallAction> {
+ public:
+  // Takes the InstallPlan for input
+  typedef InstallPlan InputObjectType;
+  // On success, puts the output device path on output
+  typedef std::string OutputObjectType;
+};
+
+class InstallAction : public Action<InstallAction> {
+ public:
+  InstallAction() {}
+  typedef ActionTraits<InstallAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<InstallAction>::OutputObjectType OutputObjectType;
+  void PerformAction();
+
+  // This action is synchronous for now.
+  void TerminateProcessing() { CHECK(false); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "InstallAction"; }
+  std::string Type() const { return StaticType(); }
+
+ private:
+  // Installs 'file' into mountpoint. 'path' is the path that 'file'
+  // should have when we reboot and mountpoint is root.
+  bool InstallFile(const std::string& mountpoint,
+                   const DeltaArchiveManifest_File& file,
+                   const std::string& path,
+                   const DeltaDiffParser& parser) const;
+  // These are helpers for InstallFile. They focus on specific file types:
+  // Regular data files:
+  bool InstallFileRegularFile(const std::string& mountpoint,
+                              const DeltaArchiveManifest_File& file,
+                              const std::string& path,
+                              const DeltaDiffParser& parser,
+                              const bool exists) const;
+  // char/block devices, fifos, and sockets:
+  bool InstallFileSpecialFile(const std::string& mountpoint,
+                              const DeltaArchiveManifest_File& file,
+                              const std::string& path,
+                              const DeltaDiffParser& parser,
+                              const bool exists) const;
+  // symlinks:
+  bool InstallFileSymlink(const std::string& mountpoint,
+                          const DeltaArchiveManifest_File& file,
+                          const std::string& path,
+                          const DeltaDiffParser& parser,
+                          const bool exists) const;
+
+  DISALLOW_COPY_AND_ASSIGN(InstallAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_INSTALL_ACTION_H__
diff --git a/install_action_unittest.cc b/install_action_unittest.cc
new file mode 100644
index 0000000..dde15ea
--- /dev/null
+++ b/install_action_unittest.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 2009 The Chromium 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 <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <set>
+#include <string>
+#include <vector>
+#include "base/string_util.h"
+#include <gtest/gtest.h>
+#include "update_engine/delta_diff_generator.h"
+#include "update_engine/filesystem_iterator.h"
+#include "update_engine/install_action.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::System;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace {
+void GenerateFilesAtPath(const string& base) {
+  EXPECT_EQ(0, System(StringPrintf("echo hi > %s/hi", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p %s/dir", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo hello > %s/dir/hello", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo -n > %s/dir/newempty", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("rm -f %s/dir/bdev", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mknod %s/dir/bdev b 3 1", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("rm -f %s/cdev", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mknod %s/cdev c 2 1", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p %s/dir/subdir", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir -p %s/dir/emptydir", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo -n foo > %s/dir/bigfile",
+                                   base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("chown 501:503 %s/dir/emptydir",
+                                   base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("rm -f %s/dir/subdir/fifo", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkfifo %s/dir/subdir/fifo", base.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("ln -f -s /target %s/dir/subdir/link",
+                                   base.c_str())));
+}
+
+// Returns true if files at paths a, b are equal and there are no errors.
+bool FilesEqual(const string& a, const string& b) {
+  struct stat a_stbuf;
+  struct stat b_stbuf;
+
+  int r = lstat(a.c_str(), &a_stbuf);
+  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+  r = lstat(b.c_str(), &b_stbuf);
+  TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+
+  TEST_AND_RETURN_FALSE(a_stbuf.st_mode == b_stbuf.st_mode);
+  if (S_ISBLK(a_stbuf.st_mode) || S_ISCHR(a_stbuf.st_mode))
+    TEST_AND_RETURN_FALSE(a_stbuf.st_rdev == b_stbuf.st_rdev);
+  if (!S_ISREG(a_stbuf.st_mode)) {
+    return true;
+  }
+  // Compare files
+  TEST_AND_RETURN_FALSE(a_stbuf.st_size == b_stbuf.st_size);
+  vector<char> a_data;
+  TEST_AND_RETURN_FALSE(chromeos_update_engine::utils::ReadFile(a, &a_data));
+  vector<char> b_data;
+  TEST_AND_RETURN_FALSE(chromeos_update_engine::utils::ReadFile(b, &b_data));
+  TEST_AND_RETURN_FALSE(a_data == b_data);
+  return true;
+}
+
+class ScopedLoopDevUnmapper {
+ public:
+  explicit ScopedLoopDevUnmapper(const string& dev) : dev_(dev) {}
+  ~ScopedLoopDevUnmapper() {
+    EXPECT_EQ(0, System(string("losetup -d ") + dev_));
+  }
+ private:
+  string dev_;
+};
+
+}
+
+namespace chromeos_update_engine {
+
+class InstallActionTest : public ::testing::Test { };
+
+TEST(InstallActionTest, RunAsRootDiffTest) {
+  ASSERT_EQ(0, getuid());
+  string loop_dev = GetUnusedLoopDevice();
+  ScopedLoopDevUnmapper loop_dev_unmapper(loop_dev);
+  LOG(INFO) << "Using loop device: " << loop_dev;
+  const string original_image("orig.image");
+  const string original_dir("orig");
+  const string new_dir("new");
+
+  ASSERT_EQ(0, System(string("dd if=/dev/zero of=") + original_image +
+                      " bs=5M count=1"));
+  ASSERT_EQ(0, System(string("mkfs.ext3 -F ") + original_image));
+  ASSERT_EQ(0, System(string("losetup ") + loop_dev + " " + original_image));
+  ASSERT_EQ(0, System(string("mkdir ") + original_dir));
+  ASSERT_EQ(0, System(string("mount ") + loop_dev + " " + original_dir));
+  ASSERT_EQ(0, System(string("mkdir ") + new_dir));
+
+  GenerateFilesAtPath(original_dir);
+  GenerateFilesAtPath(new_dir);
+
+  {
+    // Fill bigfile w/ some data in the new folder
+    vector<char> buf(100 * 1024);
+    for (unsigned int i = 0; i < buf.size(); i++) {
+      buf[i] = static_cast<char>(i);
+    }
+    EXPECT_TRUE(WriteFileVector(new_dir + "/dir/bigfile", buf));
+  }
+  // Make a diff
+  DeltaArchiveManifest* delta =
+      DeltaDiffGenerator::EncodeMetadataToProtoBuffer(new_dir.c_str());
+  EXPECT_TRUE(NULL != delta);
+  EXPECT_TRUE(DeltaDiffGenerator::EncodeDataToDeltaFile(delta,
+                                                        original_dir,
+                                                        new_dir,
+                                                        "delta"));
+
+  ASSERT_EQ(0, System(string("umount ") + original_dir));
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallAction install_action;
+  ObjectCollectorAction<string> collector_action;
+
+  BondActions(&feeder_action, &install_action);
+  BondActions(&install_action, &collector_action);
+
+  ActionProcessor processor;
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&install_action);
+  processor.EnqueueAction(&collector_action);
+
+  InstallPlan install_plan(false, "", "", "delta", loop_dev);
+  feeder_action.set_obj(install_plan);
+
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning()) << "Update to handle async actions";
+
+  EXPECT_EQ(loop_dev, collector_action.object());
+
+  ASSERT_EQ(0, System(string("mount ") + loop_dev + " " + original_dir));
+
+  // Check that original_dir and new_dir are equal
+  int original_count = 0;
+  LOG(INFO) << "checking old";
+  {
+    FilesystemIterator iter(original_dir,
+                            utils::SetWithValue<string>("/lost+found"));
+    for (; !iter.IsEnd(); iter.Increment()) {
+      original_count++;
+      LOG(INFO) << "checking path: " << iter.GetPartialPath();
+      EXPECT_TRUE(FilesEqual(original_dir + iter.GetPartialPath(),
+                             new_dir + iter.GetPartialPath()));
+    }
+    EXPECT_FALSE(iter.IsErr());
+  }
+  LOG(INFO) << "checking new";
+  int new_count = 0;
+  {
+    FilesystemIterator iter(new_dir, set<string>());
+    for (; !iter.IsEnd(); iter.Increment()) {
+      new_count++;
+      LOG(INFO) << "checking path: " << iter.GetPartialPath();
+      EXPECT_TRUE(FilesEqual(original_dir + iter.GetPartialPath(),
+                             new_dir + iter.GetPartialPath()));
+    }
+    EXPECT_FALSE(iter.IsErr());
+  }
+  LOG(INFO) << "new_count = " << new_count;
+  EXPECT_EQ(new_count, original_count);
+  EXPECT_EQ(12, original_count);  // 12 files in each dir
+
+  ASSERT_EQ(0, System(string("umount ") + original_dir));
+
+  // Cleanup generated files
+  ASSERT_EQ(0, System(string("rm -rf ") + original_dir));
+  ASSERT_EQ(0, System(string("rm -rf ") + new_dir));
+  EXPECT_EQ(0, System(string("rm -f ") + original_image));
+  ASSERT_EQ(0, system("rm -f delta"));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
new file mode 100644
index 0000000..81893d8
--- /dev/null
+++ b/install_plan.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2009 The Chromium 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_INSTALL_PLAN_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_INSTALL_PLAN_H__
+
+#include <string>
+#include "chromeos/obsolete_logging.h"
+
+// InstallPlan is a simple struct that contains relevant info for many
+// parts of the update system about the install that should happen.
+
+namespace chromeos_update_engine {
+
+struct InstallPlan {
+  InstallPlan(bool is_full,
+              const std::string& url,
+              const std::string& hash,
+              const std::string& d_path,
+              const std::string& i_path)
+      : is_full_update(is_full),
+        download_url(url),
+        download_hash(hash),
+        download_path(d_path),
+        install_path(i_path) {}
+  InstallPlan() : is_full_update(false) {}
+
+  bool is_full_update;
+  std::string download_url;  // url to download from
+  std::string download_hash;  // hash of the data at the url
+  std::string download_path;  // path to downloaded file from Omaha
+  std::string install_path;  // path to install device
+
+  bool operator==(const InstallPlan& that) const {
+    return (is_full_update == that.is_full_update) &&
+           (download_url == that.download_url) &&
+           (download_hash == that.download_hash) &&
+           (download_path == that.download_path) &&
+           (install_path == that.install_path);
+  }
+  bool operator!=(const InstallPlan& that) const {
+    return !((*this) == that);
+  }
+  void Dump() const {
+    LOG(INFO) << "InstallPlan: "
+              << (is_full_update ? "full_update" : "delta_update")
+              << ", url: " << download_url << ", hash: " << download_hash
+              << ", path: " << download_path
+              << ", install_path: " << install_path;
+  }
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_INSTALL_PLAN_H__
diff --git a/integration_unittest.cc b/integration_unittest.cc
new file mode 100644
index 0000000..671240f
--- /dev/null
+++ b/integration_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2009 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 <string>
+#include <vector>
+#include <glib.h>
+#include <pthread.h>
+#include <gtest/gtest.h>
+#include "update_engine/download_action.h"
+#include "update_engine/install_action.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/omaha_request_prep_action.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/postinstall_runner_action.h"
+#include "update_engine/set_bootable_flag_action.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_check_action.h"
+#include "update_engine/utils.h"
+
+// The tests here integrate many Action objects together. This test that
+// the objects work well together, whereas most other tests focus on a single
+// action at a time.
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+class IntegrationTest : public ::testing::Test { };
+
+namespace {
+const char* kTestDir = "/tmp/update_engine-integration-test";
+
+class IntegrationTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  IntegrationTestProcessorDelegate()
+      : loop_(NULL), processing_done_called_(false) {}
+  virtual ~IntegrationTestProcessorDelegate() {
+    EXPECT_TRUE(processing_done_called_);
+  }
+  virtual void ProcessingDone(const ActionProcessor* processor) {
+    processing_done_called_ = true;
+    g_main_loop_quit(loop_);
+  }
+
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
+                               bool success) {
+    // make sure actions always succeed
+    EXPECT_TRUE(success);
+
+    // Swap in the device path for PostinstallRunnerAction with a loop device
+    if (action->Type() == InstallAction::StaticType()) {
+      InstallAction* install_action = static_cast<InstallAction*>(action);
+      old_dev_ = install_action->GetOutputObject();
+      string dev = GetUnusedLoopDevice();
+      string cmd = string("losetup ") + dev + " " + kTestDir + "/dev2";
+      EXPECT_EQ(0, system(cmd.c_str()));
+      install_action->SetOutputObject(dev);
+    } else if (action->Type() == PostinstallRunnerAction::StaticType()) {
+      PostinstallRunnerAction* postinstall_runner_action =
+          static_cast<PostinstallRunnerAction*>(action);
+      string dev = postinstall_runner_action->GetOutputObject();
+      EXPECT_EQ(0, system((string("losetup -d ") + dev).c_str()));
+      postinstall_runner_action->SetOutputObject(old_dev_);
+      old_dev_ = "";
+    }
+  }
+
+  void set_loop(GMainLoop* loop) {
+    loop_ = loop;
+  }
+
+ private:
+  GMainLoop *loop_;
+  bool processing_done_called_;
+
+  // We have to change the dev for the PostinstallRunnerAction action.
+  // Before that runs, we store the device here, and after it runs, we
+  // restore it.
+  // This is because we use a file, rather than a device, to install into,
+  // but the PostinstallRunnerAction requires a real device. We set up a
+  // loop device pointing to the file when necessary.
+  string old_dev_;
+};
+
+gboolean TestStarter(gpointer data) {
+  ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  return FALSE;
+}
+
+}  // namespace {}
+
+TEST(IntegrationTest, DISABLED_RunAsRootFullInstallTest) {
+  ASSERT_EQ(0, getuid());
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  ActionProcessor processor;
+  IntegrationTestProcessorDelegate delegate;
+  delegate.set_loop(loop);
+  processor.set_delegate(&delegate);
+
+  // Actions:
+  OmahaRequestPrepAction request_prep_action(false);
+  UpdateCheckAction update_check_action(new LibcurlHttpFetcher);
+  OmahaResponseHandlerAction response_handler_action;
+  DownloadAction download_action(new LibcurlHttpFetcher);
+  InstallAction install_action;
+  PostinstallRunnerAction postinstall_runner_action;
+  SetBootableFlagAction set_bootable_flag_action;
+
+  // Enqueue the actions
+  processor.EnqueueAction(&request_prep_action);
+  processor.EnqueueAction(&update_check_action);
+  processor.EnqueueAction(&response_handler_action);
+  processor.EnqueueAction(&download_action);
+  processor.EnqueueAction(&install_action);
+  processor.EnqueueAction(&postinstall_runner_action);
+  processor.EnqueueAction(&set_bootable_flag_action);
+
+  // Bond them together
+  BondActions(&request_prep_action, &update_check_action);
+  BondActions(&update_check_action, &response_handler_action);
+  BondActions(&response_handler_action, &download_action);
+  BondActions(&download_action, &install_action);
+  BondActions(&install_action, &postinstall_runner_action);
+  BondActions(&postinstall_runner_action, &set_bootable_flag_action);
+
+  // Set up filesystem to trick some of the actions
+  ASSERT_EQ(0, System(string("rm -rf ") + kTestDir));
+  ASSERT_EQ(0, system("rm -f /tmp/update_engine_test_postinst_out.txt"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + kTestDir + "/etc"));
+  ASSERT_TRUE(WriteFileString(string(kTestDir) + "/etc/lsb-release",
+                              "GOOGLE_RELEASE=0.2.0.0\n"
+                              "GOOGLE_TRACK=unittest-track"));
+  ASSERT_EQ(0, System(string("touch ") + kTestDir + "/dev1"));
+  ASSERT_EQ(0, System(string("touch ") + kTestDir + "/dev2"));
+  ASSERT_TRUE(WriteFileVector(string(kTestDir) + "/dev", GenerateSampleMbr()));
+
+  request_prep_action.set_root(kTestDir);
+  response_handler_action.set_boot_device(string(kTestDir) + "/dev1");
+
+  // Run the actions
+  g_timeout_add(0, &TestStarter, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+
+  // Verify the results
+  struct stat stbuf;
+  ASSERT_EQ(0, lstat((string(kTestDir) + "/dev1").c_str(), &stbuf));
+  EXPECT_EQ(0, stbuf.st_size);
+  EXPECT_TRUE(S_ISREG(stbuf.st_mode));
+  ASSERT_EQ(0, lstat((string(kTestDir) + "/dev2").c_str(), &stbuf));
+  EXPECT_EQ(996147200, stbuf.st_size);
+  EXPECT_TRUE(S_ISREG(stbuf.st_mode));
+  ASSERT_EQ(0, lstat((string(kTestDir) + "/dev").c_str(), &stbuf));
+  ASSERT_EQ(512, stbuf.st_size);
+  EXPECT_TRUE(S_ISREG(stbuf.st_mode));
+  vector<char> new_mbr;
+  EXPECT_TRUE(utils::ReadFile((string(kTestDir) + "/dev").c_str(), &new_mbr));
+
+  // Check bootable flag in MBR
+  for (int i = 0; i < 4; i++) {
+    char expected_flag = '\0';
+    if (i == 1)
+      expected_flag = 0x80;
+    EXPECT_EQ(expected_flag, new_mbr[446 + 16 * i]);
+  }
+  // Check MBR signature
+  EXPECT_EQ(static_cast<char>(0x55), new_mbr[510]);
+  EXPECT_EQ(static_cast<char>(0xaa), new_mbr[511]);
+
+  ASSERT_EQ(0, lstat("/tmp/update_engine_test_postinst_out.txt", &stbuf));
+  EXPECT_TRUE(S_ISREG(stbuf.st_mode));
+  string file_data;
+  EXPECT_TRUE(utils::ReadFileToString(
+      "/tmp/update_engine_test_postinst_out.txt",
+      &file_data));
+  EXPECT_EQ("POSTINST_DONE\n", file_data);
+
+  // cleanup
+  ASSERT_EQ(0, System(string("rm -rf ") + kTestDir));
+  ASSERT_EQ(0, system("rm -f /tmp/update_engine_test_postinst_out.txt"));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/local_coverage_rate.sh b/local_coverage_rate.sh
new file mode 100755
index 0000000..33c06a7
--- /dev/null
+++ b/local_coverage_rate.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+# Copyright (c) 2009 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.
+
+# Calculates the test-coverage percentage for non-test files in the
+# update_engine directory. Requires a file 'app.info' to contain the
+# results of running the unittests while collecting coverage data.
+
+cat app.info | awk -F '[,:]' '
+
+BEGIN { OFS = ":"; }
+
+/^SF:/{ FILEN = $2; }
+
+/^end_of_record$/{ FILEN = ""; }
+
+/^DA:/{ print FILEN, $2, $3; }
+
+' | sort | awk -F : '
+BEGIN {
+  OFS = ":";
+  FILEN = "";
+  LINE = "";
+  HITS = 0;
+}
+{
+  NEWFILEN = $1;
+  NEWLINE = $2;
+  if ((NEWFILEN == FILEN) && (NEWLINE == LINE)) {
+    HITS += $3
+  } else {
+    if (FILEN != "") {
+      print FILEN, LINE, HITS;
+    }
+    FILEN = NEWFILEN;
+    LINE = NEWLINE;
+    HITS = $3;
+  }
+}
+' | grep '^.*\/trunk\/src\/platform\/update_engine\/' | \
+fgrep -v '_unittest.cc:' | \
+fgrep -v '/test_utils.' | \
+fgrep -v '/test_http_server.cc' | \
+fgrep -v '/testrunner.cc' | \
+fgrep -v '/mock' | \
+fgrep -v '.pb.cc' | \
+awk -F : '
+
+function printfile() {
+  if (FNAME != "")
+    printf "%-40s %4d / %4d: %5.1f%%\n", FNAME, FILE_GOOD_LINES,
+        (FILE_BAD_LINES + FILE_GOOD_LINES),
+        (FILE_GOOD_LINES * 100) / (FILE_BAD_LINES + FILE_GOOD_LINES);
+}
+
+BEGIN {
+  FNAME = "";
+  FILE_BAD_LINES = 0;
+  FILE_GOOD_LINES = 0;
+}
+{
+  // calc filename
+  ARR_SIZE = split($1, PARTS, "/");
+  NEWFNAME = PARTS[ARR_SIZE];
+  if (NEWFNAME != FNAME) {
+    printfile();
+    FILE_BAD_LINES = 0;
+    FILE_GOOD_LINES = 0;
+    FNAME = NEWFNAME;
+  }
+  if ($3 == "0") {
+    BAD_LINES += 1;
+    FILE_BAD_LINES += 1;
+  } else {
+    GOOD_LINES += 1;
+    FILE_GOOD_LINES += 1;
+  }
+}
+
+END {
+  printfile();
+  print "---\nSummary: tested " GOOD_LINES " / " (BAD_LINES + GOOD_LINES);
+  printf "Test coverage: %.1f%%\n", ((GOOD_LINES * 100) / (BAD_LINES + GOOD_LINES));
+}
+'
diff --git a/omaha_request_prep_action.cc b/omaha_request_prep_action.cc
new file mode 100644
index 0000000..21d5799
--- /dev/null
+++ b/omaha_request_prep_action.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2009 The Chromium 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/omaha_request_prep_action.h"
+#include <sys/utsname.h>
+#include <string>
+#include "update_engine/utils.h"
+
+using std::string;
+
+// This gathers local system information and prepares info used by the
+// update check action.
+
+namespace chromeos_update_engine {
+
+void OmahaRequestPrepAction::PerformAction() {
+  // TODO(adlr): honor force_full_update_
+  const string machine_id(GetMachineId());
+  const string version(GetLsbValue("GOOGLE_RELEASE"));
+  const string sp(version + "_" + GetMachineType());
+  const string track(GetLsbValue("GOOGLE_TRACK"));
+
+  UpdateCheckParams out(machine_id,  // machine_id
+                        machine_id,  // user_id (use machine_id)
+                        UpdateCheckParams::kOsPlatform,
+                        UpdateCheckParams::kOsVersion,
+                        sp,  // e.g. 0.2.3.3_i686
+                        UpdateCheckParams::kAppId,
+                        version,  // app version (from lsb-release)
+                        "en-US",  //lang
+                        track);  // track
+
+  CHECK(HasOutputPipe());
+  SetOutputObject(out);
+  processor_->ActionComplete(this, true);
+}
+
+std::string OmahaRequestPrepAction::GetMachineId() const {
+  FILE* fp = popen("/sbin/ifconfig", "r");
+  if (!fp)
+    return "";
+  string data;
+  for (;;) {
+    char buffer[1000];
+    size_t r = fread(buffer, 1, sizeof(buffer), fp);
+    if (r <= 0)
+      break;
+    data.insert(data.end(), buffer, buffer + r);
+  }
+  fclose(fp);
+  // scan data for MAC address
+  string::size_type pos = data.find(" HWaddr ");
+  if (pos == string::npos)
+    return "";
+  // 3 * 6 - 1 is the number of bytes of the hwaddr.
+  return data.substr(pos + strlen(" HWaddr "), 3 * 6 - 1);
+}
+
+std::string OmahaRequestPrepAction::GetLsbValue(const std::string& key) const {
+  string files[] = {utils::kStatefulPartition + "/etc/lsb-release",
+                    "/etc/lsb-release"};
+  for (unsigned int i = 0; i < arraysize(files); i++) {
+    string file_data;
+    if (!utils::ReadFileToString(root_ + files[i], &file_data))
+      continue;
+    string::size_type pos = 0;
+    if (!utils::StringHasPrefix(file_data, key + "=")) {
+      pos = file_data.find(string("\n") + key + "=");
+      if (pos != string::npos)
+        pos++;  // advance past \n
+    }
+    if (pos == string::npos)
+      continue;
+    pos += key.size() + 1;  // advance past the key and the '='
+    string::size_type endpos = file_data.find('\n', pos);
+    string::size_type length =
+        (endpos == string::npos ? string::npos : endpos - pos);
+    return file_data.substr(pos, length);
+  }
+  // not found
+  return "";
+}
+
+std::string OmahaRequestPrepAction::GetMachineType() const {
+  struct utsname buf;
+  string ret;
+  if (uname(&buf) == 0)
+    ret = buf.machine;
+  return ret;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_request_prep_action.h b/omaha_request_prep_action.h
new file mode 100644
index 0000000..ca7fd17
--- /dev/null
+++ b/omaha_request_prep_action.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2009 The Chromium 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_OMAHA_RESQUEST_PREP_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_RESQUEST_PREP_ACTION_H__
+
+#include <string>
+#include "update_engine/action.h"
+#include "update_engine/update_check_action.h"
+
+// This gathers local system information and prepares info used by the
+// update check action.
+
+namespace chromeos_update_engine {
+
+class OmahaRequestPrepAction;
+class NoneType;
+
+template<>
+class ActionTraits<OmahaRequestPrepAction> {
+ public:
+  typedef NoneType InputObjectType;
+  typedef UpdateCheckParams OutputObjectType;
+};
+
+class OmahaRequestPrepAction : public Action<OmahaRequestPrepAction> {
+ public:
+  explicit OmahaRequestPrepAction(bool force_full_update)
+      : force_full_update_(force_full_update) {}
+  typedef ActionTraits<OmahaRequestPrepAction>::InputObjectType
+      InputObjectType;
+  typedef ActionTraits<OmahaRequestPrepAction>::OutputObjectType
+      OutputObjectType;
+  void PerformAction();
+
+  // This is a synchronous action, and thus TerminateProcessing() should
+  // never be called
+  void TerminateProcessing() { CHECK(false); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "OmahaRequestPrepAction"; }
+  std::string Type() const { return StaticType(); }
+
+  // For unit-tests.
+  void set_root(const std::string& root) {
+    root_ = root;
+  }
+
+ private:
+  // Gets a machine-local ID (for now, first MAC address we find)
+  std::string GetMachineId() const;
+
+  // 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;
+
+  // Gets the machine type (e.g. "i686")
+  std::string GetMachineType() const;
+
+  // Set to true if this should set up the Update Check Action to do
+  // a full update
+  bool force_full_update_;
+
+  // When reading files, prepend root_ to the paths. Useful for testing.
+  std::string root_;
+
+  DISALLOW_COPY_AND_ASSIGN(OmahaRequestPrepAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_RESQUEST_PREP_ACTION_H__
diff --git a/omaha_request_prep_action_unittest.cc b/omaha_request_prep_action_unittest.cc
new file mode 100644
index 0000000..19da5e5
--- /dev/null
+++ b/omaha_request_prep_action_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2009 The Chromium 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 <stdio.h>
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_request_prep_action.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+class OmahaRequestPrepActionTest : public ::testing::Test {
+ public:
+  // Return true iff the OmahaResponseHandlerAction succeeded.
+  // if out is non-NULL, it's set w/ the response from the action.
+  bool DoTest(bool force_full_update, UpdateCheckParams* out);
+  static const string kTestDir;
+};
+
+const string OmahaRequestPrepActionTest::kTestDir = "request_prep_action-test";
+
+class OmahaRequestPrepActionProcessorDelegate
+    : public ActionProcessorDelegate {
+ public:
+  OmahaRequestPrepActionProcessorDelegate()
+      : success_(false),
+        success_set_(false) {}
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       bool success) {
+    if (action->Type() == OmahaRequestPrepAction::StaticType()) {
+      success_ = success;
+      success_set_ = true;
+    }
+  }
+  bool success_;
+  bool success_set_;
+};
+
+bool OmahaRequestPrepActionTest::DoTest(bool force_full_update,
+                                        UpdateCheckParams* out) {
+  ActionProcessor processor;
+  OmahaRequestPrepActionProcessorDelegate delegate;
+  processor.set_delegate(&delegate);
+
+  OmahaRequestPrepAction request_prep_action(force_full_update);
+  request_prep_action.set_root(string("./") + kTestDir);
+  ObjectCollectorAction<UpdateCheckParams> collector_action;
+  BondActions(&request_prep_action, &collector_action);
+  processor.EnqueueAction(&request_prep_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_TRUE(!processor.IsRunning())
+      << "Update test to handle non-asynch actions";
+  if (out)
+    *out = collector_action.object();
+  EXPECT_TRUE(delegate.success_set_);
+  return delegate.success_;
+}
+
+namespace {
+// Returns true iff str is formatted as a mac address
+bool IsValidMac(const string& str) {
+  if (str.size() != (3 * 6 - 1))
+    return false;
+  for (unsigned int i = 0; i < str.size(); i++) {
+    char c = str[i];
+    switch (i % 3) {
+      case 0:  // fall through
+      case 1:
+        if ((c >= '0') && (c <= '9'))
+          break;
+        if ((c >= 'a') && (c <= 'f'))
+          break;
+        if ((c >= 'A') && (c <= 'F'))
+          break;
+        return false;
+      case 2:
+        if (c == ':')
+          break;
+        return false;
+    }
+  }
+  return true;
+}
+string GetMachineType() {
+  FILE* fp = popen("uname -m", "r");
+  if (!fp)
+    return "";
+  string ret;
+  for (;;) {
+    char buffer[10];
+    size_t r = fread(buffer, 1, sizeof(buffer), fp);
+    if (r == 0)
+      break;
+    ret.insert(ret.begin(), buffer, buffer + r);
+  }
+  // strip trailing '\n' if it exists
+  if ((*ret.rbegin()) == '\n')
+    ret.resize(ret.size() - 1);
+  fclose(fp);
+  return ret;
+}
+}  // namespace {}
+
+TEST_F(OmahaRequestPrepActionTest, SimpleTest) {
+  ASSERT_EQ(0, System(string("mkdir -p ") + kTestDir + "/etc"));
+  {
+    ASSERT_TRUE(WriteFileString(
+        kTestDir + "/etc/lsb-release",
+        "GOOGLE_FOO=bar\nGOOGLE_RELEASE=0.2.2.3\nGOOGLE_TRACK=footrack"));
+    UpdateCheckParams out;
+    EXPECT_TRUE(DoTest(false, &out));
+    EXPECT_TRUE(IsValidMac(out.machine_id));
+    // for now we're just using the machine id here
+    EXPECT_TRUE(IsValidMac(out.user_id));
+    EXPECT_EQ("Chrome OS", out.os_platform);
+    EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp);
+    EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.app_id);
+    EXPECT_EQ("0.2.2.3", out.app_version);
+    EXPECT_EQ("en-US", out.app_lang);
+    EXPECT_EQ("footrack", out.app_track);
+  }
+  EXPECT_EQ(0, System(string("rm -rf ") + kTestDir));
+}
+
+TEST_F(OmahaRequestPrepActionTest, MissingTrackTest) {
+  ASSERT_EQ(0, System(string("mkdir -p ") + kTestDir + "/etc"));
+  {
+    ASSERT_TRUE(WriteFileString(
+        kTestDir + "/etc/lsb-release",
+        "GOOGLE_FOO=bar\nGOOGLE_RELEASE=0.2.2.3\nGOOGLE_TRXCK=footrack"));
+    UpdateCheckParams out;
+    EXPECT_TRUE(DoTest(false, &out));
+    EXPECT_TRUE(IsValidMac(out.machine_id));
+    // for now we're just using the machine id here
+    EXPECT_TRUE(IsValidMac(out.user_id));
+    EXPECT_EQ("Chrome OS", out.os_platform);
+    EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp);
+    EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.app_id);
+    EXPECT_EQ("0.2.2.3", out.app_version);
+    EXPECT_EQ("en-US", out.app_lang);
+    EXPECT_EQ("", out.app_track);
+  }
+  EXPECT_EQ(0, System(string("rm -rf ") + kTestDir));
+}
+
+TEST_F(OmahaRequestPrepActionTest, ConfusingReleaseTest) {
+  ASSERT_EQ(0, System(string("mkdir -p ") + kTestDir + "/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"));
+    UpdateCheckParams out;
+    EXPECT_TRUE(DoTest(false, &out));
+    EXPECT_TRUE(IsValidMac(out.machine_id));
+    // for now we're just using the machine id here
+    EXPECT_TRUE(IsValidMac(out.user_id));
+    EXPECT_EQ("Chrome OS", out.os_platform);
+    EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp);
+    EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.app_id);
+    EXPECT_EQ("0.2.2.3", out.app_version);
+    EXPECT_EQ("en-US", out.app_lang);
+    EXPECT_EQ("", out.app_track);
+  }
+  EXPECT_EQ(0, System(string("rm -rf ") + kTestDir));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
new file mode 100644
index 0000000..0a8472e
--- /dev/null
+++ b/omaha_response_handler_action.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2009 The Chromium 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/omaha_response_handler_action.h"
+#include <string>
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// If the file part of the download URL contains kFullUpdateTag, then and
+// only then do we assume it's a full update. Otherwise, we assume it's a
+// delta update.
+const string kFullUpdateTag = "_FULL_";
+}  // namespace
+
+void OmahaResponseHandlerAction::PerformAction() {
+  CHECK(HasInputObject());
+  ScopedActionCompleter completer(processor_, this);
+  const UpdateCheckResponse& response = GetInputObject();
+  if (!response.update_exists) {
+    LOG(INFO) << "There are no updates. Aborting.";
+    return;
+  }
+  InstallPlan install_plan;
+  install_plan.download_url = response.codebase;
+  install_plan.download_hash = response.hash;
+  TEST_AND_RETURN(GetInstallDev(
+      (!boot_device_.empty() ? boot_device_ : utils::BootDevice()),
+      &install_plan.install_path));
+
+  // Get the filename part of the url. Assume that if it has kFullUpdateTag
+  // in the name, it's a full update.
+  string::size_type last_slash = response.codebase.rfind('/');
+  string filename;
+  if (last_slash == string::npos)
+    filename = response.codebase;
+  else
+    filename = response.codebase.substr(last_slash + 1);
+  install_plan.is_full_update = (filename.find(kFullUpdateTag) != string::npos);
+
+  if (filename.size() > 255) {
+    // Very long name. Let's shorten it
+    filename.resize(255);
+  }
+  // TODO(adlr): come up with a better place to download to:
+  install_plan.download_path = utils::kStatefulPartition + "/" + filename;
+  if (HasOutputPipe())
+    SetOutputObject(install_plan);
+  LOG(INFO) << "Using this install plan:";
+  install_plan.Dump();
+  completer.set_success(true);
+}
+
+bool OmahaResponseHandlerAction::GetInstallDev(const std::string& boot_dev,
+                                               std::string* install_dev) {
+  TEST_AND_RETURN_FALSE(!boot_dev.empty());
+  string ret(boot_dev);
+  char last_char = *ret.rbegin();
+  TEST_AND_RETURN_FALSE((last_char >= '0') && (last_char <= '9'));
+  // Chop off last char
+  ret.resize(ret.size() - 1);
+  // If last_char is odd (1 or 3), increase it, otherwise decrease
+  if (last_char % 2)
+    last_char++;
+  else
+    last_char--;
+  ret += last_char;
+  *install_dev = ret;
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
new file mode 100644
index 0000000..e25de28
--- /dev/null
+++ b/omaha_response_handler_action.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2009 The Chromium 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_OMAHA_RESPONSE_HANDLER_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H__
+
+#include <string>
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/update_check_action.h"
+
+// This class reads in an Omaha response and converts what it sees into
+// an install plan which is passed out.
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerAction;
+
+template<>
+class ActionTraits<OmahaResponseHandlerAction> {
+ public:
+  typedef UpdateCheckResponse InputObjectType;
+  typedef InstallPlan OutputObjectType;
+};
+
+class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> {
+ public:
+  OmahaResponseHandlerAction() {}
+  typedef ActionTraits<OmahaResponseHandlerAction>::InputObjectType
+      InputObjectType;
+  typedef ActionTraits<OmahaResponseHandlerAction>::OutputObjectType
+      OutputObjectType;
+  void PerformAction();
+
+  // This is a synchronous action, and thus TerminateProcessing() should
+  // never be called
+  void TerminateProcessing() { CHECK(false); }
+
+  // For unit-testing
+  void set_boot_device(const std::string& boot_device) {
+    boot_device_ = boot_device;
+  }
+
+  // Debugging/logging
+  static std::string StaticType() { return "OmahaResponseHandlerAction"; }
+  std::string Type() const { return StaticType(); }
+
+ private:
+  // Assumes you want to install on the "other" device, where the other
+  // device is what you get if you swap 1 for 2 or 3 for 4 or vice versa
+  // for the number at the end of the boot device. E.g., /dev/sda1 -> /dev/sda2
+  // or /dev/sda4 -> /dev/sda3
+  static bool GetInstallDev(const std::string& boot_dev,
+                            std::string* install_dev);
+
+  // set to non-empty in unit tests
+  std::string boot_device_;
+
+  DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H__
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
new file mode 100644
index 0000000..0248daf
--- /dev/null
+++ b/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2009 The Chromium 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 <string>
+#include <gtest/gtest.h>
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ public:
+  // Return true iff the OmahaResponseHandlerAction succeeded.
+  // If out is non-NULL, it's set w/ the response from the action.
+  bool DoTest(const UpdateCheckResponse& in,
+              const string& boot_dev,
+              InstallPlan* out);
+};
+
+class OmahaResponseHandlerActionProcessorDelegate
+    : public ActionProcessorDelegate {
+ public:
+  OmahaResponseHandlerActionProcessorDelegate()
+      : success_(false),
+        success_set_(false) {}
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       bool success) {
+    if (action->Type() == OmahaResponseHandlerAction::StaticType()) {
+      success_ = success;
+      success_set_ = true;
+    }
+  }
+  bool success_;
+  bool success_set_;
+};
+
+namespace {
+const string kLongName =
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "-the_update_a.b.c.d_DELTA_.tgz";
+}  // namespace {}
+
+bool OmahaResponseHandlerActionTest::DoTest(const UpdateCheckResponse& in,
+                                            const string& boot_dev,
+                                            InstallPlan* out) {
+  ActionProcessor processor;
+  OmahaResponseHandlerActionProcessorDelegate delegate;
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<UpdateCheckResponse> feeder_action;
+  feeder_action.set_obj(in);
+  OmahaResponseHandlerAction response_handler_action;
+  response_handler_action.set_boot_device(boot_dev);
+  BondActions(&feeder_action, &response_handler_action);
+  ObjectCollectorAction<InstallPlan> collector_action;
+  BondActions(&response_handler_action, &collector_action);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&response_handler_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_TRUE(!processor.IsRunning())
+      << "Update test to handle non-asynch actions";
+  if (out)
+    *out = collector_action.object();
+  EXPECT_TRUE(delegate.success_set_);
+  return delegate.success_;
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
+  {
+    UpdateCheckResponse in;
+    in.update_exists = true;
+    in.display_version = "a.b.c.d";
+    in.codebase = "http://foo/the_update_a.b.c.d_FULL_.tgz";
+    in.more_info_url = "http://more/info";
+    in.hash = "HASH+";
+    in.size = 12;
+    in.needs_admin = true;
+    in.prompt = false;
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, "/dev/sda1", &install_plan));
+    EXPECT_TRUE(install_plan.is_full_update);
+    EXPECT_EQ(in.codebase, install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.download_hash);
+    EXPECT_EQ(utils::kStatefulPartition + "/the_update_a.b.c.d_FULL_.tgz",
+              install_plan.download_path);
+    EXPECT_EQ("/dev/sda2", install_plan.install_path);
+  }
+  {
+    UpdateCheckResponse in;
+    in.update_exists = true;
+    in.display_version = "a.b.c.d";
+    in.codebase = "http://foo/the_update_a.b.c.d_DELTA_.tgz";
+    in.more_info_url = "http://more/info";
+    in.hash = "HASHj+";
+    in.size = 12;
+    in.needs_admin = true;
+    in.prompt = true;
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, "/dev/sda4", &install_plan));
+    EXPECT_FALSE(install_plan.is_full_update);
+    EXPECT_EQ(in.codebase, install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.download_hash);
+    EXPECT_EQ(utils::kStatefulPartition + "/the_update_a.b.c.d_DELTA_.tgz",
+              install_plan.download_path);
+    EXPECT_EQ("/dev/sda3", install_plan.install_path);
+  }
+  {
+    UpdateCheckResponse in;
+    in.update_exists = true;
+    in.display_version = "a.b.c.d";
+    in.codebase = kLongName;
+    in.more_info_url = "http://more/info";
+    in.hash = "HASHj+";
+    in.size = 12;
+    in.needs_admin = true;
+    in.prompt = true;
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, "/dev/sda4", &install_plan));
+    EXPECT_FALSE(install_plan.is_full_update);
+    EXPECT_EQ(in.codebase, install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.download_hash);
+    EXPECT_EQ(utils::kStatefulPartition + "/" + kLongName.substr(0, 255),
+              install_plan.download_path);
+    EXPECT_EQ("/dev/sda3", install_plan.install_path);
+  }
+}
+
+TEST_F(OmahaResponseHandlerActionTest, NoUpdatesTest) {
+  UpdateCheckResponse in;
+  in.update_exists = false;
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "/dev/sda1", &install_plan));
+  EXPECT_FALSE(install_plan.is_full_update);
+  EXPECT_EQ("", install_plan.download_url);
+  EXPECT_EQ("", install_plan.download_hash);
+  EXPECT_EQ("", install_plan.download_path);
+  EXPECT_EQ("", install_plan.install_path);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
new file mode 100644
index 0000000..4122860
--- /dev/null
+++ b/postinstall_runner_action.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2009 The Chromium 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/postinstall_runner_action.h"
+#include <sys/mount.h>
+#include <stdlib.h>
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+
+namespace {
+const string kMountPath(utils::kStatefulPartition + "/au_destination");
+const string kPostinstallScript("/postinst");
+}
+
+void PostinstallRunnerAction::PerformAction() {
+  CHECK(HasInputObject());
+  const string install_device = GetInputObject();
+
+  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;
+  }
+
+  // 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;
+  }
+
+  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);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/postinstall_runner_action.h b/postinstall_runner_action.h
new file mode 100644
index 0000000..00478cc
--- /dev/null
+++ b/postinstall_runner_action.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2009 The Chromium 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_POSTINSTALL_RUNNER_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H__
+
+#include <string>
+#include "update_engine/action.h"
+
+// The Postinstall Runner Action is responsible for running the postinstall
+// script of a successfully downloaded update.
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerAction;
+class NoneType;
+
+template<>
+class ActionTraits<PostinstallRunnerAction> {
+ public:
+  // Takes the device path as input
+  typedef std::string InputObjectType;
+  // Passes the device path as output
+  typedef std::string OutputObjectType;
+};
+
+class PostinstallRunnerAction : public Action<PostinstallRunnerAction> {
+ public:
+  PostinstallRunnerAction() {}
+  typedef ActionTraits<PostinstallRunnerAction>::InputObjectType
+      InputObjectType;
+  typedef ActionTraits<PostinstallRunnerAction>::OutputObjectType
+      OutputObjectType;
+  void PerformAction();
+
+  // This is a synchronous action, and thus TerminateProcessing() should
+  // never be called
+  void TerminateProcessing() { CHECK(false); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "PostinstallRunnerAction"; }
+  std::string Type() const { return StaticType(); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H__
diff --git a/postinstall_runner_action_unittest.cc b/postinstall_runner_action_unittest.cc
new file mode 100644
index 0000000..c43cb8a
--- /dev/null
+++ b/postinstall_runner_action_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2009 The Chromium 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/postinstall_runner_action.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerActionTest : public ::testing::Test {
+ public:
+  void DoTest(bool do_losetup, bool do_err_script);
+};
+
+class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  PostinstActionProcessorDelegate() : success_(false), success_set_(false) {}
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       bool success) {
+    if (action->Type() == PostinstallRunnerAction::StaticType()) {
+      success_ = success;
+      success_set_ = true;
+    }
+  }
+  bool success_;
+  bool success_set_;
+};
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(true, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(false, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
+  ASSERT_EQ(0, getuid());
+  DoTest(true, true);
+}
+
+void PostinstallRunnerActionTest::DoTest(bool do_losetup, bool do_err_script) {
+  ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests "
+                         << "as root, tho.";
+
+  const string mountpoint(utils::kStatefulPartition + "/au_destination");
+
+  string cwd;
+  {
+    vector<char> buf(1000);
+    ASSERT_EQ(&buf[0], getcwd(&buf[0], buf.size()));
+    cwd = string(&buf[0], strlen(&buf[0]));
+  }
+
+  // create the au destination, if it doesn't exist
+  ASSERT_EQ(0, System(string("mkdir -p ") + mountpoint));
+
+  // create 10MiB sparse file
+  ASSERT_EQ(0, system("dd if=/dev/zero of=image.dat seek=10485759 bs=1 "
+                      "count=1"));
+
+  // format it as ext3
+  ASSERT_EQ(0, system("mkfs.ext3 -F image.dat"));
+
+  // mount it
+  ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint));
+
+  // put a postinst script in
+  string script = string("#!/bin/bash\ntouch ") + cwd + "/postinst_called\n";
+  if (do_err_script) {
+    script = "#!/bin/bash\nexit 1";
+  }
+  ASSERT_TRUE(WriteFileString(mountpoint + "/postinst", script));
+  ASSERT_EQ(0, System(string("chmod a+x ") + mountpoint + "/postinst"));
+
+  ASSERT_EQ(0, System(string("umount ") + mountpoint));
+
+  ASSERT_EQ(0, System(string("rm -f ") + cwd + "/postinst_called"));
+
+  // get a loop device we can use for the install device
+  FILE* find_dev_cmd = popen("losetup -f", "r");
+  ASSERT_TRUE(find_dev_cmd);
+
+  char dev[100] = {0};
+  size_t r = fread(dev, 1, sizeof(dev), find_dev_cmd);
+  ASSERT_GT(r, 0);
+  ASSERT_LT(r, sizeof(dev));
+  ASSERT_TRUE(feof(find_dev_cmd));
+  fclose(find_dev_cmd);
+
+  // strip trailing newline on dev
+  if (dev[strlen(dev) - 1] == '\n')
+    dev[strlen(dev) - 1] = '\0';
+
+  if (do_losetup)
+    ASSERT_EQ(0, System(string("losetup ") + dev + " " + cwd + "/image.dat"));
+
+  ActionProcessor processor;
+  ObjectFeederAction<string> feeder_action;
+  feeder_action.set_obj(dev);
+  PostinstallRunnerAction runner_action;
+  BondActions(&feeder_action, &runner_action);
+  ObjectCollectorAction<string> collector_action;
+  BondActions(&runner_action, &collector_action);
+  PostinstActionProcessorDelegate delegate;
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&runner_action);
+  processor.EnqueueAction(&collector_action);
+  processor.set_delegate(&delegate);
+  processor.StartProcessing();
+  ASSERT_FALSE(processor.IsRunning())
+      << "Update test to handle non-asynch actions";
+
+  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());
+  if (do_losetup && !do_err_script) {
+    EXPECT_EQ(dev, collector_action.object());
+  }
+
+  struct stat stbuf;
+  int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf);
+  if (do_losetup && !do_err_script)
+    ASSERT_EQ(0, rc);
+  else
+    ASSERT_LT(rc, 0);
+
+  if (do_losetup)
+    ASSERT_EQ(0, System(string("losetup -d ") + dev));
+  ASSERT_EQ(0, System(string("rm -f ") + cwd + "/postinst_called"));
+  ASSERT_EQ(0, System(string("rm -f ") + cwd + "/image.dat"));
+}
+
+// Death tests don't seem to be working on Hardy
+TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
+  ASSERT_EQ(0, getuid());
+  PostinstallRunnerAction runner_action;
+  ASSERT_DEATH({ runner_action.TerminateProcessing(); },
+               "postinstall_runner_action.h:.*] Check failed");
+}
+
+}  // namespace chromeos_update_engine
diff --git a/set_bootable_flag_action.cc b/set_bootable_flag_action.cc
new file mode 100644
index 0000000..8075b2c
--- /dev/null
+++ b/set_bootable_flag_action.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2009 The Chromium 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/set_bootable_flag_action.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string>
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+void SetBootableFlagAction::PerformAction() {
+  ScopedActionCompleter completer(processor_, this);
+
+  if (!HasInputObject() || GetInputObject().empty()) {
+    LOG(ERROR) << "SetBootableFlagAction: No input object.";
+    return;
+  }
+  string device = GetInputObject();
+
+  if (device.size() < 2) {
+    LOG(ERROR) << "Device name too short: " << device;
+    return;
+  }
+
+  // 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());
+    }
+  }
+}
+
+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
new file mode 100644
index 0000000..44e9555
--- /dev/null
+++ b/set_bootable_flag_action.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2009 The Chromium 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_SET_BOOTABLE_FLAG_ACTION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_SET_BOOTABLE_FLAG_ACTION_H__
+
+#include <string>
+#include "update_engine/action.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).
+// It will make that device bootable by editing the partition table
+// in the root device. Currently, this class doesn't support extended
+// partitions.
+
+namespace chromeos_update_engine {
+
+class SetBootableFlagAction;
+
+template<>
+class ActionTraits<SetBootableFlagAction> {
+ public:
+  // Takes the device path as input.
+  typedef std::string InputObjectType;
+  // Passes the device path as output
+  typedef std::string OutputObjectType;
+};
+
+class SetBootableFlagAction : public Action<SetBootableFlagAction> {
+ public:
+  SetBootableFlagAction() {}
+  typedef ActionTraits<SetBootableFlagAction>::InputObjectType
+      InputObjectType;
+  typedef ActionTraits<SetBootableFlagAction>::OutputObjectType
+      OutputObjectType;
+  void PerformAction();
+
+  // This is a synchronous action, and thus TerminateProcessing() should
+  // never be called
+  void TerminateProcessing() { CHECK(false); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "SetBootableFlagAction"; }
+  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);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_SET_BOOTABLE_FLAG_ACTION_H__
diff --git a/set_bootable_flag_action_unittest.cc b/set_bootable_flag_action_unittest.cc
new file mode 100644
index 0000000..215c189
--- /dev/null
+++ b/set_bootable_flag_action_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2009 The Chromium 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/set_bootable_flag_action.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+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 {
+ 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 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 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;
+}
+
+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, 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
+}
+
+}  // namespace chromeos_update_engine
diff --git a/subprocess.cc b/subprocess.cc
new file mode 100644
index 0000000..299d758
--- /dev/null
+++ b/subprocess.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2009 The Chromium 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/subprocess.h"
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include "chromeos/obsolete_logging.h"
+#include "base/scoped_ptr.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void Subprocess::GChildExitedCallback(GPid pid, gint status, gpointer data) {
+  COMPILE_ASSERT(sizeof(guint) == sizeof(uint32),
+                 guint_uint32_size_mismatch);
+  guint *tag = reinterpret_cast<guint*>(data);
+  const SubprocessCallbackRecord& record = Get().callback_records_[*tag];
+  if (record.callback)
+    record.callback(status, record.callback_data);
+  g_spawn_close_pid(pid);
+  Get().callback_records_.erase(*tag);
+  delete tag;
+}
+
+namespace {
+void FreeArgv(char** argv) {
+  for (int i = 0; argv[i]; i++) {
+    free(argv[i]);
+    argv[i] = NULL;
+  }
+}
+}  // namespace {}
+
+uint32 Subprocess::Exec(const std::vector<std::string>& cmd,
+                        ExecCallback callback,
+                        void *p) {
+  GPid child_pid;
+  GError *err;
+  scoped_array<char *> argv(new char*[cmd.size() + 1]);
+  for (unsigned int i = 0; i < cmd.size(); i++) {
+    argv[i] = strdup(cmd[i].c_str());
+  }
+  argv[cmd.size()] = NULL;
+  char *argp[1];
+  argp[0] = NULL;
+
+  SubprocessCallbackRecord callback_record;
+  callback_record.callback = callback;
+  callback_record.callback_data = p;
+
+  bool success = g_spawn_async(NULL,  // working directory
+                               argv.get(),
+                               argp,
+                               G_SPAWN_DO_NOT_REAP_CHILD,  // flags
+                               NULL,  // child setup function
+                               NULL,  // child setup data pointer
+                               &child_pid,
+                               &err);
+  FreeArgv(argv.get());
+  if (!success) {
+    LOG(ERROR) << "g_spawn_async failed";
+    return 0;
+  }
+  guint *tag = new guint;
+  *tag = g_child_watch_add(child_pid, GChildExitedCallback, tag);
+  callback_records_[*tag] = callback_record;
+  return *tag;
+}
+
+void Subprocess::CancelExec(uint32 tag) {
+  if (callback_records_[tag].callback) {
+    callback_records_[tag].callback = NULL;
+  }
+}
+
+bool Subprocess::SynchronousExec(const std::vector<std::string>& cmd,
+                                 int* return_code) {
+  GError *err;
+  scoped_array<char *> argv(new char*[cmd.size() + 1]);
+  for (unsigned int i = 0; i < cmd.size(); i++) {
+    argv[i] = strdup(cmd[i].c_str());
+  }
+  argv[cmd.size()] = NULL;
+  char *argp[1];
+  argp[0] = NULL;
+
+  bool success = g_spawn_sync(NULL,  // working directory
+                              argv.get(),
+                              argp,
+                              static_cast<GSpawnFlags>(NULL),  // flags
+                              NULL,  // child setup function
+                              NULL,  // data for child setup function
+                              NULL,  // return location for stdout
+                              NULL,  // return location for stderr
+                              return_code,
+                              &err);
+  FreeArgv(argv.get());
+  return success;
+}
+
+Subprocess* Subprocess::subprocess_singleton_ = NULL;
+
+}  // namespace chromeos_update_engine
diff --git a/subprocess.h b/subprocess.h
new file mode 100644
index 0000000..92064a6
--- /dev/null
+++ b/subprocess.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2009 The Chromium 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_SUBPROCESS_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_SUBPROCESS_H__
+
+#include <map>
+#include <string>
+#include <vector>
+#include <glib.h>
+#include "chromeos/obsolete_logging.h"
+#include "base/basictypes.h"
+
+// The Subprocess class is a singleton. It's used to spawn off a subprocess
+// and get notified when the subprocess exits. The result of Exec() can
+// be saved and used to cancel the callback request. If you know you won't
+// call CancelExec(), you may safely lose the return value from Exec().
+
+namespace chromeos_update_engine {
+
+class Subprocess {
+ public:
+  static void Init() {
+    CHECK(!subprocess_singleton_);
+    subprocess_singleton_ = new Subprocess;
+  }
+   
+  typedef void(*ExecCallback)(int return_code, void *p);
+
+  // Returns a tag > 0 on success.
+  uint32 Exec(const std::vector<std::string>& cmd,
+              ExecCallback callback,
+              void* p);
+
+  // Used to cancel the callback. The process will still run to completion.
+  void CancelExec(uint32 tag);
+
+  // Executes a command synchronously. Returns true on success.
+  static bool SynchronousExec(const std::vector<std::string>& cmd,
+                              int* return_code);
+
+  // Gets the one instance
+  static Subprocess& Get() {
+    return *subprocess_singleton_;
+  }
+  
+  // Returns true iff there is at least one subprocess we're waiting on.
+  bool SubprocessInFlight() {
+    for (std::map<int, SubprocessCallbackRecord>::iterator it =
+             callback_records_.begin();
+         it != callback_records_.end(); ++it) {
+      if (it->second.callback)
+        return true;
+    }
+    return false;
+  }
+ private:
+  // The global instance
+  static Subprocess* subprocess_singleton_;
+
+  // Callback for when any subprocess terminates. This calls the user
+  // requested callback.
+  static void GChildExitedCallback(GPid pid, gint status, gpointer data);
+
+  struct SubprocessCallbackRecord {
+    ExecCallback callback;
+    void* callback_data;
+  };
+
+  std::map<int, SubprocessCallbackRecord> callback_records_;
+
+  Subprocess() {}
+  DISALLOW_COPY_AND_ASSIGN(Subprocess);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_SUBPROCESS_H__
diff --git a/subprocess_unittest.cc b/subprocess_unittest.cc
new file mode 100644
index 0000000..430f39b
--- /dev/null
+++ b/subprocess_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2009 The Chromium 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include "base/string_util.h"
+#include <glib.h>
+#include <gtest/gtest.h>
+#include "update_engine/subprocess.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class SubprocessTest : public ::testing::Test {
+ protected:
+  bool callback_done;
+};
+
+namespace {
+const int kLocalHttpPort = 8080;
+
+void Callback(int return_code, void *p) {
+  EXPECT_EQ(256, return_code);
+  GMainLoop* loop = reinterpret_cast<GMainLoop*>(p);
+  g_main_loop_quit(loop);
+}
+
+gboolean LaunchFalseInMainLoop(gpointer data) {
+  vector<string> cmd;
+  cmd.push_back("/bin/false");
+  Subprocess::Get().Exec(cmd, Callback, data);
+  return FALSE;
+}
+}  // namespace {}
+
+TEST(SubprocessTest, SimpleTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  g_timeout_add(0, &LaunchFalseInMainLoop, loop);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+}
+
+namespace {
+void CallbackBad(int return_code, void *p) {
+  CHECK(false) << "should never be called.";
+}
+
+struct CancelTestData {
+  bool spawned;
+  GMainLoop *loop;
+};
+
+gboolean StartAndCancelInRunLoop(gpointer data) {
+  CancelTestData* cancel_test_data = reinterpret_cast<CancelTestData*>(data);
+  vector<string> cmd;
+  cmd.push_back("./test_http_server");
+  uint32 tag = Subprocess::Get().Exec(cmd, CallbackBad, NULL);
+  EXPECT_NE(0, tag);
+  cancel_test_data->spawned = true;
+  printf("spawned\n");
+  // Wait for server to be up and running
+  for (;;) {
+    int status =
+        System(StringPrintf("wget -O /dev/null http://127.0.0.1:%d/foo",
+                            kLocalHttpPort));
+    EXPECT_NE(-1, status) << "system() failed";
+    EXPECT_TRUE(WIFEXITED(status))
+        << "command failed to run or died abnormally";
+    if (0 == WEXITSTATUS(status))
+      break;
+    usleep(100 * 1000);  // 100 ms
+  }
+  Subprocess::Get().CancelExec(tag);
+  return FALSE;
+}
+}  // namespace {}
+
+gboolean ExitWhenDone(gpointer data) {
+  CancelTestData* cancel_test_data = reinterpret_cast<CancelTestData*>(data);
+  if (cancel_test_data->spawned && !Subprocess::Get().SubprocessInFlight()) {
+    // tear down the sub process
+    printf("tear down time\n");
+    int status =
+        System(StringPrintf("wget http://127.0.0.1:%d/quitquitquit",
+                            kLocalHttpPort));
+    EXPECT_NE(-1, status) << "system() failed";
+    EXPECT_TRUE(WIFEXITED(status))
+        << "command failed to run or died abnormally";
+    g_main_loop_quit(cancel_test_data->loop);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+TEST(SubprocessTest, CancelTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  CancelTestData cancel_test_data;
+  cancel_test_data.spawned = false;
+  cancel_test_data.loop = loop;
+  g_timeout_add(100, &StartAndCancelInRunLoop, &cancel_test_data);
+  g_timeout_add(10, &ExitWhenDone, &cancel_test_data);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+  printf("here\n");
+}
+
+}  // namespace chromeos_update_engine
diff --git a/test_http_server.cc b/test_http_server.cc
new file mode 100644
index 0000000..94c88fa
--- /dev/null
+++ b/test_http_server.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2009 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.
+
+// This file implements a simple HTTP server. It can exhibit odd behavior
+// that's useful for testing. For example, it's useful to test that
+// the updater can continue a connection if it's dropped, or that it
+// handles very slow data transfers.
+
+// To use this, simply make an HTTP connection to localhost:port and
+// GET a url.
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include "chromeos/obsolete_logging.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+struct HttpRequest {
+  string url;
+  off_t offset;
+};
+
+namespace {
+const int kPort = 8080;  // hardcoded to 8080 for now
+const int kBigLength = 100000;
+}
+
+bool ParseRequest(int fd, HttpRequest* request) {
+  string headers;
+  while(headers.find("\r\n\r\n") == string::npos) {
+    vector<char> buf(1024);
+    memset(&buf[0], 0, buf.size());
+    ssize_t r = read(fd, &buf[0], buf.size() - 1);
+    if (r < 0) {
+      perror("read");
+      exit(1);
+    }
+    buf.resize(r);
+
+    headers.insert(headers.end(), buf.begin(), buf.end());
+  }
+  LOG(INFO) << "got headers: " << headers;
+
+  string::size_type url_start, url_end;
+  CHECK_NE(headers.find("GET "), string::npos);
+  url_start = headers.find("GET ") + strlen("GET ");
+  url_end = headers.find(' ', url_start);
+  CHECK_NE(string::npos, url_end);
+  string url = headers.substr(url_start, url_end - url_start);
+  LOG(INFO) << "URL: " << url;
+
+  string::size_type range_start, range_end;
+  if (headers.find("\r\nRange: ") == string::npos) {
+    request->offset = 0;
+  } else {
+    range_start = headers.find("\r\nRange: ") + strlen("\r\nRange: ");
+    range_end = headers.find('\r', range_start);
+    CHECK_NE(string::npos, range_end);
+    string range_header = headers.substr(range_start, range_end - range_start);
+
+    LOG(INFO) << "Range: " << range_header;
+    CHECK(*range_header.rbegin() == '-');
+    request->offset = atoll(range_header.c_str() + strlen("bytes="));
+    LOG(INFO) << "Offset: " << request->offset;
+  }
+  request->url = url;
+  return true;
+}
+
+void WriteString(int fd, const string& str) {
+  unsigned int bytes_written = 0;
+  while (bytes_written < str.size()) {
+    ssize_t r = write(fd, str.c_str() + bytes_written,
+                      str.size() - bytes_written);
+    LOG(INFO) << "write() wrote " << r << " bytes";
+    if (r < 0) {
+      perror("write");
+      return;
+    }
+    bytes_written += r;
+  }
+  LOG(INFO) << "WriteString wrote " << bytes_written << " bytes";
+}
+
+string Itoa(off_t num) {
+  char buf[100] = {0};
+  snprintf(buf, sizeof(buf), "%lld", num);
+  return buf;
+}
+
+void WriteHeaders(int fd, bool support_range, off_t full_size,
+                  off_t start_offset) {
+  LOG(INFO) << "writing headers";
+  WriteString(fd, "HTTP/1.1 200 OK\r\n");
+  WriteString(fd, "Content-Type: application/octet-stream\r\n");
+  if (support_range) {
+    WriteString(fd, "Accept-Ranges: bytes\r\n");
+    WriteString(fd, string("Content-Range: bytes ") + Itoa(start_offset) +
+                "-" + Itoa(full_size - 1) + "/" + Itoa(full_size) + "\r\n");
+  }
+  off_t content_length = full_size;
+  if (support_range)
+    content_length -= start_offset;
+  WriteString(fd, string("Content-Length: ") + Itoa(content_length) + "\r\n");
+  WriteString(fd, "\r\n");
+}
+
+void HandleQuitQuitQuit(int fd) {
+  exit(0);
+}
+
+void HandleBig(int fd, const HttpRequest& request) {
+  const off_t full_length = kBigLength;
+  WriteHeaders(fd, true, full_length, request.offset);
+  const off_t content_length = full_length - request.offset;
+  int i = request.offset;
+  for (; i % 10; i++)
+    WriteString(fd, string(1, 'a' + (i % 10)));
+  CHECK_EQ(i % 10, 0);
+  for (; i < content_length; i += 10)
+    WriteString(fd, "abcdefghij");
+  CHECK_EQ(i, full_length);
+}
+
+// This is like /big, but it writes at most 9000 bytes. Also,
+// half way through it sleeps for 70 seconds
+// (technically, when (offset % (9000 * 7)) == 0).
+void HandleFlaky(int fd, const HttpRequest& request) {
+  const off_t full_length = kBigLength;
+  WriteHeaders(fd, true, full_length, request.offset);
+  const off_t content_length = min(9000LL, full_length - request.offset);
+  const bool should_sleep = (request.offset % (9000 * 7)) == 0;
+
+  string buf;
+
+  for (int i = request.offset; i % 10; i++)
+    buf.append(1, 'a' + (i % 10));
+  while (buf.size() < content_length)
+    buf.append("abcdefghij");
+  buf.resize(content_length);
+
+  if (!should_sleep) {
+    LOG(INFO) << "writing data blob of size " << buf.size();
+    WriteString(fd, buf);
+  } else {
+    string::size_type half_way_point = buf.size() / 2;
+    LOG(INFO) << "writing small data blob of size " << half_way_point;
+    WriteString(fd, buf.substr(0, half_way_point));
+    sleep(10);
+    LOG(INFO) << "writing small data blob of size "
+              << (buf.size() - half_way_point);
+    WriteString(fd, buf.substr(half_way_point, buf.size() - half_way_point));
+  }
+}
+
+void HandleDefault(int fd, const HttpRequest& request) {
+  const string data("unhandled path");
+  WriteHeaders(fd, true, data.size(), request.offset);
+  const string data_to_write(data.substr(request.offset,
+                                         data.size() - request.offset));
+  WriteString(fd, data_to_write);
+}
+
+void HandleConnection(int fd) {
+  HttpRequest request;
+  ParseRequest(fd, &request);
+
+  if (request.url == "/quitquitquit")
+    HandleQuitQuitQuit(fd);
+  else if (request.url == "/big")
+    HandleBig(fd, request);
+  else if (request.url == "/flaky")
+    HandleFlaky(fd, request);
+  else
+    HandleDefault(fd, request);
+
+  close(fd);
+}
+
+}  // namespace chromeos_update_engine
+
+using namespace chromeos_update_engine;
+
+int main(int argc, char** argv) {
+  socklen_t clilen;
+  struct sockaddr_in server_addr;
+  struct sockaddr_in client_addr;
+  memset(&server_addr, 0, sizeof(server_addr));
+  memset(&client_addr, 0, sizeof(client_addr));
+
+  int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
+  if (listen_fd < 0)
+    LOG(FATAL) << "socket() failed";
+
+  server_addr.sin_family = AF_INET;
+  server_addr.sin_addr.s_addr = INADDR_ANY;
+  server_addr.sin_port = htons(kPort);
+
+  {
+    // Get rid of "Address in use" error
+    int tr = 1;
+    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
+                   sizeof(int)) == -1) {
+      perror("setsockopt");
+      exit(1);
+    }
+  }
+
+  if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
+           sizeof(server_addr)) < 0) {
+    perror("bind");
+    exit(1);
+  }
+  CHECK_EQ(listen(listen_fd,5), 0);
+  while (1) {
+    clilen = sizeof(client_addr);
+    int client_fd = accept(listen_fd,
+                           (struct sockaddr *) &client_addr,
+                           &clilen);
+    LOG(INFO) << "got past accept";
+    if (client_fd < 0)
+      LOG(FATAL) << "ERROR on accept";
+    HandleConnection(client_fd);
+  }
+  return 0;
+}
diff --git a/update_metadata.proto b/update_metadata.proto
new file mode 100644
index 0000000..48935d8
--- /dev/null
+++ b/update_metadata.proto
@@ -0,0 +1,127 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Update file format: A delta update file contains all the deltas needed
+// to update a system from one specific version to another specific
+// version. The update format is represented by this struct pseudocode:
+// struct delta_update_file {
+//   char magic[4] = "CrAU";
+//   uint64 bom_offset;  // Offset of protobuf DeltaArchiveManifest
+//   uint64 bom_size;  // Sise of protobuf DeltaArchiveManifest
+//
+//   // Data blobs for files, no specific format. The specific offset
+//   // and length of each data blob is recorded in the DeltaArchiveManifest.
+//   struct {
+//     char data[];
+//   } blobs[];
+//
+//   // The Gzip compressed DeltaArchiveManifest
+//   char bom[];
+// };
+
+// The DeltaArchiveManifest protobuf is an ordered list of File objects.
+// These File objects are stored in a linear array in the
+// DeltaArchiveManifest, each with a specific index. Each File object
+// can contain children in its children list. Each child in the list
+// has a name and an index. The index refers to the index within
+// DeltaArchiveManifest.files. Thus, the DeltaArchiveManifest.files
+// can be seen as a tree structure that mimicks the filesystem.
+// The root object (the object an index 0) has no name, since names
+// for children are stored in the parent.
+
+// The DeltaArchiveManifest will contain one File entry for each
+// file that will be on the resultant filesystem. Because we have
+// a tree structure, and children are ordered alphabetically within
+// a parent, we can do log-time˜path lookup on a DeltaArchiveManifest
+// object. We can also iterate through a DeltaArchiveManifest object
+// using a preorder tree traversal to see each file in the
+// DeltaArchiveManifest, seeing each directory before any of its children;
+// this takes linear time.
+
+// Here's an example from Dan Erat showing DeltaArchiveManifest
+// for a filesystem with files /bin/cat and /bin/ls.:
+
+// files[0] {  // "/" directory
+//   children[0] {
+//     name "bin"
+//     index 1
+//   }
+// }
+// files[1] {  // "/bin" directory
+//   children[0] {
+//     name "cat"
+//     index 2
+//   }
+//   children[1] {
+//     name "ls"
+//     index 3
+//   }
+// }
+// files[2] {  // "/bin/cat"
+// }
+// files[3] {  // "/bin/ls"
+// }
+
+// If a file has a data_format set, it should also have data_offset and
+// data_length set. data_offset and data_length refer to a range of bytes
+// in the delta update file itself which have the format specified by
+// data_format. FULL and FULL_GZ mean the entire file is present (FULL_GZ,
+// gzip compressed). BSDIFF means the old file with the same path should be
+// patched with 'bspatch' to produce the desired output file. COURGETTE
+// is not yet used, but it will be another binary diff format.
+
+// Directories should not have any data.
+
+// There are other types of files, too: symlinks, block and character devices,
+// fifos, and sockets. Fifos and sockets contain no data. Block and
+// character devices have data. It must be the format FULL or FULL_GZ, and
+// the contents are a serialized LinuxDevice protobuf. Symlinks must either
+// be FULL, FULL_GZ, or have no data. A symlink with no data is unchanged,
+// and with data it's set to that data.
+
+// TODO(adlr): Add support for hard links; CL is prepared already.
+// Extended attributes are unsupported at this time.
+
+package chromeos_update_engine;
+
+message DeltaArchiveManifest {
+  message File {
+    // This is st_mode from struct stat. It includes file type and permission
+    // bits.
+    optional uint32 mode = 1;
+    optional uint32 uid = 2;
+    optional uint32 gid = 3;
+
+    // File Data, not for directories
+    enum DataFormat {
+      FULL = 0;  // The data is the complete file
+      FULL_GZ = 1;  // The data is the complete file gzipped
+      BSDIFF = 2;  // The data is a bsdiff binary diff
+      COURGETTE = 3;  // The data is a courgette binary diff
+    }
+    // If present, there is data associated with this File object and
+    // data_offset and data_size must be set.
+    optional DataFormat data_format = 4;
+    // The offset into the delta file where the data (if any) is stored
+    optional uint32 data_offset = 5;
+    // The length of the data in the delta file
+    optional uint32 data_length = 6;
+
+    message Child {
+      // A File that's a directory (and only those types of File objects)
+      // will have exactly one Child submessage per child.
+      required string name = 1;  // File name of child
+
+      // Index into DeltaArchiveManifest.files for the File object of the child.
+      required uint32 index = 2;
+    }
+    repeated Child children = 7;
+  }
+  repeated File files = 1;
+}
+
+message LinuxDevice {
+  required int32 major = 1;
+  required int32 minor = 2;
+}
\ No newline at end of file
diff --git a/utils.cc b/utils.cc
new file mode 100644
index 0000000..693062d
--- /dev/null
+++ b/utils.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2009 The Chromium 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/utils.h"
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <algorithm>
+#include "chromeos/obsolete_logging.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace utils {
+
+bool ReadFile(const std::string& path, std::vector<char>* out) {
+  CHECK(out);
+  FILE* fp = fopen(path.c_str(), "r");
+  if (!fp)
+    return false;
+  const size_t kChunkSize = 1024;
+  size_t read_size;
+  do {
+    char buf[kChunkSize];
+    read_size = fread(buf, 1, kChunkSize, fp);
+    if (read_size == 0)
+      break;
+    out->insert(out->end(), buf, buf + read_size);
+  } while (read_size == kChunkSize);
+  bool success = !ferror(fp);
+  TEST_AND_RETURN_FALSE_ERRNO(fclose(fp) == 0);
+  return success;
+}
+
+bool ReadFileToString(const std::string& path, std::string* out) {
+  vector<char> data;
+  bool success = ReadFile(path, &data);
+  if (!success) {
+    return false;
+  }
+  (*out) = string(&data[0], data.size());
+  return true;
+}
+
+void HexDumpArray(const unsigned char* const arr, const size_t length) {
+  const unsigned char* const char_arr =
+      reinterpret_cast<const unsigned char* const>(arr);
+  LOG(INFO) << "Logging array of length: " << length;
+  const unsigned int bytes_per_line = 16;
+  for (size_t i = 0; i < length; i += bytes_per_line) {
+    const unsigned int bytes_remaining = length - i;
+    const unsigned int bytes_per_this_line = min(bytes_per_line,
+                                                 bytes_remaining);
+    char header[100];
+    int r = snprintf(header, sizeof(header), "0x%08x : ", i);
+    TEST_AND_RETURN(r == 13);
+    string line = header;
+    for (unsigned int j = 0; j < bytes_per_this_line; j++) {
+      char buf[20];
+      unsigned char c = char_arr[i + j];
+      r = snprintf(buf, sizeof(buf), "%02x ", static_cast<unsigned int>(c));
+      TEST_AND_RETURN(r == 3);
+      line += buf;
+    }
+    LOG(INFO) << line;
+  }
+}
+
+namespace {
+class ScopedDirCloser {
+ public:
+  explicit ScopedDirCloser(DIR** dir) : dir_(dir) {}
+  ~ScopedDirCloser() {
+    if (dir_ && *dir_) {
+      int r = closedir(*dir_);
+      TEST_AND_RETURN_ERRNO(r == 0);
+      *dir_ = NULL;
+      dir_ = NULL;
+    }
+  }
+ private:
+  DIR** dir_;
+};
+}  // namespace {}
+
+bool RecursiveUnlinkDir(const std::string& path) {
+  struct stat stbuf;
+  int r = lstat(path.c_str(), &stbuf);
+  TEST_AND_RETURN_FALSE_ERRNO((r == 0) || (errno == ENOENT));
+  if ((r < 0) && (errno == ENOENT))
+    // path request is missing. that's fine.
+    return true;
+  if (!S_ISDIR(stbuf.st_mode)) {
+    TEST_AND_RETURN_FALSE_ERRNO((unlink(path.c_str()) == 0) ||
+                                 (errno == ENOENT));
+    // success or path disappeared before we could unlink.
+    return true;
+  }
+  {
+    // We have a dir, unlink all children, then delete dir
+    DIR *dir = opendir(path.c_str());
+    TEST_AND_RETURN_FALSE_ERRNO(dir);
+    ScopedDirCloser dir_closer(&dir);
+    struct dirent dir_entry;
+    struct dirent *dir_entry_p;
+    int err = 0;
+    while ((err = readdir_r(dir, &dir_entry, &dir_entry_p)) == 0) {
+      if (dir_entry_p == NULL) {
+        // end of stream reached
+        break;
+      }
+      // Skip . and ..
+      if (!strcmp(dir_entry_p->d_name, ".") ||
+          !strcmp(dir_entry_p->d_name, ".."))
+        continue;
+      TEST_AND_RETURN_FALSE(RecursiveUnlinkDir(path + "/" +
+                                                dir_entry_p->d_name));
+    }
+    TEST_AND_RETURN_FALSE(err == 0);
+  }
+  // unlink dir
+  TEST_AND_RETURN_FALSE_ERRNO((rmdir(path.c_str()) == 0) || (errno == ENOENT));
+  return true;
+}
+
+std::string ErrnoNumberAsString(int err) {
+  char buf[100];
+  buf[0] = '\0';
+  return strerror_r(err, buf, sizeof(buf));
+}
+
+std::string NormalizePath(const std::string& path, bool strip_trailing_slash) {
+  string ret;
+  bool last_insert_was_slash = false;
+  for (string::const_iterator it = path.begin(); it != path.end(); ++it) {
+    if (*it == '/') {
+      if (last_insert_was_slash)
+        continue;
+      last_insert_was_slash = true;
+    } else {
+      last_insert_was_slash = false;
+    }
+    ret.push_back(*it);
+  }
+  if (strip_trailing_slash && last_insert_was_slash) {
+    string::size_type last_non_slash = ret.find_last_not_of('/');
+    if (last_non_slash != string::npos) {
+      ret.resize(last_non_slash + 1);
+    } else {
+      ret = "";
+    }
+  }
+  return ret;
+}
+
+bool FileExists(const char* path) {
+  struct stat stbuf;
+  return 0 == lstat(path, &stbuf);
+}
+
+std::string TempFilename(string path) {
+  static const string suffix("XXXXXX");
+  CHECK(StringHasSuffix(path, suffix));
+  do {
+    string new_suffix;
+    for (unsigned int i = 0; i < suffix.size(); i++) {
+      int r = rand() % (26 * 2 + 10);  // [a-zA-Z0-9]
+      if (r < 26)
+        new_suffix.append(1, 'a' + r);
+      else if (r < (26 * 2))
+        new_suffix.append(1, 'A' + r - 26);
+      else
+        new_suffix.append(1, '0' + r - (26 * 2));
+    }
+    CHECK_EQ(new_suffix.size(), suffix.size());
+    path.resize(path.size() - new_suffix.size());
+    path.append(new_suffix);
+  } while (FileExists(path.c_str()));
+  return path;
+}
+
+bool StringHasSuffix(const std::string& str, const std::string& suffix) {
+  if (suffix.size() > str.size())
+    return false;
+  return 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
+}
+
+bool StringHasPrefix(const std::string& str, const std::string& prefix) {
+  if (prefix.size() > str.size())
+    return false;
+  return 0 == str.compare(0, prefix.size(), prefix);
+}
+
+const std::string BootDevice() {
+  string proc_cmdline;
+  if (!ReadFileToString("/proc/cmdline", &proc_cmdline))
+    return "";
+  // look for "root=" in the command line
+  string::size_type pos = 0;
+  if (!StringHasPrefix(proc_cmdline, "root=")) {
+    pos = proc_cmdline.find(" root=") + 1;
+  }
+  if (pos == string::npos) {
+    // can't find root=
+    return "";
+  }
+  // at this point, pos is the point in the string where "root=" starts
+  string ret;
+  pos += strlen("root=");  // advance to the device name itself
+  while (pos < proc_cmdline.size()) {
+    char c = proc_cmdline[pos];
+    if (c == ' ')
+      break;
+    ret += c;
+    pos++;
+  }
+  return ret;
+  // TODO(adlr): use findfs to figure out UUID= or LABEL= filesystems
+}
+
+bool MountFilesystem(const string& device,
+                     const string& mountpoint) {
+  int rc = mount(device.c_str(), mountpoint.c_str(), "ext3", 0, NULL);
+  if (rc < 0) {
+    string msg = ErrnoNumberAsString(errno);
+    LOG(ERROR) << "Unable to mount destination device: " << msg << ". "
+               << device << " on " << mountpoint;
+    return false;
+  }
+  return true;
+}
+
+bool UnmountFilesystem(const string& mountpoint) {
+  TEST_AND_RETURN_FALSE_ERRNO(umount(mountpoint.c_str()) == 0);
+  return true;
+}
+
+const string kStatefulPartition = "/mnt/stateful_partition";
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
+
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..e475783
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,184 @@
+// Copyright (c) 2009 The Chromium 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_UTILS_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_UTILS_H__
+
+#include <set>
+#include <string>
+#include <vector>
+#include "update_engine/action.h"
+#include "update_engine/action_processor.h"
+
+namespace chromeos_update_engine {
+
+namespace utils {
+
+// Returns the entire contents of the file at path. Returns true on success.
+bool ReadFile(const std::string& path, std::vector<char>* out);
+bool ReadFileToString(const std::string& path, std::string* out);
+
+std::string ErrnoNumberAsString(int err);
+
+// Strips duplicate slashes, and optionally removes all trailing slashes.
+// Does not compact /./ or /../.
+std::string NormalizePath(const std::string& path, bool strip_trailing_slash);
+
+// Returns true if the file exists for sure. Returns false if it doesn't exist,
+// or an error occurs.
+bool FileExists(const char* path);
+
+// The last 6 chars of path must be XXXXXX. They will be randomly changed
+// and a non-existent path will be returned. Intentionally makes a copy
+// of the string passed in.
+// NEVER CALL THIS FUNCTION UNLESS YOU ARE SURE
+// THAT YOUR PROCESS WILL BE THE ONLY THING WRITING FILES IN THIS DIRECTORY.
+std::string TempFilename(string path);
+
+// Deletes a directory and all its contents synchronously. Returns true
+// on success. This may be called with a regular file--it will just unlink it.
+// This WILL cross filesystem boundaries.
+bool RecursiveUnlinkDir(const std::string& path);
+
+// Synchronously mount or unmount a filesystem. Return true on success.
+// Mounts as ext3 with default options.
+bool MountFilesystem(const string& device, const string& mountpoint);
+bool UnmountFilesystem(const string& mountpoint);
+
+// Log a string in hex to LOG(INFO). Useful for debugging.
+void HexDumpArray(const unsigned char* const arr, const size_t length);
+inline void HexDumpString(const std::string& str) {
+  HexDumpArray(reinterpret_cast<const unsigned char*>(str.data()), str.size());
+}
+inline void HexDumpVector(const std::vector<char>& vect) {
+  HexDumpArray(reinterpret_cast<const unsigned char*>(&vect[0]), vect.size());
+}
+
+extern const string kStatefulPartition;
+
+bool StringHasSuffix(const std::string& str, const std::string& suffix);
+bool StringHasPrefix(const std::string& str, const std::string& prefix);
+
+template<typename KeyType, typename ValueType>
+bool MapContainsKey(const std::map<KeyType, ValueType>& m, const KeyType& k) {
+  return m.find(k) != m.end();
+}
+
+template<typename ValueType>
+std::set<ValueType> SetWithValue(const ValueType& value) {
+  std::set<ValueType> ret;
+  ret.insert(value);
+  return ret;
+}
+
+// Returns the currently booted device. "/dev/sda1", for example.
+// This will not interpret LABEL= or UUID=. You'll need to use findfs
+// or something with equivalent funcionality to interpret those.
+const std::string BootDevice();
+
+}  // namespace utils
+
+// Class to unmount FS when object goes out of scope
+class ScopedFilesystemUnmounter {
+ public:
+  explicit ScopedFilesystemUnmounter(const std::string& mountpoint)
+      : mountpoint_(mountpoint) {}
+  ~ScopedFilesystemUnmounter() {
+    utils::UnmountFilesystem(mountpoint_);
+  }
+ private:
+  const std::string mountpoint_;
+};
+
+// Utility class to close a file descriptor
+class ScopedFdCloser {
+ public:
+  explicit ScopedFdCloser(int* fd) : fd_(fd), should_close_(true) {}
+  void set_should_close(bool should_close) { should_close_ = should_close; }
+  ~ScopedFdCloser() {
+    if (!should_close_)
+      return;
+    if (fd_ && (*fd_ >= 0)) {
+      close(*fd_);
+      *fd_ = -1;
+    }
+  }
+ private:
+  int* fd_;
+  bool should_close_;
+};
+
+// A little object to call ActionComplete on the ActionProcessor when
+// it's destructed.
+class ScopedActionCompleter {
+ public:
+  explicit ScopedActionCompleter(ActionProcessor* processor,
+                                 AbstractAction* action)
+      : processor_(processor),
+        action_(action),
+        success_(false),
+        should_complete_(true) {}
+  ~ScopedActionCompleter() {
+    if (should_complete_)
+      processor_->ActionComplete(action_, success_);
+  }
+  void set_success(bool success) {
+    success_ = success;
+  }
+  void set_should_complete(bool should_complete) {
+    should_complete_ = should_complete;
+  }
+ private:
+  ActionProcessor* processor_;
+  AbstractAction* action_;
+  bool success_;
+  bool should_complete_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
+};
+
+}  // namespace chromeos_update_engine
+
+#define TEST_AND_RETURN_FALSE_ERRNO(_x)                                        \
+  do {                                                                         \
+    bool _success = (_x);                                                      \
+    if (!_success) {                                                           \
+      std::string _msg =                                                       \
+          chromeos_update_engine::utils::ErrnoNumberAsString(errno);           \
+      LOG(ERROR) << #_x " failed: " << _msg;                                   \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_FALSE(_x)                                              \
+  do {                                                                         \
+    bool _success = (_x);                                                      \
+    if (!_success) {                                                           \
+      LOG(ERROR) << #_x " failed.";                                            \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_ERRNO(_x)                                              \
+  do {                                                                         \
+    bool _success = (_x);                                                      \
+    if (!_success) {                                                           \
+      std::string _msg =                                                       \
+          chromeos_update_engine::utils::ErrnoNumberAsString(errno);           \
+      LOG(ERROR) << #_x " failed: " << _msg;                                   \
+      return;                                                                  \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN(_x)                                                    \
+  do {                                                                         \
+    bool _success = (_x);                                                      \
+    if (!_success) {                                                           \
+      LOG(ERROR) << #_x " failed.";                                            \
+      return;                                                                  \
+    }                                                                          \
+  } while (0)
+
+
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_UTILS_H__
diff --git a/utils_unittest.cc b/utils_unittest.cc
new file mode 100644
index 0000000..4fee544
--- /dev/null
+++ b/utils_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 2009 The Chromium 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 <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class UtilsTest : public ::testing::Test { };
+
+TEST(UtilsTest, NormalizePathTest) {
+  EXPECT_EQ("", utils::NormalizePath("", false));
+  EXPECT_EQ("", utils::NormalizePath("", true));
+  EXPECT_EQ("/", utils::NormalizePath("/", false));
+  EXPECT_EQ("", utils::NormalizePath("/", true));
+  EXPECT_EQ("/", utils::NormalizePath("//", false));
+  EXPECT_EQ("", utils::NormalizePath("//", true));
+  EXPECT_EQ("foo", utils::NormalizePath("foo", false));
+  EXPECT_EQ("foo", utils::NormalizePath("foo", true));
+  EXPECT_EQ("/foo/", utils::NormalizePath("/foo//", false));
+  EXPECT_EQ("/foo", utils::NormalizePath("/foo//", true));
+  EXPECT_EQ("bar/baz/foo/adlr", utils::NormalizePath("bar/baz//foo/adlr",
+                                                     false));
+  EXPECT_EQ("bar/baz/foo/adlr", utils::NormalizePath("bar/baz//foo/adlr",
+                                                     true));
+  EXPECT_EQ("/bar/baz/foo/adlr/", utils::NormalizePath("/bar/baz//foo/adlr/",
+                                                       false));
+  EXPECT_EQ("/bar/baz/foo/adlr", utils::NormalizePath("/bar/baz//foo/adlr/",
+                                                      true));
+  EXPECT_EQ("\\\\", utils::NormalizePath("\\\\", false));
+  EXPECT_EQ("\\\\", utils::NormalizePath("\\\\", true));
+  EXPECT_EQ("\\:/;$PATH\n\\", utils::NormalizePath("\\://;$PATH\n\\", false));
+  EXPECT_EQ("\\:/;$PATH\n\\", utils::NormalizePath("\\://;$PATH\n\\", true));
+  EXPECT_EQ("/spaces s/ ok/s / / /",
+            utils::NormalizePath("/spaces s/ ok/s / / /", false));
+  EXPECT_EQ("/spaces s/ ok/s / / ",
+            utils::NormalizePath("/spaces s/ ok/s / / /", true));
+}
+
+TEST(UtilsTest, ReadFileFailure) {
+  vector<char> empty;
+  EXPECT_FALSE(utils::ReadFile("/this/doesn't/exist", &empty));
+}
+
+TEST(UtilsTest, ErrnoNumberAsStringTest) {
+  EXPECT_EQ("No such file or directory", utils::ErrnoNumberAsString(ENOENT));
+}
+
+TEST(UtilsTest, StringHasSuffixTest) {
+  EXPECT_TRUE(utils::StringHasSuffix("foo", "foo"));
+  EXPECT_TRUE(utils::StringHasSuffix("foo", "o"));
+  EXPECT_TRUE(utils::StringHasSuffix("", ""));
+  EXPECT_TRUE(utils::StringHasSuffix("abcabc", "abc"));
+  EXPECT_TRUE(utils::StringHasSuffix("adlrwashere", "ere"));
+  EXPECT_TRUE(utils::StringHasSuffix("abcdefgh", "gh"));
+  EXPECT_TRUE(utils::StringHasSuffix("abcdefgh", ""));
+  EXPECT_FALSE(utils::StringHasSuffix("foo", "afoo"));
+  EXPECT_FALSE(utils::StringHasSuffix("", "x"));
+  EXPECT_FALSE(utils::StringHasSuffix("abcdefgh", "fg"));
+  EXPECT_FALSE(utils::StringHasSuffix("abcdefgh", "ab"));
+}
+
+TEST(UtilsTest, StringHasPrefixTest) {
+  EXPECT_TRUE(utils::StringHasPrefix("foo", "foo"));
+  EXPECT_TRUE(utils::StringHasPrefix("foo", "f"));
+  EXPECT_TRUE(utils::StringHasPrefix("", ""));
+  EXPECT_TRUE(utils::StringHasPrefix("abcabc", "abc"));
+  EXPECT_TRUE(utils::StringHasPrefix("adlrwashere", "adl"));
+  EXPECT_TRUE(utils::StringHasPrefix("abcdefgh", "ab"));
+  EXPECT_TRUE(utils::StringHasPrefix("abcdefgh", ""));
+  EXPECT_FALSE(utils::StringHasPrefix("foo", "fooa"));
+  EXPECT_FALSE(utils::StringHasPrefix("", "x"));
+  EXPECT_FALSE(utils::StringHasPrefix("abcdefgh", "bc"));
+  EXPECT_FALSE(utils::StringHasPrefix("abcdefgh", "gh"));
+}
+
+TEST(UtilsTest, BootDeviceTest) {
+  // Pretty lame test...
+  EXPECT_FALSE(utils::BootDevice().empty());
+}
+
+TEST(UtilsTest, RecursiveUnlinkDirTest) {
+  EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-a", 0755));
+  EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-b", 0755));
+  EXPECT_EQ(0, symlink("../RecursiveUnlinkDirTest-a",
+                       "RecursiveUnlinkDirTest-b/link"));
+  EXPECT_EQ(0, system("echo hi > RecursiveUnlinkDirTest-b/file"));
+  EXPECT_EQ(0, mkdir("RecursiveUnlinkDirTest-b/dir", 0755));
+  EXPECT_EQ(0, system("echo ok > RecursiveUnlinkDirTest-b/dir/subfile"));
+  EXPECT_TRUE(utils::RecursiveUnlinkDir("RecursiveUnlinkDirTest-b"));
+  EXPECT_TRUE(utils::FileExists("RecursiveUnlinkDirTest-a"));
+  EXPECT_EQ(0, system("rm -rf RecursiveUnlinkDirTest-a"));
+  EXPECT_FALSE(utils::FileExists("RecursiveUnlinkDirTest-b"));
+  EXPECT_TRUE(utils::RecursiveUnlinkDir("/something/that/doesnt/exist"));
+}
+
+TEST(UtilsTest, TempFilenameTest) {
+  const string original = "/foo.XXXXXX";
+  const string result = utils::TempFilename(original);
+  EXPECT_EQ(original.size(), result.size());
+  EXPECT_TRUE(utils::StringHasPrefix(result, "/foo."));
+  EXPECT_FALSE(utils::StringHasSuffix(result, "XXXXXX"));
+}
+
+}  // namespace chromeos_update_engine