Move InstallPlan partitions to a list of partitions.

This patch changes the InstallPlan instance from having hard-coded
rootfs and kernel paritions to have a list of partitions with a
name, source and target information.

The FilesystemVerifierAction, DeltaPerformer and PostInstallAction were
adapter to use the list of partitions instead.

In delta payloads (only supported in the current major version 1) the
list of operations is still fixed: the rootfs first and the kernel.
This list is now populated by the FilesystemVerifierAction including
the size of these partitions, until the whole source partition hash
checking is deprecated (b/23182225).

The PostIntallAction now relies on the DeltaPerformer to populate the
post-install information from the payload. This means that in rollback
we won't run any device-specific post-install operation, and will
simply flip the slots in the bootloader.

Bug: 24667689
Test: Updated unittests. Tested on a dragonboard and a link.

Change-Id: I8277e3190ac74e57832a58dc0730e3713f48af8a
diff --git a/delta_performer.cc b/delta_performer.cc
index 8aff634..df128f3 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -65,7 +65,7 @@
 const uint64_t DeltaPerformer::kDeltaMetadataSignatureSizeSize = 4;
 const uint64_t DeltaPerformer::kMaxPayloadHeaderSize = 24;
 const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 1;
-const uint64_t DeltaPerformer::kSupportedMinorPayloadVersion = 2;
+const uint32_t DeltaPerformer::kSupportedMinorPayloadVersion = 2;
 
 const unsigned DeltaPerformer::kProgressLogMaxChunks = 10;
 const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
@@ -109,9 +109,8 @@
 
 // Opens path for read/write. On success returns an open FileDescriptor
 // and sets *err to 0. On failure, sets *err to errno and returns nullptr.
-FileDescriptorPtr OpenFile(const char* path, int* err) {
+FileDescriptorPtr OpenFile(const char* path, int mode, int* err) {
   FileDescriptorPtr fd = CreateFileDescriptor(path);
-  int mode = O_RDWR;
 #if USE_MTD
   // On NAND devices, we can either read, or write, but not both. So here we
   // use O_WRONLY.
@@ -252,58 +251,9 @@
   return false;
 }
 
-int DeltaPerformer::Open(const char* path, int flags, mode_t mode) {
-  int err;
-  fd_ = OpenFile(path, &err);
-  if (fd_)
-    path_ = path;
-  return -err;
-}
-
-bool DeltaPerformer::OpenKernel(const char* kernel_path) {
-  int err;
-  kernel_fd_ = OpenFile(kernel_path, &err);
-  if (kernel_fd_)
-    kernel_path_ = kernel_path;
-  return static_cast<bool>(kernel_fd_);
-}
-
-bool DeltaPerformer::OpenSourceRootfs(const string& source_path) {
-  int err;
-  source_fd_ = OpenFile(source_path.c_str(), &err);
-  return static_cast<bool>(source_fd_);
-}
-
-bool DeltaPerformer::OpenSourceKernel(const string& source_kernel_path) {
-  int err;
-  source_kernel_fd_ = OpenFile(source_kernel_path.c_str(), &err);
-  return static_cast<bool>(source_kernel_fd_);
-}
-
 int DeltaPerformer::Close() {
-  int err = 0;
-  if (kernel_fd_ && !kernel_fd_->Close()) {
-    err = errno;
-    PLOG(ERROR) << "Unable to close kernel fd:";
-  }
-  if (!fd_->Close()) {
-    err = errno;
-    PLOG(ERROR) << "Unable to close rootfs fd:";
-  }
-  if (source_fd_ && !source_fd_->Close()) {
-    err = errno;
-    PLOG(ERROR) << "Unable to close source rootfs fd:";
-  }
-  if (source_kernel_fd_ && !source_kernel_fd_->Close()) {
-    err = errno;
-    PLOG(ERROR) << "Unable to close source kernel fd:";
-  }
+  int err = -CloseCurrentPartition();
   LOG_IF(ERROR, !hash_calculator_.Finalize()) << "Unable to finalize the hash.";
-  fd_.reset();  // Set to invalid so that calls to Open() will fail.
-  kernel_fd_.reset();
-  source_fd_.reset();
-  source_kernel_fd_.reset();
-  path_ = "";
   if (!buffer_.empty()) {
     LOG(INFO) << "Discarding " << buffer_.size() << " unused downloaded bytes";
     if (err >= 0)
@@ -312,6 +262,61 @@
   return -err;
 }
 
+int DeltaPerformer::CloseCurrentPartition() {
+  int err = 0;
+  if (source_fd_ && !source_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Error closing source partition";
+    if (!err)
+      err = 1;
+  }
+  source_fd_.reset();
+  source_path_.clear();
+
+  if (target_fd_ && !target_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Error closing target partition";
+    if (!err)
+      err = 1;
+  }
+  target_fd_.reset();
+  target_path_.clear();
+  return -err;
+}
+
+bool DeltaPerformer::OpenCurrentPartition() {
+  if (current_partition_ >= partitions_.size())
+    return false;
+
+  const PartitionUpdate& partition = partitions_[current_partition_];
+  // Open source fds if we have a delta payload with minor version 2.
+  if (!install_plan_->is_full_update &&
+      GetMinorVersion() == kSourceMinorPayloadVersion) {
+    source_path_ = install_plan_->partitions[current_partition_].source_path;
+    int err;
+    source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, &err);
+    if (!source_fd_) {
+      LOG(ERROR) << "Unable to open source partition "
+                 << partition.partition_name() << " on slot "
+                 << BootControlInterface::SlotName(install_plan_->source_slot)
+                 << ", file " << source_path_;
+      return false;
+    }
+  }
+
+  target_path_ = install_plan_->partitions[current_partition_].target_path;
+  int err;
+  target_fd_ = OpenFile(target_path_.c_str(), O_RDWR, &err);
+  if (!target_fd_) {
+    LOG(ERROR) << "Unable to open target partition "
+               << partition.partition_name() << " on slot "
+               << BootControlInterface::SlotName(install_plan_->target_slot)
+               << ", file " << target_path_;
+    return false;
+  }
+  return true;
+}
+
 namespace {
 
 void LogPartitionInfoHash(const PartitionInfo& info, const string& tag) {
@@ -320,15 +325,13 @@
             << " size: " << info.size();
 }
 
-void LogPartitionInfo(const DeltaArchiveManifest& manifest) {
-  if (manifest.has_old_kernel_info())
-    LogPartitionInfoHash(manifest.old_kernel_info(), "old_kernel_info");
-  if (manifest.has_old_rootfs_info())
-    LogPartitionInfoHash(manifest.old_rootfs_info(), "old_rootfs_info");
-  if (manifest.has_new_kernel_info())
-    LogPartitionInfoHash(manifest.new_kernel_info(), "new_kernel_info");
-  if (manifest.has_new_rootfs_info())
-    LogPartitionInfoHash(manifest.new_rootfs_info(), "new_rootfs_info");
+void LogPartitionInfo(const std::vector<PartitionUpdate>& partitions) {
+  for (const PartitionUpdate& partition : partitions) {
+    LogPartitionInfoHash(partition.old_partition_info(),
+                         "old " + partition.partition_name());
+    LogPartitionInfoHash(partition.new_partition_info(),
+                         "new " + partition.partition_name());
+  }
 }
 
 }  // namespace
@@ -560,37 +563,33 @@
 
     // Clear the download buffer.
     DiscardBuffer(false);
+
+    // This populates |partitions_| and the |install_plan.partitions| with the
+    // list of partitions from the manifest.
+    if (!ParseManifestPartitions(error))
+      return false;
+
+    num_total_operations_ = 0;
+    for (const auto& partition : partitions_) {
+      num_total_operations_ += partition.operations_size();
+      acc_num_operations_.push_back(num_total_operations_);
+    }
+
     LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize,
                                       metadata_size_))
         << "Unable to save the manifest metadata size.";
 
-    LogPartitionInfo(manifest_);
     if (!PrimeUpdateState()) {
       *error = ErrorCode::kDownloadStateInitializationError;
       LOG(ERROR) << "Unable to prime the update state.";
       return false;
     }
 
-    // Open source fds if we have a delta payload with minor version 2.
-    if (!install_plan_->is_full_update &&
-        GetMinorVersion() == kSourceMinorPayloadVersion) {
-      if (!OpenSourceRootfs(install_plan_->source_path)) {
-        LOG(ERROR) << "Unable to open source rootfs partition file "
-                   << install_plan_->source_path;
-        Close();
-        return false;
-      }
-      if (!OpenSourceKernel(install_plan_->kernel_source_path)) {
-        LOG(ERROR) << "Unable to open source kernel partition file "
-                   << install_plan_->kernel_source_path;
-        Close();
-        return false;
-      }
+    if (!OpenCurrentPartition()) {
+      *error = ErrorCode::kInstallDeviceOpenError;
+      return false;
     }
 
-    num_rootfs_operations_ = manifest_.install_operations_size();
-    num_total_operations_ =
-        num_rootfs_operations_ + manifest_.kernel_install_operations_size();
     if (next_operation_num_ > 0)
       UpdateOverallProgress(true, "Resuming after ");
     LOG(INFO) << "Starting to apply update payload operations";
@@ -599,17 +598,25 @@
   while (next_operation_num_ < num_total_operations_) {
     // Check if we should cancel the current attempt for any reason.
     // In this case, *error will have already been populated with the reason
-    // why we're cancelling.
+    // why we're canceling.
     if (system_state_->update_attempter()->ShouldCancel(error))
       return false;
 
-    const bool is_kernel_partition =
-        (next_operation_num_ >= num_rootfs_operations_);
+    // We know there are more operations to perform because we didn't reach the
+    // |num_total_operations_| limit yet.
+    while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+      CloseCurrentPartition();
+      current_partition_++;
+      if (!OpenCurrentPartition()) {
+        *error = ErrorCode::kInstallDeviceOpenError;
+        return false;
+      }
+    }
+    const size_t partition_operation_num = next_operation_num_ - (
+        current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0);
+
     const InstallOperation& op =
-        is_kernel_partition ?
-            manifest_.kernel_install_operations(next_operation_num_ -
-                                                num_rootfs_operations_) :
-            manifest_.install_operations(next_operation_num_);
+        partitions_[current_partition_].operations(partition_operation_num);
 
     CopyDataToBuffer(&c_bytes, &count, op.data_length());
 
@@ -649,23 +656,23 @@
       case InstallOperation::REPLACE:
       case InstallOperation::REPLACE_BZ:
       case InstallOperation::REPLACE_XZ:
-        op_result = PerformReplaceOperation(op, is_kernel_partition);
+        op_result = PerformReplaceOperation(op);
         break;
       case InstallOperation::ZERO:
       case InstallOperation::DISCARD:
-        op_result = PerformZeroOrDiscardOperation(op, is_kernel_partition);
+        op_result = PerformZeroOrDiscardOperation(op);
         break;
       case InstallOperation::MOVE:
-        op_result = PerformMoveOperation(op, is_kernel_partition);
+        op_result = PerformMoveOperation(op);
         break;
       case InstallOperation::BSDIFF:
-        op_result = PerformBsdiffOperation(op, is_kernel_partition);
+        op_result = PerformBsdiffOperation(op);
         break;
       case InstallOperation::SOURCE_COPY:
-        op_result = PerformSourceCopyOperation(op, is_kernel_partition);
+        op_result = PerformSourceCopyOperation(op);
         break;
       case InstallOperation::SOURCE_BSDIFF:
-        op_result = PerformSourceBsdiffOperation(op, is_kernel_partition);
+        op_result = PerformSourceBsdiffOperation(op);
         break;
       default:
        op_result = false;
@@ -684,6 +691,94 @@
   return manifest_valid_;
 }
 
+bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) {
+  if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+    partitions_.clear();
+    for (const PartitionUpdate& partition : manifest_.partitions()) {
+      partitions_.push_back(partition);
+    }
+    manifest_.clear_partitions();
+  } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
+    LOG(INFO) << "Converting update information from old format.";
+    PartitionUpdate root_part;
+    root_part.set_partition_name(kLegacyPartitionNameRoot);
+    root_part.set_run_postinstall(true);
+    if (manifest_.has_old_rootfs_info()) {
+      *root_part.mutable_old_partition_info() = manifest_.old_rootfs_info();
+      manifest_.clear_old_rootfs_info();
+    }
+    if (manifest_.has_new_rootfs_info()) {
+      *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info();
+      manifest_.clear_new_rootfs_info();
+    }
+    *root_part.mutable_operations() = manifest_.install_operations();
+    manifest_.clear_install_operations();
+    partitions_.push_back(std::move(root_part));
+
+    PartitionUpdate kern_part;
+    kern_part.set_partition_name(kLegacyPartitionNameKernel);
+    kern_part.set_run_postinstall(false);
+    if (manifest_.has_old_kernel_info()) {
+      *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info();
+      manifest_.clear_old_kernel_info();
+    }
+    if (manifest_.has_new_kernel_info()) {
+      *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info();
+      manifest_.clear_new_kernel_info();
+    }
+    *kern_part.mutable_operations() = manifest_.kernel_install_operations();
+    manifest_.clear_kernel_install_operations();
+    partitions_.push_back(std::move(kern_part));
+  }
+
+  // TODO(deymo): Remove this block of code once we switched to optional
+  // source partition verification. This list of partitions in the InstallPlan
+  // is initialized with the expected hashes in the payload major version 1,
+  // so we need to check those now if already set. See b/23182225.
+  if (!install_plan_->partitions.empty()) {
+    if (!VerifySourcePartitions()) {
+      *error = ErrorCode::kDownloadStateInitializationError;
+      return false;
+    }
+  }
+
+  // Fill in the InstallPlan::partitions based on the partitions from the
+  // payload.
+  install_plan_->partitions.clear();
+  for (const auto& partition : partitions_) {
+    InstallPlan::Partition install_part;
+    install_part.name = partition.partition_name();
+    install_part.run_postinstall =
+        partition.has_run_postinstall() && partition.run_postinstall();
+
+    if (partition.has_old_partition_info()) {
+      const PartitionInfo& info = partition.old_partition_info();
+      install_part.source_size = info.size();
+      install_part.source_hash.assign(info.hash().begin(), info.hash().end());
+    }
+
+    if (!partition.has_new_partition_info()) {
+      LOG(ERROR) << "Unable to get new partition hash info on partition "
+                 << install_part.name << ".";
+      *error = ErrorCode::kDownloadNewPartitionInfoError;
+      return false;
+    }
+    const PartitionInfo& info = partition.new_partition_info();
+    install_part.target_size = info.size();
+    install_part.target_hash.assign(info.hash().begin(), info.hash().end());
+
+    install_plan_->partitions.push_back(install_part);
+  }
+
+  if (!install_plan_->LoadPartitionsFromSlots(system_state_)) {
+    LOG(ERROR) << "Unable to determine all the partition devices.";
+    *error = ErrorCode::kInstallDeviceOpenError;
+    return false;
+  }
+  LogPartitionInfo(partitions_);
+  return true;
+}
+
 bool DeltaPerformer::CanPerformInstallOperation(
     const chromeos_update_engine::InstallOperation& operation) {
   // Move and source_copy operations don't require any data blob, so they can
@@ -702,8 +797,8 @@
           buffer_offset_ + buffer_.size());
 }
 
-bool DeltaPerformer::PerformReplaceOperation(const InstallOperation& operation,
-                                             bool is_kernel_partition) {
+bool DeltaPerformer::PerformReplaceOperation(
+    const InstallOperation& operation) {
   CHECK(operation.type() == InstallOperation::REPLACE ||
         operation.type() == InstallOperation::REPLACE_BZ ||
         operation.type() == InstallOperation::REPLACE_XZ);
@@ -733,9 +828,7 @@
     extents.push_back(operation.dst_extents(i));
   }
 
-  FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
-
-  TEST_AND_RETURN_FALSE(writer->Init(fd, extents, block_size_));
+  TEST_AND_RETURN_FALSE(writer->Init(target_fd_, extents, block_size_));
   TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length()));
   TEST_AND_RETURN_FALSE(writer->End());
 
@@ -745,8 +838,7 @@
 }
 
 bool DeltaPerformer::PerformZeroOrDiscardOperation(
-    const InstallOperation& operation,
-    bool is_kernel_partition) {
+    const InstallOperation& operation) {
   CHECK(operation.type() == InstallOperation::DISCARD ||
         operation.type() == InstallOperation::ZERO);
 
@@ -757,7 +849,6 @@
   int request =
       (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD);
 
-  FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
   bool attempt_ioctl = true;
   chromeos::Blob zeros;
   for (int i = 0; i < operation.dst_extents_size(); i++) {
@@ -766,7 +857,7 @@
     const uint64_t length = extent.num_blocks() * block_size_;
     if (attempt_ioctl) {
       int result = 0;
-      if (fd->BlkIoctl(request, start, length, &result) && result == 0)
+      if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0)
         continue;
       attempt_ioctl = false;
       zeros.resize(16 * block_size_);
@@ -776,14 +867,13 @@
       uint64_t chunk_length = min(length - offset,
                                   static_cast<uint64_t>(zeros.size()));
       TEST_AND_RETURN_FALSE(
-          utils::PWriteAll(fd, zeros.data(), chunk_length, start + offset));
+          utils::PWriteAll(target_fd_, zeros.data(), chunk_length, start + offset));
     }
   }
   return true;
 }
 
-bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation,
-                                          bool is_kernel_partition) {
+bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation) {
   // Calculate buffer size. Note, this function doesn't do a sliding
   // window to copy in case the source and destination blocks overlap.
   // If we wanted to do a sliding window, we could program the server
@@ -800,8 +890,6 @@
   DCHECK_EQ(blocks_to_write, blocks_to_read);
   chromeos::Blob buf(blocks_to_write * block_size_);
 
-  FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
-
   // Read in bytes.
   ssize_t bytes_read = 0;
   for (int i = 0; i < operation.src_extents_size(); i++) {
@@ -809,7 +897,7 @@
     const Extent& extent = operation.src_extents(i);
     const size_t bytes = extent.num_blocks() * block_size_;
     TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
-    TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+    TEST_AND_RETURN_FALSE(utils::PReadAll(target_fd_,
                                           &buf[bytes_read],
                                           bytes,
                                           extent.start_block() * block_size_,
@@ -825,7 +913,7 @@
     const Extent& extent = operation.dst_extents(i);
     const size_t bytes = extent.num_blocks() * block_size_;
     TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
-    TEST_AND_RETURN_FALSE(utils::PWriteAll(fd,
+    TEST_AND_RETURN_FALSE(utils::PWriteAll(target_fd_,
                                            &buf[bytes_written],
                                            bytes,
                                            extent.start_block() * block_size_));
@@ -860,8 +948,7 @@
 }  // namespace
 
 bool DeltaPerformer::PerformSourceCopyOperation(
-    const InstallOperation& operation,
-    bool is_kernel_partition) {
+    const InstallOperation& operation) {
   if (operation.has_src_length())
     TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
   if (operation.has_dst_length())
@@ -879,10 +966,6 @@
   DCHECK_EQ(src_blocks.size(), blocks_to_read);
   DCHECK_EQ(src_blocks.size(), dst_blocks.size());
 
-  FileDescriptorPtr src_fd =
-      is_kernel_partition ? source_kernel_fd_ : source_fd_;
-  FileDescriptorPtr dst_fd = is_kernel_partition? kernel_fd_ : fd_;
-
   chromeos::Blob buf(block_size_);
   ssize_t bytes_read = 0;
   // Read/write one block at a time.
@@ -893,7 +976,7 @@
 
     // Read in bytes.
     TEST_AND_RETURN_FALSE(
-        utils::PReadAll(src_fd,
+        utils::PReadAll(source_fd_,
                         buf.data(),
                         block_size_,
                         src_block * block_size_,
@@ -901,7 +984,7 @@
 
     // Write bytes out.
     TEST_AND_RETURN_FALSE(
-        utils::PWriteAll(dst_fd,
+        utils::PWriteAll(target_fd_,
                          buf.data(),
                          block_size_,
                          dst_block * block_size_));
@@ -936,8 +1019,7 @@
   return true;
 }
 
-bool DeltaPerformer::PerformBsdiffOperation(const InstallOperation& operation,
-                                            bool is_kernel_partition) {
+bool DeltaPerformer::PerformBsdiffOperation(const InstallOperation& operation) {
   // Since we delete data off the beginning of the buffer as we use it,
   // the data we need should be exactly at the beginning of the buffer.
   TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
@@ -970,8 +1052,7 @@
   // file is written out.
   DiscardBuffer(true);
 
-  const string& path = is_kernel_partition ? kernel_path_ : path_;
-  vector<string> cmd{kBspatchPath, path, path, temp_filename,
+  vector<string> cmd{kBspatchPath, target_path_, target_path_, temp_filename,
                      input_positions, output_positions};
 
   int return_code = 0;
@@ -990,16 +1071,14 @@
     const uint64_t begin_byte =
         end_byte - (block_size_ - operation.dst_length() % block_size_);
     chromeos::Blob zeros(end_byte - begin_byte);
-    FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
     TEST_AND_RETURN_FALSE(
-        utils::PWriteAll(fd, zeros.data(), end_byte - begin_byte, begin_byte));
+        utils::PWriteAll(target_fd_, zeros.data(), end_byte - begin_byte, begin_byte));
   }
   return true;
 }
 
 bool DeltaPerformer::PerformSourceBsdiffOperation(
-    const InstallOperation& operation,
-    bool is_kernel_partition) {
+    const InstallOperation& operation) {
   // Since we delete data off the beginning of the buffer as we use it,
   // the data we need should be exactly at the beginning of the buffer.
   TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
@@ -1036,11 +1115,7 @@
   // file is written out.
   DiscardBuffer(true);
 
-  const string& src_path = is_kernel_partition ?
-                           install_plan_->kernel_source_path :
-                           install_plan_->source_path;
-  const string& dst_path = is_kernel_partition ? kernel_path_ : path_;
-  vector<string> cmd{kBspatchPath, src_path, dst_path, temp_filename,
+  vector<string> cmd{kBspatchPath, source_path_, target_path_, temp_filename,
                      input_positions, output_positions};
 
   int return_code = 0;
@@ -1354,29 +1429,12 @@
   return ErrorCode::kSuccess;
 }
 
-bool DeltaPerformer::GetNewPartitionInfo(uint64_t* kernel_size,
-                                         chromeos::Blob* kernel_hash,
-                                         uint64_t* rootfs_size,
-                                         chromeos::Blob* rootfs_hash) {
-  TEST_AND_RETURN_FALSE(manifest_valid_ &&
-                        manifest_.has_new_kernel_info() &&
-                        manifest_.has_new_rootfs_info());
-  *kernel_size = manifest_.new_kernel_info().size();
-  *rootfs_size = manifest_.new_rootfs_info().size();
-  chromeos::Blob new_kernel_hash(manifest_.new_kernel_info().hash().begin(),
-                                 manifest_.new_kernel_info().hash().end());
-  chromeos::Blob new_rootfs_hash(manifest_.new_rootfs_info().hash().begin(),
-                                 manifest_.new_rootfs_info().hash().end());
-  kernel_hash->swap(new_kernel_hash);
-  rootfs_hash->swap(new_rootfs_hash);
-  return true;
-}
-
 namespace {
-void LogVerifyError(bool is_kern,
+void LogVerifyError(const string& type,
+                    const string& device,
+                    uint64_t size,
                     const string& local_hash,
                     const string& expected_hash) {
-  const char* type = is_kern ? "kernel" : "rootfs";
   LOG(ERROR) << "This is a server-side error due to "
              << "mismatched delta update image!";
   LOG(ERROR) << "The delta I've been given contains a " << type << " delta "
@@ -1387,19 +1445,10 @@
              << "system. The " << type << " partition I have has hash: "
              << local_hash << " but the update expected me to have "
              << expected_hash << " .";
-  if (is_kern) {
-    LOG(INFO) << "To get the checksum of a kernel partition on a "
-              << "booted machine, run this command (change /dev/sda2 "
-              << "as needed): dd if=/dev/sda2 bs=1M 2>/dev/null | "
-              << "openssl dgst -sha256 -binary | openssl base64";
-  } else {
-    LOG(INFO) << "To get the checksum of a rootfs partition on a "
-              << "booted machine, run this command (change /dev/sda3 "
-              << "as needed): dd if=/dev/sda3 bs=1M count=$(( "
-              << "$(dumpe2fs /dev/sda3  2>/dev/null | grep 'Block count' "
-              << "| sed 's/[^0-9]*//') / 256 )) | "
-              << "openssl dgst -sha256 -binary | openssl base64";
-  }
+  LOG(INFO) << "To get the checksum of the " << type << " partition run this"
+               "command: dd if=" << device << " bs=1M count=" << size
+            << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 -binary "
+               "| openssl base64";
   LOG(INFO) << "To get the checksum of partitions in a bin file, "
             << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
 }
@@ -1413,41 +1462,43 @@
   LOG(INFO) << "Verifying source partitions.";
   CHECK(manifest_valid_);
   CHECK(install_plan_);
-  if (manifest_.has_old_kernel_info()) {
-    const PartitionInfo& info = manifest_.old_kernel_info();
-    bool valid =
-        !install_plan_->source_kernel_hash.empty() &&
-        install_plan_->source_kernel_hash.size() == info.hash().size() &&
-        memcmp(install_plan_->source_kernel_hash.data(),
-               info.hash().data(),
-               install_plan_->source_kernel_hash.size()) == 0;
-    if (!valid) {
-      LogVerifyError(true,
-                     StringForHashBytes(
-                         install_plan_->source_kernel_hash.data(),
-                         install_plan_->source_kernel_hash.size()),
-                     StringForHashBytes(info.hash().data(),
-                                        info.hash().size()));
-    }
-    TEST_AND_RETURN_FALSE(valid);
+  if (install_plan_->partitions.size() != partitions_.size()) {
+    DLOG(ERROR) << "The list of partitions in the InstallPlan doesn't match the "
+                   "list received in the payload. The InstallPlan has "
+                << install_plan_->partitions.size()
+                << " partitions while the payload has " << partitions_.size()
+                << " partitions.";
+    return false;
   }
-  if (manifest_.has_old_rootfs_info()) {
-    const PartitionInfo& info = manifest_.old_rootfs_info();
+  for (size_t i = 0; i < partitions_.size(); ++i) {
+    if (partitions_[i].partition_name() != install_plan_->partitions[i].name) {
+      DLOG(ERROR) << "The InstallPlan's partition " << i << " is \""
+                  << install_plan_->partitions[i].name
+                  << "\" but the payload expects it to be \""
+                  << partitions_[i].partition_name()
+                  << "\". This is an error in the DeltaPerformer setup.";
+      return false;
+    }
+    if (!partitions_[i].has_old_partition_info())
+      continue;
+    const PartitionInfo& info = partitions_[i].old_partition_info();
+    const InstallPlan::Partition& plan_part = install_plan_->partitions[i];
     bool valid =
-        !install_plan_->source_rootfs_hash.empty() &&
-        install_plan_->source_rootfs_hash.size() == info.hash().size() &&
-        memcmp(install_plan_->source_rootfs_hash.data(),
+        !plan_part.source_hash.empty() &&
+        plan_part.source_hash.size() == info.hash().size() &&
+        memcmp(plan_part.source_hash.data(),
                info.hash().data(),
-               install_plan_->source_rootfs_hash.size()) == 0;
+               plan_part.source_hash.size()) == 0;
     if (!valid) {
-      LogVerifyError(false,
-                     StringForHashBytes(
-                         install_plan_->source_rootfs_hash.data(),
-                         install_plan_->source_rootfs_hash.size()),
+      LogVerifyError(partitions_[i].partition_name(),
+                     plan_part.source_path,
+                     info.hash().size(),
+                     StringForHashBytes(plan_part.source_hash.data(),
+                                        plan_part.source_hash.size()),
                      StringForHashBytes(info.hash().data(),
                                         info.hash().size()));
+      return false;
     }
-    TEST_AND_RETURN_FALSE(valid);
   }
   return true;
 }
@@ -1531,13 +1582,13 @@
     last_updated_buffer_offset_ = buffer_offset_;
 
     if (next_operation_num_ < num_total_operations_) {
-      const bool is_kernel_partition =
-          next_operation_num_ >= num_rootfs_operations_;
+      size_t partition_index = current_partition_;
+      while (next_operation_num_ >= acc_num_operations_[partition_index])
+        partition_index++;
+      const size_t partition_operation_num = next_operation_num_ - (
+          partition_index ? acc_num_operations_[partition_index - 1] : 0);
       const InstallOperation& op =
-          is_kernel_partition
-              ? manifest_.kernel_install_operations(next_operation_num_ -
-                                                    num_rootfs_operations_)
-              : manifest_.install_operations(next_operation_num_);
+          partitions_[partition_index].operations(partition_operation_num);
       TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
                                              op.data_length()));
     } else {
@@ -1559,7 +1610,6 @@
       next_operation == kUpdateStateOperationInvalid ||
       next_operation <= 0) {
     // Initiating a new update, no more state needs to be initialized.
-    TEST_AND_RETURN_FALSE(VerifySourcePartitions());
     return true;
   }
   next_operation_num_ = next_operation;
diff --git a/delta_performer.h b/delta_performer.h
index 34cb137..64ad4af 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -57,7 +57,7 @@
   static const uint64_t kDeltaMetadataSignatureSizeSize;
   static const uint64_t kMaxPayloadHeaderSize;
   static const uint64_t kSupportedMajorPayloadVersion;
-  static const uint64_t kSupportedMinorPayloadVersion;
+  static const uint32_t kSupportedMinorPayloadVersion;
 
   // Defines the granularity of progress logging in terms of how many "completed
   // chunks" we want to report at the most.
@@ -77,44 +77,7 @@
                  InstallPlan* install_plan)
       : prefs_(prefs),
         system_state_(system_state),
-        install_plan_(install_plan),
-        fd_(nullptr),
-        kernel_fd_(nullptr),
-        source_fd_(nullptr),
-        source_kernel_fd_(nullptr),
-        manifest_parsed_(false),
-        manifest_valid_(false),
-        metadata_size_(0),
-        manifest_size_(0),
-        major_payload_version_(0),
-        next_operation_num_(0),
-        buffer_offset_(0),
-        last_updated_buffer_offset_(kuint64max),
-        block_size_(0),
-        public_key_path_(constants::kUpdatePayloadPublicKeyPath),
-        total_bytes_received_(0),
-        num_rootfs_operations_(0),
-        num_total_operations_(0),
-        overall_progress_(0),
-        last_progress_chunk_(0),
-        forced_progress_log_wait_(
-            base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)),
-        supported_major_version_(kSupportedMajorPayloadVersion),
-        supported_minor_version_(kSupportedMinorPayloadVersion) {}
-
-  // Opens the kernel. Should be called before or after Open(), but before
-  // Write(). The kernel file will be close()d when Close() is called.
-  bool OpenKernel(const char* kernel_path);
-
-  // Opens the source partition. The file will be closed when Close() is called.
-  bool OpenSourceRootfs(const std::string& kernel_path);
-
-  // Opens the source kernel. The file will be closed when Close() is called.
-  bool OpenSourceKernel(const std::string& source_kernel_path);
-
-  // flags and mode ignored. Once Close()d, a DeltaPerformer can't be
-  // Open()ed again.
-  int Open(const char* path, int flags, mode_t mode) override;
+        install_plan_(install_plan) {}
 
   // FileWriter's Write implementation where caller doesn't care about
   // error codes.
@@ -131,6 +94,15 @@
   // Closes both 'path' given to Open() and the kernel path.
   int Close() override;
 
+  // Open the target and source (if delta payload) file descriptors for the
+  // |current_partition_|. The manifest needs to be already parsed for this to
+  // work. Returns whether the required file descriptors were successfully open.
+  bool OpenCurrentPartition();
+
+  // Closes the current partition file descriptors if open. Returns 0 on success
+  // or -errno on error.
+  int CloseCurrentPartition();
+
   // Returns |true| only if the manifest has been processed and it's valid.
   bool IsManifestValid();
 
@@ -144,17 +116,6 @@
   ErrorCode VerifyPayload(const std::string& update_check_response_hash,
                           const uint64_t update_check_response_size);
 
-  // Reads from the update manifest the expected sizes and hashes of the target
-  // kernel and rootfs partitions. These values can be used for applied update
-  // hash verification. This method must be called after the update manifest has
-  // been parsed (e.g., after closing the stream). Returns true on success, and
-  // false on failure (e.g., when the values are not present in the update
-  // manifest).
-  bool GetNewPartitionInfo(uint64_t* kernel_size,
-                           chromeos::Blob* kernel_hash,
-                           uint64_t* rootfs_size,
-                           chromeos::Blob* rootfs_hash);
-
   // Converts an ordered collection of Extent objects which contain data of
   // length full_length to a comma-separated string. For each Extent, the
   // string will have the start offset and then the length in bytes.
@@ -226,6 +187,11 @@
   friend class DeltaPerformerIntegrationTest;
   FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
 
+  // Parse and move the update instructions of all partitions into our local
+  // |partitions_| variable based on the version of the payload. Requires the
+  // manifest to be parsed and valid.
+  bool ParseManifestPartitions(ErrorCode* error);
+
   // Appends up to |*count_p| bytes from |*bytes_p| to |buffer_|, but only to
   // the extent that the size of |buffer_| does not exceed |max|. Advances
   // |*cbytes_p| and decreases |*count_p| by the actual number of bytes copied,
@@ -277,18 +243,12 @@
   bool PerformInstallOperation(const InstallOperation& operation);
 
   // These perform a specific type of operation and return true on success.
-  bool PerformReplaceOperation(const InstallOperation& operation,
-                               bool is_kernel_partition);
-  bool PerformZeroOrDiscardOperation(const InstallOperation& operation,
-                                     bool is_kernel_partition);
-  bool PerformMoveOperation(const InstallOperation& operation,
-                            bool is_kernel_partition);
-  bool PerformBsdiffOperation(const InstallOperation& operation,
-                              bool is_kernel_partition);
-  bool PerformSourceCopyOperation(const InstallOperation& operation,
-                                  bool is_kernel_partition);
-  bool PerformSourceBsdiffOperation(const InstallOperation& operation,
-                                    bool is_kernel_partition);
+  bool PerformReplaceOperation(const InstallOperation& operation);
+  bool PerformZeroOrDiscardOperation(const InstallOperation& operation);
+  bool PerformMoveOperation(const InstallOperation& operation);
+  bool PerformBsdiffOperation(const InstallOperation& operation);
+  bool PerformSourceCopyOperation(const InstallOperation& operation);
+  bool PerformSourceBsdiffOperation(const InstallOperation& operation);
 
   // Returns true if the payload signature message has been extracted from
   // |operation|, false otherwise.
@@ -325,43 +285,61 @@
   // Install Plan based on Omaha Response.
   InstallPlan* install_plan_;
 
-  // File descriptor of open device.
-  FileDescriptorPtr fd_;
+  // File descriptor of the source partition. Only set while updating a
+  // partition when using a delta payload.
+  FileDescriptorPtr source_fd_{nullptr};
 
-  // File descriptor of the kernel device.
-  FileDescriptorPtr kernel_fd_;
+  // File descriptor of the target partition. Only set while performing the
+  // operations of a given partition.
+  FileDescriptorPtr target_fd_{nullptr};
 
-  // File descriptor of the source device.
-  FileDescriptorPtr source_fd_;
+  // Paths the |source_fd_| and |target_fd_| refer to.
+  std::string source_path_;
+  std::string target_path_;
 
-  // File descriptor of the source kernel device.
-  FileDescriptorPtr source_kernel_fd_;
-
-  std::string path_;  // Path that fd_ refers to.
-  std::string kernel_path_;  // Path that kernel_fd_ refers to.
-
+  // Parsed manifest. Set after enough bytes to parse the manifest were
+  // downloaded.
   DeltaArchiveManifest manifest_;
-  bool manifest_parsed_;
-  bool manifest_valid_;
-  uint64_t metadata_size_;
-  uint64_t manifest_size_;
-  uint64_t major_payload_version_;
+  bool manifest_parsed_{false};
+  bool manifest_valid_{false};
+  uint64_t metadata_size_{0};
+  uint64_t manifest_size_{0};
+  uint64_t major_payload_version_{0};
 
-  // Index of the next operation to perform in the manifest.
-  size_t next_operation_num_;
+  // Accumulated number of operations per partition. The i-th element is the
+  // sum of the number of operations for all the partitions from 0 to i
+  // inclusive. Valid when |manifest_valid_| is true.
+  std::vector<size_t> acc_num_operations_;
+
+  // The total operations in a payload. Valid when |manifest_valid_| is true,
+  // otherwise 0.
+  size_t num_total_operations_{0};
+
+  // The list of partitions to update as found in the manifest major version 2.
+  // When parsing an older manifest format, the information is converted over to
+  // this format instead.
+  std::vector<PartitionUpdate> partitions_;
+
+  // Index in the list of partitions (|partitions_| member) of the current
+  // partition being processed.
+  size_t current_partition_{0};
+
+  // Index of the next operation to perform in the manifest. The index is linear
+  // on the total number of operation on the manifest.
+  size_t next_operation_num_{0};
 
   // A buffer used for accumulating downloaded data. Initially, it stores the
   // payload metadata; once that's downloaded and parsed, it stores data for the
   // next update operation.
   chromeos::Blob buffer_;
   // Offset of buffer_ in the binary blobs section of the update.
-  uint64_t buffer_offset_;
+  uint64_t buffer_offset_{0};
 
   // Last |buffer_offset_| value updated as part of the progress update.
-  uint64_t last_updated_buffer_offset_;
+  uint64_t last_updated_buffer_offset_{kuint64max};
 
   // The block size (parsed from the manifest).
-  uint32_t block_size_;
+  uint32_t block_size_{0};
 
   // Calculates the payload hash.
   OmahaHashCalculator hash_calculator_;
@@ -374,32 +352,29 @@
 
   // The public key to be used. Provided as a member so that tests can
   // override with test keys.
-  std::string public_key_path_;
+  std::string public_key_path_{constants::kUpdatePayloadPublicKeyPath};
 
   // The number of bytes received so far, used for progress tracking.
-  size_t total_bytes_received_;
-
-  // The number rootfs and total operations in a payload, once we know them.
-  size_t num_rootfs_operations_;
-  size_t num_total_operations_;
+  size_t total_bytes_received_{0};
 
   // An overall progress counter, which should reflect both download progress
   // and the ratio of applied operations. Range is 0-100.
-  unsigned overall_progress_;
+  unsigned overall_progress_{0};
 
   // The last progress chunk recorded.
-  unsigned last_progress_chunk_;
+  unsigned last_progress_chunk_{0};
 
   // The timeout after which we should force emitting a progress log (constant),
   // and the actual point in time for the next forced log to be emitted.
-  const base::TimeDelta forced_progress_log_wait_;
+  const base::TimeDelta forced_progress_log_wait_{
+      base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)};
   base::Time forced_progress_log_time_;
 
   // The payload major payload version supported by DeltaPerformer.
-  uint64_t supported_major_version_;
+  uint64_t supported_major_version_{kSupportedMajorPayloadVersion};
 
   // The delta minor payload version supported by DeltaPerformer.
-  uint32_t supported_minor_version_;
+  uint32_t supported_minor_version_{kSupportedMinorPayloadVersion};
 
   DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
 };
diff --git a/delta_performer_integration_test.cc b/delta_performer_integration_test.cc
index cdba884..036b7c1 100644
--- a/delta_performer_integration_test.cc
+++ b/delta_performer_integration_test.cc
@@ -25,8 +25,8 @@
 
 #include <base/files/file_path.h>
 #include <base/files/file_util.h>
-#include <base/strings/stringprintf.h>
 #include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
 #include <google/protobuf/repeated_field.h>
 #include <gtest/gtest.h>
 
@@ -46,11 +46,11 @@
 
 using std::string;
 using std::vector;
-using testing::Return;
-using testing::_;
-using test_utils::kRandomString;
 using test_utils::ScopedLoopMounter;
 using test_utils::System;
+using test_utils::kRandomString;
+using testing::Return;
+using testing::_;
 
 extern const char* kUnittestPrivateKeyPath;
 extern const char* kUnittestPublicKeyPath;
@@ -81,6 +81,10 @@
   chromeos::Blob result_kernel_data;
   size_t kernel_size;
 
+  // The InstallPlan referenced by the DeltaPerformer. This needs to outlive
+  // the DeltaPerformer.
+  InstallPlan install_plan;
+
   // The in-memory copy of delta file.
   chromeos::Blob delta;
 
@@ -699,12 +703,18 @@
   }
 
   // Update the A image in place.
-  InstallPlan install_plan;
-  install_plan.hash_checks_mandatory = hash_checks_mandatory;
-  install_plan.metadata_size = state->metadata_size;
-  install_plan.is_full_update = full_kernel && full_rootfs;
-  install_plan.source_path = state->a_img.c_str();
-  install_plan.kernel_source_path = state->old_kernel.c_str();
+  InstallPlan* install_plan = &state->install_plan;
+  install_plan->hash_checks_mandatory = hash_checks_mandatory;
+  install_plan->metadata_size = state->metadata_size;
+  install_plan->is_full_update = full_kernel && full_rootfs;
+  install_plan->source_slot = 0;
+  install_plan->target_slot = 1;
+
+  InstallPlan::Partition root_part;
+  root_part.name = kLegacyPartitionNameRoot;
+
+  InstallPlan::Partition kernel_part;
+  kernel_part.name = kLegacyPartitionNameKernel;
 
   LOG(INFO) << "Setting payload metadata size in Omaha  = "
             << state->metadata_size;
@@ -712,12 +722,12 @@
       state->delta.data(),
       state->metadata_size,
       kUnittestPrivateKeyPath,
-      &install_plan.metadata_signature));
-  EXPECT_FALSE(install_plan.metadata_signature.empty());
+      &install_plan->metadata_signature));
+  EXPECT_FALSE(install_plan->metadata_signature.empty());
 
   *performer = new DeltaPerformer(&prefs,
                                   &state->fake_system_state,
-                                  &install_plan);
+                                  install_plan);
   EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
   (*performer)->set_public_key_path(kUnittestPublicKeyPath);
   DeltaPerformerIntegrationTest::SetSupportedVersion(*performer, minor_version);
@@ -726,21 +736,34 @@
             OmahaHashCalculator::RawHashOfFile(
                 state->a_img,
                 state->image_size,
-                &install_plan.source_rootfs_hash));
+                &root_part.source_hash));
   EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(
                   state->old_kernel_data,
-                  &install_plan.source_kernel_hash));
+                  &kernel_part.source_hash));
+
+  // This partitions are normally filed by the FilesystemVerifierAction with
+  // the source hashes used for deltas.
+  install_plan->partitions = {root_part, kernel_part};
 
   // With minor version 2, we want the target to be the new image, result_img,
   // but with version 1, we want to update A in place.
