aconfig_storage: create aconfig storage c++ flag value write api
Bug: b/312444587
Test: atest aconfig_storage_write_api.test.cpp
Change-Id: Ib08575b7e6ca23141ebbf739bf604a66da472dc2
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index 2f1694b..0ea8fea 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -82,6 +82,10 @@
"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
"name": "aconfig_storage_read_api.test.rust"
},
diff --git a/tools/aconfig/aconfig_storage_write_api/Android.bp b/tools/aconfig/aconfig_storage_write_api/Android.bp
index 1382aba..0f15b9c 100644
--- a/tools/aconfig/aconfig_storage_write_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_write_api/Android.bp
@@ -35,3 +35,47 @@
"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",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/Cargo.toml b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
index 494c19c..eaa55f2 100644
--- a/tools/aconfig/aconfig_storage_write_api/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
@@ -9,11 +9,11 @@
[dependencies]
anyhow = "1.0.69"
+cxx = "1.0"
memmap2 = "0.8.0"
tempfile = "3.9.0"
thiserror = "1.0.56"
protobuf = "3.2.0"
-once_cell = "1.19.0"
aconfig_storage_file = { path = "../aconfig_storage_file" }
aconfig_storage_read_api = { path = "../aconfig_storage_read_api" }
diff --git a/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp
new file mode 100644
index 0000000..391b305
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp
@@ -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
diff --git a/tools/aconfig/aconfig_storage_write_api/build.rs b/tools/aconfig/aconfig_storage_write_api/build.rs
new file mode 100644
index 0000000..7b1aa53
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+ let _ = cxx_build::bridge("src/lib.rs");
+ println!("cargo:rerun-if-changed=src/lib.rs");
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp b/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp
new file mode 100644
index 0000000..9e6332a
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp
@@ -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
diff --git a/tools/aconfig/aconfig_storage_write_api/src/lib.rs b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
index 17a6538..5562d6a 100644
--- a/tools/aconfig/aconfig_storage_write_api/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
@@ -65,6 +65,45 @@
})
}
+// *************************************** //
+// 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)]
mod tests {
use super::*;
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
index bb8c6df..d2a52fe 100644
--- a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
+++ b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
@@ -17,3 +17,26 @@
],
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",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp
new file mode 100644
index 0000000..3035f9e
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp
@@ -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.)"));
+}