Merge "Fix AVB key path again."
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 4bf791e..62f6ac7 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -454,6 +454,8 @@
" --skip-reboot Don't reboot device after flashing.\n"
" --disable-verity Sets disable-verity when flashing vbmeta.\n"
" --disable-verification Sets disable-verification when flashing vbmeta.\n"
+ " --fs-options=OPTION[,OPTION]\n"
+ " Enable filesystem features. OPTION supports casefold, projid, compress\n"
#if !defined(_WIN32)
" --wipe-and-use-fbe Enable file-based encryption, wiping userdata.\n"
#endif
@@ -1581,7 +1583,7 @@
static void fb_perform_format(
const std::string& partition, int skip_if_not_supported,
const std::string& type_override, const std::string& size_override,
- const std::string& initial_dir) {
+ const std::string& initial_dir, const unsigned fs_options) {
std::string partition_type, partition_size;
struct fastboot_buffer buf;
@@ -1644,7 +1646,7 @@
logicalBlkSize = fb_get_flash_block_size("logical-block-size");
if (fs_generator_generate(gen, output.path, size, initial_dir,
- eraseBlkSize, logicalBlkSize)) {
+ eraseBlkSize, logicalBlkSize, fs_options)) {
die("Cannot generate image for %s", partition.c_str());
}
@@ -1778,6 +1780,7 @@
bool skip_secondary = false;
bool set_fbe_marker = false;
bool force_flash = false;
+ unsigned fs_options = 0;
int longindex;
std::string slot_override;
std::string next_active;
@@ -1795,6 +1798,7 @@
{"disable-verification", no_argument, 0, 0},
{"disable-verity", no_argument, 0, 0},
{"force", no_argument, 0, 0},
+ {"fs-options", required_argument, 0, 0},
{"header-version", required_argument, 0, 0},
{"help", no_argument, 0, 'h'},
{"kernel-offset", required_argument, 0, 0},
@@ -1834,6 +1838,8 @@
g_disable_verity = true;
} else if (name == "force") {
force_flash = true;
+ } else if (name == "fs-options") {
+ fs_options = ParseFsOption(optarg);
} else if (name == "header-version") {
g_boot_img_hdr.header_version = strtoul(optarg, nullptr, 0);
} else if (name == "dtb") {
@@ -1990,7 +1996,7 @@
std::string partition = next_arg(&args);
auto format = [&](const std::string& partition) {
- fb_perform_format(partition, 0, type_override, size_override, "");
+ fb_perform_format(partition, 0, type_override, size_override, "", fs_options);
};
do_for_partitions(partition, slot_override, format, true);
} else if (command == "signature") {
@@ -2180,10 +2186,10 @@
if (partition == "userdata" && set_fbe_marker) {
fprintf(stderr, "setting FBE marker on initial userdata...\n");
std::string initial_userdata_dir = create_fbemarker_tmpdir();
- fb_perform_format(partition, 1, partition_type, "", initial_userdata_dir);
+ fb_perform_format(partition, 1, partition_type, "", initial_userdata_dir, fs_options);
delete_fbemarker_tmpdir(initial_userdata_dir);
} else {
- fb_perform_format(partition, 1, partition_type, "", "");
+ fb_perform_format(partition, 1, partition_type, "", "", fs_options);
}
}
}
@@ -2233,3 +2239,23 @@
}
hdr->SetOsVersion(major, minor, patch);
}
+
+unsigned FastBootTool::ParseFsOption(const char* arg) {
+ unsigned fsOptions = 0;
+
+ std::vector<std::string> options = android::base::Split(arg, ",");
+ if (options.size() < 1)
+ syntax_error("bad options: %s", arg);
+
+ for (size_t i = 0; i < options.size(); ++i) {
+ if (options[i] == "casefold")
+ fsOptions |= (1 << FS_OPT_CASEFOLD);
+ else if (options[i] == "projid")
+ fsOptions |= (1 << FS_OPT_PROJID);
+ else if (options[i] == "compress")
+ fsOptions |= (1 << FS_OPT_COMPRESS);
+ else
+ syntax_error("unsupported options: %s", options[i].c_str());
+ }
+ return fsOptions;
+}
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index 9f18253..c23793a 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -34,4 +34,5 @@
void ParseOsPatchLevel(boot_img_hdr_v1*, const char*);
void ParseOsVersion(boot_img_hdr_v1*, const char*);
+ unsigned ParseFsOption(const char*);
};
diff --git a/fastboot/fs.cpp b/fastboot/fs.cpp
index 8c0aa6b..8addcb6 100644
--- a/fastboot/fs.cpp
+++ b/fastboot/fs.cpp
@@ -113,7 +113,7 @@
static int generate_ext4_image(const char* fileName, long long partSize,
const std::string& initial_dir, unsigned eraseBlkSize,
- unsigned logicalBlkSize) {
+ unsigned logicalBlkSize, const unsigned fsOptions) {
static constexpr int block_size = 4096;
const std::string exec_dir = android::base::GetExecutableDirectory();
@@ -137,6 +137,12 @@
mke2fs_args.push_back(ext_attr.c_str());
mke2fs_args.push_back("-O");
mke2fs_args.push_back("uninit_bg");
+
+ if (fsOptions & (1 << FS_OPT_PROJID)) {
+ mke2fs_args.push_back("-I");
+ mke2fs_args.push_back("512");
+ }
+
mke2fs_args.push_back(fileName);
std::string size_str = std::to_string(partSize / block_size);
@@ -162,9 +168,9 @@
return exec_cmd(e2fsdroid_args[0], e2fsdroid_args.data(), nullptr);
}
-static int generate_f2fs_image(const char* fileName, long long partSize, const std::string& initial_dir,
- unsigned /* unused */, unsigned /* unused */)
-{
+static int generate_f2fs_image(const char* fileName, long long partSize,
+ const std::string& initial_dir, unsigned /* unused */,
+ unsigned /* unused */, const unsigned fsOptions) {
const std::string exec_dir = android::base::GetExecutableDirectory();
const std::string mkf2fs_path = exec_dir + "/make_f2fs";
std::vector<const char*> mkf2fs_args = {mkf2fs_path.c_str()};
@@ -174,6 +180,26 @@
mkf2fs_args.push_back(size_str.c_str());
mkf2fs_args.push_back("-g");
mkf2fs_args.push_back("android");
+
+ if (fsOptions & (1 << FS_OPT_PROJID)) {
+ mkf2fs_args.push_back("-O");
+ mkf2fs_args.push_back("project_quota,extra_attr");
+ }
+
+ if (fsOptions & (1 << FS_OPT_CASEFOLD)) {
+ mkf2fs_args.push_back("-O");
+ mkf2fs_args.push_back("casefold");
+ mkf2fs_args.push_back("-C");
+ mkf2fs_args.push_back("utf8");
+ }
+
+ if (fsOptions & (1 << FS_OPT_COMPRESS)) {
+ mkf2fs_args.push_back("-O");
+ mkf2fs_args.push_back("compression");
+ mkf2fs_args.push_back("-O");
+ mkf2fs_args.push_back("extra_attr");
+ }
+
mkf2fs_args.push_back(fileName);
mkf2fs_args.push_back(nullptr);
@@ -198,7 +224,7 @@
//returns 0 or error value
int (*generate)(const char* fileName, long long partSize, const std::string& initial_dir,
- unsigned eraseBlkSize, unsigned logicalBlkSize);
+ unsigned eraseBlkSize, unsigned logicalBlkSize, const unsigned fsOptions);
} generators[] = {
{ "ext4", generate_ext4_image},
@@ -215,7 +241,7 @@
}
int fs_generator_generate(const struct fs_generator* gen, const char* fileName, long long partSize,
- const std::string& initial_dir, unsigned eraseBlkSize, unsigned logicalBlkSize)
-{
- return gen->generate(fileName, partSize, initial_dir, eraseBlkSize, logicalBlkSize);
+ const std::string& initial_dir, unsigned eraseBlkSize,
+ unsigned logicalBlkSize, const unsigned fsOptions) {
+ return gen->generate(fileName, partSize, initial_dir, eraseBlkSize, logicalBlkSize, fsOptions);
}
diff --git a/fastboot/fs.h b/fastboot/fs.h
index 331100d..f832938 100644
--- a/fastboot/fs.h
+++ b/fastboot/fs.h
@@ -5,6 +5,13 @@
struct fs_generator;
+enum FS_OPTION {
+ FS_OPT_CASEFOLD,
+ FS_OPT_PROJID,
+ FS_OPT_COMPRESS,
+};
+
const struct fs_generator* fs_get_generator(const std::string& fs_type);
int fs_generator_generate(const struct fs_generator* gen, const char* fileName, long long partSize,
- const std::string& initial_dir, unsigned eraseBlkSize = 0, unsigned logicalBlkSize = 0);
+ const std::string& initial_dir, unsigned eraseBlkSize = 0,
+ unsigned logicalBlkSize = 0, unsigned fsOptions = 0);
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 4b79466..42459ec 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -692,14 +692,18 @@
return false;
}
if (!is_proc_mounts && !access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
+ // This is expected to fail if host is android Q, since Q doesn't
+ // support DSU slotting. The DSU "active" indicator file would be
+ // non-existent or empty if DSU is enabled within the guest system.
+ // In that case, just use the default slot name "dsu".
std::string dsu_slot;
if (!android::gsi::GetActiveDsu(&dsu_slot)) {
- // This is expected to fail if host is android Q, since Q doesn't
- // support DSU slotting.
- // In that case, just use the default slot name "dsu".
PWARNING << __FUNCTION__ << "(): failed to get active dsu slot";
+ }
+ if (dsu_slot.empty()) {
dsu_slot = "dsu";
}
+
std::string lp_names;
ReadFileToString(gsi::kGsiLpNamesFile, &lp_names);
TransformFstabForDsu(fstab, dsu_slot, Split(lp_names, ","));
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index e52d8d5..388c296 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -1097,7 +1097,7 @@
static bool CreateScratchOnData(std::string* scratch_device, bool* partition_exists, bool* change) {
*partition_exists = false;
- *change = false;
+ if (change) *change = false;
auto images = IImageManager::Open("remount", 10s);
if (!images) {
@@ -1117,7 +1117,7 @@
return false;
}
- *change = true;
+ if (change) *change = true;
// Note: calling RemoveDisabledImages here ensures that we do not race with
// clean_scratch_files and accidentally try to map an image that will be
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 9a44020..4db6584 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -264,10 +264,7 @@
ASSERT_EQ(size_before, size_after);
struct stat buf;
- if (fstat(cow_->fd, &buf) < 0) {
- perror("Fails to determine size of cow image written");
- FAIL();
- }
+ ASSERT_GE(fstat(cow_->fd, &buf), 0) << strerror(errno);
ASSERT_EQ(buf.st_size, writer.GetCowSize());
}
@@ -408,7 +405,7 @@
// Get the last known good label
CowReader label_reader;
uint64_t label;
- ASSERT_TRUE(label_reader.Parse(cow_->fd));
+ ASSERT_TRUE(label_reader.Parse(cow_->fd, {5}));
ASSERT_TRUE(label_reader.GetLastLabel(&label));
ASSERT_EQ(label, 5);
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 517cd9c..6b7ada5 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -31,14 +31,7 @@
namespace android {
namespace snapshot {
-CowReader::CowReader()
- : fd_(-1),
- header_(),
- footer_(),
- fd_size_(0),
- has_footer_(false),
- last_label_(0),
- has_last_label_(false) {}
+CowReader::CowReader() : fd_(-1), header_(), fd_size_(0) {}
static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
@@ -49,12 +42,12 @@
#endif
}
-bool CowReader::Parse(android::base::unique_fd&& fd) {
+bool CowReader::Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label) {
owned_fd_ = std::move(fd);
- return Parse(android::base::borrowed_fd{owned_fd_});
+ return Parse(android::base::borrowed_fd{owned_fd_}, label);
}
-bool CowReader::Parse(android::base::borrowed_fd fd) {
+bool CowReader::Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label) {
fd_ = fd;
auto pos = lseek(fd_.get(), 0, SEEK_END);
@@ -99,100 +92,122 @@
return false;
}
- auto footer_pos = lseek(fd_.get(), -header_.footer_size, SEEK_END);
- if (footer_pos != fd_size_ - header_.footer_size) {
- LOG(ERROR) << "Failed to read full footer!";
- return false;
- }
- if (!android::base::ReadFully(fd_, &footer_, sizeof(footer_))) {
- PLOG(ERROR) << "read footer failed";
- return false;
- }
- has_footer_ = (footer_.op.type == kCowFooterOp);
- return ParseOps();
+ return ParseOps(label);
}
-bool CowReader::ParseOps() {
+bool CowReader::ParseOps(std::optional<uint64_t> label) {
uint64_t pos = lseek(fd_.get(), sizeof(header_), SEEK_SET);
if (pos != sizeof(header_)) {
PLOG(ERROR) << "lseek ops failed";
return false;
}
- std::optional<uint64_t> next_last_label;
+
auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
- if (has_footer_) ops_buffer->reserve(footer_.op.num_ops);
- uint64_t current_op_num = 0;
- // Look until we reach the last possible non-footer position.
- uint64_t last_pos = fd_size_ - (has_footer_ ? sizeof(footer_) : sizeof(CowOperation));
// Alternating op and data
- while (pos < last_pos) {
- ops_buffer->resize(current_op_num + 1);
- if (!android::base::ReadFully(fd_, ops_buffer->data() + current_op_num,
- sizeof(CowOperation))) {
+ while (true) {
+ ops_buffer->emplace_back();
+ if (!android::base::ReadFully(fd_, &ops_buffer->back(), sizeof(CowOperation))) {
PLOG(ERROR) << "read op failed";
return false;
}
- auto& current_op = ops_buffer->data()[current_op_num];
- pos = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
- if (pos == uint64_t(-1)) {
+
+ auto& current_op = ops_buffer->back();
+ off_t offs = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
+ if (offs < 0) {
PLOG(ERROR) << "lseek next op failed";
return false;
}
- current_op_num++;
- if (next_last_label) {
- last_label_ = next_last_label.value();
- has_last_label_ = true;
- }
+ pos = static_cast<uint64_t>(offs);
+
if (current_op.type == kCowLabelOp) {
- // If we don't have a footer, the last label may be incomplete.
- // If we see any operation after it, we can infer the flush finished.
- if (has_footer_) {
- has_last_label_ = true;
- last_label_ = current_op.source;
- } else {
- next_last_label = {current_op.source};
+ last_label_ = {current_op.source};
+
+ // If we reach the requested label, stop reading.
+ if (label && label.value() == current_op.source) {
+ break;
}
} else if (current_op.type == kCowFooterOp) {
- memcpy(&footer_.op, ¤t_op, sizeof(footer_.op));
- // we don't consider this an operation for the checksum
- current_op_num--;
- if (android::base::ReadFully(fd_, &footer_.data, sizeof(footer_.data))) {
- has_footer_ = true;
- if (next_last_label) {
- last_label_ = next_last_label.value();
- has_last_label_ = true;
- }
+ footer_.emplace();
+
+ CowFooter* footer = &footer_.value();
+ memcpy(&footer_->op, ¤t_op, sizeof(footer->op));
+
+ if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
+ LOG(ERROR) << "Could not read COW footer";
+ return false;
}
+
+ // Drop the footer from the op stream.
+ ops_buffer->pop_back();
break;
}
}
+ // To successfully parse a COW file, we need either:
+ // (1) a label to read up to, and for that label to be found, or
+ // (2) a valid footer.
+ if (label) {
+ if (!last_label_) {
+ LOG(ERROR) << "Did not find label " << label.value()
+ << " while reading COW (no labels found)";
+ return false;
+ }
+ if (last_label_.value() != label.value()) {
+ LOG(ERROR) << "Did not find label " << label.value()
+ << ", last label=" << last_label_.value();
+ return false;
+ }
+ } else if (!footer_) {
+ LOG(ERROR) << "No COW footer found";
+ return false;
+ }
+
uint8_t csum[32];
memset(csum, 0, sizeof(uint8_t) * 32);
- if (has_footer_) {
- if (ops_buffer->size() != footer_.op.num_ops) {
+ if (footer_) {
+ if (ops_buffer->size() != footer_->op.num_ops) {
LOG(ERROR) << "num ops does not match";
return false;
}
- if (ops_buffer->size() * sizeof(CowOperation) != footer_.op.ops_size) {
+ if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
LOG(ERROR) << "ops size does not match ";
return false;
}
- SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
- if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
+ SHA256(&footer_->op, sizeof(footer_->op), footer_->data.footer_checksum);
+ if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
LOG(ERROR) << "ops checksum does not match";
return false;
}
- SHA256(ops_buffer.get()->data(), footer_.op.ops_size, csum);
- if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
+ SHA256(ops_buffer.get()->data(), footer_->op.ops_size, csum);
+ if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
LOG(ERROR) << "ops checksum does not match";
return false;
}
} else {
- LOG(INFO) << "No Footer, recovered data";
+ LOG(INFO) << "No COW Footer, recovered data";
}
+
+ if (header_.num_merge_ops > 0) {
+ uint64_t merge_ops = header_.num_merge_ops;
+ uint64_t metadata_ops = 0;
+ uint64_t current_op_num = 0;
+
+ CHECK(ops_buffer->size() >= merge_ops);
+ while (merge_ops) {
+ auto& current_op = ops_buffer->data()[current_op_num];
+ if (current_op.type == kCowLabelOp || current_op.type == kCowFooterOp) {
+ metadata_ops += 1;
+ } else {
+ merge_ops -= 1;
+ }
+ current_op_num += 1;
+ }
+ ops_buffer->erase(ops_buffer.get()->begin(),
+ ops_buffer.get()->begin() + header_.num_merge_ops + metadata_ops);
+ }
+
ops_ = ops_buffer;
return true;
}
@@ -203,14 +218,14 @@
}
bool CowReader::GetFooter(CowFooter* footer) {
- if (!has_footer_) return false;
- *footer = footer_;
+ if (!footer_) return false;
+ *footer = footer_.value();
return true;
}
bool CowReader::GetLastLabel(uint64_t* label) {
- if (!has_last_label_) return false;
- *label = last_label_;
+ if (!last_label_) return false;
+ *label = last_label_.value();
return true;
}
@@ -246,14 +261,50 @@
return (*op_iter_);
}
+class CowOpReverseIter final : public ICowOpReverseIter {
+ public:
+ explicit CowOpReverseIter(std::shared_ptr<std::vector<CowOperation>> ops);
+
+ bool Done() override;
+ const CowOperation& Get() override;
+ void Next() override;
+
+ private:
+ std::shared_ptr<std::vector<CowOperation>> ops_;
+ std::vector<CowOperation>::reverse_iterator op_riter_;
+};
+
+CowOpReverseIter::CowOpReverseIter(std::shared_ptr<std::vector<CowOperation>> ops) {
+ ops_ = ops;
+ op_riter_ = ops_.get()->rbegin();
+}
+
+bool CowOpReverseIter::Done() {
+ return op_riter_ == ops_.get()->rend();
+}
+
+void CowOpReverseIter::Next() {
+ CHECK(!Done());
+ op_riter_++;
+}
+
+const CowOperation& CowOpReverseIter::Get() {
+ CHECK(!Done());
+ return (*op_riter_);
+}
+
std::unique_ptr<ICowOpIter> CowReader::GetOpIter() {
return std::make_unique<CowOpIter>(ops_);
}
+std::unique_ptr<ICowOpReverseIter> CowReader::GetRevOpIter() {
+ return std::make_unique<CowOpReverseIter>(ops_);
+}
+
bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
// Validate the offset, taking care to acknowledge possible overflow of offset+len.
- if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(footer_) || len >= fd_size_ ||
- offset + len > fd_size_ - sizeof(footer_)) {
+ if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
+ offset + len > fd_size_ - sizeof(CowFooter)) {
LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
return false;
}
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 589ae30..7eddf8c 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -108,6 +108,8 @@
std::unique_ptr<uint8_t[]> product_buffer_;
void Init();
+ void InitCowDevices();
+ void InitDaemon();
void CreateCowDevice(std::unique_ptr<TemporaryFile>& cow);
void CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow);
void CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow);
@@ -238,12 +240,6 @@
system_device_name_.clear();
system_device_ctrl_name_.clear();
- // Create a COW device. Number of sectors is chosen random which can
- // hold at least 400MB of data
-
- int err = ioctl(sys_fd_.get(), BLKGETSIZE, &system_blksize_);
- ASSERT_GE(err, 0);
-
std::string str(cow->path);
std::size_t found = str.find_last_of("/\\");
ASSERT_NE(found, std::string::npos);
@@ -280,12 +276,6 @@
product_device_name_.clear();
product_device_ctrl_name_.clear();
- // Create a COW device. Number of sectors is chosen random which can
- // hold at least 400MB of data
-
- int err = ioctl(product_fd_.get(), BLKGETSIZE, &product_blksize_);
- ASSERT_GE(err, 0);
-
std::string str(cow->path);
std::size_t found = str.find_last_of("/\\");
ASSERT_NE(found, std::string::npos);
@@ -297,12 +287,15 @@
system(cmd.c_str());
}
-void SnapuserdTest::StartSnapuserdDaemon() {
- ASSERT_TRUE(EnsureSnapuserdStarted());
+void SnapuserdTest::InitCowDevices() {
+ system_blksize_ = client_->InitDmUserCow(cow_system_->path);
+ ASSERT_NE(system_blksize_, 0);
- client_ = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
- ASSERT_NE(client_, nullptr);
+ product_blksize_ = client_->InitDmUserCow(cow_product_->path);
+ ASSERT_NE(product_blksize_, 0);
+}
+void SnapuserdTest::InitDaemon() {
bool ok = client_->InitializeSnapuserd(cow_system_->path, system_a_loop_->device(),
GetSystemControlPath());
ASSERT_TRUE(ok);
@@ -312,6 +305,13 @@
ASSERT_TRUE(ok);
}
+void SnapuserdTest::StartSnapuserdDaemon() {
+ ASSERT_TRUE(EnsureSnapuserdStarted());
+
+ client_ = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+ ASSERT_NE(client_, nullptr);
+}
+
void SnapuserdTest::CreateSnapshotDevices() {
std::string cmd;
@@ -435,10 +435,13 @@
CreateCowDevice(cow_system_);
CreateCowDevice(cow_product_);
+ StartSnapuserdDaemon();
+ InitCowDevices();
+
CreateSystemDmUser(cow_system_);
CreateProductDmUser(cow_product_);
- StartSnapuserdDaemon();
+ InitDaemon();
CreateSnapshotDevices();
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index f550e54..957ba35 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -91,6 +91,7 @@
header_.header_size = sizeof(CowHeader);
header_.footer_size = sizeof(CowFooter);
header_.block_size = options_.block_size;
+ header_.num_merge_ops = 0;
footer_ = {};
footer_.op.data_length = 64;
footer_.op.type = kCowFooterOp;
@@ -121,10 +122,23 @@
is_dev_null_ = true;
} else {
fd_ = fd;
+
+ struct stat stat;
+ if (fstat(fd.get(), &stat) < 0) {
+ PLOG(ERROR) << "fstat failed";
+ return false;
+ }
+ is_block_device_ = S_ISBLK(stat.st_mode);
}
return true;
}
+void CowWriter::InitializeMerge(borrowed_fd fd, CowHeader* header) {
+ fd_ = fd;
+ memcpy(&header_, header, sizeof(CowHeader));
+ merge_in_progress_ = true;
+}
+
bool CowWriter::Initialize(unique_fd&& fd) {
owned_fd_ = std::move(fd);
return Initialize(borrowed_fd{owned_fd_});
@@ -177,12 +191,10 @@
bool CowWriter::OpenForAppend(uint64_t label) {
auto reader = std::make_unique<CowReader>();
std::queue<CowOperation> toAdd;
- bool found_label = false;
- if (!reader->Parse(fd_) || !reader->GetHeader(&header_)) {
+ if (!reader->Parse(fd_, {label}) || !reader->GetHeader(&header_)) {
return false;
}
- reader->GetFooter(&footer_);
options_.block_size = header_.block_size;
@@ -192,30 +204,19 @@
ops_.resize(0);
auto iter = reader->GetOpIter();
- while (!iter->Done() && !found_label) {
- const CowOperation& op = iter->Get();
-
- if (op.type == kCowFooterOp) break;
- if (op.type == kCowLabelOp && op.source == label) found_label = true;
- AddOperation(op);
-
+ while (!iter->Done()) {
+ AddOperation(iter->Get());
iter->Next();
}
- if (!found_label) {
- LOG(ERROR) << "Failed to find last label";
- return false;
- }
-
// Free reader so we own the descriptor position again.
reader = nullptr;
- // Position for new writing
- if (ftruncate(fd_.get(), next_op_pos_) != 0) {
- PLOG(ERROR) << "Failed to trim file";
+ // Remove excess data
+ if (!Truncate(next_op_pos_)) {
return false;
}
- if (lseek(fd_.get(), 0, SEEK_END) < 0) {
+ if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek failed";
return false;
}
@@ -223,6 +224,7 @@
}
bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
+ CHECK(!merge_in_progress_);
CowOperation op = {};
op.type = kCowCopyOp;
op.new_block = new_block;
@@ -233,6 +235,7 @@
bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
uint64_t pos;
+ CHECK(!merge_in_progress_);
for (size_t i = 0; i < size / header_.block_size; i++) {
CowOperation op = {};
op.type = kCowReplaceOp;
@@ -271,6 +274,7 @@
}
bool CowWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
+ CHECK(!merge_in_progress_);
for (uint64_t i = 0; i < num_blocks; i++) {
CowOperation op = {};
op.type = kCowZeroOp;
@@ -282,6 +286,7 @@
}
bool CowWriter::EmitLabel(uint64_t label) {
+ CHECK(!merge_in_progress_);
CowOperation op = {};
op.type = kCowLabelOp;
op.source = label;
@@ -416,5 +421,34 @@
return true;
}
+bool CowWriter::CommitMerge(int merged_ops) {
+ CHECK(merge_in_progress_);
+ header_.num_merge_ops += merged_ops;
+
+ if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek failed";
+ return false;
+ }
+
+ if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&header_),
+ sizeof(header_))) {
+ PLOG(ERROR) << "WriteFully failed";
+ return false;
+ }
+
+ return Sync();
+}
+
+bool CowWriter::Truncate(off_t length) {
+ if (is_dev_null_ || is_block_device_) {
+ return true;
+ }
+ if (ftruncate(fd_.get(), length) < 0) {
+ PLOG(ERROR) << "Failed to truncate.";
+ return false;
+ }
+ return true;
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 2291e30..80766ff 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -59,6 +59,9 @@
// The size of block operations, in bytes.
uint32_t block_size;
+
+ // Tracks merge operations completed
+ uint64_t num_merge_ops;
} __attribute__((packed));
// This structure is the same size of a normal Operation, but is repurposed for the footer.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index b863ff2..be69225 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -18,6 +18,7 @@
#include <functional>
#include <memory>
+#include <optional>
#include <android-base/unique_fd.h>
#include <libsnapshot/cow_format.h>
@@ -26,6 +27,7 @@
namespace snapshot {
class ICowOpIter;
+class ICowOpReverseIter;
// A ByteSink object handles requests for a buffer of a specific size. It
// always owns the underlying buffer. It's designed to minimize potential
@@ -73,6 +75,9 @@
// Return an iterator for retrieving CowOperation entries.
virtual std::unique_ptr<ICowOpIter> GetOpIter() = 0;
+ // Return an reverse iterator for retrieving CowOperation entries.
+ virtual std::unique_ptr<ICowOpReverseIter> GetRevOpIter() = 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;
@@ -93,12 +98,29 @@
virtual void Next() = 0;
};
+// Reverse Iterate over a sequence of COW operations.
+class ICowOpReverseIter {
+ public:
+ virtual ~ICowOpReverseIter() {}
+
+ // True if there are more items to read, false otherwise.
+ virtual bool Done() = 0;
+
+ // Read the current operation.
+ virtual const CowOperation& Get() = 0;
+
+ // Advance to the next item.
+ virtual void Next() = 0;
+};
+
class CowReader : public ICowReader {
public:
CowReader();
- bool Parse(android::base::unique_fd&& fd);
- bool Parse(android::base::borrowed_fd fd);
+ // Parse the COW, optionally, up to the given label. If no label is
+ // specified, the COW must have an intact footer.
+ bool Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label = {});
+ bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
bool GetHeader(CowHeader* header) override;
bool GetFooter(CowFooter* footer) override;
@@ -107,23 +129,26 @@
// Create a CowOpIter object which contains footer_.num_ops
// CowOperation objects. Get() returns a unique CowOperation object
- // whose lifetime depends on the CowOpIter object
+ // whose lifetime depends on the CowOpIter object; the return
+ // value of these will never be null.
std::unique_ptr<ICowOpIter> GetOpIter() override;
+ std::unique_ptr<ICowOpReverseIter> GetRevOpIter() override;
+
bool ReadData(const CowOperation& op, IByteSink* sink) override;
bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
+ void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; }
+
private:
- bool ParseOps();
+ bool ParseOps(std::optional<uint64_t> label);
android::base::unique_fd owned_fd_;
android::base::borrowed_fd fd_;
CowHeader header_;
- CowFooter footer_;
+ std::optional<CowFooter> footer_;
uint64_t fd_size_;
- bool has_footer_;
- uint64_t last_label_;
- bool has_last_label_;
+ std::optional<uint64_t> last_label_;
std::shared_ptr<std::vector<CowOperation>> ops_;
};
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 95c735d..e9320b0 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -97,6 +97,9 @@
bool InitializeAppend(android::base::unique_fd&&, uint64_t label);
bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
+ void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header);
+ bool CommitMerge(int merged_ops);
+
bool Finalize() override;
uint64_t GetCowSize() override;
@@ -120,6 +123,7 @@
bool SetFd(android::base::borrowed_fd fd);
bool Sync();
+ bool Truncate(off_t length);
private:
android::base::unique_fd owned_fd_;
@@ -129,6 +133,8 @@
int compression_ = 0;
uint64_t next_op_pos_ = 0;
bool is_dev_null_ = false;
+ bool merge_in_progress_ = false;
+ bool is_block_device_ = false;
// :TODO: this is not efficient, but stringstream ubsan aborts because some
// bytes overflow a signed char.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
index 80f87d9..cd8b080 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
@@ -24,6 +24,7 @@
#include <limits>
#include <string>
#include <thread>
+#include <unordered_map>
#include <vector>
#include <android-base/file.h>
@@ -60,30 +61,38 @@
class Snapuserd final {
public:
- Snapuserd(const std::string& in_cow_device, const std::string& in_backing_store_device,
- const std::string& in_control_device)
- : cow_device_(in_cow_device),
- backing_store_device_(in_backing_store_device),
- control_device_(in_control_device),
- metadata_read_done_(false) {}
-
- bool Init();
+ bool InitBackingAndControlDevice(std::string& backing_device, std::string& control_device);
+ bool InitCowDevice(std::string& cow_device);
int Run();
+ const std::string& GetControlDevicePath() { return control_device_; }
+ const std::string& GetCowDevice() { return cow_device_; }
+ uint64_t GetNumSectors() { return num_sectors_; }
+
+ private:
int ReadDmUserHeader();
+ bool ReadDmUserPayload(void* buffer, size_t size);
int WriteDmUserPayload(size_t size);
int ConstructKernelCowHeader();
- int ReadMetadata();
+ bool ReadMetadata();
int ZerofillDiskExceptions(size_t read_size);
int ReadDiskExceptions(chunk_t chunk, size_t size);
int ReadData(chunk_t chunk, size_t size);
+ bool IsChunkIdMetadata(chunk_t chunk);
+ chunk_t GetNextAllocatableChunkId(chunk_t chunk);
- const std::string& GetControlDevicePath() { return control_device_; }
-
- private:
int ProcessReplaceOp(const CowOperation* cow_op);
int ProcessCopyOp(const CowOperation* cow_op);
int ProcessZeroOp();
+ loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
+ int* unmerged_exceptions);
+ int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
+ int unmerged_exceptions);
+ bool AdvanceMergedOps(int merged_ops_cur_iter);
+ bool ProcessMergeComplete(chunk_t chunk, void* buffer);
+ sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+ chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
std::string cow_device_;
std::string backing_store_device_;
std::string control_device_;
@@ -93,19 +102,22 @@
unique_fd ctrl_fd_;
uint32_t exceptions_per_area_;
+ uint64_t num_sectors_;
std::unique_ptr<ICowOpIter> cowop_iter_;
+ std::unique_ptr<ICowOpReverseIter> cowop_riter_;
std::unique_ptr<CowReader> reader_;
+ std::unique_ptr<CowWriter> writer_;
// Vector of disk exception which is a
// mapping of old-chunk to new-chunk
std::vector<std::unique_ptr<uint8_t[]>> vec_;
- // Index - Chunk ID
+ // Key - Chunk ID
// Value - cow operation
- std::vector<const CowOperation*> chunk_vec_;
+ std::unordered_map<chunk_t, const CowOperation*> chunk_map_;
- bool metadata_read_done_;
+ bool metadata_read_done_ = false;
BufferSink bufsink_;
};
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
index aaec229..b5e5a96 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
@@ -58,7 +58,7 @@
std::chrono::milliseconds timeout_ms);
bool StopSnapuserd();
- int RestartSnapuserd(std::vector<std::vector<std::string>>& vec);
+ uint64_t InitDmUserCow(const std::string& cow_device);
bool InitializeSnapuserd(const std::string& cow_device, const std::string& backing_device,
const std::string& control_device);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
index 181ee33..be48400 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
@@ -36,6 +36,7 @@
static constexpr uint32_t MAX_PACKET_SIZE = 512;
enum class DaemonOperations {
+ INIT,
START,
QUERY,
STOP,
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 793680b..c4c557e 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -386,24 +386,6 @@
auto& dm = DeviceMapper::Instance();
- // Use the size of the base device for the COW device. It doesn't really
- // matter, it just needs to look similar enough so the kernel doesn't complain
- // about alignment or being too small.
- uint64_t base_sectors = 0;
- {
- unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
- if (fd < 0) {
- PLOG(ERROR) << "open failed: " << base_device;
- return false;
- }
- auto dev_size = get_block_device_size(fd);
- if (!dev_size) {
- PLOG(ERROR) << "Could not determine block device size: " << base_device;
- return false;
- }
- base_sectors = dev_size / kSectorSize;
- }
-
// Use an extra decoration for first-stage init, so we can transition
// to a new table entry in second-stage.
std::string misc_name = name;
@@ -411,13 +393,19 @@
misc_name += "-init";
}
- DmTable table;
- table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
- if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+ if (!EnsureSnapuserdConnected()) {
return false;
}
- if (!EnsureSnapuserdConnected()) {
+ uint64_t base_sectors = snapuserd_client_->InitDmUserCow(cow_file);
+ if (base_sectors == 0) {
+ LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
+ return false;
+ }
+
+ DmTable table;
+ table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
+ if (!dm.CreateDevice(name, table, path, timeout_ms)) {
return false;
}
@@ -1366,6 +1354,15 @@
continue;
}
+ uint64_t base_sectors = snapuserd_client_->InitDmUserCow(cow_device);
+ if (base_sectors == 0) {
+ // Unrecoverable as metadata reads from cow device failed
+ LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
+ return false;
+ }
+
+ CHECK(base_sectors == target.spec.length);
+
if (!snapuserd_client_->InitializeSnapuserd(cow_device, backing_device, control_device)) {
// This error is unrecoverable. We cannot proceed because reads to
// the underlying device will fail.
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 40f26d6..7c393fc 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -159,7 +159,7 @@
CHECK((read_size & (BLOCK_SIZE - 1)) == 0);
while (read_size > 0) {
- const CowOperation* cow_op = chunk_vec_[chunk_key];
+ const CowOperation* cow_op = chunk_map_[chunk_key];
CHECK(cow_op != nullptr);
int result;
@@ -202,6 +202,8 @@
// are contiguous
chunk_key += 1;
+ if (cow_op->type == kCowCopyOp) CHECK(read_size == 0);
+
// This is similar to the way when chunk IDs were assigned
// in ReadMetadata().
//
@@ -287,6 +289,171 @@
return size;
}
+loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
+ int* unmerged_exceptions) {
+ loff_t offset = 0;
+ *unmerged_exceptions = 0;
+
+ while (*unmerged_exceptions <= exceptions_per_area_) {
+ struct disk_exception* merged_de =
+ reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+ struct disk_exception* cow_de =
+ reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+ // Unmerged op by the kernel
+ if (merged_de->old_chunk != 0) {
+ CHECK(merged_de->new_chunk != 0);
+ CHECK(merged_de->old_chunk == cow_de->old_chunk);
+ CHECK(merged_de->new_chunk == cow_de->new_chunk);
+
+ offset += sizeof(struct disk_exception);
+ *unmerged_exceptions += 1;
+ continue;
+ }
+
+ // Merge complete on this exception. However, we don't know how many
+ // merged in this cycle; hence break here.
+ CHECK(merged_de->new_chunk == 0);
+ CHECK(merged_de->old_chunk == 0);
+
+ break;
+ }
+
+ CHECK(!(*unmerged_exceptions == exceptions_per_area_));
+
+ LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
+ return offset;
+}
+
+int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
+ int unmerged_exceptions) {
+ int merged_ops_cur_iter = 0;
+
+ // Find the operations which are merged in this cycle.
+ while ((unmerged_exceptions + merged_ops_cur_iter) <= exceptions_per_area_) {
+ struct disk_exception* merged_de =
+ reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+ struct disk_exception* cow_de =
+ reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+ CHECK(merged_de->new_chunk == 0);
+ CHECK(merged_de->old_chunk == 0);
+
+ if (cow_de->new_chunk != 0) {
+ merged_ops_cur_iter += 1;
+ offset += sizeof(struct disk_exception);
+ // zero out to indicate that operation is merged.
+ cow_de->old_chunk = 0;
+ cow_de->new_chunk = 0;
+ } else if (cow_de->old_chunk == 0) {
+ // Already merged op in previous iteration or
+ // This could also represent a partially filled area.
+ //
+ // If the op was merged in previous cycle, we don't have
+ // to count them.
+ CHECK(cow_de->new_chunk == 0);
+ break;
+ } else {
+ LOG(ERROR) << "Error in merge operation. Found invalid metadata";
+ LOG(ERROR) << "merged_de-old-chunk: " << merged_de->old_chunk;
+ LOG(ERROR) << "merged_de-new-chunk: " << merged_de->new_chunk;
+ LOG(ERROR) << "cow_de-old-chunk: " << cow_de->old_chunk;
+ LOG(ERROR) << "cow_de-new-chunk: " << cow_de->new_chunk;
+ return -1;
+ }
+ }
+
+ return merged_ops_cur_iter;
+}
+
+bool Snapuserd::AdvanceMergedOps(int merged_ops_cur_iter) {
+ // Advance the merge operation pointer in the
+ // vector.
+ //
+ // cowop_iter_ is already initialized in ReadMetadata(). Just resume the
+ // merge process
+ while (!cowop_iter_->Done() && merged_ops_cur_iter) {
+ const CowOperation* cow_op = &cowop_iter_->Get();
+ CHECK(cow_op != nullptr);
+
+ if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
+ cowop_iter_->Next();
+ continue;
+ }
+
+ if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
+ cow_op->type == kCowCopyOp)) {
+ LOG(ERROR) << "Unknown operation-type found during merge: " << cow_op->type;
+ return false;
+ }
+
+ merged_ops_cur_iter -= 1;
+ LOG(DEBUG) << "Merge op found of type " << cow_op->type
+ << "Pending-merge-ops: " << merged_ops_cur_iter;
+ cowop_iter_->Next();
+ }
+
+ if (cowop_iter_->Done()) {
+ CHECK(merged_ops_cur_iter == 0);
+ LOG(DEBUG) << "All cow operations merged successfully in this cycle";
+ }
+
+ return true;
+}
+
+bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) {
+ uint32_t stride = exceptions_per_area_ + 1;
+ CowHeader header;
+
+ if (!reader_->GetHeader(&header)) {
+ LOG(ERROR) << "Failed to get header";
+ return false;
+ }
+
+ // ChunkID to vector index
+ lldiv_t divresult = lldiv(chunk, stride);
+ CHECK(divresult.quot < vec_.size());
+ LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk << " Metadata-Index: " << divresult.quot;
+
+ int unmerged_exceptions = 0;
+ loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions);
+
+ int merged_ops_cur_iter =
+ GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions);
+
+ // There should be at least one operation merged in this cycle
+ CHECK(merged_ops_cur_iter > 0);
+ if (!AdvanceMergedOps(merged_ops_cur_iter)) return false;
+
+ header.num_merge_ops += merged_ops_cur_iter;
+ reader_->UpdateMergeProgress(merged_ops_cur_iter);
+ if (!writer_->CommitMerge(merged_ops_cur_iter)) {
+ LOG(ERROR) << "CommitMerge failed...";
+ return false;
+ }
+
+ LOG(DEBUG) << "Merge success";
+ return true;
+}
+
+bool Snapuserd::IsChunkIdMetadata(chunk_t chunk) {
+ uint32_t stride = exceptions_per_area_ + 1;
+ lldiv_t divresult = lldiv(chunk, stride);
+
+ return (divresult.rem == NUM_SNAPSHOT_HDR_CHUNKS);
+}
+
+// Find the next free chunk-id to be assigned. Check if the next free
+// chunk-id represents a metadata page. If so, skip it.
+chunk_t Snapuserd::GetNextAllocatableChunkId(chunk_t chunk) {
+ chunk_t next_chunk = chunk + 1;
+
+ if (IsChunkIdMetadata(next_chunk)) {
+ next_chunk += 1;
+ }
+ return next_chunk;
+}
+
/*
* Read the metadata from COW device and
* construct the metadata as required by the kernel.
@@ -304,12 +471,26 @@
* This represents the old_chunk in the kernel COW format
* 4: We need to assign new_chunk for a corresponding old_chunk
* 5: The algorithm is similar to how kernel assigns chunk number
- * while creating exceptions.
+ * while creating exceptions. However, there are few cases
+ * which needs to be addressed here:
+ * a: During merge process, kernel scans the metadata page
+ * from backwards when merge is initiated. Since, we need
+ * to make sure that the merge ordering follows our COW format,
+ * we read the COW operation from backwards and populate the
+ * metadata so that when kernel starts the merging from backwards,
+ * those ops correspond to the beginning of our COW format.
+ * b: Kernel can merge successive operations if the two chunk IDs
+ * are contiguous. This can be problematic when there is a crash
+ * during merge; specifically when the merge operation has dependency.
+ * These dependencies can only happen during copy operations.
+ *
+ * To avoid this problem, we make sure that no two copy-operations
+ * do not have contiguous chunk IDs. Additionally, we make sure
+ * that each copy operation is merged individually.
* 6: Use a monotonically increasing chunk number to assign the
* new_chunk
* 7: Each chunk-id represents either a: Metadata page or b: Data page
- * 8: Chunk-id representing a data page is stored in a vector. Index is the
- * chunk-id and value is the pointer to the CowOperation
+ * 8: Chunk-id representing a data page is stored in a map.
* 9: Chunk-id representing a metadata page is converted into a vector
* index. We store this in vector as kernel requests metadata during
* two stage:
@@ -324,130 +505,128 @@
* exceptions_per_area_
* 12: Kernel will stop issuing metadata IO request when new-chunk ID is 0.
*/
-int Snapuserd::ReadMetadata() {
+bool Snapuserd::ReadMetadata() {
reader_ = std::make_unique<CowReader>();
CowHeader header;
- CowFooter footer;
+ CowOptions options;
+ bool prev_copy_op = false;
+ bool metadata_found = false;
+
+ LOG(DEBUG) << "ReadMetadata Start...";
if (!reader_->Parse(cow_fd_)) {
LOG(ERROR) << "Failed to parse";
- return 1;
+ return false;
}
if (!reader_->GetHeader(&header)) {
LOG(ERROR) << "Failed to get header";
- return 1;
- }
-
- if (!reader_->GetFooter(&footer)) {
- LOG(ERROR) << "Failed to get footer";
- return 1;
+ return false;
}
CHECK(header.block_size == BLOCK_SIZE);
- LOG(DEBUG) << "Num-ops: " << std::hex << footer.op.num_ops;
- LOG(DEBUG) << "ops-size: " << std::hex << footer.op.ops_size;
+ LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
- cowop_iter_ = reader_->GetOpIter();
+ writer_ = std::make_unique<CowWriter>(options);
+ writer_->InitializeMerge(cow_fd_.get(), &header);
- if (cowop_iter_ == nullptr) {
- LOG(ERROR) << "Failed to get cowop_iter";
- return 1;
- }
+ // Initialize the iterator for reading metadata
+ cowop_riter_ = reader_->GetRevOpIter();
exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
// Start from chunk number 2. Chunk 0 represents header and chunk 1
// represents first metadata page.
chunk_t next_free = NUM_SNAPSHOT_HDR_CHUNKS + 1;
- chunk_vec_.push_back(nullptr);
- chunk_vec_.push_back(nullptr);
loff_t offset = 0;
std::unique_ptr<uint8_t[]> de_ptr =
std::make_unique<uint8_t[]>(exceptions_per_area_ * sizeof(struct disk_exception));
// This memset is important. Kernel will stop issuing IO when new-chunk ID
- // is 0. When Area is not filled completely will all 256 exceptions,
+ // is 0. When Area is not filled completely with all 256 exceptions,
// this memset will ensure that metadata read is completed.
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
size_t num_ops = 0;
- while (!cowop_iter_->Done()) {
- const CowOperation* cow_op = &cowop_iter_->Get();
+ while (!cowop_riter_->Done()) {
+ const CowOperation* cow_op = &cowop_riter_->Get();
struct disk_exception* de =
reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
- cowop_iter_->Next();
+ cowop_riter_->Next();
continue;
}
if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
cow_op->type == kCowCopyOp)) {
LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
- return 1;
+ return false;
}
+ metadata_found = true;
+ if ((cow_op->type == kCowCopyOp || prev_copy_op)) {
+ next_free = GetNextAllocatableChunkId(next_free);
+ }
+
+ prev_copy_op = (cow_op->type == kCowCopyOp);
+
// Construct the disk-exception
de->old_chunk = cow_op->new_block;
de->new_chunk = next_free;
LOG(DEBUG) << "Old-chunk: " << de->old_chunk << "New-chunk: " << de->new_chunk;
- // Store operation pointer. Note, new-chunk ID is the index
- chunk_vec_.push_back(cow_op);
- CHECK(next_free == (chunk_vec_.size() - 1));
+ // Store operation pointer.
+ chunk_map_[next_free] = cow_op;
+ num_ops += 1;
offset += sizeof(struct disk_exception);
- cowop_iter_->Next();
+ cowop_riter_->Next();
- // Find the next free chunk-id to be assigned. Check if the next free
- // chunk-id represents a metadata page. If so, skip it.
- next_free += 1;
- uint32_t stride = exceptions_per_area_ + 1;
- lldiv_t divresult = lldiv(next_free, stride);
- num_ops += 1;
-
- if (divresult.rem == NUM_SNAPSHOT_HDR_CHUNKS) {
- CHECK(num_ops == exceptions_per_area_);
+ if (num_ops == exceptions_per_area_) {
// Store it in vector at the right index. This maps the chunk-id to
// vector index.
vec_.push_back(std::move(de_ptr));
offset = 0;
num_ops = 0;
- chunk_t metadata_chunk = (next_free - exceptions_per_area_ - NUM_SNAPSHOT_HDR_CHUNKS);
-
- LOG(DEBUG) << "Area: " << vec_.size() - 1;
- LOG(DEBUG) << "Metadata-chunk: " << metadata_chunk;
- LOG(DEBUG) << "Sector number of Metadata-chunk: " << (metadata_chunk << CHUNK_SHIFT);
-
// Create buffer for next area
de_ptr = std::make_unique<uint8_t[]>(exceptions_per_area_ *
sizeof(struct disk_exception));
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- // Since this is a metadata, store at this index
- chunk_vec_.push_back(nullptr);
-
- // Find the next free chunk-id
- next_free += 1;
- if (cowop_iter_->Done()) {
+ if (cowop_riter_->Done()) {
vec_.push_back(std::move(de_ptr));
+ LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
}
}
+
+ next_free = GetNextAllocatableChunkId(next_free);
}
- // Partially filled area
- if (num_ops) {
- LOG(DEBUG) << "Partially filled area num_ops: " << num_ops;
+ // Partially filled area or there is no metadata
+ // If there is no metadata, fill with zero so that kernel
+ // is aware that merge is completed.
+ if (num_ops || !metadata_found) {
vec_.push_back(std::move(de_ptr));
+ LOG(DEBUG) << "ReadMetadata() completed. Partially filled area num_ops: " << num_ops
+ << "Areas : " << vec_.size();
}
- return 0;
+ LOG(DEBUG) << "ReadMetadata() completed. chunk_id: " << next_free
+ << "Num Sector: " << ChunkToSector(next_free);
+
+ // Initialize the iterator for merging
+ cowop_iter_ = reader_->GetOpIter();
+
+ // Total number of sectors required for creating dm-user device
+ num_sectors_ = ChunkToSector(next_free);
+ metadata_read_done_ = true;
+ return true;
}
void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
@@ -484,29 +663,24 @@
return sizeof(struct dm_user_header) + size;
}
-bool Snapuserd::Init() {
- backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
- if (backing_store_fd_ < 0) {
- PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
+ if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
+ PLOG(ERROR) << "ReadDmUserPayload failed";
return false;
}
+ return true;
+}
+
+bool Snapuserd::InitCowDevice(std::string& cow_device) {
+ cow_device_ = cow_device;
+
cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
if (cow_fd_ < 0) {
PLOG(ERROR) << "Open Failed: " << cow_device_;
return false;
}
- std::string control_path = GetControlDevicePath();
-
- LOG(DEBUG) << "Opening control device " << control_path;
-
- ctrl_fd_.reset(open(control_path.c_str(), O_RDWR));
- if (ctrl_fd_ < 0) {
- PLOG(ERROR) << "Unable to open " << control_path;
- return false;
- }
-
// Allocate the buffer which is used to communicate between
// daemon and dm-user. The buffer comprises of header and a fixed payload.
// If the dm-user requests a big IO, the IO will be broken into chunks
@@ -514,6 +688,26 @@
size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
bufsink_.Initialize(buf_size);
+ return ReadMetadata();
+}
+
+bool Snapuserd::InitBackingAndControlDevice(std::string& backing_device,
+ std::string& control_device) {
+ backing_store_device_ = backing_device;
+ control_device_ = control_device;
+
+ backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+ if (backing_store_fd_ < 0) {
+ PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+ return false;
+ }
+
+ ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+ if (ctrl_fd_ < 0) {
+ PLOG(ERROR) << "Unable to open " << control_device_;
+ return false;
+ }
+
return true;
}
@@ -549,15 +743,7 @@
// never see multiple IO requests. Additionally this IO
// will always be a single 4k.
if (header->sector == 0) {
- // Read the metadata from internal COW device
- // and build the in-memory data structures
- // for all the operations in the internal COW.
- if (!metadata_read_done_ && ReadMetadata()) {
- LOG(ERROR) << "Metadata read failed";
- return 1;
- }
- metadata_read_done_ = true;
-
+ CHECK(metadata_read_done_ == true);
CHECK(read_size == BLOCK_SIZE);
ret = ConstructKernelCowHeader();
if (ret < 0) return ret;
@@ -567,15 +753,9 @@
// Check if the chunk ID represents a metadata
// page. If the chunk ID is not found in the
// vector, then it points to a metadata page.
- chunk_t chunk = (header->sector >> CHUNK_SHIFT);
+ chunk_t chunk = SectorToChunk(header->sector);
- if (chunk >= chunk_vec_.size()) {
- ret = ZerofillDiskExceptions(read_size);
- if (ret < 0) {
- LOG(ERROR) << "ZerofillDiskExceptions failed";
- return ret;
- }
- } else if (chunk_vec_[chunk] == nullptr) {
+ if (chunk_map_.find(chunk) == chunk_map_.end()) {
ret = ReadDiskExceptions(chunk, read_size);
if (ret < 0) {
LOG(ERROR) << "ReadDiskExceptions failed";
@@ -611,12 +791,29 @@
}
case DM_USER_MAP_WRITE: {
- // TODO: Bug: 168311203: After merge operation is completed, kernel issues write
- // to flush all the exception mappings where the merge is
- // completed. If dm-user routes the WRITE IO, we need to clear
- // in-memory data structures representing those exception
- // mappings.
- abort();
+ size_t remaining_size = header->len;
+ size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+ CHECK(read_size == BLOCK_SIZE);
+ CHECK(header->sector > 0);
+ chunk_t chunk = SectorToChunk(header->sector);
+ CHECK(chunk_map_.find(chunk) == chunk_map_.end());
+
+ void* buffer = bufsink_.GetPayloadBuffer(read_size);
+ CHECK(buffer != nullptr);
+
+ if (!ReadDmUserPayload(buffer, read_size)) {
+ return 1;
+ }
+
+ if (!ProcessMergeComplete(chunk, buffer)) {
+ LOG(ERROR) << "ProcessMergeComplete failed...";
+ return 1;
+ }
+
+ // Write the header only.
+ ssize_t written = WriteDmUserPayload(0);
+ if (written < 0) return written;
+
break;
}
}
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 5650139..d7fdb43 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -27,9 +27,12 @@
#include <unistd.h>
#include <chrono>
+#include <sstream>
#include <android-base/logging.h>
+#include <android-base/parseint.h>
#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <libsnapshot/snapuserd_client.h>
namespace android {
@@ -114,7 +117,7 @@
std::string str = Receivemsg();
// If the daemon is passive then fallback to secondary active daemon. Daemon
- // is passive during transition phase. Please see RestartSnapuserd()
+ // is passive during transition phase.
if (str.find("passive") != std::string::npos) {
LOG(ERROR) << "Snapuserd is terminating";
return false;
@@ -199,77 +202,31 @@
return true;
}
-/*
- * Transition from first stage snapuserd daemon to second stage daemon involves
- * series of steps viz:
- *
- * 1: Create new dm-user devices - This is done by libsnapshot
- *
- * 2: Spawn the new snapuserd daemon - This is the second stage daemon which
- * will start the server but the dm-user misc devices is not binded yet.
- *
- * 3: Vector to this function contains pair of cow_device and source device.
- * Ex: {{system_cow,system_a}, {product_cow, product_a}, {vendor_cow,
- * vendor_a}}. This vector will be populated by the libsnapshot.
- *
- * 4: Initialize the Second stage daemon passing the information from the
- * vector. This will bind the daemon with dm-user misc device and will be ready
- * to serve the IO. Up until this point, first stage daemon is still active.
- * However, client library will mark the first stage daemon as passive and hence
- * all the control message from hereon will be sent to active second stage
- * daemon.
- *
- * 5: Create new dm-snapshot table. This is done by libsnapshot. When new table
- * is created, kernel will issue metadata read once again which will be served
- * by second stage daemon. However, any active IO will still be served by first
- * stage daemon.
- *
- * 6: Swap the snapshot table atomically - This is done by libsnapshot. Once
- * the swapping is done, all the IO will be served by second stage daemon.
- *
- * 7: Stop the first stage daemon. After this point second stage daemon is
- * completely active to serve the IO and merging process.
- *
- */
-int SnapuserdClient::RestartSnapuserd(std::vector<std::vector<std::string>>& vec) {
- std::string msg = "terminate-request";
+uint64_t SnapuserdClient::InitDmUserCow(const std::string& cow_device) {
+ std::string msg = "init," + cow_device;
if (!Sendmsg(msg)) {
LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";
- return -1;
+ return 0;
}
std::string str = Receivemsg();
- if (str.find("fail") != std::string::npos) {
- LOG(ERROR) << "Failed to receive ack for " << msg << " from snapuserd daemon";
- return -1;
+ std::vector<std::string> input = android::base::Split(str, ",");
+
+ if (input[0] != "success") {
+ LOG(ERROR) << "Failed to receive number of sectors for " << msg << " from snapuserd daemon";
+ return 0;
}
- CHECK(str.find("success") != std::string::npos);
+ LOG(DEBUG) << "Snapuserd daemon COW device initialized: " << cow_device
+ << " Num-sectors: " << input[1];
- // Start the new daemon
- if (!EnsureSnapuserdStarted()) {
- LOG(ERROR) << "Failed to start new daemon";
- return -1;
+ uint64_t num_sectors = 0;
+ if (!android::base::ParseUint(input[1], &num_sectors)) {
+ LOG(ERROR) << "Failed to parse input string to sectors";
+ return 0;
}
-
- LOG(DEBUG) << "Second stage Snapuserd daemon created successfully";
-
- // Vector contains all the device information to be passed to the new
- // daemon. Note that the caller can choose to initialize separately
- // by calling InitializeSnapuserd() API as well. In that case, vector
- // should be empty
- for (int i = 0; i < vec.size(); i++) {
- std::string& cow_device = vec[i][0];
- std::string& base_device = vec[i][1];
- std::string& control_device = vec[i][2];
-
- InitializeSnapuserd(cow_device, base_device, control_device);
- LOG(DEBUG) << "Daemon initialized with " << cow_device << ", " << base_device << " and "
- << control_device;
- }
-
- return 0;
+ return num_sectors;
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 6a89218..3aa6136 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -33,6 +33,7 @@
namespace snapshot {
DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
+ if (input == "init") return DaemonOperations::INIT;
if (input == "start") return DaemonOperations::START;
if (input == "stop") return DaemonOperations::STOP;
if (input == "query") return DaemonOperations::QUERY;
@@ -123,6 +124,32 @@
DaemonOperations op = Resolveop(out[0]);
switch (op) {
+ case DaemonOperations::INIT: {
+ // Message format:
+ // init,<cow_device_path>
+ //
+ // Reads the metadata and send the number of sectors
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
+ }
+
+ auto snapuserd = std::make_unique<Snapuserd>();
+ if (!snapuserd->InitCowDevice(out[1])) {
+ LOG(ERROR) << "Failed to initialize Snapuserd";
+ return Sendmsg(fd, "fail");
+ }
+
+ std::string retval = "success," + std::to_string(snapuserd->GetNumSectors());
+
+ auto handler = std::make_unique<DmUserHandler>(std::move(snapuserd));
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ dm_users_.push_back(std::move(handler));
+ }
+
+ return Sendmsg(fd, retval);
+ }
case DaemonOperations::START: {
// Message format:
// start,<cow_device_path>,<source_device_path>,<control_device>
@@ -133,21 +160,29 @@
return Sendmsg(fd, "fail");
}
- auto snapuserd = std::make_unique<Snapuserd>(out[1], out[2], out[3]);
- if (!snapuserd->Init()) {
- LOG(ERROR) << "Failed to initialize Snapuserd";
- return Sendmsg(fd, "fail");
- }
-
- auto handler = std::make_unique<DmUserHandler>(std::move(snapuserd));
+ bool found = false;
{
std::lock_guard<std::mutex> lock(lock_);
-
- handler->thread() =
- std::thread(std::bind(&SnapuserdServer::RunThread, this, handler.get()));
- dm_users_.push_back(std::move(handler));
+ auto iter = dm_users_.begin();
+ while (iter != dm_users_.end()) {
+ if ((*iter)->snapuserd()->GetCowDevice() == out[1]) {
+ if (!((*iter)->snapuserd()->InitBackingAndControlDevice(out[2], out[3]))) {
+ LOG(ERROR) << "Failed to initialize control device: " << out[3];
+ break;
+ }
+ (*iter)->thread() = std::thread(
+ std::bind(&SnapuserdServer::RunThread, this, (*iter).get()));
+ found = true;
+ break;
+ }
+ iter++;
+ }
}
- return Sendmsg(fd, "success");
+ if (found) {
+ return Sendmsg(fd, "success");
+ } else {
+ return Sendmsg(fd, "fail");
+ }
}
case DaemonOperations::STOP: {
// Message format: stop
@@ -172,7 +207,7 @@
}
case DaemonOperations::DELETE: {
// Message format:
- // delete,<cow_device_path>
+ // delete,<control_device_path>
if (out.size() != 2) {
LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
diff --git a/gatekeeperd/binder/android/service/gatekeeper/IGateKeeperService.aidl b/gatekeeperd/binder/android/service/gatekeeper/IGateKeeperService.aidl
index 4646efc..67e8633 100644
--- a/gatekeeperd/binder/android/service/gatekeeper/IGateKeeperService.aidl
+++ b/gatekeeperd/binder/android/service/gatekeeper/IGateKeeperService.aidl
@@ -27,6 +27,7 @@
*
* @hide
*/
+@SensitiveData
interface IGateKeeperService {
/**
* Enrolls a password, returning the handle to the enrollment to be stored locally.
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index f1f24c3..2505f44 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -215,4 +215,16 @@
EXPECT_TRUE(tmp.isStaticString());
}
+TEST(String16Test, OverreadUtf8Conversion) {
+ char tmp[] = {'a', static_cast<char>(0xe0), '\0'};
+ String16 another(tmp);
+ EXPECT_TRUE(another.size() == 0);
+}
+
+TEST(String16Test, ValidUtf8Conversion) {
+ String16 another("abcdef");
+ EXPECT_EQ(6U, another.size());
+ EXPECT_STR16EQ(another, u"abcdef");
+}
+
} // namespace android
diff --git a/libutils/String8_test.cpp b/libutils/String8_test.cpp
index 3947a5f..9efcc6f 100644
--- a/libutils/String8_test.cpp
+++ b/libutils/String8_test.cpp
@@ -96,4 +96,9 @@
EXPECT_EQ(10U, string8.length());
}
+TEST_F(String8Test, ValidUtf16Conversion) {
+ char16_t tmp[] = u"abcdef";
+ String8 valid = String8(String16(tmp));
+ EXPECT_STREQ(valid, "abcdef");
+}
}
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index 96b5e0d..a21f686 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -24,3 +24,9 @@
src: "ueventd.rc",
recovery_available: true,
}
+
+// TODO(b/147210213) Generate list of libraries during build and fill in at build time
+linker_config {
+ name: "system_linker_config",
+ src: "etc/linker.config.json",
+}
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 2bceb75..c4c8768 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -69,7 +69,7 @@
EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS :=
ifeq ($(CLANG_COVERAGE),true)
- EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%p-%m.profraw
+ EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profraw
endif
# Put it here instead of in init.rc module definition,
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
new file mode 100644
index 0000000..d66ab73
--- /dev/null
+++ b/rootdir/etc/linker.config.json
@@ -0,0 +1,72 @@
+{
+ // These are list of libraries which has stub interface and installed
+ // in system image so other partition and APEX modules can link to it.
+ // TODO(b/147210213) : Generate this list on build and read from the file
+ "provideLibs": [
+ // LLNDK libraries
+ "libEGL.so",
+ "libGLESv1_CM.so",
+ "libGLESv2.so",
+ "libGLESv3.so",
+ "libRS.so",
+ "libandroid_net.so",
+ "libbinder_ndk.so",
+ "libc.so",
+ "libcgrouprc.so",
+ "libclang_rt.asan-arm-android.so",
+ "libclang_rt.asan-i686-android.so",
+ "libclang_rt.asan-x86_64-android.so",
+ "libdl.so",
+ "libft2.so",
+ "liblog.so",
+ "libm.so",
+ "libmediandk.so",
+ "libnativewindow.so",
+ "libsync.so",
+ "libvndksupport.so",
+ "libvulkan.so",
+ // NDK libraries
+ "libaaudio.so",
+ "libandroid.so",
+ // adb
+ "libadbd_auth.so",
+ "libadbd_fs.so",
+ // bionic
+ "libdl_android.so",
+ // statsd
+ "libincident.so",
+ // media
+ "libmediametrics.so",
+ // nn
+ "libneuralnetworks_packageinfo.so",
+ // SELinux
+ "libselinux.so"
+ ],
+ "requireLibs": [
+ // Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
+ "libdexfile_external.so",
+ "libdexfiled_external.so",
+ "libnativebridge.so",
+ "libnativehelper.so",
+ "libnativeloader.so",
+ "libandroidicu.so",
+ "libicu.so",
+ // TODO(b/122876336): Remove libpac.so once it's migrated to Webview
+ "libpac.so",
+ // TODO(b/120786417 or b/134659294): libicuuc.so
+ // and libicui18n.so are kept for app compat.
+ "libicui18n.so",
+ "libicuuc.so",
+ // resolv
+ "libnetd_resolv.so",
+ // nn
+ "libneuralnetworks.so",
+ // statsd
+ "libstatspull.so",
+ "libstatssocket.so",
+ // adbd
+ "libadb_pairing_auth.so",
+ "libadb_pairing_connection.so",
+ "libadb_pairing_server.so"
+ ]
+}
\ No newline at end of file
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 746fc61..fbb48e8 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -792,6 +792,7 @@
# Delete these if need be, per b/139193659
mkdir /data/rollback 0700 system system encryption=DeleteIfNecessary
mkdir /data/rollback-observer 0700 system system encryption=DeleteIfNecessary
+ mkdir /data/rollback-history 0700 system system encryption=DeleteIfNecessary
# Create root dir for Incremental Service
mkdir /data/incremental 0771 system system encryption=Require