aconfig_storage: create aconfig storage c++ flag value write api
Bug: b/312444587 Test: atest aconfig_storage_write_api.test.cpp Change-Id: Ib08575b7e6ca23141ebbf739bf604a66da472dc2
This commit is contained in:
parent
7c30411d3d
commit
1fdb6cc456
9 changed files with 410 additions and 1 deletions
|
@ -81,6 +81,10 @@
|
||||||
// aconfig_storage write api rust integration tests
|
// aconfig_storage write api rust integration tests
|
||||||
"name": "aconfig_storage_write_api.test.rust"
|
"name": "aconfig_storage_write_api.test.rust"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// aconfig_storage write api cpp integration tests
|
||||||
|
"name": "aconfig_storage_write_api.test.cpp"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// aconfig_storage read api rust integration tests
|
// aconfig_storage read api rust integration tests
|
||||||
"name": "aconfig_storage_read_api.test.rust"
|
"name": "aconfig_storage_read_api.test.rust"
|
||||||
|
|
|
@ -35,3 +35,47 @@ rust_test_host {
|
||||||
"libaconfig_storage_read_api",
|
"libaconfig_storage_read_api",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cxx source codegen from rust api
|
||||||
|
genrule {
|
||||||
|
name: "libcxx_aconfig_storage_write_api_bridge_code",
|
||||||
|
tools: ["cxxbridge"],
|
||||||
|
cmd: "$(location cxxbridge) $(in) > $(out)",
|
||||||
|
srcs: ["src/lib.rs"],
|
||||||
|
out: ["aconfig_storage/lib.rs.cc"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// cxx header codegen from rust api
|
||||||
|
genrule {
|
||||||
|
name: "libcxx_aconfig_storage_write_api_bridge_header",
|
||||||
|
tools: ["cxxbridge"],
|
||||||
|
cmd: "$(location cxxbridge) $(in) --header > $(out)",
|
||||||
|
srcs: ["src/lib.rs"],
|
||||||
|
out: ["aconfig_storage/lib.rs.h"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// a static cc lib based on generated code
|
||||||
|
rust_ffi_static {
|
||||||
|
name: "libaconfig_storage_write_api_cxx_bridge",
|
||||||
|
crate_name: "aconfig_storage_write_api_cxx_bridge",
|
||||||
|
host_supported: true,
|
||||||
|
defaults: ["aconfig_storage_write_api.defaults"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// flag write api cc interface
|
||||||
|
cc_library_static {
|
||||||
|
name: "libaconfig_storage_write_api_cc",
|
||||||
|
srcs: ["aconfig_storage_write_api.cpp"],
|
||||||
|
generated_headers: [
|
||||||
|
"cxx-bridge-header",
|
||||||
|
"libcxx_aconfig_storage_write_api_bridge_header"
|
||||||
|
],
|
||||||
|
generated_sources: ["libcxx_aconfig_storage_write_api_bridge_code"],
|
||||||
|
whole_static_libs: ["libaconfig_storage_write_api_cxx_bridge"],
|
||||||
|
export_include_dirs: ["include"],
|
||||||
|
static_libs: [
|
||||||
|
"libaconfig_storage_protos_cc",
|
||||||
|
"libprotobuf-cpp-lite",
|
||||||
|
"libbase",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ cargo = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.69"
|
anyhow = "1.0.69"
|
||||||
|
cxx = "1.0"
|
||||||
memmap2 = "0.8.0"
|
memmap2 = "0.8.0"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.56"
|
||||||
protobuf = "3.2.0"
|
protobuf = "3.2.0"
|
||||||
once_cell = "1.19.0"
|
|
||||||
aconfig_storage_file = { path = "../aconfig_storage_file" }
|
aconfig_storage_file = { path = "../aconfig_storage_file" }
|
||||||
aconfig_storage_read_api = { path = "../aconfig_storage_read_api" }
|
aconfig_storage_read_api = { path = "../aconfig_storage_read_api" }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <protos/aconfig_storage_metadata.pb.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "rust/cxx.h"
|
||||||
|
#include "aconfig_storage/lib.rs.h"
|
||||||
|
#include "aconfig_storage/aconfig_storage_write_api.hpp"
|
||||||
|
|
||||||
|
using storage_records_pb = android::aconfig_storage_metadata::storage_files;
|
||||||
|
using storage_record_pb = android::aconfig_storage_metadata::storage_file_info;
|
||||||
|
using namespace android::base;
|
||||||
|
|
||||||
|
namespace aconfig_storage {
|
||||||
|
|
||||||
|
/// Storage location pb file
|
||||||
|
static constexpr char kPersistStorageRecordsPb[] =
|
||||||
|
"/metadata/aconfig/persistent_storage_file_records.pb";
|
||||||
|
|
||||||
|
/// Read aconfig storage records pb file
|
||||||
|
static Result<storage_records_pb> read_storage_records_pb(std::string const& pb_file) {
|
||||||
|
auto records = storage_records_pb();
|
||||||
|
auto content = std::string();
|
||||||
|
if (!ReadFileToString(pb_file, &content)) {
|
||||||
|
return ErrnoError() << "ReadFileToString failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!records.ParseFromString(content)) {
|
||||||
|
return ErrnoError() << "Unable to parse persistent storage records protobuf";
|
||||||
|
}
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get storage file path
|
||||||
|
static Result<std::string> find_storage_file(
|
||||||
|
std::string const& pb_file,
|
||||||
|
std::string const& container) {
|
||||||
|
auto records_pb = read_storage_records_pb(pb_file);
|
||||||
|
if (!records_pb.ok()) {
|
||||||
|
return Error() << "Unable to read storage records from " << pb_file
|
||||||
|
<< " : " << records_pb.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& entry : records_pb->files()) {
|
||||||
|
if (entry.container() == container) {
|
||||||
|
return entry.flag_val();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error() << "Unable to find storage files for container " << container;;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map a storage file
|
||||||
|
static Result<MappedFlagValueFile> map_storage_file(std::string const& file) {
|
||||||
|
struct stat file_stat;
|
||||||
|
if (stat(file.c_str(), &file_stat) < 0) {
|
||||||
|
return Error() << "fstat failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((file_stat.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) {
|
||||||
|
return Error() << "cannot map nonwriteable file";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t file_size = file_stat.st_size;
|
||||||
|
|
||||||
|
const int fd = open(file.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC);
|
||||||
|
if (fd == -1) {
|
||||||
|
return Error() << "failed to open " << file;
|
||||||
|
};
|
||||||
|
|
||||||
|
void* const map_result =
|
||||||
|
mmap(nullptr, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if (map_result == MAP_FAILED) {
|
||||||
|
return Error() << "mmap failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mapped_file = MappedFlagValueFile();
|
||||||
|
mapped_file.file_ptr = map_result;
|
||||||
|
mapped_file.file_size = file_size;
|
||||||
|
|
||||||
|
return mapped_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace private_internal_api {
|
||||||
|
|
||||||
|
/// Get mapped file implementation.
|
||||||
|
Result<MappedFlagValueFile> get_mapped_flag_value_file_impl(
|
||||||
|
std::string const& pb_file,
|
||||||
|
std::string const& container) {
|
||||||
|
auto file_result = find_storage_file(pb_file, container);
|
||||||
|
if (!file_result.ok()) {
|
||||||
|
return Error() << file_result.error();
|
||||||
|
}
|
||||||
|
return map_storage_file(*file_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace private internal api
|
||||||
|
|
||||||
|
/// Get mapped writeable flag value file
|
||||||
|
Result<MappedFlagValueFile> get_mapped_flag_value_file(
|
||||||
|
std::string const& container) {
|
||||||
|
return private_internal_api::get_mapped_flag_value_file_impl(
|
||||||
|
kPersistStorageRecordsPb, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set boolean flag value
|
||||||
|
Result<void> set_boolean_flag_value(
|
||||||
|
const MappedFlagValueFile& file,
|
||||||
|
uint32_t offset,
|
||||||
|
bool value) {
|
||||||
|
auto content = rust::Slice<uint8_t>(
|
||||||
|
static_cast<uint8_t*>(file.file_ptr), file.file_size);
|
||||||
|
auto update_cxx = update_boolean_flag_value_cxx(content, offset, value);
|
||||||
|
if (!update_cxx.update_success) {
|
||||||
|
return Error() << std::string(update_cxx.error_message.c_str());
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aconfig_storage
|
4
tools/aconfig/aconfig_storage_write_api/build.rs
Normal file
4
tools/aconfig/aconfig_storage_write_api/build.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
fn main() {
|
||||||
|
let _ = cxx_build::bridge("src/lib.rs");
|
||||||
|
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/result.h>
|
||||||
|
|
||||||
|
using namespace android::base;
|
||||||
|
|
||||||
|
namespace aconfig_storage {
|
||||||
|
|
||||||
|
/// Mapped flag value file
|
||||||
|
struct MappedFlagValueFile{
|
||||||
|
void* file_ptr;
|
||||||
|
size_t file_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY
|
||||||
|
namespace private_internal_api {
|
||||||
|
|
||||||
|
Result<MappedFlagValueFile> get_mapped_flag_value_file_impl(
|
||||||
|
std::string const& pb_file,
|
||||||
|
std::string const& container);
|
||||||
|
|
||||||
|
} // namespace private_internal_api
|
||||||
|
|
||||||
|
/// Get mapped writeable flag value file
|
||||||
|
Result<MappedFlagValueFile> get_mapped_flag_value_file(
|
||||||
|
std::string const& container);
|
||||||
|
|
||||||
|
/// Set boolean flag value
|
||||||
|
Result<void> set_boolean_flag_value(
|
||||||
|
const MappedFlagValueFile& file,
|
||||||
|
uint32_t offset,
|
||||||
|
bool value);
|
||||||
|
|
||||||
|
} // namespace aconfig_storage
|
|
@ -65,6 +65,45 @@ pub fn set_boolean_flag_value(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *************************************** //
|
||||||
|
// CC INTERLOP
|
||||||
|
// *************************************** //
|
||||||
|
|
||||||
|
// Exported rust data structure and methods, c++ code will be generated
|
||||||
|
#[cxx::bridge]
|
||||||
|
mod ffi {
|
||||||
|
// Flag value update return for cc interlop
|
||||||
|
pub struct BooleanFlagValueUpdateCXX {
|
||||||
|
pub update_success: bool,
|
||||||
|
pub error_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rust export to c++
|
||||||
|
extern "Rust" {
|
||||||
|
pub fn update_boolean_flag_value_cxx(
|
||||||
|
file: &mut [u8],
|
||||||
|
offset: u32,
|
||||||
|
value: bool,
|
||||||
|
) -> BooleanFlagValueUpdateCXX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_boolean_flag_value_cxx(
|
||||||
|
file: &mut [u8],
|
||||||
|
offset: u32,
|
||||||
|
value: bool,
|
||||||
|
) -> ffi::BooleanFlagValueUpdateCXX {
|
||||||
|
match crate::flag_value_update::update_boolean_flag_value(file, offset, value) {
|
||||||
|
Ok(()) => {
|
||||||
|
ffi::BooleanFlagValueUpdateCXX { update_success: true, error_message: String::from("") }
|
||||||
|
}
|
||||||
|
Err(errmsg) => ffi::BooleanFlagValueUpdateCXX {
|
||||||
|
update_success: false,
|
||||||
|
error_message: format!("{:?}", errmsg),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -17,3 +17,26 @@ rust_test {
|
||||||
],
|
],
|
||||||
test_suites: ["general-tests"],
|
test_suites: ["general-tests"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cc_test {
|
||||||
|
name: "aconfig_storage_write_api.test.cpp",
|
||||||
|
srcs: [
|
||||||
|
"storage_write_api_test.cpp",
|
||||||
|
],
|
||||||
|
static_libs: [
|
||||||
|
"libgmock",
|
||||||
|
"libaconfig_storage_protos_cc",
|
||||||
|
"libprotobuf-cpp-lite",
|
||||||
|
"libaconfig_storage_read_api_cc",
|
||||||
|
"libaconfig_storage_write_api_cc",
|
||||||
|
"libbase",
|
||||||
|
"liblog",
|
||||||
|
],
|
||||||
|
data: [
|
||||||
|
"flag.val",
|
||||||
|
],
|
||||||
|
test_suites: [
|
||||||
|
"device-tests",
|
||||||
|
"general-tests",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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 <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include "aconfig_storage/aconfig_storage_read_api.hpp"
|
||||||
|
#include "aconfig_storage/aconfig_storage_write_api.hpp"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <protos/aconfig_storage_metadata.pb.h>
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/result.h>
|
||||||
|
|
||||||
|
using android::aconfig_storage_metadata::storage_files;
|
||||||
|
using namespace android::base;
|
||||||
|
|
||||||
|
namespace api = aconfig_storage;
|
||||||
|
namespace private_api = aconfig_storage::private_internal_api;
|
||||||
|
|
||||||
|
class AconfigStorageTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
Result<std::string> copy_to_rw_temp_file(std::string const& source_file) {
|
||||||
|
auto temp_file = std::string(std::tmpnam(nullptr));
|
||||||
|
auto content = std::string();
|
||||||
|
if (!ReadFileToString(source_file, &content)) {
|
||||||
|
return Error() << "failed to read file: " << source_file;
|
||||||
|
}
|
||||||
|
if (!WriteStringToFile(content, temp_file)) {
|
||||||
|
return Error() << "failed to copy file: " << source_file;
|
||||||
|
}
|
||||||
|
if (chmod(temp_file.c_str(),
|
||||||
|
S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH) == -1) {
|
||||||
|
return Error() << "failed to chmod";
|
||||||
|
}
|
||||||
|
return temp_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::string> write_storage_location_pb_file(std::string const& flag_val) {
|
||||||
|
auto temp_file = std::tmpnam(nullptr);
|
||||||
|
auto proto = storage_files();
|
||||||
|
auto* info = proto.add_files();
|
||||||
|
info->set_version(0);
|
||||||
|
info->set_container("system");
|
||||||
|
info->set_package_map("some_package.map");
|
||||||
|
info->set_flag_map("some_flag.map");
|
||||||
|
info->set_flag_val(flag_val);
|
||||||
|
info->set_timestamp(12345);
|
||||||
|
|
||||||
|
auto content = std::string();
|
||||||
|
proto.SerializeToString(&content);
|
||||||
|
if (!WriteStringToFile(content, temp_file)) {
|
||||||
|
return Error() << "failed to write storage records pb file";
|
||||||
|
}
|
||||||
|
return temp_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
auto const test_dir = android::base::GetExecutableDirectory();
|
||||||
|
flag_val = *copy_to_rw_temp_file(test_dir + "/flag.val");
|
||||||
|
storage_record_pb = *write_storage_location_pb_file(flag_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
std::remove(flag_val.c_str());
|
||||||
|
std::remove(storage_record_pb.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string flag_val;
|
||||||
|
std::string storage_record_pb;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Negative test to lock down the error when mapping none exist storage files
|
||||||
|
TEST_F(AconfigStorageTest, test_none_exist_storage_file_mapping) {
|
||||||
|
auto mapped_file_result = private_api::get_mapped_flag_value_file_impl(
|
||||||
|
storage_record_pb, "vendor");
|
||||||
|
ASSERT_FALSE(mapped_file_result.ok());
|
||||||
|
ASSERT_EQ(mapped_file_result.error().message(),
|
||||||
|
"Unable to find storage files for container vendor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Negative test to lock down the error when mapping a non writeable storage file
|
||||||
|
TEST_F(AconfigStorageTest, test_non_writable_storage_file_mapping) {
|
||||||
|
ASSERT_TRUE(chmod(flag_val.c_str(), S_IRUSR | S_IRGRP | S_IROTH) != -1);
|
||||||
|
auto mapped_file_result = private_api::get_mapped_flag_value_file_impl(
|
||||||
|
storage_record_pb, "system");
|
||||||
|
ASSERT_FALSE(mapped_file_result.ok());
|
||||||
|
ASSERT_EQ(mapped_file_result.error().message(), "cannot map nonwriteable file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test to lock down storage flag value update api
|
||||||
|
TEST_F(AconfigStorageTest, test_boolean_flag_value_update) {
|
||||||
|
auto mapped_file_result = private_api::get_mapped_flag_value_file_impl(
|
||||||
|
storage_record_pb, "system");
|
||||||
|
ASSERT_TRUE(mapped_file_result.ok());
|
||||||
|
auto mapped_file = *mapped_file_result;
|
||||||
|
|
||||||
|
for (int offset = 0; offset < 8; ++offset) {
|
||||||
|
auto update_result = api::set_boolean_flag_value(mapped_file, offset, true);
|
||||||
|
ASSERT_TRUE(update_result.ok());
|
||||||
|
auto ro_mapped_file = api::MappedStorageFile();
|
||||||
|
ro_mapped_file.file_ptr = mapped_file.file_ptr;
|
||||||
|
ro_mapped_file.file_size = mapped_file.file_size;
|
||||||
|
auto value_query = api::get_boolean_flag_value(ro_mapped_file, offset);
|
||||||
|
ASSERT_TRUE(value_query.query_success);
|
||||||
|
ASSERT_TRUE(value_query.flag_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Negative test to lock down the error when querying flag value out of range
|
||||||
|
TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_update) {
|
||||||
|
auto mapped_file_result = private_api::get_mapped_flag_value_file_impl(
|
||||||
|
storage_record_pb, "system");
|
||||||
|
ASSERT_TRUE(mapped_file_result.ok());
|
||||||
|
auto mapped_file = *mapped_file_result;
|
||||||
|
auto update_result = api::set_boolean_flag_value(mapped_file, 8, true);
|
||||||
|
ASSERT_FALSE(update_result.ok());
|
||||||
|
ASSERT_EQ(update_result.error().message(),
|
||||||
|
std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
|
||||||
|
}
|
Loading…
Reference in a new issue