Implement ZERO and DISCARD operations in update_engine.
ZERO and DISCARD operations are implemented using the specific ioctl()
that allow SSD to do a faster operation when writing zeros. In case of
failure, such as when they are not supported, the operation will fall
back to write the zeros directly.
Bug: 23180264
Test: Added a unittest.
Change-Id: I0f12ab9864620b50250157c1de17b7fc58c7ea1c
diff --git a/delta_performer.cc b/delta_performer.cc
index 4025b1f..96850df 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -18,6 +18,7 @@
#include <endian.h>
#include <errno.h>
+#include <linux/fs.h>
#include <algorithm>
#include <cstring>
@@ -650,6 +651,10 @@
case InstallOperation::REPLACE_XZ:
op_result = PerformReplaceOperation(op, is_kernel_partition);
break;
+ case InstallOperation::ZERO:
+ case InstallOperation::DISCARD:
+ op_result = PerformZeroOrDiscardOperation(op, is_kernel_partition);
+ break;
case InstallOperation::MOVE:
op_result = PerformMoveOperation(op, is_kernel_partition);
break;
@@ -739,6 +744,43 @@
return true;
}
+bool DeltaPerformer::PerformZeroOrDiscardOperation(
+ const InstallOperation& operation,
+ bool is_kernel_partition) {
+ CHECK(operation.type() == InstallOperation::DISCARD ||
+ operation.type() == InstallOperation::ZERO);
+
+ // These operations have no blob.
+ TEST_AND_RETURN_FALSE(!operation.has_data_offset());
+ TEST_AND_RETURN_FALSE(!operation.has_data_length());
+
+ 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++) {
+ Extent extent = operation.dst_extents(i);
+ const uint64_t start = extent.start_block() * block_size_;
+ const uint64_t length = extent.num_blocks() * block_size_;
+ if (attempt_ioctl) {
+ int result = 0;
+ if (fd->BlkIoctl(request, start, length, &result) && result == 0)
+ continue;
+ attempt_ioctl = false;
+ zeros.resize(16 * block_size_);
+ }
+ // In case of failure, we fall back to writing 0 to the selected region.
+ for (uint64_t offset = 0; offset < length; offset += zeros.size()) {
+ uint64_t chunk_length = min(length - offset, zeros.size());
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(fd, zeros.data(), chunk_length, start + offset));
+ }
+ }
+ return true;
+}
+
bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation,
bool is_kernel_partition) {
// Calculate buffer size. Note, this function doesn't do a sliding
diff --git a/delta_performer.h b/delta_performer.h
index 8345174..34cb137 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -279,6 +279,8 @@
// 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,
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index d2ec5b0..12dd1d3 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -138,14 +138,29 @@
// Apply |payload_data| on partition specified in |source_path|.
chromeos::Blob ApplyPayload(const chromeos::Blob& payload_data,
const string& source_path) {
- install_plan_.source_path = source_path;
- install_plan_.kernel_source_path = "/dev/null";
+ return ApplyPayloadToData(payload_data, source_path, chromeos::Blob());
+ }
+ // Apply the payload provided in |payload_data| reading from the |source_path|
+ // file and writing the contents to a new partition. The existing data in the
+ // new target file are set to |target_data| before applying the payload.
+ // Returns the result of the payload application.
+ chromeos::Blob ApplyPayloadToData(const chromeos::Blob& payload_data,
+ const string& source_path,
+ const chromeos::Blob& target_data) {
string new_part;
EXPECT_TRUE(utils::MakeTempFile("Partition-XXXXXX", &new_part, nullptr));
ScopedPathUnlinker partition_unlinker(new_part);
+ 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";
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());
@@ -285,7 +300,7 @@
chromeos::Blob payload_data = GeneratePayload(expected_data, aops, false,
kFullPayloadMinorVersion);
- EXPECT_EQ(expected_data, ApplyPayload(payload_data, ""));
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
}
TEST_F(DeltaPerformerTest, ReplaceOperationTest) {
@@ -348,6 +363,29 @@
EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
}
+TEST_F(DeltaPerformerTest, ZeroOperationTest) {
+ chromeos::Blob existing_data = chromeos::Blob(4096 * 10, 'a');
+ chromeos::Blob expected_data = existing_data;
+ // Blocks 4, 5 and 7 should have zeros instead of 'a' after the operation is
+ // applied.
+ std::fill(expected_data.data() + 4096 * 4, expected_data.data() + 4096 * 6,
+ 0);
+ std::fill(expected_data.data() + 4096 * 7, expected_data.data() + 4096 * 8,
+ 0);
+
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(4, 2);
+ *(aop.op.add_dst_extents()) = ExtentForRange(7, 1);
+ aop.op.set_type(InstallOperation::ZERO);
+ vector<AnnotatedOperation> aops = {aop};
+
+ chromeos::Blob payload_data = GeneratePayload(chromeos::Blob(), aops, false,
+ kSourceMinorPayloadVersion);
+
+ EXPECT_EQ(expected_data,
+ ApplyPayloadToData(payload_data, "/dev/null", existing_data));
+}
+
TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
chromeos::Blob expected_data = chromeos::Blob(std::begin(kRandomString),
std::end(kRandomString));
diff --git a/file_descriptor.cc b/file_descriptor.cc
index 8b8fa72..4718528 100644
--- a/file_descriptor.cc
+++ b/file_descriptor.cc
@@ -17,6 +17,8 @@
#include "update_engine/file_descriptor.h"
#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -63,6 +65,42 @@
return lseek64(fd_, offset, whence);
}
+bool EintrSafeFileDescriptor::BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) {
+ DCHECK(request == BLKDISCARD || request == BLKZEROOUT ||
+ request == BLKSECDISCARD);
+ // On some devices, the BLKDISCARD will actually read back as zeros, instead
+ // of "undefined" data. The BLKDISCARDZEROES ioctl tells whether that's the
+ // case, so we issue a BLKDISCARD in those cases to speed up the writes.
+ unsigned int arg;
+ if (request == BLKZEROOUT && ioctl(fd_, BLKDISCARDZEROES, &arg) == 0 && arg)
+ request = BLKDISCARD;
+
+ // Ensure the |fd_| is in O_DIRECT mode during this operation, so the write
+ // cache for this region is invalidated. This is required since otherwise
+ // reading back this region could consume stale data from the cache.
+ int flags = fcntl(fd_, F_GETFL, 0);
+ if (flags == -1) {
+ PLOG(WARNING) << "Couldn't get flags on fd " << fd_;
+ return false;
+ }
+ if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags | O_DIRECT) == -1) {
+ PLOG(WARNING) << "Couldn't set O_DIRECT on fd " << fd_;
+ return false;
+ }
+
+ uint64_t range[2] = {start, length};
+ *result = ioctl(fd_, request, range);
+
+ if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags) == -1) {
+ PLOG(WARNING) << "Couldn't remove O_DIRECT on fd " << fd_;
+ return false;
+ }
+ return true;
+}
+
bool EintrSafeFileDescriptor::Close() {
CHECK_GE(fd_, 0);
if (IGNORE_EINTR(close(fd_)))
diff --git a/file_descriptor.h b/file_descriptor.h
index 4d6e970..bf13611 100644
--- a/file_descriptor.h
+++ b/file_descriptor.h
@@ -78,6 +78,17 @@
// may set errno accordingly.
virtual off64_t Seek(off64_t offset, int whence) = 0;
+ // Runs a ioctl() on the file descriptor if supported. Returns whether
+ // the operation is supported. The |request| can be one of BLKDISCARD,
+ // BLKZEROOUT and BLKSECDISCARD to discard, write zeros or securely discard
+ // the blocks. These ioctls accept a range of bytes (|start| and |length|)
+ // over which they perform the operation. The return value from the ioctl is
+ // stored in |result|.
+ virtual bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) = 0;
+
// Closes a file descriptor. The descriptor must be open prior to this call.
// Returns true on success, false otherwise. Specific implementations may set
// errno accordingly.
@@ -108,6 +119,10 @@
ssize_t Read(void* buf, size_t count) override;
ssize_t Write(const void* buf, size_t count) override;
off64_t Seek(off64_t offset, int whence) override;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override;
bool Close() override;
void Reset() override;
bool IsSettingErrno() override {
diff --git a/mtd_file_descriptor.h b/mtd_file_descriptor.h
index 1d023c4..954e5e5 100644
--- a/mtd_file_descriptor.h
+++ b/mtd_file_descriptor.h
@@ -40,6 +40,12 @@
ssize_t Read(void* buf, size_t count) override;
ssize_t Write(const void* buf, size_t count) override;
off64_t Seek(off64_t offset, int whence) override;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override {
+ return false;
+ }
bool Close() override;
private:
@@ -69,6 +75,12 @@
ssize_t Read(void* buf, size_t count) override;
ssize_t Write(const void* buf, size_t count) override;
off64_t Seek(off64_t offset, int whence) override;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override {
+ return false;
+ }
bool Close() override;
private: