Loading fs_mgr/libsnapshot/cow_api_test.cpp +200 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ #include <libsnapshot/cow_reader.h> #include <libsnapshot/cow_writer.h> using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; namespace android { namespace snapshot { Loading Loading @@ -781,6 +785,202 @@ TEST_F(CowTest, AppendAfterFinalize) { ASSERT_TRUE(reader.Parse(cow_->fd)); } AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) { data.resize(writer->options().block_size, '\0'); if (!writer->AddRawBlocks(new_block, data.data(), data.size())) { return AssertionFailure() << "Failed to add raw block"; } return AssertionSuccess(); } AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op, const std::string& data) { CowHeader header; reader->GetHeader(&header); std::string cmp = data; cmp.resize(header.block_size, '\0'); StringSink sink; if (!reader->ReadData(op, &sink)) { return AssertionFailure() << "Failed to read data block"; } if (cmp != sink.stream()) { return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got " << sink.stream(); } return AssertionSuccess(); } TEST_F(CowTest, ResumeMidCluster) { CowOptions options; options.cluster_ops = 7; auto writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->Initialize(cow_->fd)); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_TRUE(writer->AddLabel(2)); 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(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->Done()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op.type == kCowReplaceOp) { 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) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 8); ASSERT_EQ(max_in_cluster, 7); ASSERT_EQ(num_clusters, 2); } TEST_F(CowTest, ResumeEndCluster) { CowOptions options; int cluster_ops = 5; options.cluster_ops = cluster_ops; auto writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->Initialize(cow_->fd)); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_TRUE(writer->AddLabel(2)); 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(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->Done()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op.type == kCowReplaceOp) { 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) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 8); ASSERT_EQ(max_in_cluster, cluster_ops); ASSERT_EQ(num_clusters, 3); } TEST_F(CowTest, DeleteMidCluster) { CowOptions options; options.cluster_ops = 7; auto writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->Initialize(cow_->fd)); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); 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(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->Done()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op.type == kCowReplaceOp) { 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) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 3); ASSERT_EQ(max_in_cluster, 5); // 3 data, 1 label, 1 cluster op ASSERT_EQ(num_clusters, 1); } } // namespace snapshot } // namespace android Loading fs_mgr/libsnapshot/cow_writer.cpp +36 −11 Original line number Diff line number Diff line Loading @@ -232,15 +232,11 @@ bool CowWriter::OpenForAppend(uint64_t label) { // Free reader so we own the descriptor position again. reader = nullptr; // Remove excess data if (!Truncate(next_op_pos_)) { return false; } if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "lseek failed"; return false; } return true; return EmitClusterIfNeeded(); } bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) { Loading Loading @@ -319,6 +315,14 @@ bool CowWriter::EmitCluster() { return WriteOperation(op); } bool CowWriter::EmitClusterIfNeeded() { // If there isn't room for another op and the cluster end op, end the current cluster if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) { if (!EmitCluster()) return false; } return true; } std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) { switch (compression_) { case kCowCompressGz: { Loading Loading @@ -379,6 +383,21 @@ bool CowWriter::Finalize() { auto continue_num_ops = footer_.op.num_ops; bool extra_cluster = false; // Blank out extra ops, in case we're in append mode and dropped ops. if (cluster_size_) { auto unused_cluster_space = cluster_size_ - current_cluster_size_; std::string clr; clr.resize(unused_cluster_space, '\0'); if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "Failed to seek to footer position."; return false; } if (!android::base::WriteFully(fd_, clr.data(), clr.size())) { PLOG(ERROR) << "clearing unused cluster area failed"; return false; } } // Footer should be at the end of a file, so if there is data after the current block, end it // and start a new cluster. if (cluster_size_ && current_data_size_ > 0) { Loading @@ -403,6 +422,17 @@ bool CowWriter::Finalize() { return false; } // Remove excess data, if we're in append mode and threw away more data // than we wrote before. off_t offs = lseek(fd_.get(), 0, SEEK_CUR); if (offs < 0) { PLOG(ERROR) << "Failed to lseek to find current position"; return false; } if (!Truncate(offs)) { return false; } // Reposition for additional Writing if (extra_cluster) { current_cluster_size_ = continue_cluster_size; Loading Loading @@ -445,12 +475,7 @@ bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t if (!WriteRawData(data, size)) return false; } AddOperation(op); // If there isn't room for another op and the cluster end op, end the current cluster if (cluster_size_ && op.type != kCowClusterOp && cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) { if (!EmitCluster()) return false; } return true; return EmitClusterIfNeeded(); } void CowWriter::AddOperation(const CowOperation& op) { Loading fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h +1 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,7 @@ class CowWriter : public ICowWriter { private: bool EmitCluster(); bool EmitClusterIfNeeded(); void SetupHeaders(); bool ParseOptions(); bool OpenForWrite(); Loading rootdir/Android.bp +8 −0 Original line number Diff line number Diff line Loading @@ -35,3 +35,11 @@ linker_config { src: "etc/linker.config.json", installable: false, } // TODO(b/185211376) Scope the native APIs that microdroid will provide to the app payload prebuilt_etc { name: "public.libraries.android.txt", src: "etc/public.libraries.android.txt", filename: "public.libraries.txt", installable: false, } No newline at end of file rootdir/init.rc +2 −0 Original line number Diff line number Diff line Loading @@ -755,6 +755,8 @@ on post-fs-data mkdir /data/misc/snapshotctl_log 0755 root root # create location to store pre-reboot information mkdir /data/misc/prereboot 0700 system system # directory used for on-device refresh metrics file. mkdir /data/misc/odrefresh 0777 system system # directory used for on-device signing key blob mkdir /data/misc/odsign 0700 root root Loading Loading
fs_mgr/libsnapshot/cow_api_test.cpp +200 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ #include <libsnapshot/cow_reader.h> #include <libsnapshot/cow_writer.h> using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; namespace android { namespace snapshot { Loading Loading @@ -781,6 +785,202 @@ TEST_F(CowTest, AppendAfterFinalize) { ASSERT_TRUE(reader.Parse(cow_->fd)); } AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) { data.resize(writer->options().block_size, '\0'); if (!writer->AddRawBlocks(new_block, data.data(), data.size())) { return AssertionFailure() << "Failed to add raw block"; } return AssertionSuccess(); } AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op, const std::string& data) { CowHeader header; reader->GetHeader(&header); std::string cmp = data; cmp.resize(header.block_size, '\0'); StringSink sink; if (!reader->ReadData(op, &sink)) { return AssertionFailure() << "Failed to read data block"; } if (cmp != sink.stream()) { return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got " << sink.stream(); } return AssertionSuccess(); } TEST_F(CowTest, ResumeMidCluster) { CowOptions options; options.cluster_ops = 7; auto writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->Initialize(cow_->fd)); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_TRUE(writer->AddLabel(2)); 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(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->Done()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op.type == kCowReplaceOp) { 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) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 8); ASSERT_EQ(max_in_cluster, 7); ASSERT_EQ(num_clusters, 2); } TEST_F(CowTest, ResumeEndCluster) { CowOptions options; int cluster_ops = 5; options.cluster_ops = cluster_ops; auto writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->Initialize(cow_->fd)); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_TRUE(writer->AddLabel(2)); 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(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->Done()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op.type == kCowReplaceOp) { 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) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 8); ASSERT_EQ(max_in_cluster, cluster_ops); ASSERT_EQ(num_clusters, 3); } TEST_F(CowTest, DeleteMidCluster) { CowOptions options; options.cluster_ops = 7; auto writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->Initialize(cow_->fd)); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique<CowWriter>(options); ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1)); 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(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->Done()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op.type == kCowReplaceOp) { 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) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 3); ASSERT_EQ(max_in_cluster, 5); // 3 data, 1 label, 1 cluster op ASSERT_EQ(num_clusters, 1); } } // namespace snapshot } // namespace android Loading
fs_mgr/libsnapshot/cow_writer.cpp +36 −11 Original line number Diff line number Diff line Loading @@ -232,15 +232,11 @@ bool CowWriter::OpenForAppend(uint64_t label) { // Free reader so we own the descriptor position again. reader = nullptr; // Remove excess data if (!Truncate(next_op_pos_)) { return false; } if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "lseek failed"; return false; } return true; return EmitClusterIfNeeded(); } bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) { Loading Loading @@ -319,6 +315,14 @@ bool CowWriter::EmitCluster() { return WriteOperation(op); } bool CowWriter::EmitClusterIfNeeded() { // If there isn't room for another op and the cluster end op, end the current cluster if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) { if (!EmitCluster()) return false; } return true; } std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) { switch (compression_) { case kCowCompressGz: { Loading Loading @@ -379,6 +383,21 @@ bool CowWriter::Finalize() { auto continue_num_ops = footer_.op.num_ops; bool extra_cluster = false; // Blank out extra ops, in case we're in append mode and dropped ops. if (cluster_size_) { auto unused_cluster_space = cluster_size_ - current_cluster_size_; std::string clr; clr.resize(unused_cluster_space, '\0'); if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "Failed to seek to footer position."; return false; } if (!android::base::WriteFully(fd_, clr.data(), clr.size())) { PLOG(ERROR) << "clearing unused cluster area failed"; return false; } } // Footer should be at the end of a file, so if there is data after the current block, end it // and start a new cluster. if (cluster_size_ && current_data_size_ > 0) { Loading @@ -403,6 +422,17 @@ bool CowWriter::Finalize() { return false; } // Remove excess data, if we're in append mode and threw away more data // than we wrote before. off_t offs = lseek(fd_.get(), 0, SEEK_CUR); if (offs < 0) { PLOG(ERROR) << "Failed to lseek to find current position"; return false; } if (!Truncate(offs)) { return false; } // Reposition for additional Writing if (extra_cluster) { current_cluster_size_ = continue_cluster_size; Loading Loading @@ -445,12 +475,7 @@ bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t if (!WriteRawData(data, size)) return false; } AddOperation(op); // If there isn't room for another op and the cluster end op, end the current cluster if (cluster_size_ && op.type != kCowClusterOp && cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) { if (!EmitCluster()) return false; } return true; return EmitClusterIfNeeded(); } void CowWriter::AddOperation(const CowOperation& op) { Loading
fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h +1 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,7 @@ class CowWriter : public ICowWriter { private: bool EmitCluster(); bool EmitClusterIfNeeded(); void SetupHeaders(); bool ParseOptions(); bool OpenForWrite(); Loading
rootdir/Android.bp +8 −0 Original line number Diff line number Diff line Loading @@ -35,3 +35,11 @@ linker_config { src: "etc/linker.config.json", installable: false, } // TODO(b/185211376) Scope the native APIs that microdroid will provide to the app payload prebuilt_etc { name: "public.libraries.android.txt", src: "etc/public.libraries.android.txt", filename: "public.libraries.txt", installable: false, } No newline at end of file
rootdir/init.rc +2 −0 Original line number Diff line number Diff line Loading @@ -755,6 +755,8 @@ on post-fs-data mkdir /data/misc/snapshotctl_log 0755 root root # create location to store pre-reboot information mkdir /data/misc/prereboot 0700 system system # directory used for on-device refresh metrics file. mkdir /data/misc/odrefresh 0777 system system # directory used for on-device signing key blob mkdir /data/misc/odsign 0700 root root Loading