+  string target_root, target_kernel;
   if (minor_version == kSourceMinorPayloadVersion) {
-    EXPECT_EQ(0, (*performer)->Open(state->result_img.c_str(), 0, 0));
-    EXPECT_TRUE((*performer)->OpenKernel(state->result_kernel.c_str()));
+    target_root = state->result_img;
+    target_kernel = state->result_kernel;
   } else {
-    EXPECT_EQ(0, (*performer)->Open(state->a_img.c_str(), 0, 0));
-    EXPECT_TRUE((*performer)->OpenKernel(state->old_kernel.c_str()));
+    target_root = state->a_img;
+    target_kernel = state->old_kernel;
   }
 
+  state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+      kLegacyPartitionNameRoot, install_plan->source_slot, state->a_img);
+  state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+      kLegacyPartitionNameKernel, install_plan->source_slot, state->old_kernel);
+  state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+      kLegacyPartitionNameRoot, install_plan->target_slot, target_root);
+  state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+      kLegacyPartitionNameKernel, install_plan->target_slot, target_kernel);
 
   ErrorCode expected_error, actual_error;
   bool continue_writing;
@@ -844,26 +867,24 @@
   EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData),
                          updated_kernel_partition.begin()));
 
-  uint64_t new_kernel_size;
-  chromeos::Blob new_kernel_hash;
-  uint64_t new_rootfs_size;
-  chromeos::Blob new_rootfs_hash;
-  EXPECT_TRUE(performer->GetNewPartitionInfo(&new_kernel_size,
-                                             &new_kernel_hash,
-                                             &new_rootfs_size,
-                                             &new_rootfs_hash));
-  EXPECT_EQ(kDefaultKernelSize, new_kernel_size);
+  const auto& partitions = state->install_plan.partitions;
+  EXPECT_EQ(2, partitions.size());
+  EXPECT_EQ(kLegacyPartitionNameRoot, partitions[0].name);
+  EXPECT_EQ(kLegacyPartitionNameKernel, partitions[1].name);
+
+  EXPECT_EQ(kDefaultKernelSize, partitions[1].target_size);
   chromeos::Blob expected_new_kernel_hash;
   EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(state->new_kernel_data,
                                                  &expected_new_kernel_hash));
-  EXPECT_TRUE(expected_new_kernel_hash == new_kernel_hash);
-  EXPECT_EQ(state->image_size, new_rootfs_size);
+  EXPECT_EQ(expected_new_kernel_hash, partitions[1].target_hash);
+
+  EXPECT_EQ(state->image_size, partitions[0].target_size);
   chromeos::Blob expected_new_rootfs_hash;
   EXPECT_EQ(state->image_size,
             OmahaHashCalculator::RawHashOfFile(state->b_img,
                                                state->image_size,
                                                &expected_new_rootfs_hash));
-  EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
+  EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
 }
 
 void VerifyPayload(DeltaPerformer* performer,
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index 12dd1d3..fa0692f 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -84,6 +84,10 @@
 
 class DeltaPerformerTest : public ::testing::Test {
  protected:
+  void SetUp() override {
+    install_plan_.source_slot = 0;
+    install_plan_.target_slot = 1;
+  }
 
   // Test helper placed where it can easily be friended from DeltaPerformer.
   void RunManifestValidation(const DeltaArchiveManifest& manifest,
@@ -118,11 +122,17 @@
 
     PartitionConfig old_part(kLegacyPartitionNameRoot);
     PartitionConfig new_part(kLegacyPartitionNameRoot);
-    new_part.path = blob_path;
-    new_part.size = blob_data.size();
+    new_part.path = "/dev/zero";
+    new_part.size = 1234;
 
     payload.AddPartition(old_part, new_part, aops);
 
+    // We include a kernel partition without operations.
+    old_part.name = kLegacyPartitionNameKernel;
+    new_part.name = kLegacyPartitionNameKernel;
+    new_part.size = 0;
+    payload.AddPartition(old_part, new_part, {});
+
     string payload_path;
     EXPECT_TRUE(utils::MakeTempFile("Payload-XXXXXX", &payload_path, nullptr));
     ScopedPathUnlinker payload_unlinker(payload_path);
@@ -154,13 +164,17 @@
     EXPECT_TRUE(utils::WriteFile(new_part.c_str(), target_data.data(),
                                  target_data.size()));
 
-    install_plan_.source_path = source_path;
-    install_plan_.kernel_source_path = "/dev/null";
-    install_plan_.install_path = new_part;
-    install_plan_.kernel_install_path = "/dev/null";
+    // We installed the operations only in the rootfs partition, but the
+    // delta performer needs to access all the partitions.
+    fake_system_state_.fake_boot_control()->SetPartitionDevice(
+        kLegacyPartitionNameRoot, install_plan_.target_slot, new_part);
+    fake_system_state_.fake_boot_control()->SetPartitionDevice(
+        kLegacyPartitionNameRoot, install_plan_.source_slot, source_path);
+    fake_system_state_.fake_boot_control()->SetPartitionDevice(
+        kLegacyPartitionNameKernel, install_plan_.target_slot, "/dev/null");
+    fake_system_state_.fake_boot_control()->SetPartitionDevice(
+        kLegacyPartitionNameKernel, install_plan_.source_slot, "/dev/null");
 
-    EXPECT_EQ(0, performer_.Open(new_part.c_str(), 0, 0));
-    EXPECT_TRUE(performer_.OpenSourceRootfs(source_path.c_str()));
     EXPECT_TRUE(performer_.Write(payload_data.data(), payload_data.size()));
     EXPECT_EQ(0, performer_.Close());
 
@@ -178,8 +192,6 @@
                           uint64_t actual_metadata_size,
                           bool hash_checks_mandatory) {
     install_plan_.hash_checks_mandatory = hash_checks_mandatory;
-    EXPECT_EQ(0, performer_.Open("/dev/null", 0, 0));
-    EXPECT_TRUE(performer_.OpenKernel("/dev/null"));
 
     // Set a valid magic string and version number 1.
     EXPECT_TRUE(performer_.Write("CrAU", 4));
@@ -503,8 +515,6 @@
 
 TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
   SetSupportedMajorVersion(kBrilloMajorPayloadVersion);
-  EXPECT_EQ(0, performer_.Open("/dev/null", 0, 0));
-  EXPECT_TRUE(performer_.OpenKernel("/dev/null"));
   EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
 
   uint64_t major_version = htobe64(kBrilloMajorPayloadVersion);
@@ -530,17 +540,12 @@
 }
 
 TEST_F(DeltaPerformerTest, BadDeltaMagicTest) {
-  EXPECT_EQ(0, performer_.Open("/dev/null", 0, 0));
-  EXPECT_TRUE(performer_.OpenKernel("/dev/null"));
   EXPECT_TRUE(performer_.Write("junk", 4));
   EXPECT_FALSE(performer_.Write("morejunk", 8));
   EXPECT_LT(performer_.Close(), 0);
 }
 
 TEST_F(DeltaPerformerTest, WriteUpdatesPayloadState) {
-  EXPECT_EQ(0, performer_.Open("/dev/null", 0, 0));
-  EXPECT_TRUE(performer_.OpenKernel("/dev/null"));
-
   EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
               DownloadProgress(4)).Times(1);
   EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
diff --git a/download_action.cc b/download_action.cc
index a31c8a3..97bdd12 100644
--- a/download_action.cc
+++ b/download_action.cc
@@ -17,6 +17,7 @@
 #include "update_engine/download_action.h"
 
 #include <errno.h>
+
 #include <algorithm>
 #include <string>
 #include <vector>
@@ -185,24 +186,6 @@
                                               &install_plan_));
     writer_ = delta_performer_.get();
   }
-  int rc = writer_->Open(install_plan_.install_path.c_str(),
-                         O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE,
-                         0644);
-  if (rc < 0) {
-    LOG(ERROR) << "Unable to open output file " << install_plan_.install_path;
-    // report error to processor
-    processor_->ActionComplete(this, ErrorCode::kInstallDeviceOpenError);
-    return;
-  }
-  if (delta_performer_.get() &&
-      !delta_performer_->OpenKernel(
-          install_plan_.kernel_install_path.c_str())) {
-    LOG(ERROR) << "Unable to open kernel file "
-               << install_plan_.kernel_install_path.c_str();
-    writer_->Close();
-    processor_->ActionComplete(this, ErrorCode::kKernelDeviceOpenError);
-    return;
-  }
   if (delegate_) {
     delegate_->SetDownloadStatus(true);  // Set to active.
   }
@@ -319,13 +302,6 @@
       // Delete p2p file, if applicable.
       if (!p2p_file_id_.empty())
         CloseP2PSharingFd(true);
-    } else if (!delta_performer_->GetNewPartitionInfo(
-        &install_plan_.kernel_size,
-        &install_plan_.kernel_hash,
-        &install_plan_.rootfs_size,
-        &install_plan_.rootfs_hash)) {
-      LOG(ERROR) << "Unable to get new partition hash info.";
-      code = ErrorCode::kDownloadNewPartitionInfoError;
     }
   }
 
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
index d1f4bc9..e5f78d6 100644
--- a/download_action_unittest.cc
+++ b/download_action_unittest.cc
@@ -51,11 +51,11 @@
 using std::string;
 using std::unique_ptr;
 using std::vector;
+using test_utils::ScopedTempFile;
 using testing::AtLeast;
 using testing::InSequence;
 using testing::Return;
 using testing::_;
-using test_utils::ScopedTempFile;
 
 class DownloadActionTest : public ::testing::Test { };
 
@@ -139,10 +139,12 @@
   // TODO(adlr): see if we need a different file for build bots
   ScopedTempFile output_temp_file;
   TestDirectFileWriter writer;
+  EXPECT_EQ(0, writer.Open(output_temp_file.GetPath().c_str(),
+                           O_WRONLY | O_CREAT,
+                           0));
   writer.set_fail_write(fail_write);
 
   // We pull off the first byte from data and seek past it.
-
   string hash =
       OmahaHashCalculator::OmahaHashOfBytes(&data[1], data.size() - 1);
   uint64_t size = data.size();
@@ -153,10 +155,6 @@
                            hash,
                            0,
                            "",
-                           output_temp_file.GetPath(),
-                           "",
-                           "",
-                           "",
                            "");
   install_plan.source_slot = 0;
   install_plan.target_slot = 1;
@@ -277,11 +275,13 @@
   ScopedTempFile temp_file;
   {
     DirectFileWriter writer;
+    EXPECT_EQ(0, writer.Open(temp_file.GetPath().c_str(),
+                             O_WRONLY | O_CREAT,
+                             0));
 
     // takes ownership of passed in HttpFetcher
     ObjectFeederAction<InstallPlan> feeder_action;
-    InstallPlan install_plan(false, false, "", 0, "", 0, "",
-                             temp_file.GetPath(), "", "", "", "");
+    InstallPlan install_plan(false, false, "", 0, "", 0, "", "");
     feeder_action.set_obj(install_plan);
     FakeSystemState fake_system_state_;
     MockPrefs prefs;
@@ -375,6 +375,7 @@
   loop.SetAsCurrent();
 
   DirectFileWriter writer;
+  EXPECT_EQ(0, writer.Open("/dev/null", O_WRONLY | O_CREAT, 0));
 
   // takes ownership of passed in HttpFetcher
   InstallPlan install_plan(false,
@@ -384,10 +385,6 @@
                            OmahaHashCalculator::OmahaHashOfString("x"),
                            0,
                            "",
-                           "/dev/null",
-                           "/dev/null",
-                           "/dev/null",
-                           "/dev/null",
                            "");
   ObjectFeederAction<InstallPlan> feeder_action;
   feeder_action.set_obj(install_plan);
@@ -417,36 +414,6 @@
   EXPECT_EQ(true, test_action.did_run_);
 }
 
-TEST(DownloadActionTest, BadOutFileTest) {
-  chromeos::FakeMessageLoop loop(nullptr);
-  loop.SetAsCurrent();
-
-  const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
-  DirectFileWriter writer;
-
-  // takes ownership of passed in HttpFetcher
-  InstallPlan install_plan(
-      false, false, "", 0, "", 0, "", path, "", "", "", "");
-  ObjectFeederAction<InstallPlan> feeder_action;
-  feeder_action.set_obj(install_plan);
-  MockPrefs prefs;
-  FakeSystemState fake_system_state_;
-  DownloadAction download_action(&prefs, &fake_system_state_,
-                                 new MockHttpFetcher("x", 1, nullptr));
-  download_action.SetTestFileWriter(&writer);
-
-  BondActions(&feeder_action, &download_action);
-
-  ActionProcessor processor;
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&download_action);
-  processor.StartProcessing();
-  ASSERT_FALSE(processor.IsRunning());
-
-  loop.Run();
-  EXPECT_FALSE(loop.PendingTasks());
-}
-
 // Test fixture for P2P tests.
 class P2PDownloadActionTest : public testing::Test {
  protected:
@@ -493,6 +460,9 @@
 
     ScopedTempFile output_temp_file;
     TestDirectFileWriter writer;
+    EXPECT_EQ(0, writer.Open(output_temp_file.GetPath().c_str(),
+                             O_WRONLY | O_CREAT,
+                             0));
     InstallPlan install_plan(false,
                              false,
                              "",
@@ -500,10 +470,6 @@
                              "1234hash",
                              0,
                              "",
-                             output_temp_file.GetPath(),
-                             "",
-                             "",
-                             "",
                              "");
     ObjectFeederAction<InstallPlan> feeder_action;
     feeder_action.set_obj(install_plan);
diff --git a/file_writer.h b/file_writer.h
index 3652b86..fbda009 100644
--- a/file_writer.h
+++ b/file_writer.h
@@ -39,9 +39,6 @@
   FileWriter() {}
   virtual ~FileWriter() {}
 
-  // Wrapper around open. Returns 0 on success or -errno on error.
-  virtual int Open(const char* path, int flags, mode_t mode) = 0;
-
   // Wrapper around write. Returns true if all requested bytes
   // were written, or false on any error, regardless of progress.
   virtual bool Write(const void* bytes, size_t count) = 0;
@@ -69,16 +66,19 @@
 
 class DirectFileWriter : public FileWriter {
  public:
-  DirectFileWriter() : fd_(-1) {}
+  DirectFileWriter() = default;
 
-  int Open(const char* path, int flags, mode_t mode) override;
+  // FileWriter overrides.
   bool Write(const void* bytes, size_t count) override;
   int Close() override;
 
+  // Wrapper around open. Returns 0 on success or -errno on error.
+  int Open(const char* path, int flags, mode_t mode);
+
   int fd() const { return fd_; }
 
  private:
-  int fd_;
+  int fd_{-1};
 
   DISALLOW_COPY_AND_ASSIGN(DirectFileWriter);
 };
diff --git a/filesystem_verifier_action.cc b/filesystem_verifier_action.cc
index 2291bc9..5ca80b1 100644
--- a/filesystem_verifier_action.cc
+++ b/filesystem_verifier_action.cc
@@ -30,7 +30,6 @@
 
 #include "update_engine/boot_control_interface.h"
 #include "update_engine/payload_constants.h"
-#include "update_engine/system_state.h"
 #include "update_engine/utils.h"
 
 using std::string;
@@ -41,27 +40,11 @@
 const off_t kReadFileBufferSize = 128 * 1024;
 }  // namespace
 
-string PartitionTypeToString(const PartitionType partition_type) {
-  // TODO(deymo): The PartitionType class should be replaced with just the
-  // string name that comes from the payload. This function should be deleted
-  // then.
-  switch (partition_type) {
-    case PartitionType::kRootfs:
-    case PartitionType::kSourceRootfs:
-      return kLegacyPartitionNameRoot;
-    case PartitionType::kKernel:
-    case PartitionType::kSourceKernel:
-      return kLegacyPartitionNameKernel;
-  }
-  return "<unknown>";
-}
-
 FilesystemVerifierAction::FilesystemVerifierAction(
-    SystemState* system_state,
-    PartitionType partition_type)
-    : partition_type_(partition_type),
-      remaining_size_(kint64max),
-      system_state_(system_state) {}
+    const BootControlInterface* boot_control,
+    VerifierMode verifier_mode)
+    : verifier_mode_(verifier_mode),
+      boot_control_(boot_control) {}
 
 void FilesystemVerifierAction::PerformAction() {
   // Will tell the ActionProcessor we've failed if we return.
@@ -73,68 +56,48 @@
   }
   install_plan_ = GetInputObject();
 
-  if (install_plan_.is_full_update &&
-      (partition_type_ == PartitionType::kSourceRootfs ||
-       partition_type_ == PartitionType::kSourceKernel)) {
-    // No hash verification needed. Done!
-    LOG(INFO) << "filesystem verifying skipped on full update.";
+  // For delta updates (major version 1) we need to populate the source
+  // partition hash if not pre-populated.
+  if (!install_plan_.is_full_update && install_plan_.partitions.empty() &&
+      verifier_mode_ == VerifierMode::kComputeSourceHash) {
+    LOG(INFO) << "Using legacy partition names.";
+    InstallPlan::Partition part;
+    string part_path;
+
+    part.name = kLegacyPartitionNameRoot;
+    if (!boot_control_->GetPartitionDevice(
+        part.name, install_plan_.source_slot, &part_path))
+      return;
+    int block_count = 0, block_size = 0;
+    if (utils::GetFilesystemSize(part_path, &block_count, &block_size)) {
+      part.source_size = static_cast<int64_t>(block_count) * block_size;
+      LOG(INFO) << "Partition " << part.name << " size: " << part.source_size
+                << " bytes (" << block_count << "x" << block_size << ").";
+    }
+    install_plan_.partitions.push_back(part);
+
+    part.name = kLegacyPartitionNameKernel;
+    if (!boot_control_->GetPartitionDevice(
+        part.name, install_plan_.source_slot, &part_path))
+      return;
+    off_t kernel_part_size = utils::FileSize(part_path);
+    if (kernel_part_size < 0)
+      return;
+    LOG(INFO) << "Partition " << part.name << " size: " << kernel_part_size
+              << " bytes.";
+    part.source_size = kernel_part_size;
+    install_plan_.partitions.push_back(part);
+  }
+
+  if (install_plan_.partitions.empty()) {
+    LOG(INFO) << "No partitions to verify.";
     if (HasOutputPipe())
       SetOutputObject(install_plan_);
     abort_action_completer.set_code(ErrorCode::kSuccess);
     return;
   }
 
-  string target_path;
-  string partition_name = PartitionTypeToString(partition_type_);
-  switch (partition_type_) {
-    case PartitionType::kRootfs:
-      target_path = install_plan_.install_path;
-      if (target_path.empty()) {
-        system_state_->boot_control()->GetPartitionDevice(
-            partition_name, install_plan_.target_slot, &target_path);
-      }
-      break;
-    case PartitionType::kKernel:
-      target_path = install_plan_.kernel_install_path;
-      if (target_path.empty()) {
-        system_state_->boot_control()->GetPartitionDevice(
-            partition_name, install_plan_.target_slot, &target_path);
-      }
-      break;
-    case PartitionType::kSourceRootfs:
-      target_path = install_plan_.source_path;
-      if (target_path.empty()) {
-        system_state_->boot_control()->GetPartitionDevice(
-            partition_name, install_plan_.source_slot, &target_path);
-      }
-      break;
-    case PartitionType::kSourceKernel:
-      target_path = install_plan_.kernel_source_path;
-      if (target_path.empty()) {
-        system_state_->boot_control()->GetPartitionDevice(
-            partition_name, install_plan_.source_slot, &target_path);
-      }
-      break;
-  }
-
-  chromeos::ErrorPtr error;
-  src_stream_ = chromeos::FileStream::Open(
-      base::FilePath(target_path),
-      chromeos::Stream::AccessMode::READ,
-      chromeos::FileStream::Disposition::OPEN_EXISTING,
-      &error);
-
-  if (!src_stream_) {
-    LOG(ERROR) << "Unable to open " << target_path << " for reading";
-    return;
-  }
-
-  DetermineFilesystemSize(target_path);
-  buffer_.resize(kReadFileBufferSize);
-
-  // Start the first read.
-  ScheduleRead();
-
+  StartPartitionHashing();
   abort_action_completer.set_should_complete(false);
 }
 
@@ -159,6 +122,52 @@
   processor_->ActionComplete(this, code);
 }
 
+void FilesystemVerifierAction::StartPartitionHashing() {
+  if (partition_index_ == install_plan_.partitions.size()) {
+    Cleanup(ErrorCode::kSuccess);
+    return;
+  }
+  InstallPlan::Partition& partition =
+      install_plan_.partitions[partition_index_];
+
+  string part_path;
+  switch (verifier_mode_) {
+    case VerifierMode::kComputeSourceHash:
+      boot_control_->GetPartitionDevice(
+          partition.name, install_plan_.source_slot, &part_path);
+      remaining_size_ = partition.source_size;
+      break;
+    case VerifierMode::kVerifyTargetHash:
+      boot_control_->GetPartitionDevice(
+          partition.name, install_plan_.target_slot, &part_path);
+      remaining_size_ = partition.target_size;
+      break;
+  }
+  LOG(INFO) << "Hashing partition " << partition_index_ << " ("
+            << partition.name << ") on device " << part_path;
+  if (part_path.empty())
+    return Cleanup(ErrorCode::kFilesystemVerifierError);
+
+  chromeos::ErrorPtr error;
+  src_stream_ = chromeos::FileStream::Open(
+      base::FilePath(part_path),
+      chromeos::Stream::AccessMode::READ,
+      chromeos::FileStream::Disposition::OPEN_EXISTING,
+      &error);
+
+  if (!src_stream_) {
+    LOG(ERROR) << "Unable to open " << part_path << " for reading";
+    return Cleanup(ErrorCode::kFilesystemVerifierError);
+  }
+
+  buffer_.resize(kReadFileBufferSize);
+  read_done_ = false;
+  hasher_.reset(new OmahaHashCalculator());
+
+  // Start the first read.
+  ScheduleRead();
+}
+
 void FilesystemVerifierAction::ScheduleRead() {
   size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
                                   remaining_size_);
@@ -188,16 +197,27 @@
   } else {
     remaining_size_ -= bytes_read;
     CHECK(!read_done_);
-    if (!hasher_.Update(buffer_.data(), bytes_read)) {
+    if (!hasher_->Update(buffer_.data(), bytes_read)) {
       LOG(ERROR) << "Unable to update the hash.";
       Cleanup(ErrorCode::kError);
       return;
     }
   }
 
-  // We either terminate the action or have more data to read.
-  if (!CheckTerminationConditions())
-    ScheduleRead();
+  // We either terminate the current partition or have more data to read.
+  if (cancelled_)
+    return Cleanup(ErrorCode::kError);
+
+  if (read_done_ || remaining_size_ == 0) {
+    if (remaining_size_ != 0) {
+      LOG(ERROR) << "Failed to read the remaining " << remaining_size_
+                 << " bytes from partition "
+                 << install_plan_.partitions[partition_index_].name;
+      return Cleanup(ErrorCode::kFilesystemVerifierError);
+    }
+    return FinishPartitionHashing();
+  }
+  ScheduleRead();
 }
 
 void FilesystemVerifierAction::OnReadErrorCallback(
@@ -207,70 +227,33 @@
   Cleanup(ErrorCode::kError);
 }
 
