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.