AU: Extent writer utility classes

These are similar to the FileWriter classes, but focus on writing to
extents within a file (or device) rather than writing a file in order.

Again there is a ExtentWriter interface from which all types of extent
writers interit.

There are also two basic extent writers: a direct extent writer that
writes data directly to the file descriptor, and a zero padding extent
writer that piggy-backs on another extent writer. When the
zero-padding extent writer is End()ed, it fills out the rest of the
extent with zeros.

Also, BzipExtentWriter: an ExtentWriter concrete subclass that writes
to another ExtentWriter.  BzipExtentWriter bzip2 decompresses all data
passed through.

Review URL: http://codereview.chromium.org/551132
diff --git a/SConstruct b/SConstruct
index d408cb7..38ae3f1 100644
--- a/SConstruct
+++ b/SConstruct
@@ -80,14 +80,16 @@
 
 if ARGUMENTS.get('debug', 0):
   env['CCFLAGS'] += ' -fprofile-arcs -ftest-coverage'
-  env['LIBS'] += ['gcov']
+  env['LIBS'] += ['bz2', 'gcov']
 
 
 
 sources = Split("""action_processor.cc
+                   bzip_extent_writer.cc
                    decompressing_file_writer.cc
                    delta_diff_parser.cc
                    download_action.cc
+                   extent_writer.cc
                    filesystem_copier_action.cc
                    filesystem_iterator.cc
                    file_writer.cc
@@ -107,9 +109,11 @@
 unittest_sources = Split("""action_unittest.cc
                             action_pipe_unittest.cc
                             action_processor_unittest.cc
+                            bzip_extent_writer_unittest.cc
                             decompressing_file_writer_unittest.cc
                             delta_diff_generator_unittest.cc
                             download_action_unittest.cc
+                            extent_writer_unittest.cc
                             file_writer_unittest.cc
                             filesystem_copier_action_unittest.cc
                             filesystem_iterator_unittest.cc
diff --git a/bzip_extent_writer.cc b/bzip_extent_writer.cc
new file mode 100644
index 0000000..cdf0ce3
--- /dev/null
+++ b/bzip_extent_writer.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/bzip_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const vector<char>::size_type kOutputBufferLength = 1024 * 1024;
+}
+
+bool BzipExtentWriter::Init(int fd,
+                            const vector<Extent>& extents,
+                            uint32 block_size) {
+  // Init bzip2 stream
+  int rc = BZ2_bzDecompressInit(&stream_,
+                                0,  // verbosity. (0 == silent)
+                                0  // 0 = faster algo, more memory
+                                );
+  TEST_AND_RETURN_FALSE(rc == BZ_OK);
+
+  return next_->Init(fd, extents, block_size);
+}
+
+bool BzipExtentWriter::Write(const void* bytes, size_t count) {
+  vector<char> output_buffer(kOutputBufferLength);
+
+  const char* c_bytes = reinterpret_cast<const char*>(bytes);
+
+  input_buffer_.insert(input_buffer_.end(), c_bytes, c_bytes + count);
+  
+  stream_.next_in = &input_buffer_[0];
+  stream_.avail_in = input_buffer_.size();
+  
+  for (;;) {
+    stream_.next_out = &output_buffer[0];
+    stream_.avail_out = output_buffer.size();
+
+    int rc = BZ2_bzDecompress(&stream_);
+    TEST_AND_RETURN_FALSE(rc == BZ_OK || rc == BZ_STREAM_END);
+    
+    if (stream_.avail_out == output_buffer.size())
+      break;  // got no new bytes
+    
+    TEST_AND_RETURN_FALSE(
+        next_->Write(&output_buffer[0],
+                     output_buffer.size() - stream_.avail_out));
+    
+    if (rc == BZ_STREAM_END)
+      CHECK_EQ(stream_.avail_in, 0);
+    if (stream_.avail_in == 0)
+      break;  // no more input to process
+  }
+
+  // store unconsumed data in input_buffer_.
+  
+  vector<char> new_input_buffer(input_buffer_.end() - stream_.avail_in,
+                                input_buffer_.end());
+  new_input_buffer.swap(input_buffer_);
+  
+  return true;
+}
+
+bool BzipExtentWriter::EndImpl() {
+  TEST_AND_RETURN_FALSE(input_buffer_.empty());
+  TEST_AND_RETURN_FALSE(BZ2_bzDecompressEnd(&stream_) == BZ_OK);
+  return next_->End();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/bzip_extent_writer.h b/bzip_extent_writer.h
new file mode 100644
index 0000000..fdd9671
--- /dev/null
+++ b/bzip_extent_writer.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_BZIP_EXTENT_WRITER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_BZIP_EXTENT_WRITER_H__
+
+#include <vector>
+#include <bzlib.h>
+#include "update_engine/extent_writer.h"
+#include "update_engine/utils.h"
+
+// BzipExtentWriter is a concrete ExtentWriter subclass that bzip-decompresses
+// what it's given in Write. It passes the decompressed data to an underlying
+// ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class BzipExtentWriter : public ExtentWriter {
+ public:
+  BzipExtentWriter(ExtentWriter* next) : next_(next) {
+    memset(&stream_, 0, sizeof(stream_));
+  }
+  ~BzipExtentWriter() {}
+
+  bool Init(int fd, const std::vector<Extent>& extents, uint32 block_size);
+  bool Write(const void* bytes, size_t count);
+  bool EndImpl();
+
+ private:
+  ExtentWriter* const next_;  // The underlying ExtentWriter.
+  bz_stream stream_;  // the libbz2 stream
+  std::vector<char> input_buffer_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_BZIP_EXTENT_WRITER_H__
diff --git a/bzip_extent_writer_unittest.cc b/bzip_extent_writer_unittest.cc
new file mode 100644
index 0000000..3f12c58
--- /dev/null
+++ b/bzip_extent_writer_unittest.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/bzip_extent_writer.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char kPathTemplate[] = "./BzipExtentWriterTest-file.XXXXXX";
+const uint32 kBlockSize = 4096;
+}
+
+class BzipExtentWriterTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+    fd_ = mkstemp(path_);
+    ASSERT_GE(fd_, 0);
+  }
+  virtual void TearDown() {
+    close(fd_);
+    LOG(INFO) << "unlink: " << path_;
+    unlink(path_);
+  }
+  int fd() { return fd_; }
+  const char* path() { return path_; }
+  void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+  void TestZeroPad(bool aligned_size);
+ private:
+  int fd_;
+  char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(BzipExtentWriterTest, SimpleTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  // 'echo test | bzip2 | hexdump' yields:
+  const char test_uncompressed[] = "test\n";
+  unsigned char test[] = {
+    0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xcc, 0xc3,
+    0x71, 0xd4, 0x00, 0x00, 0x02, 0x41, 0x80, 0x00, 0x10, 0x02, 0x00, 0x0c,
+    0x00, 0x20, 0x00, 0x21, 0x9a, 0x68, 0x33, 0x4d, 0x19, 0x97, 0x8b, 0xb9,
+    0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,   
+  };
+  
+  DirectExtentWriter direct_writer;
+  BzipExtentWriter bzip_writer(&direct_writer);
+  EXPECT_TRUE(bzip_writer.Init(fd(), extents, kBlockSize));
+  EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
+  EXPECT_TRUE(bzip_writer.End());
+  
+  char buf[sizeof(test_uncompressed) + 1];
+  memset(buf, 0, sizeof(buf));
+  ssize_t bytes_read = pread(fd(), buf, sizeof(buf) - 1, 0);
+  EXPECT_EQ(strlen(test_uncompressed), bytes_read);
+  EXPECT_EQ(string(buf), string(test_uncompressed));
+}
+
+TEST_F(BzipExtentWriterTest, ChunkedTest) {
+  const vector<char>::size_type kDecompressedLength = 2048 * 1024;  // 2 MiB
+  const string kDecompressedPath = "BzipExtentWriterTest-file-decompressed";
+  const string kCompressedPath = "BzipExtentWriterTest-file-compressed";
+  const size_t kChunkSize = 3;
+  
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(0);
+  extent.set_num_blocks(kDecompressedLength / kBlockSize + 1);
+  extents.push_back(extent);
+
+  vector<char> decompressed_data(kDecompressedLength);
+  FillWithData(&decompressed_data);
+  
+  EXPECT_TRUE(WriteFileVector(kDecompressedPath, decompressed_data));
+  
+  EXPECT_EQ(0, System(string("cat ") + kDecompressedPath + "|bzip2>" +
+                      kCompressedPath));
+
+  vector<char> compressed_data;
+  EXPECT_TRUE(utils::ReadFile(kCompressedPath, &compressed_data));
+  
+  DirectExtentWriter direct_writer;
+  BzipExtentWriter bzip_writer(&direct_writer);
+  EXPECT_TRUE(bzip_writer.Init(fd(), extents, kBlockSize));
+
+  for (vector<char>::size_type i = 0; i < compressed_data.size();
+       i += kChunkSize) {
+    size_t this_chunk_size = min(kChunkSize, compressed_data.size() - i);
+    EXPECT_TRUE(bzip_writer.Write(&compressed_data[i], this_chunk_size));
+  }
+  EXPECT_TRUE(bzip_writer.End());
+  
+  vector<char> output(kDecompressedLength + 1);
+  ssize_t bytes_read = pread(fd(), &output[0], output.size(), 0);
+  EXPECT_EQ(kDecompressedLength, bytes_read);
+  output.resize(kDecompressedLength);
+  ExpectVectorsEq(decompressed_data, output);
+  
+  unlink(kDecompressedPath.c_str());
+  unlink(kCompressedPath.c_str());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/extent_writer.cc b/extent_writer.cc
new file mode 100644
index 0000000..1ae565b
--- /dev/null
+++ b/extent_writer.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/extent_writer.h"
+#include <errno.h>
+#include <unistd.h>
+#include <algorithm>
+
+using std::min;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Returns true on success.
+bool WriteAll(int fd, const void *buf, size_t count) {
+  const char* c_buf = reinterpret_cast<const char*>(buf);
+  ssize_t bytes_written = 0;
+  while (bytes_written < static_cast<ssize_t>(count)) {
+    ssize_t rc = write(fd, c_buf + bytes_written, count - bytes_written);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+}
+
+bool DirectExtentWriter::Write(const void* bytes, size_t count) {
+  if (count == 0)
+    return true;
+  const char* c_bytes = reinterpret_cast<const char*>(bytes);
+  size_t bytes_written = 0;
+  while (count - bytes_written > 0) {
+    TEST_AND_RETURN_FALSE(next_extent_index_ < extents_.size());
+    uint64 bytes_remaining_next_extent =
+        extents_[next_extent_index_].num_blocks() * block_size_ -
+        extent_bytes_written_;
+    CHECK_NE(bytes_remaining_next_extent, 0);
+    size_t bytes_to_write =
+        static_cast<size_t>(min(static_cast<uint64>(count - bytes_written),
+                                bytes_remaining_next_extent));
+    TEST_AND_RETURN_FALSE(bytes_to_write > 0);
+
+    if (extents_[next_extent_index_].start_block() != kSparseHole) {
+      const off64_t offset =
+          extents_[next_extent_index_].start_block() * block_size_ +
+          extent_bytes_written_;
+      TEST_AND_RETURN_FALSE_ERRNO(lseek64(fd_, offset, SEEK_SET) !=
+                                  static_cast<off64_t>(-1));
+      TEST_AND_RETURN_FALSE(
+          WriteAll(fd_, c_bytes + bytes_written, bytes_to_write));
+    }
+    bytes_written += bytes_to_write;
+    extent_bytes_written_ += bytes_to_write;
+    if (bytes_remaining_next_extent == bytes_to_write) {
+      // We filled this extent
+      CHECK_EQ(extent_bytes_written_,
+               extents_[next_extent_index_].num_blocks() * block_size_);
+      // move to next extent
+      extent_bytes_written_ = 0;
+      next_extent_index_++;
+    }
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/extent_writer.h b/extent_writer.h
new file mode 100644
index 0000000..cbb62fe
--- /dev/null
+++ b/extent_writer.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_EXTENT_WRITER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_EXTENT_WRITER_H__
+
+#include <vector>
+#include "chromeos/obsolete_logging.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// ExtentWriter is an abstract class which synchronously writes to a given
+// file descriptor at the extents given.
+
+namespace chromeos_update_engine {
+
+// When an extent's start block is kSparseHole, that data written for that
+// extent will be dropped rather than written to the unerlying fd.
+const uint64 kSparseHole = kuint64max;
+
+class ExtentWriter {
+ public:
+  ExtentWriter() : end_called_(false) {}
+  virtual ~ExtentWriter() {
+    LOG_IF(ERROR, !end_called_) << "End() not called on ExtentWriter.";
+  }
+
+  // Returns true on success.
+  virtual bool Init(int fd,
+                    const std::vector<Extent>& extents,
+                    size_t block_size) = 0;
+
+  // Returns true on success.
+  virtual bool Write(const void* bytes, size_t count) = 0;
+
+  // Should be called when all writing is complete. Returns true on success.
+  // The fd is not closed. Caller is responsible for closing it.
+  bool End() {
+    end_called_ = true;
+    return EndImpl();
+  }
+  virtual bool EndImpl() = 0;
+ private:
+  bool end_called_;
+};
+
+// DirectExtentWriter is probably the simplest ExtentWriter implementation.
+// It writes the data directly into the extents.
+
+class DirectExtentWriter : public ExtentWriter {
+ public:
+  DirectExtentWriter()
+      : fd_(-1),
+        block_size_(0),
+        extent_bytes_written_(0),
+        next_extent_index_(0) {}
+  ~DirectExtentWriter() {}
+
+  bool Init(int fd, const std::vector<Extent>& extents, size_t block_size) {
+    fd_ = fd;
+    block_size_ = block_size;
+    extents_ = extents;
+    return true;
+  }
+  bool Write(const void* bytes, size_t count);
+  bool EndImpl() {
+    return true;
+  }
+
+ private:
+  int fd_;
+  
+  size_t block_size_;
+  // Bytes written into next_extent_index_ thus far
+  uint64 extent_bytes_written_;
+  std::vector<Extent> extents_;
+  // The next call to write should correspond to extents_[next_extent_index_]
+  std::vector<Extent>::size_type next_extent_index_;
+};
+
+// Takes an underlying ExtentWriter to which all operations are delegated.
+// When End() is called, ZeroPadExtentWriter ensures that the total number
+// of bytes written is a multiple of block_size_. If not, it writes zeros
+// to pad as needed.
+
+class ZeroPadExtentWriter : public ExtentWriter {
+ public:
+  ZeroPadExtentWriter(ExtentWriter* underlying_extent_writer)
+      : underlying_extent_writer_(underlying_extent_writer),
+        block_size_(0),
+        bytes_written_mod_block_size_(0) {}
+  ~ZeroPadExtentWriter() {}
+
+  bool Init(int fd, const std::vector<Extent>& extents, size_t block_size) {
+    block_size_ = block_size;
+    return underlying_extent_writer_->Init(fd, extents, block_size);
+  }
+  bool Write(const void* bytes, size_t count) {
+    if (underlying_extent_writer_->Write(bytes, count)) {
+      bytes_written_mod_block_size_ += count;
+      bytes_written_mod_block_size_ %= block_size_;
+      return true;
+    }
+    return false;
+  }
+  bool EndImpl() {
+    if (bytes_written_mod_block_size_) {
+      const size_t write_size = block_size_ - bytes_written_mod_block_size_;
+      std::vector<char> zeros(write_size, 0);
+      TEST_AND_RETURN_FALSE(underlying_extent_writer_->Write(&zeros[0],
+                                                             write_size));
+    }
+    return underlying_extent_writer_->End();
+  }
+
+ private:
+  ExtentWriter* underlying_extent_writer_;  // The underlying ExtentWriter.
+  size_t block_size_;
+  size_t bytes_written_mod_block_size_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_EXTENT_WRITER_H__
diff --git a/extent_writer_unittest.cc b/extent_writer_unittest.cc
new file mode 100644
index 0000000..a35ba26
--- /dev/null
+++ b/extent_writer_unittest.cc
@@ -0,0 +1,255 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/extent_writer.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64_bit);
+
+namespace {
+const char kPathTemplate[] = "./ExtentWriterTest-file.XXXXXX";
+const size_t kBlockSize = 4096;
+}
+
+class ExtentWriterTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+    fd_ = mkstemp(path_);
+    ASSERT_GE(fd_, 0);
+  }
+  virtual void TearDown() {
+    close(fd_);
+    unlink(path_);
+  }
+  int fd() { return fd_; }
+  const char* path() { return path_; }
+  
+  // Writes data to an extent writer in 'chunk_size' chunks with
+  // the first chunk of size first_chunk_size. It calculates what the
+  // resultant file should look like and ensure that the extent writer
+  // wrote the file correctly.
+  void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+  void TestZeroPad(bool aligned_size);
+ private:
+  int fd_;
+  char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(ExtentWriterTest, SimpleTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  
+  const string bytes = "1234";
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd(), extents, kBlockSize));
+  EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
+  EXPECT_TRUE(direct_writer.End());
+  
+  struct stat stbuf;
+  EXPECT_EQ(0, fstat(fd(), &stbuf));
+  EXPECT_EQ(kBlockSize + bytes.size(), stbuf.st_size);
+  
+  vector<char> result_file;
+  EXPECT_TRUE(utils::ReadFile(path(), &result_file));
+  
+  vector<char> expected_file(kBlockSize);
+  expected_file.insert(expected_file.end(),
+                       bytes.data(), bytes.data() + bytes.size());
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroLengthTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd(), extents, kBlockSize));
+  EXPECT_TRUE(direct_writer.Write(NULL, 0));
+  EXPECT_TRUE(direct_writer.End());
+}
+
+TEST_F(ExtentWriterTest, OverflowExtentTest) {
+  WriteAlignedExtents(kBlockSize * 3, kBlockSize * 3);
+}
+
+TEST_F(ExtentWriterTest, UnalignedWriteTest) {
+  WriteAlignedExtents(7, 7);
+}
+
+TEST_F(ExtentWriterTest, LargeUnalignedWriteTest) {
+  WriteAlignedExtents(kBlockSize * 2, kBlockSize / 2);
+}
+
+void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size,
+                                           size_t first_chunk_size) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(2);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  
+  vector<char> data(kBlockSize * 3);
+  FillWithData(&data);
+  
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd(), extents, kBlockSize));
+  
+  size_t bytes_written = 0;
+  while (bytes_written < data.size()) {
+    size_t bytes_to_write = min(data.size() - bytes_written, chunk_size);
+    if (bytes_written == 0) {
+      bytes_to_write = min(data.size() - bytes_written, first_chunk_size);
+    }
+    EXPECT_TRUE(direct_writer.Write(&data[bytes_written], bytes_to_write));
+    bytes_written += bytes_to_write;
+  }
+  EXPECT_TRUE(direct_writer.End());
+  
+  struct stat stbuf;
+  EXPECT_EQ(0, fstat(fd(), &stbuf));
+  EXPECT_EQ(data.size(), stbuf.st_size);
+  
+  vector<char> result_file;
+  EXPECT_TRUE(utils::ReadFile(path(), &result_file));
+  
+  vector<char> expected_file;
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize,
+                       data.begin() + kBlockSize * 2);
+  expected_file.insert(expected_file.end(),
+                       data.begin(), data.begin() + kBlockSize);
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize * 2, data.end());
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadNullTest) {
+  TestZeroPad(true);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadFillTest) {
+  TestZeroPad(false);
+}
+
+void ExtentWriterTest::TestZeroPad(bool aligned_size) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  
+  vector<char> data(kBlockSize * 2);
+  FillWithData(&data);
+  
+  DirectExtentWriter direct_writer;
+  ZeroPadExtentWriter zero_pad_writer(&direct_writer);
+
+  EXPECT_TRUE(zero_pad_writer.Init(fd(), extents, kBlockSize));
+  size_t bytes_to_write = data.size();
+  const size_t missing_bytes = (aligned_size ? 0 : 9);
+  bytes_to_write -= missing_bytes;
+  lseek64(fd(), kBlockSize - missing_bytes, SEEK_SET);
+  EXPECT_EQ(3, write(fd(), "xxx", 3));
+  ASSERT_TRUE(zero_pad_writer.Write(&data[0], bytes_to_write));
+  EXPECT_TRUE(zero_pad_writer.End());
+  
+  struct stat stbuf;
+  EXPECT_EQ(0, fstat(fd(), &stbuf));
+  EXPECT_EQ(data.size(), stbuf.st_size);
+  
+  vector<char> result_file;
+  EXPECT_TRUE(utils::ReadFile(path(), &result_file));
+  
+  vector<char> expected_file;
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize,
+                       data.begin() + kBlockSize * 2);
+  expected_file.insert(expected_file.end(),
+                       data.begin(), data.begin() + kBlockSize);
+  if (missing_bytes) {
+    memset(&expected_file[kBlockSize - missing_bytes], 0, missing_bytes);
+  }
+
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, SparseFileTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(kSparseHole);
+  extent.set_num_blocks(2);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  const int block_count = 4;
+  const int on_disk_count = 2;
+
+  vector<char> data(17);
+  FillWithData(&data);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd(), extents, kBlockSize));
+  
+  size_t bytes_written = 0;
+  while (bytes_written < (block_count * kBlockSize)) {
+    size_t bytes_to_write = min(block_count * kBlockSize - bytes_written,
+                                data.size());
+    EXPECT_TRUE(direct_writer.Write(&data[0], bytes_to_write));
+    bytes_written += bytes_to_write;
+  }
+  EXPECT_TRUE(direct_writer.End());
+  
+  // check file size, then data inside
+  ASSERT_EQ(2 * kBlockSize, FileSize(path()));
+  
+  vector<char> resultant_data;
+  EXPECT_TRUE(utils::ReadFile(path(), &resultant_data));
+  
+  // Create expected data
+  vector<char> expected_data(on_disk_count * kBlockSize);
+  vector<char> big(block_count * kBlockSize);
+  for (vector<char>::size_type i = 0; i < big.size(); i++) {
+    big[i] = data[i % data.size()];
+  }
+  memcpy(&expected_data[kBlockSize], &big[0], kBlockSize);
+  memcpy(&expected_data[0], &big[3 * kBlockSize], kBlockSize);
+  ExpectVectorsEq(expected_data, resultant_data);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/test_utils.cc b/test_utils.cc
index f83ddf9..6c1e35b 100644
--- a/test_utils.cc
+++ b/test_utils.cc
@@ -165,16 +165,25 @@
   return ret;
 }
 
-bool ExpectVectorsEq(const vector<char>& a, const vector<char>& b) {
-  EXPECT_EQ(a.size(), b.size());
-  if (a.size() != b.size())
+bool ExpectVectorsEq(const vector<char>& expected, const vector<char>& actual) {
+  EXPECT_EQ(expected.size(), actual.size());
+  if (expected.size() != actual.size())
     return false;
-  for (unsigned int i = 0; i < a.size(); i++) {
-    EXPECT_EQ(a[i], b[i]) << "offset: " << i;
+  for (unsigned int i = 0; i < expected.size(); i++) {
+    EXPECT_EQ(expected[i], actual[i]) << "offset: " << i;
   }
   return true;
 }
 
+void FillWithData(vector<char>* buffer) {
+  size_t input_counter = 0;
+  for (vector<char>::iterator it = buffer->begin(); it != buffer->end(); ++it) {
+    *it = kRandomString[input_counter];
+    input_counter++;
+    input_counter %= sizeof(kRandomString);
+  }
+}
+
 void CreateExtImageAtPath(const string& path, vector<string>* out_paths) {
   // create 10MiB sparse file
   EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
diff --git a/test_utils.h b/test_utils.h
index cd17ec1..bba3ed1 100644
--- a/test_utils.h
+++ b/test_utils.h
@@ -44,6 +44,8 @@
   return system(cmd.c_str());
 }
 
+void FillWithData(std::vector<char>* buffer);
+
 namespace {
 // 300 byte pseudo-random string. Not null terminated.
 // This does not gzip compress well.