-bool FilesystemVerifierAction::CheckTerminationConditions() {
-  if (cancelled_) {
-    Cleanup(ErrorCode::kError);
-    return true;
-  }
-
-  if (!read_done_)
-    return false;
-
-  // We're done!
-  ErrorCode code = ErrorCode::kSuccess;
-  if (hasher_.Finalize()) {
-    LOG(INFO) << "Hash: " << hasher_.hash();
-    switch (partition_type_) {
-      case PartitionType::kRootfs:
-        if (install_plan_.rootfs_hash != hasher_.raw_hash()) {
-          code = ErrorCode::kNewRootfsVerificationError;
-          LOG(ERROR) << "New rootfs verification failed.";
-        }
-        break;
-      case PartitionType::kKernel:
-        if (install_plan_.kernel_hash != hasher_.raw_hash()) {
-          code = ErrorCode::kNewKernelVerificationError;
-          LOG(ERROR) << "New kernel verification failed.";
-        }
-        break;
-      case PartitionType::kSourceRootfs:
-        install_plan_.source_rootfs_hash = hasher_.raw_hash();
-        break;
-      case PartitionType::kSourceKernel:
-        install_plan_.source_kernel_hash = hasher_.raw_hash();
-        break;
-    }
-  } else {
+void FilesystemVerifierAction::FinishPartitionHashing() {
+  if (!hasher_->Finalize()) {
     LOG(ERROR) << "Unable to finalize the hash.";
-    code = ErrorCode::kError;
+    return Cleanup(ErrorCode::kError);
   }
-  Cleanup(code);
-  return true;
-}
+  InstallPlan::Partition& partition =
+      install_plan_.partitions[partition_index_];
+  LOG(INFO) << "Hash of " << partition.name << ": " << hasher_->hash();
 
-void FilesystemVerifierAction::DetermineFilesystemSize(const string& path) {
-  switch (partition_type_) {
-    case PartitionType::kRootfs:
-      remaining_size_ = install_plan_.rootfs_size;
-      LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+  switch (verifier_mode_) {
+    case VerifierMode::kComputeSourceHash:
+      partition.source_hash = hasher_->raw_hash();
       break;
-    case PartitionType::kKernel:
-      remaining_size_ = install_plan_.kernel_size;
-      LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
-      break;
-    case PartitionType::kSourceRootfs:
-      {
-        int block_count = 0, block_size = 0;
-        if (utils::GetFilesystemSize(path, &block_count, &block_size)) {
-          remaining_size_ = static_cast<int64_t>(block_count) * block_size;
-          LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes ("
-                    << block_count << "x" << block_size << ").";
-        }
+    case VerifierMode::kVerifyTargetHash:
+      if (partition.target_hash != hasher_->raw_hash()) {
+        LOG(ERROR) << "New '" << partition.name
+                   << "' partition verification failed.";
+        return Cleanup(ErrorCode::kNewRootfsVerificationError);
       }
       break;
-    default:
-      break;
   }
+  // Start hashing the next partition, if any.
+  partition_index_++;
+  hasher_.reset();
+  buffer_.clear();
+  src_stream_->CloseBlocking(nullptr);
+  StartPartitionHashing();
 }
 
 }  // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.h b/filesystem_verifier_action.h
index 7a6930e..bfcac43 100644
--- a/filesystem_verifier_action.h
+++ b/filesystem_verifier_action.h
@@ -30,28 +30,27 @@
 #include "update_engine/install_plan.h"
 #include "update_engine/omaha_hash_calculator.h"
 
-// This action will only do real work if it's a delta update. It will
-// copy the root partition to install partition, and then terminate.
+// This action will hash all the partitions of a single slot involved in the
+// update (either source or target slot). The hashes are then either stored in
+// the InstallPlan (for source partitions) or verified against it (for target
+// partitions).
 
 namespace chromeos_update_engine {
 
-class SystemState;
-
-// The type of filesystem that we are verifying.
-enum class PartitionType {
-  kSourceRootfs,
-  kSourceKernel,
-  kRootfs,
-  kKernel,
+// The mode we are running the FilesystemVerifier on. On kComputeSourceHash mode
+// it computes the source_hash of all the partitions in the InstallPlan, based
+// on the already populated source_size values. On kVerifyTargetHash it computes
+// the hash on the target partitions based on the already populated size and
+// verifies it matches the one in the target_hash in the InstallPlan.
+enum class VerifierMode {
+  kComputeSourceHash,
+  kVerifyTargetHash,
 };
 
-// Return the partition name string for the passed partition type.
-std::string PartitionTypeToString(const PartitionType partition_type);
-
 class FilesystemVerifierAction : public InstallPlanAction {
  public:
-  FilesystemVerifierAction(SystemState* system_state,
-                           PartitionType partition_type);
+  FilesystemVerifierAction(const BootControlInterface* boot_control,
+                           VerifierMode verifier_mode);
 
   void PerformAction() override;
   void TerminateProcessing() override;
@@ -71,6 +70,10 @@
   FRIEND_TEST(FilesystemVerifierActionTest,
               RunAsRootDetermineFilesystemSizeTest);
 
+  // Starts the hashing of the current partition. If there aren't any partitions
+  // remaining to be hashed, if finishes the action.
+  void StartPartitionHashing();
+
   // Schedules the asynchronous read of the filesystem.
   void ScheduleRead();
 
@@ -79,22 +82,24 @@
   void OnReadDoneCallback(size_t bytes_read);
   void OnReadErrorCallback(const chromeos::Error* error);
 
-  // Based on the state of the read buffer, terminates read process and the
-  // action. Return whether the action was terminated.
-  bool CheckTerminationConditions();
+  // When the read is done, finalize the hash checking of the current partition
+  // and continue checking the next one.
+  void FinishPartitionHashing();
 
   // Cleans up all the variables we use for async operations and tells the
   // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
   // true if TerminateProcessing() was called.
   void Cleanup(ErrorCode code);
 
-  // Determine, if possible, the source file system size to avoid copying the
-  // whole partition. Currently this supports only the root file system assuming
-  // it's ext3-compatible.
-  void DetermineFilesystemSize(const std::string& path);
-
   // The type of the partition that we are verifying.
-  PartitionType partition_type_;
+  const VerifierMode verifier_mode_;
+
+  // The BootControlInterface used to get the partitions based on the slots.
+  const BootControlInterface* const boot_control_;
+
+  // The index in the install_plan_.partitions vector of the partition currently
+  // being hashed.
+  size_t partition_index_{0};
 
   // If not null, the FileStream used to read from the device.
   chromeos::StreamPtr src_stream_;
@@ -109,15 +114,12 @@
   InstallPlan install_plan_;
 
   // Calculates the hash of the data.
-  OmahaHashCalculator hasher_;
+  std::unique_ptr<OmahaHashCalculator> hasher_;
 
   // Reads and hashes this many bytes from the head of the input stream. This
-  // field is initialized when the action is started and decremented as more
-  // bytes get read.
-  int64_t remaining_size_;
-
-  // The global context for update_engine.
-  SystemState* system_state_;
+  // field is initialized from the corresponding InstallPlan::Partition size,
+  // when the partition starts to be hashed.
+  int64_t remaining_size_{0};
 
   DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
 };
diff --git a/filesystem_verifier_action_unittest.cc b/filesystem_verifier_action_unittest.cc
index 8280328..96bcb72 100644
--- a/filesystem_verifier_action_unittest.cc
+++ b/filesystem_verifier_action_unittest.cc
@@ -26,14 +26,15 @@
 #include <base/posix/eintr_wrapper.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
 #include <chromeos/message_loops/fake_message_loop.h>
 #include <chromeos/message_loops/message_loop_utils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "update_engine/fake_system_state.h"
-#include "update_engine/mock_hardware.h"
+#include "update_engine/fake_boot_control.h"
 #include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_constants.h"
 #include "update_engine/test_utils.h"
 #include "update_engine/utils.h"
 
@@ -57,10 +58,10 @@
   // Returns true iff test has completed successfully.
   bool DoTest(bool terminate_early,
               bool hash_fail,
-              PartitionType partition_type);
+              VerifierMode verifier_mode);
 
   chromeos::FakeMessageLoop loop_{nullptr};
-  FakeSystemState fake_system_state_;
+  FakeBootControl fake_boot_control_;
 };
 
 class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
@@ -120,17 +121,17 @@
 // issue with the chroot environment, library versions we use, etc.
 TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
   ASSERT_EQ(0, getuid());
-  bool test = DoTest(false, false, PartitionType::kKernel);
+  bool test = DoTest(false, false, VerifierMode::kComputeSourceHash);
   EXPECT_TRUE(test);
   if (!test)
     return;
-  test = DoTest(false, false, PartitionType::kRootfs);
+  test = DoTest(false, false, VerifierMode::kVerifyTargetHash);
   EXPECT_TRUE(test);
 }
 
 bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
                                           bool hash_fail,
-                                          PartitionType partition_type) {
+                                          VerifierMode verifier_mode) {
   string a_loop_file;
 
   if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
@@ -144,7 +145,6 @@
   chromeos::Blob a_loop_data(kLoopFileSize);
   test_utils::FillWithData(&a_loop_data);
 
-
   // Write data to disk
   if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
     ADD_FAILURE();
@@ -167,47 +167,36 @@
   InstallPlan install_plan;
   install_plan.source_slot = 0;
   install_plan.target_slot = 1;
-  switch (partition_type) {
-    case PartitionType::kRootfs:
-      install_plan.rootfs_size = kLoopFileSize - (hash_fail ? 1 : 0);
-      install_plan.install_path = a_dev;
-      if (!OmahaHashCalculator::RawHashOfData(
-          a_loop_data, &install_plan.rootfs_hash)) {
+  InstallPlan::Partition part;
+  part.name = "part";
+  switch (verifier_mode) {
+    case VerifierMode::kVerifyTargetHash:
+      part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
+      part.target_path = a_dev;
+      fake_boot_control_.SetPartitionDevice(
+          part.name, install_plan.target_slot, a_dev);
+      if (!OmahaHashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) {
         ADD_FAILURE();
         success = false;
       }
       break;
-    case PartitionType::kKernel:
-      install_plan.kernel_size = kLoopFileSize - (hash_fail ? 1 : 0);
-      install_plan.kernel_install_path = a_dev;
-      if (!OmahaHashCalculator::RawHashOfData(
-          a_loop_data, &install_plan.kernel_hash)) {
-        ADD_FAILURE();
-        success = false;
-      }
-      break;
-    case PartitionType::kSourceRootfs:
-      install_plan.source_path = a_dev;
-      if (!OmahaHashCalculator::RawHashOfData(
-          a_loop_data, &install_plan.source_rootfs_hash)) {
-        ADD_FAILURE();
-        success = false;
-      }
-      break;
-    case PartitionType::kSourceKernel:
-      install_plan.kernel_source_path = a_dev;
-      if (!OmahaHashCalculator::RawHashOfData(
-          a_loop_data, &install_plan.source_kernel_hash)) {
+    case VerifierMode::kComputeSourceHash:
+      part.source_size = kLoopFileSize;
+      part.source_path = a_dev;
+      fake_boot_control_.SetPartitionDevice(
+          part.name, install_plan.source_slot, a_dev);
+      if (!OmahaHashCalculator::RawHashOfData(a_loop_data, &part.source_hash)) {
         ADD_FAILURE();
         success = false;
       }
       break;
   }
+  install_plan.partitions = {part};
 
   ActionProcessor processor;
 
   ObjectFeederAction<InstallPlan> feeder_action;
-  FilesystemVerifierAction copier_action(&fake_system_state_, partition_type);
+  FilesystemVerifierAction copier_action(&fake_boot_control_, verifier_mode);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&feeder_action, &copier_action);
@@ -236,11 +225,7 @@
     return (ErrorCode::kError == delegate.code());
   }
   if (hash_fail) {
-    ErrorCode expected_exit_code =
-        ((partition_type == PartitionType::kKernel ||
-          partition_type == PartitionType::kSourceKernel) ?
-         ErrorCode::kNewKernelVerificationError :
-         ErrorCode::kNewRootfsVerificationError);
+    ErrorCode expected_exit_code = ErrorCode::kNewRootfsVerificationError;
     EXPECT_EQ(expected_exit_code, delegate.code());
     return (expected_exit_code == delegate.code());
   }
@@ -283,8 +268,8 @@
 
   processor.set_delegate(&delegate);
 
-  FilesystemVerifierAction copier_action(&fake_system_state_,
-                                         PartitionType::kRootfs);
+  FilesystemVerifierAction copier_action(&fake_boot_control_,
+                                         VerifierMode::kVerifyTargetHash);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&copier_action, &collector_action);
@@ -311,14 +296,16 @@
                            "",
                            0,
                            "",
-                           "/no/such/file",
-                           "/no/such/file",
-                           "/no/such/file",
-                           "/no/such/file",
                            "");
+  InstallPlan::Partition part;
+  part.name = "nope";
+  part.source_path = "/no/such/file";
+  part.target_path = "/no/such/file";
+  install_plan.partitions = {part};
+
   feeder_action.set_obj(install_plan);
-  FilesystemVerifierAction verifier_action(&fake_system_state_,
-                                           PartitionType::kRootfs);
+  FilesystemVerifierAction verifier_action(&fake_boot_control_,
+                                           VerifierMode::kVerifyTargetHash);
   ObjectCollectorAction<InstallPlan> collector_action;
 
   BondActions(&verifier_action, &collector_action);
@@ -334,26 +321,25 @@
 
 TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
   ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(false, false, PartitionType::kRootfs));
-  EXPECT_TRUE(DoTest(false, false, PartitionType::kKernel));
-  EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceRootfs));
-  EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceKernel));
+  EXPECT_TRUE(DoTest(false, false, VerifierMode::kVerifyTargetHash));
+  EXPECT_TRUE(DoTest(false, false, VerifierMode::kComputeSourceHash));
 }
 
 TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
   ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(false, true, PartitionType::kRootfs));
-  EXPECT_TRUE(DoTest(false, true, PartitionType::kKernel));
+  EXPECT_TRUE(DoTest(false, true, VerifierMode::kVerifyTargetHash));
 }
 
 TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
   ASSERT_EQ(0, getuid());
-  EXPECT_TRUE(DoTest(true, false, PartitionType::kKernel));
+  EXPECT_TRUE(DoTest(true, false, VerifierMode::kVerifyTargetHash));
   // TerminateEarlyTest may leak some null callbacks from the Stream class.
   while (loop_.RunOnce(false)) {}
 }
 
-TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineFilesystemSizeTest) {
+// Test that the rootfs and kernel size used for hashing in delta payloads for
+// major version 1 is properly read.
+TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineLegacySizeTest) {
   string img;
   EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
   ScopedPathUnlinker img_unlinker(img);
@@ -361,19 +347,41 @@
   // Extend the "partition" holding the file system from 10MiB to 20MiB.
   EXPECT_EQ(0, truncate(img.c_str(), 20 * 1024 * 1024));
 
-  {
-    FilesystemVerifierAction action(&fake_system_state_,
-                                    PartitionType::kSourceKernel);
-    EXPECT_EQ(kint64max, action.remaining_size_);
-    action.DetermineFilesystemSize(img);
-    EXPECT_EQ(kint64max, action.remaining_size_);
-  }
-  {
-    FilesystemVerifierAction action(&fake_system_state_,
-                                    PartitionType::kSourceRootfs);
-    action.DetermineFilesystemSize(img);
-    EXPECT_EQ(10 * 1024 * 1024, action.remaining_size_);
-  }
+  InstallPlan install_plan;
+  install_plan.source_slot = 1;
+
+  fake_boot_control_.SetPartitionDevice(
+      kLegacyPartitionNameRoot, install_plan.source_slot, img);
+  fake_boot_control_.SetPartitionDevice(
+      kLegacyPartitionNameKernel, install_plan.source_slot, img);
+  FilesystemVerifierAction action(&fake_boot_control_,
+                                  VerifierMode::kComputeSourceHash);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&feeder_action, &action);
+  BondActions(&action, &collector_action);
+  ActionProcessor processor;
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&action);
+  processor.EnqueueAction(&collector_action);
+
+  loop_.PostTask(FROM_HERE,
+                 base::Bind([&processor]{ processor.StartProcessing(); }));
+  loop_.Run();
+  install_plan = collector_action.object();
+
+  ASSERT_EQ(2, install_plan.partitions.size());
+  // When computing the size of the rootfs on legacy delta updates we use the
+  // size of the filesystem, but when updating the kernel we use the whole
+  // partition.
+  EXPECT_EQ(10 * 1024 * 1024, install_plan.partitions[0].source_size);
+  EXPECT_EQ(kLegacyPartitionNameRoot, install_plan.partitions[0].name);
+  EXPECT_EQ(20 * 1024 * 1024, install_plan.partitions[1].source_size);
+  EXPECT_EQ(kLegacyPartitionNameKernel, install_plan.partitions[1].name);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/install_plan.cc b/install_plan.cc
index ec2ee93..fa6dff7 100644
--- a/install_plan.cc
+++ b/install_plan.cc
@@ -16,7 +16,9 @@
 
 #include "update_engine/install_plan.h"
 
