Merge "remount: Increase scratch size from 50% to 85% of available data"
diff --git a/fastboot/fastboot_driver_mock.h b/fastboot/fastboot_driver_mock.h
index 62a5708..d2a123b 100644
--- a/fastboot/fastboot_driver_mock.h
+++ b/fastboot/fastboot_driver_mock.h
@@ -22,8 +22,7 @@
class MockFastbootDriver : public IFastBootDriver {
public:
- MOCK_METHOD(RetCode, FlashPartition,
- (const std::string& partition, android::base::borrowed_fd fd, uint32_t sz),
+ MOCK_METHOD(RetCode, FlashPartition, (const std::string&, android::base::borrowed_fd, uint32_t),
(override));
MOCK_METHOD(RetCode, DeletePartition, (const std::string&), (override));
MOCK_METHOD(RetCode, Reboot, (std::string*, std::vector<std::string>*), (override));
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 054c1ed..ce46e91 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -65,7 +65,7 @@
: reboot_target_(reboot_target), fp_(fp){};
void RebootTask::Run() {
- if ((reboot_target_ == "userspace" || reboot_target_ == "fastboot")) {
+ if (reboot_target_ == "fastboot") {
if (!is_userspace_fastboot()) {
reboot_to_userspace_fastboot();
fp_->fb->WaitForDisconnect();
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 145b4d7..6fc2056 100644
--- a/fastboot/task_test.cpp
+++ b/fastboot/task_test.cpp
@@ -16,6 +16,7 @@
#include "task.h"
#include "fastboot.h"
+#include "fastboot_driver_mock.h"
#include <gtest/gtest.h>
#include <fstream>
@@ -24,6 +25,7 @@
#include <unordered_map>
#include "android-base/strings.h"
using android::base::Split;
+using testing::_;
class ParseTest : public ::testing ::Test {
protected:
@@ -58,7 +60,7 @@
return ParseFastbootInfoLine(fp, vec_command);
}
-TEST_F(ParseTest, CORRECT_FlASH_TASK_FORMED) {
+TEST_F(ParseTest, CorrectFlashTaskFormed) {
std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
"flash system", "flash --apply-vbmeta vbmeta"};
@@ -86,7 +88,7 @@
}
}
-TEST_F(ParseTest, VERSION_CHECK_CORRRECT) {
+TEST_F(ParseTest, VersionCheckCorrect) {
std::vector<std::string> correct_versions = {
"version 1.0",
"version 22.00",
@@ -104,7 +106,7 @@
}
}
-TEST_F(ParseTest, BAD_FASTBOOT_INFO_INPUT) {
+TEST_F(ParseTest, BadFastbootInput) {
ASSERT_EQ(ParseCommand(fp.get(), "flash"), nullptr);
ASSERT_EQ(ParseCommand(fp.get(), "flash --slot-other --apply-vbmeta"), nullptr);
ASSERT_EQ(ParseCommand(fp.get(), "flash --apply-vbmeta"), nullptr);
@@ -121,3 +123,32 @@
ASSERT_EQ(ParseCommand(fp.get(), "erase dtbo dtbo"), nullptr);
ASSERT_EQ(ParseCommand(fp.get(), "wipe this"), nullptr);
}
+
+TEST_F(ParseTest, CorrectTaskFormed) {
+ std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
+ "reboot bootloader", "update-super", "erase cache"};
+ std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
+
+ ASSERT_TRUE(tasks[0]->AsFlashTask());
+ ASSERT_TRUE(tasks[0]->AsFlashTask());
+ ASSERT_TRUE(tasks[1]->AsFlashTask());
+ ASSERT_TRUE(tasks[2]->AsRebootTask());
+ ASSERT_TRUE(tasks[3]->AsUpdateSuperTask());
+ ASSERT_TRUE(tasks[4]->AsWipeTask());
+}
+
+TEST_F(ParseTest, CorrectDriverCalls) {
+ fastboot::MockFastbootDriver fb;
+ fp->fb = &fb;
+
+ EXPECT_CALL(fb, RebootTo(_, _, _)).Times(1);
+ EXPECT_CALL(fb, Reboot(_, _)).Times(1);
+ EXPECT_CALL(fb, WaitForDisconnect()).Times(2);
+
+ std::vector<std::string> commands = {"reboot bootloader", "reboot"};
+ std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
+
+ for (auto& task : tasks) {
+ task->Run();
+ }
+}
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index db27cf0..d357e45 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -24,9 +24,8 @@
{
"name": "vab_legacy_tests"
},
- {
- "name": "vabc_legacy_tests"
- },
+ // TODO: b/279009697
+ //{"name": "vabc_legacy_tests"},
{
"name": "cow_api_test"
}
@@ -43,9 +42,8 @@
},
{
"name": "vab_legacy_tests"
- },
- {
- "name": "vabc_legacy_tests"
}
+ // TODO: b/279009697
+ //{"name": "vabc_legacy_tests"}
]
}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index ba75a8d..6baf4be 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -15,7 +15,9 @@
#pragma once
#include <stdint.h>
-#include <string>
+
+#include <optional>
+#include <string_view>
namespace android {
namespace snapshot {
@@ -26,9 +28,6 @@
static constexpr uint32_t kCowVersionManifest = 2;
-static constexpr size_t BLOCK_SZ = 4096;
-static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
// This header appears as the first sequence of bytes in the COW. All fields
// in the layout are little-endian encoded. The on-disk layout is:
//
@@ -196,5 +195,8 @@
// Ops that have dependencies on old blocks, and must take care in their merge order
bool IsOrderedOp(const CowOperation& op);
+// Convert compression name to internal value.
+std::optional<CowCompressionAlgorithm> CompressionAlgorithmFromString(std::string_view name);
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index e8e4d72..95a1270 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -29,42 +29,13 @@
class ICowOpIter;
-// A ByteSink object handles requests for a buffer of a specific size. It
-// always owns the underlying buffer. It's designed to minimize potential
-// copying as we parse or decompress the COW.
-class IByteSink {
- public:
- virtual ~IByteSink() {}
-
- // Called when the reader has data. The size of the request is given. The
- // sink must return a valid pointer (or null on failure), and return the
- // maximum number of bytes that can be written to the returned buffer.
- //
- // The returned buffer is owned by IByteSink, but must remain valid until
- // the read operation has completed (or the entire buffer has been
- // covered by calls to ReturnData).
- //
- // After calling GetBuffer(), all previous buffers returned are no longer
- // valid.
- //
- // GetBuffer() is intended to be sequential. A returned size of N indicates
- // that the output stream will advance by N bytes, and the ReturnData call
- // indicates that those bytes have been fulfilled. Therefore, it is
- // possible to have ReturnBuffer do nothing, if the implementation doesn't
- // care about incremental writes.
- virtual void* GetBuffer(size_t requested, size_t* actual) = 0;
-
- // Called when a section returned by |GetBuffer| has been filled with data.
- virtual bool ReturnData(void* buffer, size_t length) = 0;
-};
-
// Interface for reading from a snapuserd COW.
class ICowReader {
public:
virtual ~ICowReader() {}
// Return the file header.
- virtual bool GetHeader(CowHeader* header) = 0;
+ virtual CowHeader& GetHeader() = 0;
// Return the file footer.
virtual bool GetFooter(CowFooter* footer) = 0;
@@ -83,29 +54,43 @@
virtual std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress) = 0;
// Get decoded bytes from the data section, handling any decompression.
- // All retrieved data is passed to the sink.
- virtual bool ReadData(const CowOperation& op, IByteSink* sink) = 0;
+ //
+ // If ignore_bytes is non-zero, it specifies the initial number of bytes
+ // to skip writing to |buffer|.
+ //
+ // Returns the number of bytes written to |buffer|, or -1 on failure.
+ // errno is NOT set.
+ //
+ // Partial reads are not possible unless |buffer_size| is less than the
+ // operation block size.
+ //
+ // The operation pointer must derive from ICowOpIter::Get().
+ virtual ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
+ size_t ignore_bytes = 0) = 0;
};
-// Iterate over a sequence of COW operations.
+// Iterate over a sequence of COW operations. The iterator is bidirectional.
class ICowOpIter {
public:
virtual ~ICowOpIter() {}
- // True if there are no more items to read forward, false otherwise.
- virtual bool Done() = 0;
+ // Returns true if the iterator is at the end of the operation list.
+ // If true, Get() and Next() must not be called.
+ virtual bool AtEnd() = 0;
// Read the current operation.
- virtual const CowOperation& Get() = 0;
+ virtual const CowOperation* Get() = 0;
// Advance to the next item.
virtual void Next() = 0;
+ // Returns true if the iterator is at the beginning of the operation list.
+ // If true, Prev() must not be called; Get() however will be valid if
+ // AtEnd() is not true.
+ virtual bool AtBegin() = 0;
+
// Advance to the previous item.
virtual void Prev() = 0;
-
- // True if there are no more items to read backwards, false otherwise
- virtual bool RDone() = 0;
};
class CowReader final : public ICowReader {
@@ -126,7 +111,6 @@
bool InitForMerge(android::base::unique_fd&& fd);
bool VerifyMergeOps() override;
- bool GetHeader(CowHeader* header) override;
bool GetFooter(CowFooter* footer) override;
bool GetLastLabel(uint64_t* label) override;
@@ -139,7 +123,10 @@
std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
- bool ReadData(const CowOperation& op, IByteSink* sink) override;
+ ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
+ size_t ignore_bytes = 0) override;
+
+ CowHeader& GetHeader() override { return header_; }
bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
index 862ce55..edc9d65 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
@@ -24,6 +24,7 @@
#include <gtest/gtest.h>
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
+#include "cow_decompress.h"
using testing::AssertionFailure;
using testing::AssertionResult;
@@ -44,23 +45,10 @@
std::unique_ptr<TemporaryFile> cow_;
};
-// Sink that always appends to the end of a string.
-class StringSink : public IByteSink {
- public:
- void* GetBuffer(size_t requested, size_t* actual) override {
- size_t old_size = stream_.size();
- stream_.resize(old_size + requested, '\0');
- *actual = requested;
- return stream_.data() + old_size;
- }
- bool ReturnData(void*, size_t) override { return true; }
- void Reset() { stream_.clear(); }
-
- std::string& stream() { return stream_; }
-
- private:
- std::string stream_;
-};
+// Helper to check read sizes.
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
+ return reader.ReadData(op, buffer, size) == size;
+}
TEST_F(CowTest, CopyContiguous) {
CowOptions options;
@@ -74,24 +62,25 @@
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
CowReader reader;
- CowHeader header;
- CowFooter footer;
ASSERT_TRUE(reader.Parse(cow_->fd));
- ASSERT_TRUE(reader.GetHeader(&header));
- ASSERT_TRUE(reader.GetFooter(&footer));
+
+ const auto& header = reader.GetHeader();
ASSERT_EQ(header.magic, kCowMagicNumber);
ASSERT_EQ(header.major_version, kCowVersionMajor);
ASSERT_EQ(header.minor_version, kCowVersionMinor);
ASSERT_EQ(header.block_size, options.block_size);
+
+ CowFooter footer;
+ ASSERT_TRUE(reader.GetFooter(&footer));
ASSERT_EQ(footer.op.num_ops, 100);
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
+ ASSERT_FALSE(iter->AtEnd());
size_t i = 0;
- while (!iter->Done()) {
- auto op = &iter->Get();
+ while (!iter->AtEnd()) {
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowCopyOp);
ASSERT_EQ(op->compression, kCowCompressNone);
ASSERT_EQ(op->data_length, 0);
@@ -122,21 +111,22 @@
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
CowReader reader;
- CowHeader header;
- CowFooter footer;
ASSERT_TRUE(reader.Parse(cow_->fd));
- ASSERT_TRUE(reader.GetHeader(&header));
- ASSERT_TRUE(reader.GetFooter(&footer));
+
+ const auto& header = reader.GetHeader();
ASSERT_EQ(header.magic, kCowMagicNumber);
ASSERT_EQ(header.major_version, kCowVersionMajor);
ASSERT_EQ(header.minor_version, kCowVersionMinor);
ASSERT_EQ(header.block_size, options.block_size);
+
+ CowFooter footer;
+ ASSERT_TRUE(reader.GetFooter(&footer));
ASSERT_EQ(footer.op.num_ops, 4);
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowCopyOp);
ASSERT_EQ(op->compression, kCowCompressNone);
@@ -144,22 +134,22 @@
ASSERT_EQ(op->new_block, 10);
ASSERT_EQ(op->source, 20);
- StringSink sink;
+ std::string sink(data.size(), '\0');
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
ASSERT_EQ(op->compression, kCowCompressNone);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 50);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
// Note: the zero operation gets split into two blocks.
ASSERT_EQ(op->type, kCowZeroOp);
@@ -169,8 +159,8 @@
ASSERT_EQ(op->source, 0);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
ASSERT_EQ(op->compression, kCowCompressNone);
@@ -179,7 +169,7 @@
ASSERT_EQ(op->source, 0);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, ReadWriteXor) {
@@ -200,21 +190,22 @@
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
CowReader reader;
- CowHeader header;
- CowFooter footer;
ASSERT_TRUE(reader.Parse(cow_->fd));
- ASSERT_TRUE(reader.GetHeader(&header));
- ASSERT_TRUE(reader.GetFooter(&footer));
+
+ const auto& header = reader.GetHeader();
ASSERT_EQ(header.magic, kCowMagicNumber);
ASSERT_EQ(header.major_version, kCowVersionMajor);
ASSERT_EQ(header.minor_version, kCowVersionMinor);
ASSERT_EQ(header.block_size, options.block_size);
+
+ CowFooter footer;
+ ASSERT_TRUE(reader.GetFooter(&footer));
ASSERT_EQ(footer.op.num_ops, 4);
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowCopyOp);
ASSERT_EQ(op->compression, kCowCompressNone);
@@ -222,23 +213,23 @@
ASSERT_EQ(op->new_block, 10);
ASSERT_EQ(op->source, 20);
- StringSink sink;
+ std::string sink(data.size(), '\0');
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowXorOp);
ASSERT_EQ(op->compression, kCowCompressNone);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 50);
ASSERT_EQ(op->source, 98314); // 4096 * 24 + 10
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
// Note: the zero operation gets split into two blocks.
ASSERT_EQ(op->type, kCowZeroOp);
@@ -248,8 +239,8 @@
ASSERT_EQ(op->source, 0);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
ASSERT_EQ(op->compression, kCowCompressNone);
@@ -258,7 +249,7 @@
ASSERT_EQ(op->source, 0);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, CompressGz) {
@@ -282,25 +273,25 @@
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
- StringSink sink;
+ std::string sink(data.size(), '\0');
ASSERT_EQ(op->type, kCowReplaceOp);
ASSERT_EQ(op->compression, kCowCompressGz);
ASSERT_EQ(op->data_length, 56); // compressed!
ASSERT_EQ(op->new_block, 50);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
-class CompressionRWTest : public CowTest, public testing::WithParamInterface<const char*> {};
+class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
-TEST_P(CompressionRWTest, ThreadedBatchWrites) {
+TEST_P(CompressionTest, ThreadedBatchWrites) {
CowOptions options;
options.compression = GetParam();
options.num_compress_threads = 2;
@@ -337,36 +328,37 @@
ASSERT_NE(iter, nullptr);
int total_blocks = 0;
- while (!iter->Done()) {
- auto op = &iter->Get();
+ while (!iter->AtEnd()) {
+ auto op = iter->Get();
if (op->type == kCowXorOp) {
total_blocks += 1;
- StringSink sink;
+ std::string sink(xor_data.size(), '\0');
ASSERT_EQ(op->new_block, 50);
ASSERT_EQ(op->source, 98314); // 4096 * 24 + 10
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), xor_data);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, xor_data);
}
if (op->type == kCowReplaceOp) {
total_blocks += 1;
if (op->new_block == 100) {
- StringSink sink;
- ASSERT_TRUE(reader.ReadData(*op, &sink));
data.resize(options.block_size);
- ASSERT_EQ(sink.stream(), data);
+ std::string sink(data.size(), '\0');
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink.size(), data.size());
+ ASSERT_EQ(sink, data);
}
if (op->new_block == 6000) {
- StringSink sink;
- ASSERT_TRUE(reader.ReadData(*op, &sink));
data2.resize(options.block_size);
- ASSERT_EQ(sink.stream(), data2);
+ std::string sink(data2.size(), '\0');
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data2);
}
if (op->new_block == 9000) {
- StringSink sink;
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data3);
+ std::string sink(data3.size(), '\0');
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data3);
}
}
@@ -376,7 +368,7 @@
ASSERT_EQ(total_blocks, expected_blocks);
}
-TEST_P(CompressionRWTest, NoBatchWrites) {
+TEST_P(CompressionTest, NoBatchWrites) {
CowOptions options;
options.compression = GetParam();
options.num_compress_threads = 1;
@@ -410,27 +402,27 @@
ASSERT_NE(iter, nullptr);
int total_blocks = 0;
- while (!iter->Done()) {
- auto op = &iter->Get();
+ while (!iter->AtEnd()) {
+ auto op = iter->Get();
if (op->type == kCowReplaceOp) {
total_blocks += 1;
if (op->new_block == 50) {
- StringSink sink;
- ASSERT_TRUE(reader.ReadData(*op, &sink));
data.resize(options.block_size);
- ASSERT_EQ(sink.stream(), data);
+ std::string sink(data.size(), '\0');
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data);
}
if (op->new_block == 3000) {
- StringSink sink;
- ASSERT_TRUE(reader.ReadData(*op, &sink));
data2.resize(options.block_size);
- ASSERT_EQ(sink.stream(), data2);
+ std::string sink(data2.size(), '\0');
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data2);
}
if (op->new_block == 5000) {
- StringSink sink;
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data3);
+ std::string sink(data3.size(), '\0');
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data3);
}
}
@@ -440,7 +432,66 @@
ASSERT_EQ(total_blocks, expected_blocks);
}
-INSTANTIATE_TEST_SUITE_P(CowApi, CompressionRWTest, testing::Values("none", "gz", "brotli", "lz4"));
+template <typename T>
+class HorribleStream : public IByteStream {
+ public:
+ HorribleStream(const std::basic_string<T>& input) : input_(input) {}
+
+ ssize_t Read(void* buffer, size_t length) override {
+ if (pos_ >= input_.size()) {
+ return 0;
+ }
+ if (length) {
+ *reinterpret_cast<char*>(buffer) = input_[pos_];
+ }
+ pos_++;
+ return 1;
+ }
+ size_t Size() const override { return input_.size(); }
+
+ private:
+ std::basic_string<T> input_;
+ size_t pos_ = 0;
+};
+
+TEST(HorribleStream, ReadFully) {
+ std::string expected = "this is some data";
+
+ HorribleStream<char> stream(expected);
+
+ std::string buffer(expected.size(), '\0');
+ ASSERT_TRUE(stream.ReadFully(buffer.data(), buffer.size()));
+ ASSERT_EQ(buffer, expected);
+}
+
+TEST_P(CompressionTest, HorribleStream) {
+ if (strcmp(GetParam(), "none") == 0) {
+ GTEST_SKIP();
+ }
+
+ auto algorithm = CompressionAlgorithmFromString(GetParam());
+ ASSERT_TRUE(algorithm.has_value());
+
+ std::string expected = "The quick brown fox jumps over the lazy dog.";
+ expected.resize(4096, '\0');
+
+ auto result = CompressWorker::Compress(*algorithm, expected.data(), expected.size());
+ ASSERT_FALSE(result.empty());
+
+ HorribleStream<uint8_t> stream(result);
+ auto decomp = IDecompressor::FromString(GetParam());
+ ASSERT_NE(decomp, nullptr);
+ decomp->set_stream(&stream);
+
+ expected = expected.substr(10, 500);
+
+ std::string buffer(expected.size(), '\0');
+ ASSERT_EQ(decomp->Decompress(buffer.data(), 500, 4096, 10), 500);
+ ASSERT_EQ(buffer, expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(AllCompressors, CompressionTest,
+ testing::Values("none", "gz", "brotli", "lz4"));
TEST_F(CowTest, ClusterCompressGz) {
CowOptions options;
@@ -467,43 +518,44 @@
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
- StringSink sink;
+ std::string sink(data.size(), '\0');
ASSERT_EQ(op->type, kCowReplaceOp);
ASSERT_EQ(op->compression, kCowCompressGz);
ASSERT_EQ(op->data_length, 56); // compressed!
ASSERT_EQ(op->new_block, 50);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowClusterOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
- sink.Reset();
+ sink = {};
+ sink.resize(data2.size(), '\0');
ASSERT_EQ(op->compression, kCowCompressGz);
ASSERT_EQ(op->data_length, 41); // compressed!
ASSERT_EQ(op->new_block, 51);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data2);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data2);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowClusterOp);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, CompressTwoBlocks) {
@@ -527,59 +579,19 @@
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
+ ASSERT_FALSE(iter->AtEnd());
iter->Next();
- ASSERT_FALSE(iter->Done());
+ ASSERT_FALSE(iter->AtEnd());
- StringSink sink;
+ std::string sink(options.block_size, '\0');
- auto op = &iter->Get();
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
ASSERT_EQ(op->compression, kCowCompressGz);
ASSERT_EQ(op->new_block, 51);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
}
-// Only return 1-byte buffers, to stress test the partial read logic in
-// CowReader.
-class HorribleStringSink : public StringSink {
- public:
- void* GetBuffer(size_t, size_t* actual) override { return StringSink::GetBuffer(1, actual); }
-};
-
-class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
-
-TEST_P(CompressionTest, HorribleSink) {
- CowOptions options;
- options.compression = GetParam();
- options.cluster_ops = 0;
- CowWriter writer(options);
-
- ASSERT_TRUE(writer.Initialize(cow_->fd));
-
- std::string data = "This is some data, believe it";
- data.resize(options.block_size, '\0');
-
- ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
- ASSERT_TRUE(writer.Finalize());
-
- ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
-
- CowReader reader;
- ASSERT_TRUE(reader.Parse(cow_->fd));
-
- auto iter = reader.GetOpIter();
- ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
-
- HorribleStringSink sink;
- auto op = &iter->Get();
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data);
-}
-
-INSTANTIATE_TEST_SUITE_P(CowApi, CompressionTest, testing::Values("none", "gz", "brotli"));
-
TEST_F(CowTest, GetSize) {
CowOptions options;
options.cluster_ops = 0;
@@ -641,35 +653,36 @@
ASSERT_TRUE(reader.GetLastLabel(&label));
ASSERT_EQ(label, 3);
- StringSink sink;
+ std::string sink(data.size(), '\0');
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data);
iter->Next();
- sink.Reset();
+ sink = {};
+ sink.resize(data2.size(), '\0');
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 3);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data2);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data2);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, AppendLabelMissing) {
@@ -705,25 +718,23 @@
CowReader reader;
ASSERT_TRUE(reader.Parse(cow_->fd));
- StringSink sink;
-
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 0);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, AppendExtendedCorrupted) {
@@ -765,18 +776,16 @@
CowReader reader;
ASSERT_TRUE(reader.Parse(cow_->fd));
- StringSink sink;
-
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 5);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, AppendbyLabel) {
@@ -816,55 +825,55 @@
CowReader reader;
ASSERT_TRUE(reader.Parse(cow_->fd));
- StringSink sink;
+ std::string sink(options.block_size, '\0');
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data.substr(0, options.block_size));
iter->Next();
- sink.Reset();
+ sink = {};
+ sink.resize(options.block_size, '\0');
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data.substr(options.block_size, 2 * options.block_size));
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
iter->Next();
- sink.Reset();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 4);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 5);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, ClusterTest) {
@@ -897,72 +906,71 @@
CowReader reader;
ASSERT_TRUE(reader.Parse(cow_->fd));
- StringSink sink;
+ std::string sink(data.size(), '\0');
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data.substr(0, options.block_size));
iter->Next();
- sink.Reset();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 4);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowClusterOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowZeroOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 5);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowCopyOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowClusterOp);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 6);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, ClusterAppendTest) {
@@ -997,33 +1005,33 @@
ASSERT_TRUE(reader.GetLastLabel(&label));
ASSERT_EQ(label, 50);
- StringSink sink;
+ std::string sink(data2.size(), '\0');
auto iter = reader.GetOpIter();
ASSERT_NE(iter, nullptr);
- ASSERT_FALSE(iter->Done());
- auto op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ auto op = iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
ASSERT_EQ(op->source, 50);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
- ASSERT_TRUE(reader.ReadData(*op, &sink));
- ASSERT_EQ(sink.stream(), data2);
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+ ASSERT_EQ(sink, data2);
iter->Next();
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
+ ASSERT_FALSE(iter->AtEnd());
+ op = iter->Get();
ASSERT_EQ(op->type, kCowClusterOp);
iter->Next();
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, AppendAfterFinalize) {
@@ -1058,21 +1066,20 @@
return AssertionSuccess();
}
-AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation* op,
const std::string& data) {
- CowHeader header;
- reader->GetHeader(&header);
+ const auto& header = reader->GetHeader();
std::string cmp = data;
cmp.resize(header.block_size, '\0');
- StringSink sink;
- if (!reader->ReadData(op, &sink)) {
+ std::string sink(cmp.size(), '\0');
+ if (!reader->ReadData(op, sink.data(), sink.size())) {
return AssertionFailure() << "Failed to read data block";
}
- if (cmp != sink.stream()) {
+ if (cmp != sink) {
return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
- << sink.stream();
+ << sink;
}
return AssertionSuccess();
@@ -1111,18 +1118,18 @@
size_t max_in_cluster = 0;
size_t num_in_cluster = 0;
size_t num_clusters = 0;
- while (!iter->Done()) {
+ while (!iter->AtEnd()) {
const auto& op = iter->Get();
num_in_cluster++;
max_in_cluster = std::max(max_in_cluster, num_in_cluster);
- if (op.type == kCowReplaceOp) {
+ if (op->type == kCowReplaceOp) {
num_replace++;
- ASSERT_EQ(op.new_block, num_replace);
+ ASSERT_EQ(op->new_block, num_replace);
ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
- } else if (op.type == kCowClusterOp) {
+ } else if (op->type == kCowClusterOp) {
num_in_cluster = 0;
num_clusters++;
}
@@ -1172,18 +1179,18 @@
size_t max_in_cluster = 0;
size_t num_in_cluster = 0;
size_t num_clusters = 0;
- while (!iter->Done()) {
+ while (!iter->AtEnd()) {
const auto& op = iter->Get();
num_in_cluster++;
max_in_cluster = std::max(max_in_cluster, num_in_cluster);
- if (op.type == kCowReplaceOp) {
+ if (op->type == kCowReplaceOp) {
num_replace++;
- ASSERT_EQ(op.new_block, num_replace);
+ ASSERT_EQ(op->new_block, num_replace);
ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
- } else if (op.type == kCowClusterOp) {
+ } else if (op->type == kCowClusterOp) {
num_in_cluster = 0;
num_clusters++;
}
@@ -1224,17 +1231,17 @@
size_t max_in_cluster = 0;
size_t num_in_cluster = 0;
size_t num_clusters = 0;
- while (!iter->Done()) {
+ while (!iter->AtEnd()) {
const auto& op = iter->Get();
num_in_cluster++;
max_in_cluster = std::max(max_in_cluster, num_in_cluster);
- if (op.type == kCowReplaceOp) {
+ if (op->type == kCowReplaceOp) {
num_replace++;
- ASSERT_EQ(op.new_block, num_replace);
+ ASSERT_EQ(op->new_block, num_replace);
ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
- } else if (op.type == kCowClusterOp) {
+ } else if (op->type == kCowClusterOp) {
num_in_cluster = 0;
num_clusters++;
}
@@ -1268,14 +1275,14 @@
auto iter = reader.GetRevMergeOpIter();
for (int i = 0; i < seq_len; i++) {
- ASSERT_TRUE(!iter->Done());
+ ASSERT_TRUE(!iter->AtEnd());
const auto& op = iter->Get();
- ASSERT_EQ(op.new_block, seq_len - i);
+ ASSERT_EQ(op->new_block, seq_len - i);
iter->Next();
}
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, MissingSeqOp) {
@@ -1319,7 +1326,7 @@
auto reader = std::make_unique<CowReader>();
ASSERT_TRUE(reader->Parse(cow_->fd, 1));
auto itr = reader->GetRevMergeOpIter();
- ASSERT_TRUE(itr->Done());
+ ASSERT_TRUE(itr->AtEnd());
writer = std::make_unique<CowWriter>(options);
ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
@@ -1334,17 +1341,17 @@
auto iter = reader->GetRevMergeOpIter();
uint64_t expected_block = 10;
- while (!iter->Done() && expected_block > 0) {
- ASSERT_FALSE(iter->Done());
+ while (!iter->AtEnd() && expected_block > 0) {
+ ASSERT_FALSE(iter->AtEnd());
const auto& op = iter->Get();
- ASSERT_EQ(op.new_block, expected_block);
+ ASSERT_EQ(op->new_block, expected_block);
iter->Next();
expected_block--;
}
ASSERT_EQ(expected_block, 0);
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, RevMergeOpItrTest) {
@@ -1385,16 +1392,16 @@
auto iter = reader.GetRevMergeOpIter();
auto expected_new_block = revMergeOpSequence.begin();
- while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+ while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
const auto& op = iter->Get();
- ASSERT_EQ(op.new_block, *expected_new_block);
+ ASSERT_EQ(op->new_block, *expected_new_block);
iter->Next();
expected_new_block++;
}
ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, LegacyRevMergeOpItrTest) {
@@ -1434,16 +1441,16 @@
auto iter = reader.GetRevMergeOpIter();
auto expected_new_block = revMergeOpSequence.begin();
- while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+ while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
const auto& op = iter->Get();
- ASSERT_EQ(op.new_block, *expected_new_block);
+ ASSERT_EQ(op->new_block, *expected_new_block);
iter->Next();
expected_new_block++;
}
ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
- ASSERT_TRUE(iter->Done());
+ ASSERT_TRUE(iter->AtEnd());
}
TEST_F(CowTest, InvalidMergeOrderTest) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
index 9b50986..d06c904 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
@@ -32,6 +32,21 @@
namespace android {
namespace snapshot {
+
+std::optional<CowCompressionAlgorithm> CompressionAlgorithmFromString(std::string_view name) {
+ if (name == "gz") {
+ return {kCowCompressGz};
+ } else if (name == "brotli") {
+ return {kCowCompressBrotli};
+ } else if (name == "lz4") {
+ return {kCowCompressLz4};
+ } else if (name == "none" || name.empty()) {
+ return {kCowCompressNone};
+ } else {
+ return {};
+ }
+}
+
std::basic_string<uint8_t> CompressWorker::Compress(const void* data, size_t length) {
return Compress(compression_, data, length);
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
index 139a29f..3d34413 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
@@ -16,6 +16,7 @@
#include "cow_decompress.h"
+#include <array>
#include <utility>
#include <android-base/logging.h>
@@ -26,114 +27,108 @@
namespace android {
namespace snapshot {
-class NoDecompressor final : public IDecompressor {
- public:
- bool Decompress(size_t) override;
-};
+ssize_t IByteStream::ReadFully(void* buffer, size_t buffer_size) {
+ size_t stream_remaining = Size();
-bool NoDecompressor::Decompress(size_t) {
- size_t stream_remaining = stream_->Size();
+ char* buffer_start = reinterpret_cast<char*>(buffer);
+ char* buffer_pos = buffer_start;
+ size_t buffer_remaining = buffer_size;
while (stream_remaining) {
- size_t buffer_size = stream_remaining;
- uint8_t* buffer = reinterpret_cast<uint8_t*>(sink_->GetBuffer(buffer_size, &buffer_size));
- if (!buffer) {
- LOG(ERROR) << "Could not acquire buffer from sink";
- return false;
+ const size_t to_read = std::min(buffer_remaining, stream_remaining);
+ const ssize_t actual_read = Read(buffer_pos, to_read);
+ if (actual_read < 0) {
+ return -1;
}
+ if (!actual_read) {
+ LOG(ERROR) << "Stream ended prematurely";
+ return -1;
+ }
+ CHECK_LE(actual_read, to_read);
- // Read until we can fill the buffer.
- uint8_t* buffer_pos = buffer;
- size_t bytes_to_read = std::min(buffer_size, stream_remaining);
- while (bytes_to_read) {
- size_t read;
- if (!stream_->Read(buffer_pos, bytes_to_read, &read)) {
- return false;
- }
- if (!read) {
- LOG(ERROR) << "Stream ended prematurely";
- return false;
- }
- if (!sink_->ReturnData(buffer_pos, read)) {
- LOG(ERROR) << "Could not return buffer to sink";
- return false;
- }
- buffer_pos += read;
- bytes_to_read -= read;
- stream_remaining -= read;
- }
+ stream_remaining -= actual_read;
+ buffer_pos += actual_read;
+ buffer_remaining -= actual_read;
}
- return true;
+ return buffer_pos - buffer_start;
}
-std::unique_ptr<IDecompressor> IDecompressor::Uncompressed() {
- return std::unique_ptr<IDecompressor>(new NoDecompressor());
+std::unique_ptr<IDecompressor> IDecompressor::FromString(std::string_view compressor) {
+ if (compressor == "lz4") {
+ return IDecompressor::Lz4();
+ } else if (compressor == "brotli") {
+ return IDecompressor::Brotli();
+ } else if (compressor == "gz") {
+ return IDecompressor::Gz();
+ } else {
+ return nullptr;
+ }
}
// Read chunks of the COW and incrementally stream them to the decoder.
class StreamDecompressor : public IDecompressor {
public:
- bool Decompress(size_t output_bytes) override;
+ ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+ size_t ignore_bytes) override;
virtual bool Init() = 0;
- virtual bool DecompressInput(const uint8_t* data, size_t length) = 0;
- virtual bool Done() = 0;
+ virtual bool PartialDecompress(const uint8_t* data, size_t length) = 0;
+ bool OutputFull() const { return !ignore_bytes_ && !output_buffer_remaining_; }
protected:
- bool GetFreshBuffer();
-
- size_t output_bytes_;
size_t stream_remaining_;
uint8_t* output_buffer_ = nullptr;
size_t output_buffer_remaining_ = 0;
+ size_t ignore_bytes_ = 0;
+ bool decompressor_ended_ = false;
};
static constexpr size_t kChunkSize = 4096;
-bool StreamDecompressor::Decompress(size_t output_bytes) {
+ssize_t StreamDecompressor::Decompress(void* buffer, size_t buffer_size, size_t,
+ size_t ignore_bytes) {
if (!Init()) {
return false;
}
stream_remaining_ = stream_->Size();
- output_bytes_ = output_bytes;
+ output_buffer_ = reinterpret_cast<uint8_t*>(buffer);
+ output_buffer_remaining_ = buffer_size;
+ ignore_bytes_ = ignore_bytes;
uint8_t chunk[kChunkSize];
- while (stream_remaining_) {
- size_t read = std::min(stream_remaining_, sizeof(chunk));
- if (!stream_->Read(chunk, read, &read)) {
- return false;
+ while (stream_remaining_ && output_buffer_remaining_ && !decompressor_ended_) {
+ size_t max_read = std::min(stream_remaining_, sizeof(chunk));
+ ssize_t read = stream_->Read(chunk, max_read);
+ if (read < 0) {
+ return -1;
}
if (!read) {
LOG(ERROR) << "Stream ended prematurely";
- return false;
+ return -1;
}
- if (!DecompressInput(chunk, read)) {
- return false;
+ if (!PartialDecompress(chunk, read)) {
+ return -1;
}
-
stream_remaining_ -= read;
+ }
- if (stream_remaining_ && Done()) {
+ if (stream_remaining_) {
+ if (decompressor_ended_ && !OutputFull()) {
+ // If there's more input in the stream, but we haven't finished
+ // consuming ignored bytes or available output space yet, then
+ // something weird happened. Report it and fail.
LOG(ERROR) << "Decompressor terminated early";
- return false;
+ return -1;
+ }
+ } else {
+ if (!decompressor_ended_ && !OutputFull()) {
+ // The stream ended, but the decoder doesn't think so, and there are
+ // more bytes in the output buffer.
+ LOG(ERROR) << "Decompressor expected more bytes";
+ return -1;
}
}
- if (!Done()) {
- LOG(ERROR) << "Decompressor expected more bytes";
- return false;
- }
- return true;
-}
-
-bool StreamDecompressor::GetFreshBuffer() {
- size_t request_size = std::min(output_bytes_, kChunkSize);
- output_buffer_ =
- reinterpret_cast<uint8_t*>(sink_->GetBuffer(request_size, &output_buffer_remaining_));
- if (!output_buffer_) {
- LOG(ERROR) << "Could not acquire buffer from sink";
- return false;
- }
- return true;
+ return buffer_size - output_buffer_remaining_;
}
class GzDecompressor final : public StreamDecompressor {
@@ -141,12 +136,10 @@
~GzDecompressor();
bool Init() override;
- bool DecompressInput(const uint8_t* data, size_t length) override;
- bool Done() override { return ended_; }
+ bool PartialDecompress(const uint8_t* data, size_t length) override;
private:
z_stream z_ = {};
- bool ended_ = false;
};
bool GzDecompressor::Init() {
@@ -161,23 +154,39 @@
inflateEnd(&z_);
}
-bool GzDecompressor::DecompressInput(const uint8_t* data, size_t length) {
+bool GzDecompressor::PartialDecompress(const uint8_t* data, size_t length) {
z_.next_in = reinterpret_cast<Bytef*>(const_cast<uint8_t*>(data));
z_.avail_in = length;
- while (z_.avail_in) {
- // If no more output buffer, grab a new buffer.
- if (z_.avail_out == 0) {
- if (!GetFreshBuffer()) {
- return false;
- }
- z_.next_out = reinterpret_cast<Bytef*>(output_buffer_);
- z_.avail_out = output_buffer_remaining_;
+ // If we're asked to ignore starting bytes, we sink those into the output
+ // repeatedly until there is nothing left to ignore.
+ while (ignore_bytes_ && z_.avail_in) {
+ std::array<Bytef, kChunkSize> ignore_buffer;
+ size_t max_ignore = std::min(ignore_bytes_, ignore_buffer.size());
+ z_.next_out = ignore_buffer.data();
+ z_.avail_out = max_ignore;
+
+ int rv = inflate(&z_, Z_NO_FLUSH);
+ if (rv != Z_OK && rv != Z_STREAM_END) {
+ LOG(ERROR) << "inflate returned error code " << rv;
+ return false;
}
- // Remember the position of the output buffer so we can call ReturnData.
- auto avail_out = z_.avail_out;
+ size_t returned = max_ignore - z_.avail_out;
+ CHECK_LE(returned, ignore_bytes_);
+ ignore_bytes_ -= returned;
+
+ if (rv == Z_STREAM_END) {
+ decompressor_ended_ = true;
+ return true;
+ }
+ }
+
+ z_.next_out = reinterpret_cast<Bytef*>(output_buffer_);
+ z_.avail_out = output_buffer_remaining_;
+
+ while (z_.avail_in && z_.avail_out) {
// Decompress.
int rv = inflate(&z_, Z_NO_FLUSH);
if (rv != Z_OK && rv != Z_STREAM_END) {
@@ -185,20 +194,14 @@
return false;
}
- size_t returned = avail_out - z_.avail_out;
- if (!sink_->ReturnData(output_buffer_, returned)) {
- LOG(ERROR) << "Could not return buffer to sink";
- return false;
- }
+ size_t returned = output_buffer_remaining_ - z_.avail_out;
+ CHECK_LE(returned, output_buffer_remaining_);
+
output_buffer_ += returned;
output_buffer_remaining_ -= returned;
if (rv == Z_STREAM_END) {
- if (z_.avail_in) {
- LOG(ERROR) << "Gz stream ended prematurely";
- return false;
- }
- ended_ = true;
+ decompressor_ended_ = true;
return true;
}
}
@@ -214,8 +217,7 @@
~BrotliDecompressor();
bool Init() override;
- bool DecompressInput(const uint8_t* data, size_t length) override;
- bool Done() override { return BrotliDecoderIsFinished(decoder_); }
+ bool PartialDecompress(const uint8_t* data, size_t length) override;
private:
BrotliDecoderState* decoder_ = nullptr;
@@ -232,28 +234,41 @@
}
}
-bool BrotliDecompressor::DecompressInput(const uint8_t* data, size_t length) {
+bool BrotliDecompressor::PartialDecompress(const uint8_t* data, size_t length) {
size_t available_in = length;
const uint8_t* next_in = data;
- bool needs_more_output = false;
- while (available_in || needs_more_output) {
- if (!output_buffer_remaining_ && !GetFreshBuffer()) {
+ while (available_in && ignore_bytes_ && !BrotliDecoderIsFinished(decoder_)) {
+ std::array<uint8_t, kChunkSize> ignore_buffer;
+ size_t max_ignore = std::min(ignore_bytes_, ignore_buffer.size());
+ size_t ignore_size = max_ignore;
+
+ uint8_t* ignore_buffer_ptr = ignore_buffer.data();
+ auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in, &ignore_size,
+ &ignore_buffer_ptr, nullptr);
+ if (r == BROTLI_DECODER_RESULT_ERROR) {
+ LOG(ERROR) << "brotli decode failed";
+ return false;
+ } else if (r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && available_in) {
+ LOG(ERROR) << "brotli unexpected needs more input";
return false;
}
+ ignore_bytes_ -= max_ignore - ignore_size;
+ }
- auto output_buffer = output_buffer_;
+ while (available_in && !BrotliDecoderIsFinished(decoder_)) {
auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in,
&output_buffer_remaining_, &output_buffer_, nullptr);
if (r == BROTLI_DECODER_RESULT_ERROR) {
LOG(ERROR) << "brotli decode failed";
return false;
- }
- if (!sink_->ReturnData(output_buffer, output_buffer_ - output_buffer)) {
+ } else if (r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && available_in) {
+ LOG(ERROR) << "brotli unexpected needs more input";
return false;
}
- needs_more_output = (r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
}
+
+ decompressor_ended_ = BrotliDecoderIsFinished(decoder_);
return true;
}
@@ -265,45 +280,59 @@
public:
~Lz4Decompressor() override = default;
- bool Decompress(const size_t output_size) override {
- size_t actual_buffer_size = 0;
- auto&& output_buffer = sink_->GetBuffer(output_size, &actual_buffer_size);
- if (actual_buffer_size != output_size) {
- LOG(ERROR) << "Failed to allocate buffer of size " << output_size << " only got "
- << actual_buffer_size << " bytes";
- return false;
+ ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+ size_t ignore_bytes) override {
+ std::string input_buffer(stream_->Size(), '\0');
+ ssize_t streamed_in = stream_->ReadFully(input_buffer.data(), input_buffer.size());
+ if (streamed_in < 0) {
+ return -1;
}
- // If input size is same as output size, then input is uncompressed.
- if (stream_->Size() == output_size) {
- size_t bytes_read = 0;
- stream_->Read(output_buffer, output_size, &bytes_read);
- if (bytes_read != output_size) {
- LOG(ERROR) << "Failed to read all input at once. Expected: " << output_size
- << " actual: " << bytes_read;
- return false;
+ CHECK_EQ(streamed_in, stream_->Size());
+
+ char* decode_buffer = reinterpret_cast<char*>(buffer);
+ size_t decode_buffer_size = buffer_size;
+
+ // It's unclear if LZ4 can exactly satisfy a partial decode request, so
+ // if we get one, create a temporary buffer.
+ std::string temp;
+ if (buffer_size < decompressed_size) {
+ temp.resize(decompressed_size, '\0');
+ decode_buffer = temp.data();
+ decode_buffer_size = temp.size();
+ }
+
+ const int bytes_decompressed = LZ4_decompress_safe(input_buffer.data(), decode_buffer,
+ input_buffer.size(), decode_buffer_size);
+ if (bytes_decompressed < 0) {
+ LOG(ERROR) << "Failed to decompress LZ4 block, code: " << bytes_decompressed;
+ return -1;
+ }
+ if (bytes_decompressed != decompressed_size) {
+ LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: "
+ << bytes_decompressed << ", actual: " << bytes_decompressed;
+ return -1;
+ }
+ CHECK_LE(bytes_decompressed, decode_buffer_size);
+
+ if (ignore_bytes > bytes_decompressed) {
+ LOG(ERROR) << "Ignoring more bytes than exist in stream (ignoring " << ignore_bytes
+ << ", got " << bytes_decompressed << ")";
+ return -1;
+ }
+
+ if (temp.empty()) {
+ // LZ4's API has no way to sink out the first N bytes of decoding,
+ // so we read them all in and memmove() to drop the partial read.
+ if (ignore_bytes) {
+ memmove(decode_buffer, decode_buffer + ignore_bytes,
+ bytes_decompressed - ignore_bytes);
}
- sink_->ReturnData(output_buffer, output_size);
- return true;
+ return bytes_decompressed - ignore_bytes;
}
- std::string input_buffer;
- input_buffer.resize(stream_->Size());
- size_t bytes_read = 0;
- stream_->Read(input_buffer.data(), input_buffer.size(), &bytes_read);
- if (bytes_read != input_buffer.size()) {
- LOG(ERROR) << "Failed to read all input at once. Expected: " << input_buffer.size()
- << " actual: " << bytes_read;
- return false;
- }
- const int bytes_decompressed =
- LZ4_decompress_safe(input_buffer.data(), static_cast<char*>(output_buffer),
- input_buffer.size(), output_size);
- if (bytes_decompressed != output_size) {
- LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: " << output_size
- << ", actual: " << bytes_decompressed;
- return false;
- }
- sink_->ReturnData(output_buffer, output_size);
- return true;
+
+ size_t max_copy = std::min(bytes_decompressed - ignore_bytes, buffer_size);
+ memcpy(buffer, temp.data() + ignore_bytes, max_copy);
+ return max_copy;
}
};
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
index 7f74eda..9e83f3c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
@@ -26,11 +26,16 @@
virtual ~IByteStream() {}
// Read up to |length| bytes, storing the number of bytes read in the out-
- // parameter. If the end of the stream is reached, 0 is returned.
- virtual bool Read(void* buffer, size_t length, size_t* read) = 0;
+ // parameter. If the end of the stream is reached, 0 is returned. On error,
+ // -1 is returned. errno is NOT set.
+ virtual ssize_t Read(void* buffer, size_t length) = 0;
// Size of the stream.
virtual size_t Size() const = 0;
+
+ // Helper for Read(). Read the entire stream into |buffer|, up to |length|
+ // bytes.
+ ssize_t ReadFully(void* buffer, size_t length);
};
class IDecompressor {
@@ -43,15 +48,22 @@
static std::unique_ptr<IDecompressor> Brotli();
static std::unique_ptr<IDecompressor> Lz4();
- // |output_bytes| is the expected total number of bytes to sink.
- virtual bool Decompress(size_t output_bytes) = 0;
+ static std::unique_ptr<IDecompressor> FromString(std::string_view compressor);
+
+ // Decompress at most |buffer_size| bytes, ignoring the first |ignore_bytes|
+ // of the decoded stream. |buffer_size| must be at least one byte.
+ // |decompressed_size| is the expected total size if the entire stream were
+ // decompressed.
+ //
+ // Returns the number of bytes written to |buffer|, or -1 on error. errno
+ // is NOT set.
+ virtual ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+ size_t ignore_bytes = 0) = 0;
void set_stream(IByteStream* stream) { stream_ = stream; }
- void set_sink(IByteSink* sink) { sink_ = sink; }
protected:
IByteStream* stream_ = nullptr;
- IByteSink* sink_ = nullptr;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 94c4109..2157d0f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -57,8 +57,6 @@
os << "data_length:" << op.data_length << ",\t";
os << "new_block:" << op.new_block << ",\t";
os << "source:" << op.source;
- if (op.type == kCowXorOp)
- os << " (block:" << op.source / BLOCK_SZ << " offset:" << op.source % BLOCK_SZ << ")";
os << ")";
return os;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 45be191..4c5a8bf 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -508,47 +508,42 @@
bool CowReader::VerifyMergeOps() {
auto itr = GetMergeOpIter(true);
- std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
- while (!itr->Done()) {
- CowOperation op = itr->Get();
+ std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
+ while (!itr->AtEnd()) {
+ const auto& op = itr->Get();
uint64_t block;
bool offset;
- if (op.type == kCowCopyOp) {
- block = op.source;
+ if (op->type == kCowCopyOp) {
+ block = op->source;
offset = false;
- } else if (op.type == kCowXorOp) {
- block = op.source / BLOCK_SZ;
- offset = (op.source % BLOCK_SZ) != 0;
+ } else if (op->type == kCowXorOp) {
+ block = op->source / header_.block_size;
+ offset = (op->source % header_.block_size) != 0;
} else {
itr->Next();
continue;
}
- CowOperation* overwrite = nullptr;
+ const CowOperation* overwrite = nullptr;
if (overwritten_blocks.count(block)) {
- overwrite = &overwritten_blocks[block];
+ overwrite = overwritten_blocks[block];
LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
<< op << "\noverwritten by previously merged op:\n"
<< *overwrite;
}
if (offset && overwritten_blocks.count(block + 1)) {
- overwrite = &overwritten_blocks[block + 1];
+ overwrite = overwritten_blocks[block + 1];
LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
<< op << "\noverwritten by previously merged op:\n"
<< *overwrite;
}
if (overwrite != nullptr) return false;
- overwritten_blocks[op.new_block] = op;
+ overwritten_blocks[op->new_block] = op;
itr->Next();
}
return true;
}
-bool CowReader::GetHeader(CowHeader* header) {
- *header = header_;
- return true;
-}
-
bool CowReader::GetFooter(CowFooter* footer) {
if (!footer_) return false;
*footer = footer_.value();
@@ -565,12 +560,12 @@
public:
CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start);
- bool Done() override;
- const CowOperation& Get() override;
+ bool AtEnd() override;
+ const CowOperation* Get() override;
void Next() override;
void Prev() override;
- bool RDone() override;
+ bool AtBegin() override;
private:
std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -582,27 +577,27 @@
op_iter_ = ops_->begin() + start;
}
-bool CowOpIter::RDone() {
+bool CowOpIter::AtBegin() {
return op_iter_ == ops_->begin();
}
void CowOpIter::Prev() {
- CHECK(!RDone());
+ CHECK(!AtBegin());
op_iter_--;
}
-bool CowOpIter::Done() {
+bool CowOpIter::AtEnd() {
return op_iter_ == ops_->end();
}
void CowOpIter::Next() {
- CHECK(!Done());
+ CHECK(!AtEnd());
op_iter_++;
}
-const CowOperation& CowOpIter::Get() {
- CHECK(!Done());
- return (*op_iter_);
+const CowOperation* CowOpIter::Get() {
+ CHECK(!AtEnd());
+ return &(*op_iter_);
}
class CowRevMergeOpIter final : public ICowOpIter {
@@ -610,12 +605,12 @@
explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
- bool Done() override;
- const CowOperation& Get() override;
+ bool AtEnd() override;
+ const CowOperation* Get() override;
void Next() override;
void Prev() override;
- bool RDone() override;
+ bool AtBegin() override;
private:
std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -629,12 +624,12 @@
explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
- bool Done() override;
- const CowOperation& Get() override;
+ bool AtEnd() override;
+ const CowOperation* Get() override;
void Next() override;
void Prev() override;
- bool RDone() override;
+ bool AtBegin() override;
private:
std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -651,27 +646,27 @@
block_iter_ = cow_op_index_vec_->begin() + start;
}
-bool CowMergeOpIter::RDone() {
+bool CowMergeOpIter::AtBegin() {
return block_iter_ == cow_op_index_vec_->begin();
}
void CowMergeOpIter::Prev() {
- CHECK(!RDone());
+ CHECK(!AtBegin());
block_iter_--;
}
-bool CowMergeOpIter::Done() {
+bool CowMergeOpIter::AtEnd() {
return block_iter_ == cow_op_index_vec_->end();
}
void CowMergeOpIter::Next() {
- CHECK(!Done());
+ CHECK(!AtEnd());
block_iter_++;
}
-const CowOperation& CowMergeOpIter::Get() {
- CHECK(!Done());
- return ops_->data()[*block_iter_];
+const CowOperation* CowMergeOpIter::Get() {
+ CHECK(!AtEnd());
+ return &ops_->data()[*block_iter_];
}
CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
@@ -683,27 +678,27 @@
block_riter_ = cow_op_index_vec_->rbegin();
}
-bool CowRevMergeOpIter::RDone() {
+bool CowRevMergeOpIter::AtBegin() {
return block_riter_ == cow_op_index_vec_->rbegin();
}
void CowRevMergeOpIter::Prev() {
- CHECK(!RDone());
+ CHECK(!AtBegin());
block_riter_--;
}
-bool CowRevMergeOpIter::Done() {
+bool CowRevMergeOpIter::AtEnd() {
return block_riter_ == cow_op_index_vec_->rend() - start_;
}
void CowRevMergeOpIter::Next() {
- CHECK(!Done());
+ CHECK(!AtEnd());
block_riter_++;
}
-const CowOperation& CowRevMergeOpIter::Get() {
- CHECK(!Done());
- return ops_->data()[*block_riter_];
+const CowOperation* CowRevMergeOpIter::Get() {
+ CHECK(!AtEnd());
+ return &ops_->data()[*block_riter_];
}
std::unique_ptr<ICowOpIter> CowReader::GetOpIter(bool merge_progress) {
@@ -747,18 +742,18 @@
remaining_ = data_length_;
}
- bool Read(void* buffer, size_t length, size_t* read) override {
+ ssize_t Read(void* buffer, size_t length) override {
size_t to_read = std::min(length, remaining_);
if (!to_read) {
- *read = 0;
- return true;
+ return 0;
}
- if (!reader_->GetRawBytes(offset_, buffer, to_read, read)) {
- return false;
+ size_t read;
+ if (!reader_->GetRawBytes(offset_, buffer, to_read, &read)) {
+ return -1;
}
- offset_ += *read;
- remaining_ -= *read;
- return true;
+ offset_ += read;
+ remaining_ -= read;
+ return read;
}
size_t Size() const override { return data_length_; }
@@ -770,11 +765,11 @@
size_t remaining_;
};
-bool CowReader::ReadData(const CowOperation& op, IByteSink* sink) {
+ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
+ size_t ignore_bytes) {
std::unique_ptr<IDecompressor> decompressor;
- switch (op.compression) {
+ switch (op->compression) {
case kCowCompressNone:
- decompressor = IDecompressor::Uncompressed();
break;
case kCowCompressGz:
decompressor = IDecompressor::Gz();
@@ -783,23 +778,30 @@
decompressor = IDecompressor::Brotli();
break;
case kCowCompressLz4:
- decompressor = IDecompressor::Lz4();
+ if (header_.block_size != op->data_length) {
+ decompressor = IDecompressor::Lz4();
+ }
break;
default:
- LOG(ERROR) << "Unknown compression type: " << op.compression;
- return false;
+ LOG(ERROR) << "Unknown compression type: " << op->compression;
+ return -1;
}
uint64_t offset;
- if (op.type == kCowXorOp) {
- offset = data_loc_->at(op.new_block);
+ if (op->type == kCowXorOp) {
+ offset = data_loc_->at(op->new_block);
} else {
- offset = op.source;
+ offset = op->source;
}
- CowDataStream stream(this, offset, op.data_length);
+
+ if (!decompressor) {
+ CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes);
+ return stream.ReadFully(buffer, buffer_size);
+ }
+
+ CowDataStream stream(this, offset, op->data_length);
decompressor->set_stream(&stream);
- decompressor->set_sink(sink);
- return decompressor->Decompress(header_.block_size);
+ return decompressor->Decompress(buffer, buffer_size, header_.block_size, ignore_bytes);
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 56b48f0..0e18979 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -37,6 +37,14 @@
#include <sys/ioctl.h>
#include <unistd.h>
+// The info messages here are spammy, but as useful for update_engine. Disable
+// them when running on the host.
+#ifdef __ANDROID__
+#define LOG_INFO LOG(INFO)
+#else
+#define LOG_INFO LOG(VERBOSE)
+#endif
+
namespace android {
namespace snapshot {
@@ -194,18 +202,13 @@
}
bool CowWriter::ParseOptions() {
- if (options_.compression == "gz") {
- compression_ = kCowCompressGz;
- } else if (options_.compression == "brotli") {
- compression_ = kCowCompressBrotli;
- } else if (options_.compression == "lz4") {
- compression_ = kCowCompressLz4;
- } else if (options_.compression == "none") {
- compression_ = kCowCompressNone;
- } else if (!options_.compression.empty()) {
+ auto algorithm = CompressionAlgorithmFromString(options_.compression);
+ if (!algorithm) {
LOG(ERROR) << "unrecognized compression: " << options_.compression;
return false;
}
+ compression_ = *algorithm;
+
if (options_.cluster_ops == 1) {
LOG(ERROR) << "Clusters must contain at least two operations to function.";
return false;
@@ -239,10 +242,10 @@
return false;
}
cow_image_size_ = size_in_bytes;
- LOG(INFO) << "COW image " << file_path << " has size " << size_in_bytes;
+ LOG_INFO << "COW image " << file_path << " has size " << size_in_bytes;
} else {
- LOG(INFO) << "COW image " << file_path
- << " is not a block device, assuming unlimited space.";
+ LOG_INFO << "COW image " << file_path
+ << " is not a block device, assuming unlimited space.";
}
}
return true;
@@ -271,12 +274,12 @@
}
std::string batch_write = batch_write_ ? "enabled" : "disabled";
- LOG(INFO) << "Batch writes: " << batch_write;
+ LOG_INFO << "Batch writes: " << batch_write;
}
void CowWriter::InitWorkers() {
if (num_compress_threads_ <= 1) {
- LOG(INFO) << "Not creating new threads for compression.";
+ LOG_INFO << "Not creating new threads for compression.";
return;
}
for (int i = 0; i < num_compress_threads_; i++) {
@@ -285,7 +288,7 @@
compress_threads_.push_back(std::move(wt));
}
- LOG(INFO) << num_compress_threads_ << " thread used for compression";
+ LOG_INFO << num_compress_threads_ << " thread used for compression";
}
bool CowWriter::Initialize(unique_fd&& fd) {
@@ -389,10 +392,11 @@
auto reader = std::make_unique<CowReader>();
std::queue<CowOperation> toAdd;
- if (!reader->Parse(fd_, {label}) || !reader->GetHeader(&header_)) {
+ if (!reader->Parse(fd_, {label})) {
return false;
}
+ header_ = reader->GetHeader();
options_.block_size = header_.block_size;
options_.cluster_ops = header_.cluster_ops;
@@ -402,8 +406,8 @@
auto iter = reader->GetOpIter();
- while (!iter->Done()) {
- AddOperation(iter->Get());
+ while (!iter->AtEnd()) {
+ AddOperation(*iter->Get());
iter->Next();
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 167ff8c..917cec4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -16,6 +16,7 @@
#include <stdio.h>
#include <unistd.h>
+#include <chrono>
#include <iomanip>
#include <iostream>
#include <string>
@@ -38,16 +39,16 @@
}
static void usage(void) {
- LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
- LOG(ERROR) << "\t -s Run Silent";
- LOG(ERROR) << "\t -d Attempt to decompress";
- LOG(ERROR) << "\t -b Show data for failed decompress";
- LOG(ERROR) << "\t -l Show ops";
- LOG(ERROR) << "\t -m Show ops in reverse merge order";
- LOG(ERROR) << "\t -n Show ops in merge order";
- LOG(ERROR) << "\t -a Include merged ops in any merge order listing";
- LOG(ERROR) << "\t -o Shows sequence op block order";
- LOG(ERROR) << "\t -v Verifies merge order has no conflicts\n";
+ std::cerr << "Usage: inspect_cow [-sd] <COW_FILE>\n";
+ std::cerr << "\t -s Run Silent\n";
+ std::cerr << "\t -d Attempt to decompress\n";
+ std::cerr << "\t -b Show data for failed decompress\n";
+ std::cerr << "\t -l Show ops\n";
+ std::cerr << "\t -m Show ops in reverse merge order\n";
+ std::cerr << "\t -n Show ops in merge order\n";
+ std::cerr << "\t -a Include merged ops in any merge order listing\n";
+ std::cerr << "\t -o Shows sequence op block order\n";
+ std::cerr << "\t -v Verifies merge order has no conflicts\n";
}
enum OpIter { Normal, RevMerge, Merge };
@@ -63,37 +64,19 @@
bool include_merged;
};
-// Sink that always appends to the end of a string.
-class StringSink : public IByteSink {
- public:
- void* GetBuffer(size_t requested, size_t* actual) override {
- size_t old_size = stream_.size();
- stream_.resize(old_size + requested, '\0');
- *actual = requested;
- return stream_.data() + old_size;
- }
- bool ReturnData(void*, size_t) override { return true; }
- void Reset() { stream_.clear(); }
-
- std::string& stream() { return stream_; }
-
- private:
- std::string stream_;
-};
-
-static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+static void ShowBad(CowReader& reader, const struct CowOperation* op) {
size_t count;
- auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+ auto buffer = std::make_unique<uint8_t[]>(op->data_length);
- if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+ if (!reader.GetRawBytes(op->source, buffer.get(), op->data_length, &count)) {
std::cerr << "Failed to read at all!\n";
} else {
std::cout << "The Block data is:\n";
- for (int i = 0; i < op.data_length; i++) {
+ for (int i = 0; i < op->data_length; i++) {
std::cout << std::hex << (int)buffer[i];
}
std::cout << std::dec << "\n\n";
- if (op.data_length >= sizeof(CowOperation)) {
+ if (op->data_length >= sizeof(CowOperation)) {
std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
}
}
@@ -107,37 +90,40 @@
}
CowReader reader;
+
+ auto start_time = std::chrono::steady_clock::now();
if (!reader.Parse(fd)) {
LOG(ERROR) << "parse failed: " << path;
return false;
}
+ std::chrono::duration<double> parse_time = std::chrono::steady_clock::now() - start_time;
- CowHeader header;
- if (!reader.GetHeader(&header)) {
- LOG(ERROR) << "could not get header: " << path;
- return false;
- }
+ const CowHeader& header = reader.GetHeader();
CowFooter footer;
bool has_footer = false;
if (reader.GetFooter(&footer)) has_footer = true;
if (!opt.silent) {
- std::cout << "Major version: " << header.major_version << "\n";
- std::cout << "Minor version: " << header.minor_version << "\n";
+ std::cout << "Version: " << header.major_version << "." << header.minor_version << "\n";
std::cout << "Header size: " << header.header_size << "\n";
std::cout << "Footer size: " << header.footer_size << "\n";
std::cout << "Block size: " << header.block_size << "\n";
- std::cout << "Num merge ops: " << header.num_merge_ops << "\n";
- std::cout << "RA buffer size: " << header.buffer_size << "\n";
- std::cout << "\n";
+ std::cout << "Merge ops: " << header.num_merge_ops << "\n";
+ std::cout << "Readahead buffer: " << header.buffer_size << " bytes\n";
if (has_footer) {
- std::cout << "Total Ops size: " << footer.op.ops_size << "\n";
- std::cout << "Number of Ops: " << footer.op.num_ops << "\n";
- std::cout << "\n";
+ std::cout << "Footer: ops usage: " << footer.op.ops_size << " bytes\n";
+ std::cout << "Footer: op count: " << footer.op.num_ops << "\n";
+ } else {
+ std::cout << "Footer: none\n";
}
}
+ if (!opt.silent) {
+ std::cout << "Parse time: " << (parse_time.count() * 1000) << "ms\n";
+ }
+
if (opt.verify_sequence) {
+ std::cout << "\n";
if (reader.VerifyMergeOps()) {
std::cout << "\nMerge sequence is consistent.\n";
} else {
@@ -153,34 +139,35 @@
} else if (opt.iter_type == Merge) {
iter = reader.GetMergeOpIter(opt.include_merged);
}
- StringSink sink;
+
+ std::string buffer(header.block_size, '\0');
+
bool success = true;
uint64_t xor_ops = 0, copy_ops = 0, replace_ops = 0, zero_ops = 0;
- while (!iter->Done()) {
- const CowOperation& op = iter->Get();
+ while (!iter->AtEnd()) {
+ const CowOperation* op = iter->Get();
- if (!opt.silent && opt.show_ops) std::cout << op << "\n";
+ if (!opt.silent && opt.show_ops) std::cout << *op << "\n";
- if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
- if (!reader.ReadData(op, &sink)) {
- std::cerr << "Failed to decompress for :" << op << "\n";
+ if (opt.decompress && op->type == kCowReplaceOp && op->compression != kCowCompressNone) {
+ if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
+ std::cerr << "Failed to decompress for :" << *op << "\n";
success = false;
if (opt.show_bad) ShowBad(reader, op);
}
- sink.Reset();
}
- if (op.type == kCowSequenceOp && opt.show_seq) {
+ if (op->type == kCowSequenceOp && opt.show_seq) {
size_t read;
std::vector<uint32_t> merge_op_blocks;
- size_t seq_len = op.data_length / sizeof(uint32_t);
+ size_t seq_len = op->data_length / sizeof(uint32_t);
merge_op_blocks.resize(seq_len);
- if (!reader.GetRawBytes(op.source, merge_op_blocks.data(), op.data_length, &read)) {
+ if (!reader.GetRawBytes(op->source, merge_op_blocks.data(), op->data_length, &read)) {
PLOG(ERROR) << "Failed to read sequence op!";
return false;
}
if (!opt.silent) {
- std::cout << "Sequence for " << op << " is :\n";
+ std::cout << "Sequence for " << *op << " is :\n";
for (size_t i = 0; i < seq_len; i++) {
std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", ";
if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n";
@@ -188,13 +175,13 @@
}
}
- if (op.type == kCowCopyOp) {
+ if (op->type == kCowCopyOp) {
copy_ops++;
- } else if (op.type == kCowReplaceOp) {
+ } else if (op->type == kCowReplaceOp) {
replace_ops++;
- } else if (op.type == kCowZeroOp) {
+ } else if (op->type == kCowZeroOp) {
zero_ops++;
- } else if (op.type == kCowXorOp) {
+ } else if (op->type == kCowXorOp) {
xor_ops++;
}
@@ -203,9 +190,11 @@
if (!opt.silent) {
auto total_ops = replace_ops + zero_ops + copy_ops + xor_ops;
- std::cout << "Total-data-ops: " << total_ops << "Replace-ops: " << replace_ops
- << " Zero-ops: " << zero_ops << " Copy-ops: " << copy_ops
- << " Xor_ops: " << xor_ops << std::endl;
+ std::cout << "Data ops: " << total_ops << "\n";
+ std::cout << "Replace ops: " << replace_ops << "\n";
+ std::cout << "Zero ops: " << zero_ops << "\n";
+ std::cout << "Copy ops: " << copy_ops << "\n";
+ std::cout << "Xor ops: " << xor_ops << "\n";
}
return success;
@@ -254,15 +243,17 @@
break;
default:
android::snapshot::usage();
+ return 1;
}
}
- android::base::InitLogging(argv, android::snapshot::MyLogger);
if (argc < optind + 1) {
android::snapshot::usage();
return 1;
}
+ android::base::InitLogging(argv, android::snapshot::MyLogger);
+
if (!android::snapshot::Inspect(argv[optind], opt)) {
return 1;
}
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index 6546c2a..4e75ba7 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -80,16 +80,13 @@
bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
cow_ = std::move(cow);
- CowHeader header;
- if (!cow_->GetHeader(&header)) {
- return false;
- }
+ const auto& header = cow_->GetHeader();
block_size_ = header.block_size;
// Populate the operation map.
op_iter_ = cow_->GetOpIter();
- while (!op_iter_->Done()) {
- const CowOperation* op = &op_iter_->Get();
+ while (!op_iter_->AtEnd()) {
+ const CowOperation* op = op_iter_->Get();
if (IsMetadataOp(*op)) {
op_iter_->Next();
continue;
@@ -128,37 +125,6 @@
return source_fd_;
}
-class MemoryByteSink : public IByteSink {
- public:
- MemoryByteSink(void* buf, size_t count) {
- buf_ = reinterpret_cast<uint8_t*>(buf);
- pos_ = buf_;
- end_ = buf_ + count;
- }
-
- void* GetBuffer(size_t requested, size_t* actual) override {
- *actual = std::min(remaining(), requested);
- if (!*actual) {
- return nullptr;
- }
-
- uint8_t* start = pos_;
- pos_ += *actual;
- return start;
- }
-
- bool ReturnData(void*, size_t) override { return true; }
-
- uint8_t* buf() const { return buf_; }
- uint8_t* pos() const { return pos_; }
- size_t remaining() const { return end_ - pos_; }
-
- private:
- uint8_t* buf_;
- uint8_t* pos_;
- uint8_t* end_;
-};
-
ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) {
// Find the start and end chunks, inclusive.
uint64_t start_chunk = offset_ / block_size_;
@@ -167,69 +133,48 @@
// Chop off the first N bytes if the position is not block-aligned.
size_t start_offset = offset_ % block_size_;
- MemoryByteSink sink(buf, count);
+ uint8_t* buf_pos = reinterpret_cast<uint8_t*>(buf);
+ size_t buf_remaining = count;
- size_t initial_bytes = std::min(block_size_ - start_offset, sink.remaining());
- ssize_t rv = ReadBlock(start_chunk, &sink, start_offset, initial_bytes);
+ size_t initial_bytes = std::min(block_size_ - start_offset, buf_remaining);
+ ssize_t rv = ReadBlock(start_chunk, start_offset, buf_pos, initial_bytes);
if (rv < 0) {
return -1;
}
offset_ += rv;
+ buf_pos += rv;
+ buf_remaining -= rv;
for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) {
- ssize_t rv = ReadBlock(chunk, &sink, 0);
+ ssize_t rv = ReadBlock(chunk, 0, buf_pos, buf_remaining);
if (rv < 0) {
return -1;
}
offset_ += rv;
+ buf_pos += rv;
+ buf_remaining -= rv;
}
- if (sink.remaining()) {
- ssize_t rv = ReadBlock(end_chunk, &sink, 0, {sink.remaining()});
+ if (buf_remaining) {
+ ssize_t rv = ReadBlock(end_chunk, 0, buf_pos, buf_remaining);
if (rv < 0) {
return -1;
}
offset_ += rv;
+ buf_pos += rv;
+ buf_remaining -= rv;
}
+ CHECK_EQ(buf_pos - reinterpret_cast<uint8_t*>(buf), count);
+ CHECK_EQ(buf_remaining, 0);
+
errno = 0;
-
- DCHECK(sink.pos() - sink.buf() == count);
return count;
}
-// Discard the first N bytes of a sink request, or any excess bytes.
-class PartialSink : public MemoryByteSink {
- public:
- PartialSink(void* buffer, size_t size, size_t ignore_start)
- : MemoryByteSink(buffer, size), ignore_start_(ignore_start) {}
-
- void* GetBuffer(size_t requested, size_t* actual) override {
- // Throw away the first N bytes if needed.
- if (ignore_start_) {
- *actual = std::min({requested, ignore_start_, sizeof(discard_)});
- ignore_start_ -= *actual;
- return discard_;
- }
- // Throw away any excess bytes if needed.
- if (remaining() == 0) {
- *actual = std::min(requested, sizeof(discard_));
- return discard_;
- }
- return MemoryByteSink::GetBuffer(requested, actual);
- }
-
- private:
- size_t ignore_start_;
- char discard_[BLOCK_SZ];
-};
-
-ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
- const std::optional<uint64_t>& max_bytes) {
- size_t bytes_to_read = block_size_;
- if (max_bytes) {
- bytes_to_read = *max_bytes;
- }
+ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, size_t start_offset, void* buffer,
+ size_t buffer_size) {
+ size_t bytes_to_read = std::min(static_cast<size_t>(block_size_), buffer_size);
// The offset is relative to the chunk; we should be reading no more than
// one chunk.
@@ -240,17 +185,6 @@
op = ops_[chunk];
}
- size_t actual;
- void* buffer = sink->GetBuffer(bytes_to_read, &actual);
- if (!buffer || actual < bytes_to_read) {
- // This should never happen unless we calculated the read size wrong
- // somewhere. MemoryByteSink always fulfills the entire requested
- // region unless there's not enough buffer remaining.
- LOG(ERROR) << "Asked for buffer of size " << bytes_to_read << ", got " << actual;
- errno = EINVAL;
- return -1;
- }
-
if (!op || op->type == kCowCopyOp) {
borrowed_fd fd = GetSourceFd();
if (fd < 0) {
@@ -271,8 +205,7 @@
} else if (op->type == kCowZeroOp) {
memset(buffer, 0, bytes_to_read);
} else if (op->type == kCowReplaceOp) {
- PartialSink partial_sink(buffer, bytes_to_read, start_offset);
- if (!cow_->ReadData(*op, &partial_sink)) {
+ if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
errno = EIO;
return -1;
@@ -285,18 +218,20 @@
}
off64_t offset = op->source + start_offset;
- char data[BLOCK_SZ];
- if (!android::base::ReadFullyAtOffset(fd, &data, bytes_to_read, offset)) {
+
+ std::string data(bytes_to_read, '\0');
+ if (!android::base::ReadFullyAtOffset(fd, data.data(), data.size(), offset)) {
PLOG(ERROR) << "read " << *source_device_;
// ReadFullyAtOffset sets errno.
return -1;
}
- PartialSink partial_sink(buffer, bytes_to_read, start_offset);
- if (!cow_->ReadData(*op, &partial_sink)) {
+
+ if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
errno = EIO;
return -1;
}
+
for (size_t i = 0; i < bytes_to_read; i++) {
((char*)buffer)[i] ^= data[i];
}
diff --git a/fs_mgr/libsnapshot/snapshot_reader.h b/fs_mgr/libsnapshot/snapshot_reader.h
index a3e7e22..5e19c62 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.h
+++ b/fs_mgr/libsnapshot/snapshot_reader.h
@@ -65,8 +65,7 @@
bool Flush() override;
private:
- ssize_t ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
- const std::optional<uint64_t>& max_bytes = {});
+ ssize_t ReadBlock(uint64_t chunk, size_t start_offset, void* buffer, size_t size);
android::base::borrowed_fd GetSourceFd();
std::unique_ptr<CowReader> cow_;
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 9adc655..f25023d 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -140,6 +140,18 @@
ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
ASSERT_EQ(value, MakeNewBlockString()[1000]);
+
+ // Test a sequence of one byte reads.
+ offset = 5 * kBlockSize + 10;
+ std::string expected = MakeNewBlockString().substr(10, 20);
+ ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
+
+ std::string got;
+ while (got.size() < expected.size()) {
+ ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
+ got.push_back(value);
+ }
+ ASSERT_EQ(got, expected);
}
void TestReads(ISnapshotWriter* writer) {
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index f472bab..1fbfaf7 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2646,6 +2646,11 @@
ASSERT_TRUE(init->InitiateMerge());
ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
+ if (ShouldSkipLegacyMerging()) {
+ LOG(INFO) << "Skipping legacy merge in test";
+ return;
+ }
+
// Simulate a reboot that tries the merge again, with the non-failing dm.
ASSERT_TRUE(UnmapAll());
init = NewManagerForFirstStageMount("_b");
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 8926030..efa43b7 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -347,7 +347,6 @@
*/
bool Snapuserd::ReadMetadata() {
reader_ = std::make_unique<CowReader>();
- CowHeader header;
CowOptions options;
bool metadata_found = false;
int replace_ops = 0, zero_ops = 0, copy_ops = 0;
@@ -359,11 +358,7 @@
return false;
}
- if (!reader_->GetHeader(&header)) {
- SNAP_LOG(ERROR) << "Failed to get header";
- return false;
- }
-
+ const auto& header = reader_->GetHeader();
if (!(header.block_size == BLOCK_SZ)) {
SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
return false;
@@ -395,8 +390,8 @@
// this memset will ensure that metadata read is completed.
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- while (!cowop_rm_iter->Done()) {
- const CowOperation* cow_op = &cowop_rm_iter->Get();
+ while (!cowop_rm_iter->AtEnd()) {
+ const CowOperation* cow_op = cowop_rm_iter->Get();
struct disk_exception* de =
reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
@@ -442,7 +437,7 @@
sizeof(struct disk_exception));
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- if (cowop_rm_iter->Done()) {
+ if (cowop_rm_iter->AtEnd()) {
vec_.push_back(std::move(de_ptr));
}
}
@@ -462,9 +457,9 @@
<< " Number of replace/zero ops completed in this area: " << num_ops
<< " Pending copy ops for this area: " << pending_ordered_ops;
- while (!cowop_rm_iter->Done()) {
+ while (!cowop_rm_iter->AtEnd()) {
do {
- const CowOperation* cow_op = &cowop_rm_iter->Get();
+ const CowOperation* cow_op = cowop_rm_iter->Get();
// We have two cases specific cases:
//
@@ -514,10 +509,8 @@
// in the file.
//===========================================================
uint64_t block_source = cow_op->source;
- uint64_t block_offset = 0;
if (prev_id.has_value()) {
- if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
- (block_offset > 0 && source_blocks.count(block_source + 1))) {
+ if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
break;
}
}
@@ -525,13 +518,10 @@
pending_ordered_ops -= 1;
vec.push_back(cow_op);
dest_blocks.insert(block_source);
- if (block_offset > 0) {
- dest_blocks.insert(block_source + 1);
- }
source_blocks.insert(cow_op->new_block);
prev_id = cow_op->new_block;
cowop_rm_iter->Next();
- } while (!cowop_rm_iter->Done() && pending_ordered_ops);
+ } while (!cowop_rm_iter->AtEnd() && pending_ordered_ops);
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << vec.size()
@@ -574,7 +564,7 @@
sizeof(struct disk_exception));
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- if (cowop_rm_iter->Done()) {
+ if (cowop_rm_iter->AtEnd()) {
vec_.push_back(std::move(de_ptr));
SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
}
@@ -636,8 +626,7 @@
}
bool Snapuserd::MmapMetadata() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
if (header.major_version >= 2 && header.buffer_size > 0) {
total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
@@ -832,8 +821,7 @@
}
uint64_t Snapuserd::GetBufferMetadataOffset() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
size_t size = header.header_size + sizeof(BufferState);
return size;
@@ -848,16 +836,14 @@
*
*/
size_t Snapuserd::GetBufferMetadataSize() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
return metadata_bytes;
}
size_t Snapuserd::GetBufferDataOffset() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
return (header.header_size + GetBufferMetadataSize());
}
@@ -866,16 +852,14 @@
* (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
*/
size_t Snapuserd::GetBufferDataSize() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
size_t size = header.buffer_size - GetBufferMetadataSize();
return size;
}
struct BufferState* Snapuserd::GetBufferState() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
struct BufferState* ra_state =
reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index 01123f8..a32c2bf 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -173,16 +173,11 @@
void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
uint64_t source_block = cow_op->source;
- uint64_t source_offset = 0;
- if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
- (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+ if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
overlap_ = true;
}
dest_blocks_.insert(source_block);
- if (source_offset > 0) {
- dest_blocks_.insert(source_block + 1);
- }
source_blocks_.insert(cow_op->new_block);
}
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 965af40..922df34 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -95,11 +95,17 @@
// internal COW format and if the block is compressed,
// it will be de-compressed.
bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
- if (!reader_->ReadData(*cow_op, &bufsink_)) {
- SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (!buffer) {
+ SNAP_LOG(ERROR) << "No space in buffer sink";
return false;
}
-
+ ssize_t rv = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
+ if (rv != BLOCK_SZ) {
+ SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block
+ << ", return = " << rv;
+ return false;
+ }
return true;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
index 2e4cac6..a6b6a7f 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
@@ -25,16 +25,15 @@
namespace android {
namespace snapshot {
-class BufferSink : public IByteSink {
+class BufferSink final {
public:
void Initialize(size_t size);
void* GetBufPtr() { return buffer_.get(); }
void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
void* GetPayloadBuffer(size_t size);
- void* GetBuffer(size_t requested, size_t* actual) override;
+ void* GetBuffer(size_t requested, size_t* actual);
void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
struct dm_user_header* GetHeaderPtr();
- bool ReturnData(void*, size_t) override { return true; }
void ResetBufferOffset() { buffer_offset_ = 0; }
void* GetPayloadBufPtr();
@@ -44,12 +43,12 @@
size_t buffer_size_;
};
-class XorSink : public IByteSink {
+class XorSink final {
public:
void Initialize(BufferSink* sink, size_t size);
void Reset();
- void* GetBuffer(size_t requested, size_t* actual) override;
- bool ReturnData(void* buffer, size_t len) override;
+ void* GetBuffer(size_t requested, size_t* actual);
+ bool ReturnData(void* buffer, size_t len);
private:
BufferSink* bufsink_;
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
index c592257..46952c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
@@ -41,6 +41,9 @@
*/
static constexpr uint32_t SECTOR_SHIFT = 9;
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
typedef __u64 sector_t;
typedef sector_t chunk_t;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 25ce0ae..a519639 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -98,8 +98,7 @@
}
} else {
reader_->UpdateMergeOpsCompleted(num_merge_ops);
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) {
SNAP_PLOG(ERROR) << "lseek failed";
@@ -154,7 +153,6 @@
bool SnapshotHandler::ReadMetadata() {
reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE, true);
- CowHeader header;
CowOptions options;
SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
@@ -164,11 +162,7 @@
return false;
}
- if (!reader_->GetHeader(&header)) {
- SNAP_LOG(ERROR) << "Failed to get header";
- return false;
- }
-
+ const auto& header = reader_->GetHeader();
if (!(header.block_size == BLOCK_SZ)) {
SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
return false;
@@ -191,8 +185,8 @@
size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
- while (!cowop_iter->Done()) {
- const CowOperation* cow_op = &cowop_iter->Get();
+ while (!cowop_iter->AtEnd()) {
+ const CowOperation* cow_op = cowop_iter->Get();
if (cow_op->type == kCowCopyOp) {
copy_ops += 1;
@@ -244,8 +238,7 @@
}
bool SnapshotHandler::MmapMetadata() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
@@ -367,8 +360,7 @@
}
uint64_t SnapshotHandler::GetBufferMetadataOffset() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
return (header.header_size + sizeof(BufferState));
}
@@ -383,8 +375,7 @@
*
*/
size_t SnapshotHandler::GetBufferMetadataSize() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
size_t buffer_size = header.buffer_size;
// If there is no scratch space, then just use the
@@ -397,8 +388,7 @@
}
size_t SnapshotHandler::GetBufferDataOffset() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
return (header.header_size + GetBufferMetadataSize());
}
@@ -407,8 +397,7 @@
* (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
*/
size_t SnapshotHandler::GetBufferDataSize() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
size_t buffer_size = header.buffer_size;
// If there is no scratch space, then just use the
@@ -421,8 +410,7 @@
}
struct BufferState* SnapshotHandler::GetBufferState() {
- CowHeader header;
- reader_->GetHeader(&header);
+ const auto& header = reader_->GetHeader();
struct BufferState* ra_state =
reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
index 0d0f711..7858216 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -76,11 +76,15 @@
// internal COW format and if the block is compressed,
// it will be de-compressed.
bool Worker::ProcessReplaceOp(const CowOperation* cow_op) {
- if (!reader_->ReadData(*cow_op, &bufsink_)) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (!buffer) {
+ SNAP_LOG(ERROR) << "ProcessReplaceOp failed to allocate buffer";
+ return false;
+ }
+ if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
return false;
}
-
return true;
}
@@ -125,12 +129,26 @@
if (!ReadFromSourceDevice(cow_op)) {
return false;
}
+
xorsink_.Reset();
- if (!reader_->ReadData(*cow_op, &xorsink_)) {
- SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
+
+ size_t actual = 0;
+ void* buffer = xorsink_.GetBuffer(BLOCK_SZ, &actual);
+ if (!buffer || actual < BLOCK_SZ) {
+ SNAP_LOG(ERROR) << "ProcessXorOp failed to get buffer of " << BLOCK_SZ << " size, got "
+ << actual;
return false;
}
-
+ ssize_t size = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
+ if (size != BLOCK_SZ) {
+ SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block
+ << ", return value: " << size;
+ return false;
+ }
+ if (!xorsink_.ReturnData(buffer, size)) {
+ SNAP_LOG(ERROR) << "ProcessXorOp failed to return data";
+ return false;
+ }
return true;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index d57f434..ce95b76 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -30,8 +30,8 @@
bool checkOrderedOp = (replace_zero_vec == nullptr);
do {
- if (!cowop_iter_->Done() && num_ops) {
- const CowOperation* cow_op = &cowop_iter_->Get();
+ if (!cowop_iter_->AtEnd() && num_ops) {
+ const CowOperation* cow_op = cowop_iter_->Get();
if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
break;
}
@@ -45,8 +45,8 @@
num_ops -= 1;
nr_consecutive = 1;
- while (!cowop_iter_->Done() && num_ops) {
- const CowOperation* op = &cowop_iter_->Get();
+ while (!cowop_iter_->AtEnd() && num_ops) {
+ const CowOperation* op = cowop_iter_->Get();
if (checkOrderedOp && !IsOrderedOp(*op)) {
break;
}
@@ -85,7 +85,7 @@
SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
- while (!cowop_iter_->Done()) {
+ while (!cowop_iter_->AtEnd()) {
int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
std::vector<const CowOperation*> replace_zero_vec;
uint64_t source_offset;
@@ -93,7 +93,7 @@
int linear_blocks = PrepareMerge(&source_offset, &num_ops, &replace_zero_vec);
if (linear_blocks == 0) {
// Merge complete
- CHECK(cowop_iter_->Done());
+ CHECK(cowop_iter_->AtEnd());
break;
}
@@ -180,8 +180,8 @@
SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
- while (!cowop_iter_->Done()) {
- const CowOperation* cow_op = &cowop_iter_->Get();
+ while (!cowop_iter_->AtEnd()) {
+ const CowOperation* cow_op = cowop_iter_->Get();
if (!IsOrderedOp(*cow_op)) {
break;
}
@@ -361,8 +361,8 @@
SNAP_LOG(INFO) << "MergeOrderedOps started....";
- while (!cowop_iter_->Done()) {
- const CowOperation* cow_op = &cowop_iter_->Get();
+ while (!cowop_iter_->AtEnd()) {
+ const CowOperation* cow_op = cowop_iter_->Get();
if (!IsOrderedOp(*cow_op)) {
break;
}
@@ -443,7 +443,7 @@
if (!MergeOrderedOpsAsync()) {
SNAP_LOG(ERROR) << "MergeOrderedOpsAsync failed - Falling back to synchronous I/O";
// Reset the iter so that we retry the merge
- while (blocks_merged_in_group_ && !cowop_iter_->RDone()) {
+ while (blocks_merged_in_group_ && !cowop_iter_->AtBegin()) {
cowop_iter_->Prev();
blocks_merged_in_group_ -= 1;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index fbe57d2..17f1f0e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -114,6 +114,24 @@
return nr_consecutive;
}
+class [[nodiscard]] AutoNotifyReadAheadFailed {
+ public:
+ AutoNotifyReadAheadFailed(std::shared_ptr<SnapshotHandler> snapuserd) : snapuserd_(snapuserd) {}
+
+ ~AutoNotifyReadAheadFailed() {
+ if (cancelled_) {
+ return;
+ }
+ snapuserd_->ReadAheadIOFailed();
+ }
+
+ void Cancel() { cancelled_ = true; }
+
+ private:
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+ bool cancelled_ = false;
+};
+
bool ReadAhead::ReconstructDataFromCow() {
std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
loff_t metadata_offset = 0;
@@ -145,6 +163,8 @@
metadata_offset += sizeof(struct ScratchMetadata);
}
+ AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_);
+
// We are done re-constructing the mapping; however, we need to make sure
// all the COW operations to-be merged are present in the re-constructed
// mapping.
@@ -162,7 +182,6 @@
if (!(num_ops == 0)) {
SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
<< " Pending ops: " << num_ops;
- snapuserd_->ReadAheadIOFailed();
return false;
}
@@ -175,11 +194,11 @@
if (!snapuserd_->ReadAheadIOCompleted(true)) {
SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
- snapuserd_->ReadAheadIOFailed();
return false;
}
SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+ notify_read_ahead_failed.Cancel();
return true;
}
@@ -467,9 +486,16 @@
if (xor_op_index < xor_op_vec.size()) {
const CowOperation* xor_op = xor_op_vec[xor_op_index];
if (xor_op->new_block == new_block) {
- if (!reader_->ReadData(*xor_op, &bufsink_)) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (!buffer) {
+ SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer for block: "
+ << xor_op->new_block;
+ return false;
+ }
+ if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
SNAP_LOG(ERROR)
- << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+ << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
+ << ", return value: " << rv;
return false;
}
@@ -492,6 +518,8 @@
blocks_.clear();
std::vector<const CowOperation*> xor_op_vec;
+ AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_);
+
bufsink_.ResetBufferOffset();
// Number of ops to be merged in this window. This is a fixed size
@@ -518,8 +546,6 @@
<< " offset :" << source_offset % BLOCK_SZ
<< " buffer_offset : " << buffer_offset << " io_size : " << io_size
<< " buf-addr : " << read_ahead_buffer_;
-
- snapuserd_->ReadAheadIOFailed();
return false;
}
@@ -530,6 +556,7 @@
// Done with merging ordered ops
if (RAIterDone() && total_blocks_merged_ == 0) {
+ notify_read_ahead_failed.Cancel();
return true;
}
@@ -560,20 +587,25 @@
// Check if this block is an XOR op
if (xor_op->new_block == new_block) {
// Read the xor'ed data from COW
- if (!reader_->ReadData(*xor_op, &bufsink)) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (!buffer) {
+ SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer";
+ return false;
+ }
+ if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
SNAP_LOG(ERROR)
- << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
- snapuserd_->ReadAheadIOFailed();
+ << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
+ << ", return value: " << rv;
return false;
}
// Pointer to the data read from base device
- uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+ uint8_t* read_buffer = reinterpret_cast<uint8_t*>(bufptr);
// Get the xor'ed data read from COW device
uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink.GetPayloadBufPtr());
// Retrieve the original data
for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
- buffer[byte_offset] ^= xor_data[byte_offset];
+ read_buffer[byte_offset] ^= xor_data[byte_offset];
}
// Move to next XOR op
@@ -604,6 +636,7 @@
bm->new_block = 0;
bm->file_offset = 0;
+ notify_read_ahead_failed.Cancel();
return true;
}
@@ -776,7 +809,7 @@
}
bool ReadAhead::RAIterDone() {
- if (cowop_iter_->Done()) {
+ if (cowop_iter_->AtEnd()) {
return true;
}
@@ -794,15 +827,14 @@
}
void ReadAhead::RAResetIter(uint64_t num_blocks) {
- while (num_blocks && !cowop_iter_->RDone()) {
+ while (num_blocks && !cowop_iter_->AtBegin()) {
cowop_iter_->Prev();
num_blocks -= 1;
}
}
const CowOperation* ReadAhead::GetRAOpIter() {
- const CowOperation* cow_op = &cowop_iter_->Get();
- return cow_op;
+ return cowop_iter_->Get();
}
void ReadAhead::InitializeBuffer() {
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 838f734..534fc1a 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -18,8 +18,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_binary {
- name: "gatekeeperd",
+cc_defaults {
+ name: "gatekeeperd_defaults",
cflags: [
"-Wall",
"-Wextra",
@@ -52,6 +52,16 @@
static_libs: ["libscrypt_static"],
include_dirs: ["external/scrypt/lib/crypto"],
+}
+
+cc_binary {
+ name: "gatekeeperd",
+ defaults: [
+ "gatekeeperd_defaults",
+ ],
+ srcs: [
+ "main.cpp",
+ ],
init_rc: ["gatekeeperd.rc"],
}
@@ -88,3 +98,20 @@
"libbinder",
],
}
+
+cc_fuzz {
+ name: "gatekeeperd_service_fuzzer",
+ defaults: [
+ "gatekeeperd_defaults",
+ "service_fuzzer_defaults"
+ ],
+ srcs: [
+ "fuzzer/GateKeeperServiceFuzzer.cpp",
+ ],
+ fuzz_config: {
+ cc: [
+ "subrahmanyaman@google.com",
+ "swillden@google.com",
+ ],
+ },
+}
\ No newline at end of file
diff --git a/gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp b/gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp
new file mode 100644
index 0000000..bc0d5fe
--- /dev/null
+++ b/gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fuzzbinder/libbinder_driver.h>
+
+#include "gatekeeperd.h"
+
+using android::fuzzService;
+using android::GateKeeperProxy;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ auto gatekeeperService = new GateKeeperProxy();
+ fuzzService(gatekeeperService, FuzzedDataProvider(data, size));
+ return 0;
+}
\ No newline at end of file
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index eb43a33..e5241b5 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
#define LOG_TAG "gatekeeperd"
-#include <android/service/gatekeeper/BnGateKeeperService.h>
-#include <gatekeeper/GateKeeperResponse.h>
+#include "gatekeeperd.h"
#include <endian.h>
#include <errno.h>
@@ -39,25 +37,18 @@
#include <log/log.h>
#include <utils/String16.h>
-#include <aidl/android/hardware/gatekeeper/IGatekeeper.h>
#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
#include <aidl/android/security/authorization/IKeystoreAuthorization.h>
-#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
#include <hidl/HidlSupport.h>
using android::sp;
using android::hardware::Return;
using android::hardware::gatekeeper::V1_0::GatekeeperResponse;
using android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
-using android::hardware::gatekeeper::V1_0::IGatekeeper;
using AidlGatekeeperEnrollResp = aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
using AidlGatekeeperVerifyResp = aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
-using AidlIGatekeeper = aidl::android::hardware::gatekeeper::IGatekeeper;
-using ::android::binder::Status;
-using ::android::service::gatekeeper::BnGateKeeperService;
-using GKResponse = ::android::service::gatekeeper::GateKeeperResponse;
using GKResponseCode = ::android::service::gatekeeper::ResponseCode;
using ::aidl::android::hardware::security::keymint::HardwareAuthenticatorType;
using ::aidl::android::hardware::security::keymint::HardwareAuthToken;
@@ -70,172 +61,167 @@
static const String16 DUMP_PERMISSION("android.permission.DUMP");
constexpr const char gatekeeperServiceName[] = "android.hardware.gatekeeper.IGatekeeper/default";
-class GateKeeperProxy : public BnGateKeeperService {
- public:
- GateKeeperProxy() {
- clear_state_if_needed_done = false;
- hw_device = IGatekeeper::getService();
- ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(gatekeeperServiceName));
- aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder);
- is_running_gsi = android::base::GetBoolProperty(android::gsi::kGsiBootedProp, false);
+GateKeeperProxy::GateKeeperProxy() {
+ clear_state_if_needed_done = false;
+ hw_device = IGatekeeper::getService();
+ ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(gatekeeperServiceName));
+ aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder);
+ is_running_gsi = android::base::GetBoolProperty(android::gsi::kGsiBootedProp, false);
- if (!aidl_hw_device && !hw_device) {
- LOG(ERROR) << "Could not find Gatekeeper device, which makes me very sad.";
+ if (!aidl_hw_device && !hw_device) {
+ LOG(ERROR) << "Could not find Gatekeeper device, which makes me very sad.";
+ }
+}
+
+void GateKeeperProxy::store_sid(uint32_t userId, uint64_t sid) {
+ char filename[21];
+ snprintf(filename, sizeof(filename), "%u", userId);
+ int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ ALOGE("could not open file: %s: %s", filename, strerror(errno));
+ return;
+ }
+ write(fd, &sid, sizeof(sid));
+ close(fd);
+}
+
+void GateKeeperProxy::clear_state_if_needed() {
+ if (clear_state_if_needed_done) {
+ return;
+ }
+
+ if (mark_cold_boot() && !is_running_gsi) {
+ ALOGI("cold boot: clearing state");
+ if (aidl_hw_device) {
+ aidl_hw_device->deleteAllUsers();
+ } else if (hw_device) {
+ hw_device->deleteAllUsers([](const GatekeeperResponse&) {});
}
}
- virtual ~GateKeeperProxy() {}
+ clear_state_if_needed_done = true;
+}
- void store_sid(uint32_t userId, uint64_t sid) {
- char filename[21];
- snprintf(filename, sizeof(filename), "%u", userId);
+bool GateKeeperProxy::mark_cold_boot() {
+ const char* filename = ".coldboot";
+ if (access(filename, F_OK) == -1) {
int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0) {
- ALOGE("could not open file: %s: %s", filename, strerror(errno));
- return;
+ ALOGE("could not open file: %s : %s", filename, strerror(errno));
+ return false;
}
- write(fd, &sid, sizeof(sid));
close(fd);
+ return true;
}
+ return false;
+}
- void clear_state_if_needed() {
- if (clear_state_if_needed_done) {
- return;
- }
-
- if (mark_cold_boot() && !is_running_gsi) {
- ALOGI("cold boot: clearing state");
- if (aidl_hw_device) {
- aidl_hw_device->deleteAllUsers();
- } else if (hw_device) {
- hw_device->deleteAllUsers([](const GatekeeperResponse&) {});
- }
- }
-
- clear_state_if_needed_done = true;
+void GateKeeperProxy::maybe_store_sid(uint32_t userId, uint64_t sid) {
+ char filename[21];
+ snprintf(filename, sizeof(filename), "%u", userId);
+ if (access(filename, F_OK) == -1) {
+ store_sid(userId, sid);
}
+}
- bool mark_cold_boot() {
- const char* filename = ".coldboot";
- if (access(filename, F_OK) == -1) {
- int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
- if (fd < 0) {
- ALOGE("could not open file: %s : %s", filename, strerror(errno));
- return false;
- }
- close(fd);
- return true;
- }
- return false;
- }
+uint64_t GateKeeperProxy::read_sid(uint32_t userId) {
+ char filename[21];
+ uint64_t sid;
+ snprintf(filename, sizeof(filename), "%u", userId);
+ int fd = open(filename, O_RDONLY);
+ if (fd < 0) return 0;
+ read(fd, &sid, sizeof(sid));
+ close(fd);
+ return sid;
+}
- void maybe_store_sid(uint32_t userId, uint64_t sid) {
- char filename[21];
- snprintf(filename, sizeof(filename), "%u", userId);
- if (access(filename, F_OK) == -1) {
- store_sid(userId, sid);
- }
+void GateKeeperProxy::clear_sid(uint32_t userId) {
+ char filename[21];
+ snprintf(filename, sizeof(filename), "%u", userId);
+ if (remove(filename) < 0 && errno != ENOENT) {
+ ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno));
+ store_sid(userId, 0);
}
+}
- uint64_t read_sid(uint32_t userId) {
- char filename[21];
- uint64_t sid;
- snprintf(filename, sizeof(filename), "%u", userId);
- int fd = open(filename, O_RDONLY);
- if (fd < 0) return 0;
- read(fd, &sid, sizeof(sid));
- close(fd);
- return sid;
+uint32_t GateKeeperProxy::adjust_userId(uint32_t userId) {
+ static constexpr uint32_t kGsiOffset = 1000000;
+ CHECK(userId < kGsiOffset);
+ CHECK((aidl_hw_device != nullptr) || (hw_device != nullptr));
+ if (is_running_gsi) {
+ return userId + kGsiOffset;
}
-
- void clear_sid(uint32_t userId) {
- char filename[21];
- snprintf(filename, sizeof(filename), "%u", userId);
- if (remove(filename) < 0 && errno != ENOENT) {
- ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno));
- store_sid(userId, 0);
- }
- }
-
- // This should only be called on userIds being passed to the GateKeeper HAL. It ensures that
- // secure storage shared across a GSI image and a host image will not overlap.
- uint32_t adjust_userId(uint32_t userId) {
- static constexpr uint32_t kGsiOffset = 1000000;
- CHECK(userId < kGsiOffset);
- CHECK((aidl_hw_device != nullptr) || (hw_device != nullptr));
- if (is_running_gsi) {
- return userId + kGsiOffset;
- }
- return userId;
- }
+ return userId;
+}
#define GK_ERROR *gkResponse = GKResponse::error(), Status::ok()
- Status enroll(int32_t userId, const std::optional<std::vector<uint8_t>>& currentPasswordHandle,
- const std::optional<std::vector<uint8_t>>& currentPassword,
- const std::vector<uint8_t>& desiredPassword, GKResponse* gkResponse) override {
- IPCThreadState* ipc = IPCThreadState::self();
- const int calling_pid = ipc->getCallingPid();
- const int calling_uid = ipc->getCallingUid();
- if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
- return GK_ERROR;
- }
+Status GateKeeperProxy::enroll(int32_t userId,
+ const std::optional<std::vector<uint8_t>>& currentPasswordHandle,
+ const std::optional<std::vector<uint8_t>>& currentPassword,
+ const std::vector<uint8_t>& desiredPassword,
+ GKResponse* gkResponse) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int calling_pid = ipc->getCallingPid();
+ const int calling_uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+ return GK_ERROR;
+ }
- // Make sure to clear any state from before factory reset as soon as a credential is
- // enrolled (which may happen during device setup).
- clear_state_if_needed();
+ // Make sure to clear any state from before factory reset as soon as a credential is
+ // enrolled (which may happen during device setup).
+ clear_state_if_needed();
- // need a desired password to enroll
- if (desiredPassword.size() == 0) return GK_ERROR;
+ // need a desired password to enroll
+ if (desiredPassword.size() == 0) return GK_ERROR;
- if (!aidl_hw_device && !hw_device) {
- LOG(ERROR) << "has no HAL to talk to";
- return GK_ERROR;
- }
+ if (!aidl_hw_device && !hw_device) {
+ LOG(ERROR) << "has no HAL to talk to";
+ return GK_ERROR;
+ }
- android::hardware::hidl_vec<uint8_t> curPwdHandle;
- android::hardware::hidl_vec<uint8_t> curPwd;
+ android::hardware::hidl_vec<uint8_t> curPwdHandle;
+ android::hardware::hidl_vec<uint8_t> curPwd;
- if (currentPasswordHandle && currentPassword) {
- if (hw_device) {
- // Hidl Implementations expects passwordHandle to be in
- // gatekeeper::password_handle_t format.
- if (currentPasswordHandle->size() != sizeof(gatekeeper::password_handle_t)) {
- LOG(INFO) << "Password handle has wrong length";
- return GK_ERROR;
- }
- }
- curPwdHandle.setToExternal(const_cast<uint8_t*>(currentPasswordHandle->data()),
- currentPasswordHandle->size());
- curPwd.setToExternal(const_cast<uint8_t*>(currentPassword->data()),
- currentPassword->size());
- }
-
- android::hardware::hidl_vec<uint8_t> newPwd;
- newPwd.setToExternal(const_cast<uint8_t*>(desiredPassword.data()), desiredPassword.size());
-
- uint32_t hw_userId = adjust_userId(userId);
- uint64_t secureUserId = 0;
- if (aidl_hw_device) {
- // AIDL gatekeeper service
- AidlGatekeeperEnrollResp rsp;
- auto result = aidl_hw_device->enroll(hw_userId, curPwdHandle, curPwd, newPwd, &rsp);
- if (!result.isOk()) {
- LOG(ERROR) << "enroll transaction failed";
+ if (currentPasswordHandle && currentPassword) {
+ if (hw_device) {
+ // Hidl Implementations expects passwordHandle to be in
+ // gatekeeper::password_handle_t format.
+ if (currentPasswordHandle->size() != sizeof(gatekeeper::password_handle_t)) {
+ LOG(INFO) << "Password handle has wrong length";
return GK_ERROR;
}
- if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
- *gkResponse = GKResponse::ok({rsp.data.begin(), rsp.data.end()});
- secureUserId = static_cast<uint64_t>(rsp.secureUserId);
- } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT &&
- rsp.timeoutMs > 0) {
- *gkResponse = GKResponse::retry(rsp.timeoutMs);
- } else {
- *gkResponse = GKResponse::error();
- }
- } else if (hw_device) {
- // HIDL gatekeeper service
- Return<void> hwRes = hw_device->enroll(
+ }
+ curPwdHandle.setToExternal(const_cast<uint8_t*>(currentPasswordHandle->data()),
+ currentPasswordHandle->size());
+ curPwd.setToExternal(const_cast<uint8_t*>(currentPassword->data()),
+ currentPassword->size());
+ }
+
+ android::hardware::hidl_vec<uint8_t> newPwd;
+ newPwd.setToExternal(const_cast<uint8_t*>(desiredPassword.data()), desiredPassword.size());
+
+ uint32_t hw_userId = adjust_userId(userId);
+ uint64_t secureUserId = 0;
+ if (aidl_hw_device) {
+ // AIDL gatekeeper service
+ AidlGatekeeperEnrollResp rsp;
+ auto result = aidl_hw_device->enroll(hw_userId, curPwdHandle, curPwd, newPwd, &rsp);
+ if (!result.isOk()) {
+ LOG(ERROR) << "enroll transaction failed";
+ return GK_ERROR;
+ }
+ if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
+ *gkResponse = GKResponse::ok({rsp.data.begin(), rsp.data.end()});
+ secureUserId = static_cast<uint64_t>(rsp.secureUserId);
+ } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT && rsp.timeoutMs > 0) {
+ *gkResponse = GKResponse::retry(rsp.timeoutMs);
+ } else {
+ *gkResponse = GKResponse::error();
+ }
+ } else if (hw_device) {
+ // HIDL gatekeeper service
+ Return<void> hwRes = hw_device->enroll(
hw_userId, curPwdHandle, curPwd, newPwd,
[&gkResponse](const GatekeeperResponse& rsp) {
if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
@@ -247,110 +233,110 @@
*gkResponse = GKResponse::error();
}
});
- if (!hwRes.isOk()) {
- LOG(ERROR) << "enroll transaction failed";
+ if (!hwRes.isOk()) {
+ LOG(ERROR) << "enroll transaction failed";
+ return GK_ERROR;
+ }
+ if (gkResponse->response_code() == GKResponseCode::OK) {
+ if (gkResponse->payload().size() != sizeof(gatekeeper::password_handle_t)) {
+ LOG(ERROR) << "HAL returned password handle of invalid length "
+ << gkResponse->payload().size();
return GK_ERROR;
}
- if (gkResponse->response_code() == GKResponseCode::OK) {
- if (gkResponse->payload().size() != sizeof(gatekeeper::password_handle_t)) {
- LOG(ERROR) << "HAL returned password handle of invalid length "
- << gkResponse->payload().size();
- return GK_ERROR;
- }
- const gatekeeper::password_handle_t* handle =
+ const gatekeeper::password_handle_t* handle =
reinterpret_cast<const gatekeeper::password_handle_t*>(
- gkResponse->payload().data());
- secureUserId = handle->user_id;
- }
+ gkResponse->payload().data());
+ secureUserId = handle->user_id;
}
-
- if (gkResponse->response_code() == GKResponseCode::OK && !gkResponse->should_reenroll()) {
- store_sid(userId, secureUserId);
-
- GKResponse verifyResponse;
- // immediately verify this password so we don't ask the user to enter it again
- // if they just created it.
- auto status = verify(userId, gkResponse->payload(), desiredPassword, &verifyResponse);
- if (!status.isOk() || verifyResponse.response_code() != GKResponseCode::OK) {
- LOG(ERROR) << "Failed to verify password after enrolling";
- }
- }
-
- return Status::ok();
}
- Status verify(int32_t userId, const ::std::vector<uint8_t>& enrolledPasswordHandle,
- const ::std::vector<uint8_t>& providedPassword, GKResponse* gkResponse) override {
- return verifyChallenge(userId, 0 /* challenge */, enrolledPasswordHandle, providedPassword,
- gkResponse);
+ if (gkResponse->response_code() == GKResponseCode::OK && !gkResponse->should_reenroll()) {
+ store_sid(userId, secureUserId);
+
+ GKResponse verifyResponse;
+ // immediately verify this password so we don't ask the user to enter it again
+ // if they just created it.
+ auto status = verify(userId, gkResponse->payload(), desiredPassword, &verifyResponse);
+ if (!status.isOk() || verifyResponse.response_code() != GKResponseCode::OK) {
+ LOG(ERROR) << "Failed to verify password after enrolling";
+ }
}
- Status verifyChallenge(int32_t userId, int64_t challenge,
- const std::vector<uint8_t>& enrolledPasswordHandle,
- const std::vector<uint8_t>& providedPassword,
- GKResponse* gkResponse) override {
- IPCThreadState* ipc = IPCThreadState::self();
- const int calling_pid = ipc->getCallingPid();
- const int calling_uid = ipc->getCallingUid();
- if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+ return Status::ok();
+}
+
+Status GateKeeperProxy::verify(int32_t userId, const ::std::vector<uint8_t>& enrolledPasswordHandle,
+ const ::std::vector<uint8_t>& providedPassword,
+ GKResponse* gkResponse) {
+ return verifyChallenge(userId, 0 /* challenge */, enrolledPasswordHandle, providedPassword,
+ gkResponse);
+}
+
+Status GateKeeperProxy::verifyChallenge(int32_t userId, int64_t challenge,
+ const std::vector<uint8_t>& enrolledPasswordHandle,
+ const std::vector<uint8_t>& providedPassword,
+ GKResponse* gkResponse) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int calling_pid = ipc->getCallingPid();
+ const int calling_uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+ return GK_ERROR;
+ }
+
+ // can't verify if we're missing either param
+ if (enrolledPasswordHandle.size() == 0 || providedPassword.size() == 0) return GK_ERROR;
+
+ if (!aidl_hw_device && !hw_device) {
+ LOG(ERROR) << "has no HAL to talk to";
+ return GK_ERROR;
+ }
+
+ if (hw_device) {
+ // Hidl Implementations expects passwordHandle to be in gatekeeper::password_handle_t
+ if (enrolledPasswordHandle.size() != sizeof(gatekeeper::password_handle_t)) {
+ LOG(INFO) << "Password handle has wrong length";
return GK_ERROR;
}
+ }
- // can't verify if we're missing either param
- if (enrolledPasswordHandle.size() == 0 || providedPassword.size() == 0) return GK_ERROR;
+ uint32_t hw_userId = adjust_userId(userId);
+ android::hardware::hidl_vec<uint8_t> curPwdHandle;
+ curPwdHandle.setToExternal(const_cast<uint8_t*>(enrolledPasswordHandle.data()),
+ enrolledPasswordHandle.size());
+ android::hardware::hidl_vec<uint8_t> enteredPwd;
+ enteredPwd.setToExternal(const_cast<uint8_t*>(providedPassword.data()),
+ providedPassword.size());
- if (!aidl_hw_device && !hw_device) {
- LOG(ERROR) << "has no HAL to talk to";
+ uint64_t secureUserId = 0;
+ if (aidl_hw_device) {
+ // AIDL gatekeeper service
+ AidlGatekeeperVerifyResp rsp;
+ auto result = aidl_hw_device->verify(hw_userId, challenge, curPwdHandle, enteredPwd, &rsp);
+ if (!result.isOk()) {
+ LOG(ERROR) << "verify transaction failed";
return GK_ERROR;
}
-
- if (hw_device) {
- // Hidl Implementations expects passwordHandle to be in gatekeeper::password_handle_t
- if (enrolledPasswordHandle.size() != sizeof(gatekeeper::password_handle_t)) {
- LOG(INFO) << "Password handle has wrong length";
- return GK_ERROR;
- }
+ if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
+ secureUserId = rsp.hardwareAuthToken.userId;
+ // Serialize HardwareAuthToken to a vector as hw_auth_token_t.
+ *gkResponse = GKResponse::ok(
+ authToken2AidlVec(rsp.hardwareAuthToken),
+ rsp.statusCode == AidlIGatekeeper::STATUS_REENROLL /* reenroll */);
+ } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT) {
+ *gkResponse = GKResponse::retry(rsp.timeoutMs);
+ } else {
+ *gkResponse = GKResponse::error();
}
-
- uint32_t hw_userId = adjust_userId(userId);
- android::hardware::hidl_vec<uint8_t> curPwdHandle;
- curPwdHandle.setToExternal(const_cast<uint8_t*>(enrolledPasswordHandle.data()),
- enrolledPasswordHandle.size());
- android::hardware::hidl_vec<uint8_t> enteredPwd;
- enteredPwd.setToExternal(const_cast<uint8_t*>(providedPassword.data()),
- providedPassword.size());
-
- uint64_t secureUserId = 0;
- if (aidl_hw_device) {
- // AIDL gatekeeper service
- AidlGatekeeperVerifyResp rsp;
- auto result =
- aidl_hw_device->verify(hw_userId, challenge, curPwdHandle, enteredPwd, &rsp);
- if (!result.isOk()) {
- LOG(ERROR) << "verify transaction failed";
- return GK_ERROR;
- }
- if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
- secureUserId = rsp.hardwareAuthToken.userId;
- // Serialize HardwareAuthToken to a vector as hw_auth_token_t.
- *gkResponse = GKResponse::ok(authToken2AidlVec(rsp.hardwareAuthToken),
- rsp.statusCode ==
- AidlIGatekeeper::STATUS_REENROLL /* reenroll */);
- } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT) {
- *gkResponse = GKResponse::retry(rsp.timeoutMs);
- } else {
- *gkResponse = GKResponse::error();
- }
- } else if (hw_device) {
- // HIDL gatekeeper service
- Return<void> hwRes = hw_device->verify(
+ } else if (hw_device) {
+ // HIDL gatekeeper service
+ Return<void> hwRes = hw_device->verify(
hw_userId, challenge, curPwdHandle, enteredPwd,
[&gkResponse](const GatekeeperResponse& rsp) {
if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
*gkResponse = GKResponse::ok(
- {rsp.data.begin(), rsp.data.end()},
- rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
+ {rsp.data.begin(), rsp.data.end()},
+ rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
} else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
*gkResponse = GKResponse::retry(rsp.timeout);
} else {
@@ -358,149 +344,110 @@
}
});
- if (!hwRes.isOk()) {
- LOG(ERROR) << "verify transaction failed";
- return GK_ERROR;
- }
- const gatekeeper::password_handle_t* handle =
- reinterpret_cast<const gatekeeper::password_handle_t*>(
- enrolledPasswordHandle.data());
- secureUserId = handle->user_id;
+ if (!hwRes.isOk()) {
+ LOG(ERROR) << "verify transaction failed";
+ return GK_ERROR;
}
+ const gatekeeper::password_handle_t* handle =
+ reinterpret_cast<const gatekeeper::password_handle_t*>(
+ enrolledPasswordHandle.data());
+ secureUserId = handle->user_id;
+ }
- if (gkResponse->response_code() == GKResponseCode::OK) {
- if (gkResponse->payload().size() != 0) {
- // try to connect to IKeystoreAuthorization AIDL service first.
- AIBinder* authzAIBinder =
- AServiceManager_getService("android.security.authorization");
- ::ndk::SpAIBinder authzBinder(authzAIBinder);
- auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
- if (authzService) {
- if (gkResponse->payload().size() != sizeof(hw_auth_token_t)) {
- LOG(ERROR) << "Incorrect size of AuthToken payload.";
- return GK_ERROR;
- }
-
- const hw_auth_token_t* hwAuthToken =
- reinterpret_cast<const hw_auth_token_t*>(gkResponse->payload().data());
- HardwareAuthToken authToken;
-
- authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp);
- authToken.challenge = hwAuthToken->challenge;
- authToken.userId = hwAuthToken->user_id;
- authToken.authenticatorId = hwAuthToken->authenticator_id;
- authToken.authenticatorType = static_cast<HardwareAuthenticatorType>(
- betoh32(hwAuthToken->authenticator_type));
- authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]);
- auto result = authzService->addAuthToken(authToken);
- if (!result.isOk()) {
- LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
- return GK_ERROR;
- }
- } else {
- LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with "
- "Keystore.";
+ if (gkResponse->response_code() == GKResponseCode::OK) {
+ if (gkResponse->payload().size() != 0) {
+ // try to connect to IKeystoreAuthorization AIDL service first.
+ AIBinder* authzAIBinder = AServiceManager_getService("android.security.authorization");
+ ::ndk::SpAIBinder authzBinder(authzAIBinder);
+ auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
+ if (authzService) {
+ if (gkResponse->payload().size() != sizeof(hw_auth_token_t)) {
+ LOG(ERROR) << "Incorrect size of AuthToken payload.";
return GK_ERROR;
}
+
+ const hw_auth_token_t* hwAuthToken =
+ reinterpret_cast<const hw_auth_token_t*>(gkResponse->payload().data());
+ HardwareAuthToken authToken;
+
+ authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp);
+ authToken.challenge = hwAuthToken->challenge;
+ authToken.userId = hwAuthToken->user_id;
+ authToken.authenticatorId = hwAuthToken->authenticator_id;
+ authToken.authenticatorType = static_cast<HardwareAuthenticatorType>(
+ betoh32(hwAuthToken->authenticator_type));
+ authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]);
+ auto result = authzService->addAuthToken(authToken);
+ if (!result.isOk()) {
+ LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
+ return GK_ERROR;
+ }
+ } else {
+ LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with "
+ "Keystore.";
+ return GK_ERROR;
}
-
- maybe_store_sid(userId, secureUserId);
}
- return Status::ok();
+ maybe_store_sid(userId, secureUserId);
}
- Status getSecureUserId(int32_t userId, int64_t* sid) override {
- *sid = read_sid(userId);
- return Status::ok();
- }
-
- Status clearSecureUserId(int32_t userId) override {
- IPCThreadState* ipc = IPCThreadState::self();
- const int calling_pid = ipc->getCallingPid();
- const int calling_uid = ipc->getCallingUid();
- if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
- ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
- return Status::ok();
- }
- clear_sid(userId);
-
- uint32_t hw_userId = adjust_userId(userId);
- if (aidl_hw_device) {
- aidl_hw_device->deleteUser(hw_userId);
- } else if (hw_device) {
- hw_device->deleteUser(hw_userId, [](const GatekeeperResponse&) {});
- }
- return Status::ok();
- }
-
- Status reportDeviceSetupComplete() override {
- IPCThreadState* ipc = IPCThreadState::self();
- const int calling_pid = ipc->getCallingPid();
- const int calling_uid = ipc->getCallingUid();
- if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
- ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
- return Status::ok();
- }
-
- clear_state_if_needed();
- return Status::ok();
- }
-
- status_t dump(int fd, const Vector<String16>&) override {
- IPCThreadState* ipc = IPCThreadState::self();
- const int pid = ipc->getCallingPid();
- const int uid = ipc->getCallingUid();
- if (!PermissionCache::checkPermission(DUMP_PERMISSION, pid, uid)) {
- return PERMISSION_DENIED;
- }
-
- if (aidl_hw_device == nullptr && hw_device == nullptr) {
- const char* result = "Device not available";
- write(fd, result, strlen(result) + 1);
- } else {
- const char* result = "OK";
- write(fd, result, strlen(result) + 1);
- }
-
- return OK;
- }
-
- private:
- // AIDL gatekeeper service.
- std::shared_ptr<AidlIGatekeeper> aidl_hw_device;
- // HIDL gatekeeper service.
- sp<IGatekeeper> hw_device;
-
- bool clear_state_if_needed_done;
- bool is_running_gsi;
-};
-} // namespace android
-
-int main(int argc, char* argv[]) {
- ALOGI("Starting gatekeeperd...");
- if (argc < 2) {
- ALOGE("A directory must be specified!");
- return 1;
- }
- if (chdir(argv[1]) == -1) {
- ALOGE("chdir: %s: %s", argv[1], strerror(errno));
- return 1;
- }
-
- android::sp<android::IServiceManager> sm = android::defaultServiceManager();
- android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
- android::status_t ret =
- sm->addService(android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
- if (ret != android::OK) {
- ALOGE("Couldn't register binder service!");
- return -1;
- }
-
- /*
- * We're the only thread in existence, so we're just going to process
- * Binder transaction as a single-threaded program.
- */
- android::IPCThreadState::self()->joinThreadPool();
- return 0;
+ return Status::ok();
}
+
+Status GateKeeperProxy::getSecureUserId(int32_t userId, int64_t* sid) {
+ *sid = read_sid(userId);
+ return Status::ok();
+}
+
+Status GateKeeperProxy::clearSecureUserId(int32_t userId) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int calling_pid = ipc->getCallingPid();
+ const int calling_uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+ ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
+ return Status::ok();
+ }
+ clear_sid(userId);
+
+ uint32_t hw_userId = adjust_userId(userId);
+ if (aidl_hw_device) {
+ aidl_hw_device->deleteUser(hw_userId);
+ } else if (hw_device) {
+ hw_device->deleteUser(hw_userId, [](const GatekeeperResponse&) {});
+ }
+ return Status::ok();
+}
+
+Status GateKeeperProxy::reportDeviceSetupComplete() {
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int calling_pid = ipc->getCallingPid();
+ const int calling_uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+ ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
+ return Status::ok();
+ }
+
+ clear_state_if_needed();
+ return Status::ok();
+}
+
+status_t GateKeeperProxy::dump(int fd, const Vector<String16>&) {
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(DUMP_PERMISSION, pid, uid)) {
+ return PERMISSION_DENIED;
+ }
+
+ if (aidl_hw_device == nullptr && hw_device == nullptr) {
+ const char* result = "Device not available";
+ write(fd, result, strlen(result) + 1);
+ } else {
+ const char* result = "OK";
+ write(fd, result, strlen(result) + 1);
+ }
+
+ return OK;
+}
+} // namespace android
diff --git a/gatekeeperd/gatekeeperd.h b/gatekeeperd/gatekeeperd.h
new file mode 100644
index 0000000..29873da
--- /dev/null
+++ b/gatekeeperd/gatekeeperd.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/hardware/gatekeeper/IGatekeeper.h>
+#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
+#include <android/service/gatekeeper/BnGateKeeperService.h>
+#include <gatekeeper/GateKeeperResponse.h>
+
+using ::android::hardware::gatekeeper::V1_0::IGatekeeper;
+using AidlIGatekeeper = ::aidl::android::hardware::gatekeeper::IGatekeeper;
+using ::android::binder::Status;
+using ::android::service::gatekeeper::BnGateKeeperService;
+using GKResponse = ::android::service::gatekeeper::GateKeeperResponse;
+
+namespace android {
+
+class GateKeeperProxy : public BnGateKeeperService {
+ public:
+ GateKeeperProxy();
+
+ virtual ~GateKeeperProxy() {}
+
+ void store_sid(uint32_t userId, uint64_t sid);
+
+ void clear_state_if_needed();
+
+ bool mark_cold_boot();
+
+ void maybe_store_sid(uint32_t userId, uint64_t sid);
+
+ uint64_t read_sid(uint32_t userId);
+
+ void clear_sid(uint32_t userId);
+
+ // This should only be called on userIds being passed to the GateKeeper HAL. It ensures that
+ // secure storage shared across a GSI image and a host image will not overlap.
+ uint32_t adjust_userId(uint32_t userId);
+
+#define GK_ERROR *gkResponse = GKResponse::error(), Status::ok()
+
+ Status enroll(int32_t userId, const std::optional<std::vector<uint8_t>>& currentPasswordHandle,
+ const std::optional<std::vector<uint8_t>>& currentPassword,
+ const std::vector<uint8_t>& desiredPassword, GKResponse* gkResponse) override;
+
+ Status verify(int32_t userId, const ::std::vector<uint8_t>& enrolledPasswordHandle,
+ const ::std::vector<uint8_t>& providedPassword, GKResponse* gkResponse) override;
+
+ Status verifyChallenge(int32_t userId, int64_t challenge,
+ const std::vector<uint8_t>& enrolledPasswordHandle,
+ const std::vector<uint8_t>& providedPassword,
+ GKResponse* gkResponse) override;
+
+ Status getSecureUserId(int32_t userId, int64_t* sid) override;
+
+ Status clearSecureUserId(int32_t userId) override;
+
+ Status reportDeviceSetupComplete() override;
+
+ status_t dump(int fd, const Vector<String16>&) override;
+
+ private:
+ // AIDL gatekeeper service.
+ std::shared_ptr<AidlIGatekeeper> aidl_hw_device;
+ // HIDL gatekeeper service.
+ sp<IGatekeeper> hw_device;
+
+ bool clear_state_if_needed_done;
+ bool is_running_gsi;
+};
+} // namespace android
diff --git a/gatekeeperd/main.cpp b/gatekeeperd/main.cpp
new file mode 100644
index 0000000..a01f9de
--- /dev/null
+++ b/gatekeeperd/main.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+#include <log/log.h>
+
+#include "gatekeeperd.h"
+
+int main(int argc, char* argv[]) {
+ ALOGI("Starting gatekeeperd...");
+ if (argc < 2) {
+ ALOGE("A directory must be specified!");
+ return 1;
+ }
+ if (chdir(argv[1]) == -1) {
+ ALOGE("chdir: %s: %s", argv[1], strerror(errno));
+ return 1;
+ }
+
+ android::sp<android::IServiceManager> sm = android::defaultServiceManager();
+ android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
+ android::status_t ret = sm->addService(
+ android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
+ if (ret != android::OK) {
+ ALOGE("Couldn't register binder service!");
+ return 1;
+ }
+
+ /*
+ * We're the only thread in existence, so we're just going to process
+ * Binder transaction as a single-threaded program.
+ */
+ android::IPCThreadState::self()->joinThreadPool();
+ return 1;
+}
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 0fc3ffc..7e8513b 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -180,9 +180,11 @@
std::string init_script = R"init(
service A something
class first
+ user nobody
service A something
class second
+ user nobody
override
)init";
@@ -610,6 +612,31 @@
EXPECT_EQ(2, num_executed);
}
+TEST(init, RejectsNoUserStartingInV) {
+ std::string init_script =
+ R"init(
+service A something
+ class first
+)init";
+
+ TemporaryFile tf;
+ ASSERT_TRUE(tf.fd != -1);
+ ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd));
+
+ ServiceList service_list;
+ Parser parser;
+ parser.AddSectionParser("service",
+ std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+ ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+ if (GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+ ASSERT_EQ(1u, parser.parse_error_count());
+ } else {
+ ASSERT_EQ(0u, parser.parse_error_count());
+ }
+}
+
TEST(init, RejectsCriticalAndOneshotService) {
if (GetIntProperty("ro.product.first_api_level", 10000) < 30) {
GTEST_SKIP() << "Test only valid for devices launching with R or later";
@@ -619,6 +646,7 @@
R"init(
service A something
class first
+ user root
critical
oneshot
)init";
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index d89664c..d46e1f7 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -25,6 +25,7 @@
#include <android-base/logging.h>
#include <android-base/parseint.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <hidl-util/FQName.h>
#include <processgroup/processgroup.h>
@@ -678,8 +679,13 @@
}
if (service_->proc_attr_.parsed_uid == std::nullopt) {
- LOG(WARNING) << "No user specified for service '" << service_->name()
- << "'. Defaults to root.";
+ if (android::base::GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+ return Error() << "No user specified for service '" << service_->name()
+ << "'. Defaults to root.";
+ } else {
+ LOG(WARNING) << "No user specified for service '" << service_->name()
+ << "'. Defaults to root.";
+ }
}
if (interface_inheritance_hierarchy_) {
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 48bc0b7..dbaeb93 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -96,6 +96,10 @@
// Returns false in case of error, true in case of success
bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
+// Check if a profile can be applied without failing.
+// Returns true if it can be applied without failing, false otherwise
+bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid);
+
#endif // __ANDROID_VNDK__
__END_DECLS
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index a021594..1f29040 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -216,7 +216,6 @@
static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
int ret = 0;
auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
- auto uid_path = ConvertUidToPath(cgroup, uid);
while (retries--) {
ret = rmdir(uid_pid_path.c_str());
@@ -657,3 +656,13 @@
bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
return CgroupGetAttributePathForTask(attr_name, tid, path);
}
+
+bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid) {
+ const TaskProfile* tp = TaskProfiles::GetInstance().GetProfile(profile_name);
+
+ if (tp == nullptr) {
+ return false;
+ }
+
+ return tp->IsValidForProcess(uid, pid);
+}
\ No newline at end of file
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 1731828..44dba2a 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -259,6 +259,31 @@
return true;
}
+bool SetAttributeAction::IsValidForProcess(uid_t, pid_t pid) const {
+ return IsValidForTask(pid);
+}
+
+bool SetAttributeAction::IsValidForTask(int tid) const {
+ std::string path;
+
+ if (!attribute_->GetPathForTask(tid, &path)) {
+ return false;
+ }
+
+ if (!access(path.c_str(), W_OK)) {
+ // operation will succeed
+ return true;
+ }
+
+ if (!access(path.c_str(), F_OK)) {
+ // file exists but not writable
+ return false;
+ }
+
+ // file does not exist, ignore if optional
+ return optional_;
+}
+
SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
: controller_(c), path_(p) {
FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
@@ -396,6 +421,39 @@
FdCacheHelper::Drop(fd_[cache_type]);
}
+bool SetCgroupAction::IsValidForProcess(uid_t uid, pid_t pid) const {
+ std::lock_guard<std::mutex> lock(fd_mutex_);
+ if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
+ return true;
+ }
+
+ if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
+ return false;
+ }
+
+ std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
+ return access(procs_path.c_str(), W_OK) == 0;
+}
+
+bool SetCgroupAction::IsValidForTask(int) const {
+ std::lock_guard<std::mutex> lock(fd_mutex_);
+ if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
+ return true;
+ }
+
+ if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
+ return false;
+ }
+
+ if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
+ // application-dependent path can't be used with tid
+ return false;
+ }
+
+ std::string tasks_path = controller()->GetTasksFilePath(path_);
+ return access(tasks_path.c_str(), W_OK) == 0;
+}
+
WriteFileAction::WriteFileAction(const std::string& task_path, const std::string& proc_path,
const std::string& value, bool logfailures)
: task_path_(task_path), proc_path_(proc_path), value_(value), logfailures_(logfailures) {
@@ -532,6 +590,37 @@
FdCacheHelper::Drop(fd_[cache_type]);
}
+bool WriteFileAction::IsValidForProcess(uid_t, pid_t) const {
+ std::lock_guard<std::mutex> lock(fd_mutex_);
+ if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
+ return true;
+ }
+
+ if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
+ return false;
+ }
+
+ return access(proc_path_.empty() ? task_path_.c_str() : proc_path_.c_str(), W_OK) == 0;
+}
+
+bool WriteFileAction::IsValidForTask(int) const {
+ std::lock_guard<std::mutex> lock(fd_mutex_);
+ if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
+ return true;
+ }
+
+ if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
+ return false;
+ }
+
+ if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
+ // application-dependent path can't be used with tid
+ return false;
+ }
+
+ return access(task_path_.c_str(), W_OK) == 0;
+}
+
bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
for (const auto& profile : profiles_) {
profile->ExecuteForProcess(uid, pid);
@@ -558,6 +647,24 @@
}
}
+bool ApplyProfileAction::IsValidForProcess(uid_t uid, pid_t pid) const {
+ for (const auto& profile : profiles_) {
+ if (!profile->IsValidForProcess(uid, pid)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ApplyProfileAction::IsValidForTask(int tid) const {
+ for (const auto& profile : profiles_) {
+ if (!profile->IsValidForTask(tid)) {
+ return false;
+ }
+ }
+ return true;
+}
+
void TaskProfile::MoveTo(TaskProfile* profile) {
profile->elements_ = std::move(elements_);
profile->res_cached_ = res_cached_;
@@ -620,6 +727,20 @@
res_cached_ = false;
}
+bool TaskProfile::IsValidForProcess(uid_t uid, pid_t pid) const {
+ for (const auto& element : elements_) {
+ if (!element->IsValidForProcess(uid, pid)) return false;
+ }
+ return true;
+}
+
+bool TaskProfile::IsValidForTask(int tid) const {
+ for (const auto& element : elements_) {
+ if (!element->IsValidForTask(tid)) return false;
+ }
+ return true;
+}
+
void TaskProfiles::DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const {
for (auto& iter : profiles_) {
iter.second->DropResourceCaching(cache_type);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index a8ecb87..a62c5b0 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -72,12 +72,14 @@
virtual const char* Name() const = 0;
// Default implementations will fail
- virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
- virtual bool ExecuteForTask(int) const { return false; };
- virtual bool ExecuteForUID(uid_t) const { return false; };
+ virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; }
+ virtual bool ExecuteForTask(int) const { return false; }
+ virtual bool ExecuteForUID(uid_t) const { return false; }
virtual void EnableResourceCaching(ResourceCacheType) {}
virtual void DropResourceCaching(ResourceCacheType) {}
+ virtual bool IsValidForProcess(uid_t uid, pid_t pid) const { return false; }
+ virtual bool IsValidForTask(int tid) const { return false; }
protected:
enum CacheUseResult { SUCCESS, FAIL, UNUSED };
@@ -103,6 +105,8 @@
const char* Name() const override { return "SetTimerSlack"; }
bool ExecuteForTask(int tid) const override;
+ bool IsValidForProcess(uid_t uid, pid_t pid) const override { return true; }
+ bool IsValidForTask(int tid) const override { return true; }
private:
unsigned long slack_;
@@ -120,6 +124,8 @@
bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
bool ExecuteForTask(int tid) const override;
bool ExecuteForUID(uid_t uid) const override;
+ bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+ bool IsValidForTask(int tid) const override;
private:
const IProfileAttribute* attribute_;
@@ -137,6 +143,8 @@
bool ExecuteForTask(int tid) const override;
void EnableResourceCaching(ResourceCacheType cache_type) override;
void DropResourceCaching(ResourceCacheType cache_type) override;
+ bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+ bool IsValidForTask(int tid) const override;
const CgroupController* controller() const { return &controller_; }
@@ -161,6 +169,8 @@
bool ExecuteForTask(int tid) const override;
void EnableResourceCaching(ResourceCacheType cache_type) override;
void DropResourceCaching(ResourceCacheType cache_type) override;
+ bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+ bool IsValidForTask(int tid) const override;
private:
std::string task_path_, proc_path_, value_;
@@ -186,6 +196,8 @@
bool ExecuteForUID(uid_t uid) const;
void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
+ bool IsValidForProcess(uid_t uid, pid_t pid) const;
+ bool IsValidForTask(int tid) const;
private:
const std::string name_;
@@ -204,6 +216,8 @@
bool ExecuteForTask(int tid) const override;
void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
+ bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+ bool IsValidForTask(int tid) const override;
private:
std::vector<std::shared_ptr<TaskProfile>> profiles_;
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 6a5b48b..eadbe76 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -175,6 +175,32 @@
}
}
+class TaskProfileFixture : public TestWithParam<TestParam> {
+ public:
+ ~TaskProfileFixture() = default;
+};
+
+TEST_P(TaskProfileFixture, TaskProfile) {
+ // Treehugger runs host tests inside a container without cgroupv2 support.
+ if (!IsCgroupV2MountedRw()) {
+ GTEST_SKIP();
+ return;
+ }
+ const TestParam params = GetParam();
+ ProfileAttributeMock pa(params.attr_name);
+ // Test simple profile with one action
+ std::shared_ptr<TaskProfile> tp = std::make_shared<TaskProfile>("test_profile");
+ tp->Add(std::make_unique<SetAttributeAction>(&pa, params.attr_value, params.optional_attr));
+ EXPECT_EQ(tp->IsValidForProcess(getuid(), getpid()), params.result);
+ EXPECT_EQ(tp->IsValidForTask(getpid()), params.result);
+ // Test aggregate profile
+ TaskProfile tp2("meta_profile");
+ std::vector<std::shared_ptr<TaskProfile>> profiles = {tp};
+ tp2.Add(std::make_unique<ApplyProfileAction>(profiles));
+ EXPECT_EQ(tp2.IsValidForProcess(getuid(), getpid()), params.result);
+ EXPECT_EQ(tp2.IsValidForTask(getpid()), params.result);
+}
+
// Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
// exists }.
INSTANTIATE_TEST_SUITE_P(
@@ -215,4 +241,28 @@
.log_prefix = "Failed to write",
.log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
+// Test TaskProfile IsValid calls.
+INSTANTIATE_TEST_SUITE_P(
+ TaskProfileTestSuite, TaskProfileFixture,
+ Values(
+ // Test operating on non-existing cgroup attribute fails.
+ TestParam{.attr_name = "no-such-attribute",
+ .attr_value = ".",
+ .optional_attr = false,
+ .result = false},
+ // Test operating on optional non-existing cgroup attribute succeeds.
+ TestParam{.attr_name = "no-such-attribute",
+ .attr_value = ".",
+ .optional_attr = true,
+ .result = true},
+ // Test operating on existing cgroup attribute succeeds.
+ TestParam{.attr_name = "cgroup.procs",
+ .attr_value = ".",
+ .optional_attr = false,
+ .result = true},
+ // Test operating on optional existing cgroup attribute succeeds.
+ TestParam{.attr_name = "cgroup.procs",
+ .attr_value = ".",
+ .optional_attr = true,
+ .result = true}));
} // namespace
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index 0730cce..dde784e 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -18,6 +18,7 @@
disabled
updatable
seclabel u:r:adbd:s0
+ user root
on property:vendor.sys.usb.adb.disabled=*
setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
diff --git a/rootdir/ramdisk_node_list b/rootdir/ramdisk_node_list
index d3ab8a6..4f45faa 100644
--- a/rootdir/ramdisk_node_list
+++ b/rootdir/ramdisk_node_list
@@ -1,3 +1,4 @@
dir dev 0755 0 0
nod dev/null 0600 0 0 c 1 3
nod dev/console 0600 0 0 c 5 1
+nod dev/urandom 0600 0 0 c 1 9