+#include <base/format_macros.h>
 #include <base/logging.h>
+#include <base/strings/stringprintf.h>
 
 #include "update_engine/payload_constants.h"
 #include "update_engine/utils.h"
@@ -32,10 +34,6 @@
                          const string& payload_hash,
                          uint64_t metadata_size,
                          const string& metadata_signature,
-                         const string& install_path,
-                         const string& kernel_install_path,
-                         const string& source_path,
-                         const string& kernel_source_path,
                          const string& public_key_rsa)
     : is_resume(is_resume),
       is_full_update(is_full_update),
@@ -44,12 +42,6 @@
       payload_hash(payload_hash),
       metadata_size(metadata_size),
       metadata_signature(metadata_signature),
-      install_path(install_path),
-      kernel_install_path(kernel_install_path),
-      source_path(source_path),
-      kernel_source_path(kernel_source_path),
-      kernel_size(0),
-      rootfs_size(0),
       hash_checks_mandatory(false),
       powerwash_required(false),
       public_key_rsa(public_key_rsa) {}
@@ -65,10 +57,7 @@
           (metadata_signature == that.metadata_signature) &&
           (source_slot == that.source_slot) &&
           (target_slot == that.target_slot) &&
-          (install_path == that.install_path) &&
-          (kernel_install_path == that.kernel_install_path) &&
-          (source_path == that.source_path) &&
-          (kernel_source_path == that.kernel_source_path));
+          (partitions == that.partitions));
 }
 
 bool InstallPlan::operator!=(const InstallPlan& that) const {
@@ -76,6 +65,13 @@
 }
 
 void InstallPlan::Dump() const {
+  string partitions_str;
+  for (const auto& partition : partitions) {
+    partitions_str += base::StringPrintf(
+        ", part: %s (source_size: %" PRIu64 ", target_size %" PRIu64 ")",
+        partition.name.c_str(), partition.source_size, partition.target_size);
+  }
+
   LOG(INFO) << "InstallPlan: "
             << (is_resume ? "resume" : "new_update")
             << ", payload type: " << (is_full_update ? "full" : "delta")
@@ -86,10 +82,7 @@
             << ", payload hash: " << payload_hash
             << ", metadata size: " << metadata_size
             << ", metadata signature: " << metadata_signature
-            << ", install_path: " << install_path
-            << ", kernel_install_path: " << kernel_install_path
-            << ", source_path: " << source_path
-            << ", kernel_source_path: " << kernel_source_path
+            << partitions_str
             << ", hash_checks_mandatory: " << utils::ToString(
                 hash_checks_mandatory)
             << ", powerwash_required: " << utils::ToString(
@@ -98,26 +91,34 @@
 
 bool InstallPlan::LoadPartitionsFromSlots(SystemState* system_state) {
   bool result = true;
-  if (source_slot != BootControlInterface::kInvalidSlot) {
-    result = system_state->boot_control()->GetPartitionDevice(
-        kLegacyPartitionNameRoot, source_slot, &source_path) && result;
-    result = system_state->boot_control()->GetPartitionDevice(
-        kLegacyPartitionNameKernel, source_slot, &kernel_source_path) && result;
-  } else {
-    source_path.clear();
-    kernel_source_path.clear();
-  }
+  for (Partition& partition : partitions) {
+    if (source_slot != BootControlInterface::kInvalidSlot) {
+      result = system_state->boot_control()->GetPartitionDevice(
+          partition.name, source_slot, &partition.source_path) && result;
+    } else {
+      partition.source_path.clear();
+    }
 
-  if (target_slot != BootControlInterface::kInvalidSlot) {
-    result = system_state->boot_control()->GetPartitionDevice(
-        kLegacyPartitionNameRoot, target_slot, &install_path) && result;
-    result = system_state->boot_control()->GetPartitionDevice(
-        kLegacyPartitionNameKernel, target_slot, &kernel_install_path) && result;
-  } else {
-    install_path.clear();
-    kernel_install_path.clear();
+    if (target_slot != BootControlInterface::kInvalidSlot) {
+      result = system_state->boot_control()->GetPartitionDevice(
+          partition.name, target_slot, &partition.target_path) && result;
+    } else {
+      partition.target_path.clear();
+    }
   }
   return result;
 }
 
+bool InstallPlan::Partition::operator==(
+    const InstallPlan::Partition& that) const {
+  return (name == that.name &&
+          source_path == that.source_path &&
+          source_size == that.source_size &&
+          source_hash == that.source_hash &&
+          target_path == that.target_path &&
+          target_size == that.target_size &&
+          target_hash == that.target_hash &&
+          run_postinstall == that.run_postinstall);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
index 6eb52fc..68aa1ae 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -39,10 +39,6 @@
               const std::string& payload_hash,
               uint64_t metadata_size,
               const std::string& metadata_signature,
-              const std::string& install_path,
-              const std::string& kernel_install_path,
-              const std::string& source_path,
-              const std::string& kernel_source_path,
               const std::string& public_key_rsa);
 
   // Default constructor.
@@ -53,6 +49,9 @@
 
   void Dump() const;
 
+  // Load the |source_path| and |target_path| of all |partitions| based on the
+  // |source_slot| and |target_slot| if available. Returns whether it succeeded
+  // to load all the partitions for the valid slots.
   bool LoadPartitionsFromSlots(SystemState* system_state);
 
   bool is_resume{false};
@@ -69,29 +68,35 @@
   BootControlInterface::Slot source_slot{BootControlInterface::kInvalidSlot};
   BootControlInterface::Slot target_slot{BootControlInterface::kInvalidSlot};
 
-  // TODO(deymo): Deprecate these fields and use the slots instead.
-  std::string install_path;              // path to install device
-  std::string kernel_install_path;       // path to kernel install device
-  std::string source_path;               // path to source device
-  std::string kernel_source_path;        // path to source kernel device
-
-  // The fields below are used for kernel and rootfs verification. The flow is:
+  // The vector below is used for partition verification. The flow is:
   //
   // 1. FilesystemVerifierAction computes and fills in the source partition
-  // sizes and hashes.
+  // hash based on the guessed source size for delta major version 1 updates.
   //
   // 2. DownloadAction verifies the source partition sizes and hashes against
   // the expected values transmitted in the update manifest. It fills in the
-  // expected applied partition sizes and hashes based on the manifest.
+  // expected target partition sizes and hashes based on the manifest.
   //
-  // 3. FilesystemVerifierAction computes and verifies the applied and source
-  // partition sizes and hashes against the expected values.
-  uint64_t kernel_size{0};
-  uint64_t rootfs_size{0};
-  chromeos::Blob kernel_hash;
-  chromeos::Blob rootfs_hash;
-  chromeos::Blob source_kernel_hash;
-  chromeos::Blob source_rootfs_hash;
+  // 3. FilesystemVerifierAction computes and verifies the applied partition
+  // sizes and hashes against the expected values in target_partition_hashes.
+  struct Partition {
+    bool operator==(const Partition& that) const;
+
+    // The name of the partition.
+    std::string name;
+
+    std::string source_path;
+    uint64_t source_size{0};
+    chromeos::Blob source_hash;
+
+    std::string target_path;
+    uint64_t target_size{0};
+    chromeos::Blob target_hash;
+
+    // Whether we should run the postinstall script from this partition.
+    bool run_postinstall{false};
+  };
+  std::vector<Partition> partitions;
 
   // True if payload hash checks are mandatory based on the system state and
   // the Omaha response.
diff --git a/mock_file_writer.h b/mock_file_writer.h
index 3270cbf..0d97e33 100644
--- a/mock_file_writer.h
+++ b/mock_file_writer.h
@@ -24,7 +24,6 @@
 
 class MockFileWriter : public FileWriter {
  public:
-  MOCK_METHOD3(Open, int(const char* path, int flags, mode_t mode));
   MOCK_METHOD2(Write, ssize_t(const void* bytes, size_t count));
   MOCK_METHOD0(Close, int());
 };
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index 1153628..e512d13 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -110,7 +110,6 @@
 
   install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
   install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
-  TEST_AND_RETURN(install_plan_.LoadPartitionsFromSlots(system_state_));
 
   if (params->to_more_stable_channel() && params->is_powerwash_allowed())
     install_plan_.powerwash_required = true;
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 65dbf0f..c97ef9e 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -207,10 +207,7 @@
   in.update_exists = false;
   InstallPlan install_plan;
   EXPECT_FALSE(DoTest(in, "", &install_plan));
-  EXPECT_EQ("", install_plan.download_url);
-  EXPECT_EQ("", install_plan.payload_hash);
-  EXPECT_EQ("", install_plan.install_path);
-  EXPECT_EQ("", install_plan.version);
+  EXPECT_TRUE(install_plan.partitions.empty());
 }
 
 TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) {
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index c3232fb..341e5f5 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -181,6 +181,9 @@
   LOG(INFO) << "Done verifying signed payload.";
 }
 
+// TODO(deymo): This function is likely broken for deltas minor version 2 or
+// newer. Move this function to a new file and make the delta_performer
+// integration tests use this instead.
 void ApplyDelta(const string& in_file,
                 const string& old_kernel,
                 const string& old_rootfs,
@@ -195,24 +198,26 @@
       << "Failed to initialize preferences.";
   // Get original checksums
   LOG(INFO) << "Calculating original checksums";
-  PartitionInfo kern_info, root_info;
   ImageConfig old_image;
   old_image.partitions.emplace_back(kLegacyPartitionNameRoot);
   old_image.partitions.back().path = old_rootfs;
   old_image.partitions.emplace_back(kLegacyPartitionNameKernel);
   old_image.partitions.back().path = old_kernel;
   CHECK(old_image.LoadImageSize());
-  CHECK(diff_utils::InitializePartitionInfo(old_image.partitions[0],
-                                            &root_info));
-  CHECK(diff_utils::InitializePartitionInfo(old_image.partitions[1],
-                                            &kern_info));
-  install_plan.kernel_hash.assign(kern_info.hash().begin(),
-                                  kern_info.hash().end());
-  install_plan.rootfs_hash.assign(root_info.hash().begin(),
-                                  root_info.hash().end());
+  for (const auto& old_part : old_image.partitions) {
+    PartitionInfo part_info;
+    CHECK(diff_utils::InitializePartitionInfo(old_part, &part_info));
+    InstallPlan::Partition part;
+    part.name = old_part.name;
+    part.source_hash.assign(part_info.hash().begin(),
+                            part_info.hash().end());
+    part.source_path = old_part.path;
+    // Apply the delta in-place to the old_part.
+    part.target_path = old_part.path;
+    install_plan.partitions.push_back(part);
+  }
+
   DeltaPerformer performer(&prefs, nullptr, &install_plan);
-  CHECK_EQ(performer.Open(old_rootfs.c_str(), 0, 0), 0);
-  CHECK(performer.OpenKernel(old_kernel.c_str()));
   chromeos::Blob buf(1024 * 1024);
   int fd = open(in_file.c_str(), O_RDONLY, 0);
   CHECK_GE(fd, 0);
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
index c6879d8..8c871d0 100644
--- a/postinstall_runner_action.cc
+++ b/postinstall_runner_action.cc
@@ -21,6 +21,8 @@
 #include <vector>
 
 #include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
 
 #include "update_engine/action_processor.h"
 #include "update_engine/subprocess.h"
@@ -44,39 +46,54 @@
 void PostinstallRunnerAction::PerformAction() {
   CHECK(HasInputObject());
   install_plan_ = GetInputObject();
-  const string install_device = install_plan_.install_path;
-  ScopedActionCompleter completer(processor_, this);
-
-  // Make mountpoint.
-  TEST_AND_RETURN(
-      utils::MakeTempDirectory("au_postint_mount.XXXXXX", &temp_rootfs_dir_));
-  ScopedDirRemover temp_dir_remover(temp_rootfs_dir_);
-
-  const string mountable_device =
-      utils::MakePartitionNameForMount(install_device);
-  if (mountable_device.empty()) {
-    LOG(ERROR) << "Cannot make mountable device from " << install_device;
-    return;
-  }
-
-  if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY))
-    return;
-
-  LOG(INFO) << "Performing postinst with install device " << install_device
-            << " and mountable device " << mountable_device;
-
-  temp_dir_remover.set_should_remove(false);
-  completer.set_should_complete(false);
 
   if (install_plan_.powerwash_required) {
     if (utils::CreatePowerwashMarkerFile(powerwash_marker_file_)) {
       powerwash_marker_created_ = true;
     } else {
-      completer.set_code(ErrorCode::kPostinstallPowerwashError);
-      return;
+      return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
     }
   }
 
+  PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::PerformPartitionPostinstall() {
+  // Skip all the partitions that don't have a post-install step.
+  while (current_partition_ < install_plan_.partitions.size() &&
+         !install_plan_.partitions[current_partition_].run_postinstall) {
+    VLOG(1) << "Skipping post-install on partition "
+            << install_plan_.partitions[current_partition_].name;
+    current_partition_++;
+  }
+  if (current_partition_ == install_plan_.partitions.size())
+    return CompletePostinstall(ErrorCode::kSuccess);
+
+  const InstallPlan::Partition& partition =
+      install_plan_.partitions[current_partition_];
+
+  const string mountable_device =
+      utils::MakePartitionNameForMount(partition.target_path);
+  if (mountable_device.empty()) {
+    LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
+    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+  }
+
+  // Perform post-install for the current_partition_ partition. At this point we
+  // need to call CompletePartitionPostinstall to complete the operation and
+  // cleanup.
+  TEST_AND_RETURN(
+      utils::MakeTempDirectory("au_postint_mount.XXXXXX", &temp_rootfs_dir_));
+
+  if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY)) {
+    return CompletePartitionPostinstall(
+        1, "Error mounting the device " + mountable_device);
+  }
+
+  LOG(INFO) << "Performing postinst (" << kPostinstallScript
+            << ") installed on device " << partition.target_path
+            << " and mountable device " << mountable_device;
+
   // Logs the file format of the postinstall script we are about to run. This
   // will help debug when the postinstall script doesn't match the architecture
   // of our build.
@@ -96,59 +113,70 @@
     // If we're doing a rollback, just run our own postinstall.
     command.push_back(kPostinstallScript);
   }
-  command.push_back(install_device);
-  if (!Subprocess::Get().Exec(command,
-                              base::Bind(
-                                  &PostinstallRunnerAction::CompletePostinstall,
-                                  base::Unretained(this)))) {
-    CompletePostinstall(1, "Postinstall didn't launch");
+  command.push_back(partition.target_path);
+  if (!Subprocess::Get().Exec(
+          command,
+          base::Bind(
+              &PostinstallRunnerAction::CompletePartitionPostinstall,
+              base::Unretained(this)))) {
+    CompletePartitionPostinstall(1, "Postinstall didn't launch");
   }
 }
 
-void PostinstallRunnerAction::CompletePostinstall(int return_code,
-                                                  const string& output) {
-  ScopedActionCompleter completer(processor_, this);
-  ScopedTempUnmounter temp_unmounter(temp_rootfs_dir_);
-
-  bool success = true;
+void PostinstallRunnerAction::CompletePartitionPostinstall(
+    int return_code,
+    const string& output) {
+  utils::UnmountFilesystem(temp_rootfs_dir_);
+  if (!base::DeleteFile(base::FilePath(temp_rootfs_dir_), false)) {
+    PLOG(WARNING) << "Not removing mountpoint " << temp_rootfs_dir_;
+  }
+  temp_rootfs_dir_.clear();
 
   if (return_code != 0) {
     LOG(ERROR) << "Postinst command failed with code: " << return_code;
-    success = false;
-  }
-
-  // We only attempt to mark the new slot as active if the /postinst script
-  // succeeded.
-  if (success && !system_state_->boot_control()->SetActiveBootSlot(
-        install_plan_.target_slot)) {
-    success = false;
-  }
-
-  if (!success) {
-    LOG(ERROR) << "Postinstall action failed.";
-
-    // Undo any changes done to trigger Powerwash using clobber-state.
-    if (powerwash_marker_created_)
-      utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
+    ErrorCode error_code = ErrorCode::kPostinstallRunnerError;
 
     if (return_code == 3) {
       // This special return code means that we tried to update firmware,
       // but couldn't because we booted from FW B, and we need to reboot
       // to get back to FW A.
-      completer.set_code(ErrorCode::kPostinstallBootedFromFirmwareB);
+      error_code = ErrorCode::kPostinstallBootedFromFirmwareB;
     }
 
     if (return_code == 4) {
       // This special return code means that we tried to update firmware,
       // but couldn't because we booted from FW B, and we need to reboot
       // to get back to FW A.
-      completer.set_code(ErrorCode::kPostinstallFirmwareRONotUpdatable);
+      error_code = ErrorCode::kPostinstallFirmwareRONotUpdatable;
     }
+    return CompletePostinstall(error_code);
+  }
+  current_partition_++;
+  PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
+  // We only attempt to mark the new slot as active if all the postinstall
+  // steps succeeded.
+  if (error_code == ErrorCode::kSuccess &&
+      !system_state_->boot_control()->SetActiveBootSlot(
+          install_plan_.target_slot)) {
+    error_code = ErrorCode::kPostinstallRunnerError;
+  }
+
+  ScopedActionCompleter completer(processor_, this);
+
+  if (error_code != ErrorCode::kSuccess) {
+    LOG(ERROR) << "Postinstall action failed.";
+
+    // Undo any changes done to trigger Powerwash using clobber-state.
+    if (powerwash_marker_created_)
+      utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
 
     return;
   }
 
-  LOG(INFO) << "Postinst command succeeded";
+  LOG(INFO) << "All post-install commands succeeded";
   if (HasOutputPipe()) {
     SetOutputObject(install_plan_);
   }
diff --git a/postinstall_runner_action.h b/postinstall_runner_action.h
index c56218a..8b2f55d 100644
--- a/postinstall_runner_action.h
+++ b/postinstall_runner_action.h
@@ -51,19 +51,28 @@
       : system_state_(system_state),
         powerwash_marker_file_(powerwash_marker_file) {}
 
+  void PerformPartitionPostinstall();
+
   // Subprocess::Exec callback.
-  void CompletePostinstall(int return_code,
-                           const std::string& output);
+  void CompletePartitionPostinstall(int return_code,
+                                    const std::string& output);
+
+  //
+  void CompletePostinstall(ErrorCode error_code);
 
   InstallPlan install_plan_;
   std::string temp_rootfs_dir_;
 
+  // The partition being processed on the list of partitions specified in the
+  // InstallPlan.
+  size_t current_partition_{0};
+
   // The main SystemState singleton.
   SystemState* system_state_;
 
   // True if Powerwash Marker was created before invoking post-install script.
   // False otherwise. Used for cleaning up if post-install fails.
-  bool powerwash_marker_created_ = false;
+  bool powerwash_marker_created_{false};
 
   // Non-null value will cause post-install to override the default marker
   // file name; used for testing.
diff --git a/postinstall_runner_action_unittest.cc b/postinstall_runner_action_unittest.cc
index a44d8d3..bf42749 100644
--- a/postinstall_runner_action_unittest.cc
+++ b/postinstall_runner_action_unittest.cc
@@ -187,8 +187,12 @@
 
   ActionProcessor processor;
   ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan::Partition part;
+  part.name = "part";
+  part.target_path = dev;
+  part.run_postinstall = true;
   InstallPlan install_plan;
-  install_plan.install_path = dev;
+  install_plan.partitions = {part};
   install_plan.download_url = "http://devserver:8080/update";
   install_plan.powerwash_required = powerwash_required;
   feeder_action.set_obj(install_plan);
@@ -210,7 +214,6 @@
 
   EXPECT_TRUE(delegate.code_set_);
   EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
-  EXPECT_EQ(should_succeed, !collector_action.object().install_path.empty());
   if (should_succeed)
     EXPECT_TRUE(install_plan == collector_action.object());
 
diff --git a/update_attempter.cc b/update_attempter.cc
index 433d112..7824cd8 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -590,11 +590,8 @@
   shared_ptr<OmahaResponseHandlerAction> response_handler_action(
       new OmahaResponseHandlerAction(system_state_));
   shared_ptr<FilesystemVerifierAction> src_filesystem_verifier_action(
-      new FilesystemVerifierAction(system_state_,
-                                   PartitionType::kSourceRootfs));
-  shared_ptr<FilesystemVerifierAction> src_kernel_filesystem_verifier_action(
-      new FilesystemVerifierAction(system_state_,
-                                   PartitionType::kSourceKernel));
+      new FilesystemVerifierAction(system_state_->boot_control(),
+                                   VerifierMode::kComputeSourceHash));
 
   shared_ptr<OmahaRequestAction> download_started_action(
       new OmahaRequestAction(system_state_,
@@ -619,9 +616,8 @@
                                                     system_state_),
                              false));
   shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
-      new FilesystemVerifierAction(system_state_, PartitionType::kRootfs));
-  shared_ptr<FilesystemVerifierAction> dst_kernel_filesystem_verifier_action(
-      new FilesystemVerifierAction(system_state_, PartitionType::kKernel));
+      new FilesystemVerifierAction(system_state_->boot_control(),
+                                   VerifierMode::kVerifyTargetHash));
   shared_ptr<OmahaRequestAction> update_complete_action(
       new OmahaRequestAction(system_state_,
                              new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
@@ -637,15 +633,11 @@
   actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
   actions_.push_back(shared_ptr<AbstractAction>(
       src_filesystem_verifier_action));
-  actions_.push_back(shared_ptr<AbstractAction>(
-      src_kernel_filesystem_verifier_action));
   actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
   actions_.push_back(shared_ptr<AbstractAction>(download_action));
   actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
   actions_.push_back(shared_ptr<AbstractAction>(
       dst_filesystem_verifier_action));
-  actions_.push_back(shared_ptr<AbstractAction>(
-      dst_kernel_filesystem_verifier_action));
 
   // Bond them together. We have to use the leaf-types when calling
   // BondActions().
@@ -654,15 +646,10 @@
   BondActions(response_handler_action.get(),
               src_filesystem_verifier_action.get());
   BondActions(src_filesystem_verifier_action.get(),
-              src_kernel_filesystem_verifier_action.get());
-  BondActions(src_kernel_filesystem_verifier_action.get(),
               download_action.get());
   BondActions(download_action.get(),
               dst_filesystem_verifier_action.get());
-  BondActions(dst_filesystem_verifier_action.get(),
-              dst_kernel_filesystem_verifier_action.get());
-
-  BuildPostInstallActions(dst_kernel_filesystem_verifier_action.get());
+  BuildPostInstallActions(dst_filesystem_verifier_action.get());
 
   actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
 
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 51e7920..1f506df 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -292,7 +292,7 @@
             GetErrorCodeForAction(&omaha_response_handler_action,
                                   ErrorCode::kError));
   FilesystemVerifierAction filesystem_verifier_action(
-      &fake_system_state_, PartitionType::kRootfs);
+      fake_system_state_.boot_control(), VerifierMode::kVerifyTargetHash);
   EXPECT_EQ(ErrorCode::kFilesystemVerifierError,
             GetErrorCodeForAction(&filesystem_verifier_action,
                                   ErrorCode::kError));
@@ -383,12 +383,10 @@
   OmahaRequestAction::StaticType(),
   OmahaResponseHandlerAction::StaticType(),
   FilesystemVerifierAction::StaticType(),
-  FilesystemVerifierAction::StaticType(),
   OmahaRequestAction::StaticType(),
   DownloadAction::StaticType(),
   OmahaRequestAction::StaticType(),
   FilesystemVerifierAction::StaticType(),
-  FilesystemVerifierAction::StaticType(),
   PostinstallRunnerAction::StaticType(),
   OmahaRequestAction::StaticType()
 };
@@ -437,7 +435,7 @@
   EXPECT_EQ(attempter_.response_handler_action_.get(),
             attempter_.actions_[1].get());
   DownloadAction* download_action =
-      dynamic_cast<DownloadAction*>(attempter_.actions_[5].get());
+      dynamic_cast<DownloadAction*>(attempter_.actions_[4].get());
   ASSERT_NE(nullptr, download_action);
   EXPECT_EQ(&attempter_, download_action->delegate());
   EXPECT_EQ(UpdateStatus::CHECKING_FOR_UPDATE, attempter_.status());
@@ -453,21 +451,12 @@
   EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
   fake_system_state_.set_device_policy(device_policy);
 
-  FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
-  fake_boot_control->SetPartitionDevice(
-      kLegacyPartitionNameKernel, 0, "/dev/sdz2");
-  fake_boot_control->SetPartitionDevice(
-      kLegacyPartitionNameRoot, 0, "/dev/sdz3");
-
   if (valid_slot) {
     BootControlInterface::Slot rollback_slot = 1;
     LOG(INFO) << "Test Mark Bootable: "
               << BootControlInterface::SlotName(rollback_slot);
-    fake_boot_control->SetSlotBootable(rollback_slot, true);
-    fake_boot_control->SetPartitionDevice(
-        kLegacyPartitionNameKernel, rollback_slot, "/dev/sdz4");
-    fake_boot_control->SetPartitionDevice(
-        kLegacyPartitionNameRoot, rollback_slot, "/dev/sdz5");
+    fake_system_state_.fake_boot_control()->SetSlotBootable(rollback_slot,
+                                                            true);
   }
 
   bool is_rollback_allowed = false;
@@ -520,8 +509,7 @@
   InstallPlanAction* install_plan_action =
         dynamic_cast<InstallPlanAction*>(attempter_.actions_[0].get());
   InstallPlan* install_plan = install_plan_action->install_plan();
-  EXPECT_EQ(install_plan->install_path, string("/dev/sdz5"));
-  EXPECT_EQ(install_plan->kernel_install_path, string("/dev/sdz4"));
+  EXPECT_EQ(0, install_plan->partitions.size());
   EXPECT_EQ(install_plan->powerwash_required, true);
   loop_.BreakLoop();
 }
diff --git a/utils.h b/utils.h
index c1ee5a9..94dff9d 100644
--- a/utils.h
+++ b/utils.h
@@ -472,19 +472,6 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedDirRemover);
 };
 
-// Utility class to unmount a filesystem mounted on a temporary directory and
-// delete the temporary directory when it goes out of scope.
-class ScopedTempUnmounter : public ScopedDirRemover {
- public:
-  explicit ScopedTempUnmounter(const std::string& path) :
-      ScopedDirRemover(path) {}
-  ~ScopedTempUnmounter() {
-    utils::UnmountFilesystem(path_);
-  }
- private:
-  DISALLOW_COPY_AND_ASSIGN(ScopedTempUnmounter);
-};
-
 // A little object to call ActionComplete on the ActionProcessor when
 // it's destructed.
 class ScopedActionCompleter {