diff --git a/tools/aconfig/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs index b861c1f7ee..d4f4bc2c80 100644 --- a/tools/aconfig/aconfig/src/storage/flag_table.rs +++ b/tools/aconfig/aconfig/src/storage/flag_table.rs @@ -17,7 +17,7 @@ use crate::commands::assign_flag_ids; use crate::storage::FlagPackage; use aconfig_storage_file::{ - get_table_size, FlagTable, FlagTableHeader, FlagTableNode, FILE_VERSION, StorageFileType + get_table_size, FlagTable, FlagTableHeader, FlagTableNode, StorageFileType, FILE_VERSION, }; use anyhow::{anyhow, Result}; diff --git a/tools/aconfig/aconfig/src/storage/flag_value.rs b/tools/aconfig/aconfig/src/storage/flag_value.rs index e40bbc19d5..293615e005 100644 --- a/tools/aconfig/aconfig/src/storage/flag_value.rs +++ b/tools/aconfig/aconfig/src/storage/flag_value.rs @@ -17,7 +17,7 @@ use crate::commands::assign_flag_ids; use crate::storage::FlagPackage; use aconfig_protos::ProtoFlagState; -use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION, StorageFileType}; +use aconfig_storage_file::{FlagValueHeader, FlagValueList, StorageFileType, FILE_VERSION}; use anyhow::{anyhow, Result}; fn new_header(container: &str, num_flags: u32) -> FlagValueHeader { diff --git a/tools/aconfig/aconfig/src/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs index c818d79c14..3f5f8deb4b 100644 --- a/tools/aconfig/aconfig/src/storage/mod.rs +++ b/tools/aconfig/aconfig/src/storage/mod.rs @@ -18,7 +18,7 @@ pub mod flag_table; pub mod flag_value; pub mod package_table; -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::collections::{HashMap, HashSet}; use crate::storage::{ @@ -107,6 +107,7 @@ where let flag_value = create_flag_value(container, &packages)?; Ok(flag_value.as_bytes()) } + _ => Err(anyhow!("aconfig does not support the creation of this storage file type")), } } diff --git a/tools/aconfig/aconfig/src/storage/package_table.rs b/tools/aconfig/aconfig/src/storage/package_table.rs index bc2da4d909..ca0dec3088 100644 --- a/tools/aconfig/aconfig/src/storage/package_table.rs +++ b/tools/aconfig/aconfig/src/storage/package_table.rs @@ -17,7 +17,8 @@ use anyhow::Result; use aconfig_storage_file::{ - get_table_size, PackageTable, PackageTableHeader, PackageTableNode, FILE_VERSION, StorageFileType + get_table_size, PackageTable, PackageTableHeader, PackageTableNode, StorageFileType, + FILE_VERSION, }; use crate::storage::FlagPackage; diff --git a/tools/aconfig/aconfig_storage_file/src/flag_info.rs b/tools/aconfig/aconfig_storage_file/src/flag_info.rs new file mode 100644 index 0000000000..6ece543766 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/src/flag_info.rs @@ -0,0 +1,238 @@ +/* + * 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. + */ + +//! flag info module defines the flag info file format and methods for serialization +//! and deserialization + +use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes}; +use crate::{AconfigStorageError, StorageFileType}; +use anyhow::anyhow; +use std::fmt; + +/// Flag info header struct +#[derive(PartialEq)] +pub struct FlagInfoHeader { + pub version: u32, + pub container: String, + pub file_type: u8, + pub file_size: u32, + pub num_flags: u32, + pub boolean_flag_offset: u32, +} + +/// Implement debug print trait for header +impl fmt::Debug for FlagInfoHeader { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "Version: {}, Container: {}, File Type: {:?}, File Size: {}", + self.version, + self.container, + StorageFileType::try_from(self.file_type), + self.file_size + )?; + writeln!( + f, + "Num of Flags: {}, Boolean Flag Offset:{}", + self.num_flags, self.boolean_flag_offset + )?; + Ok(()) + } +} + +impl FlagInfoHeader { + /// Serialize to bytes + pub fn as_bytes(&self) -> Vec { + let mut result = Vec::new(); + result.extend_from_slice(&self.version.to_le_bytes()); + let container_bytes = self.container.as_bytes(); + result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes()); + result.extend_from_slice(container_bytes); + result.extend_from_slice(&self.file_type.to_le_bytes()); + result.extend_from_slice(&self.file_size.to_le_bytes()); + result.extend_from_slice(&self.num_flags.to_le_bytes()); + result.extend_from_slice(&self.boolean_flag_offset.to_le_bytes()); + result + } + + /// Deserialize from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut head = 0; + let list = Self { + version: read_u32_from_bytes(bytes, &mut head)?, + container: read_str_from_bytes(bytes, &mut head)?, + file_type: read_u8_from_bytes(bytes, &mut head)?, + file_size: read_u32_from_bytes(bytes, &mut head)?, + num_flags: read_u32_from_bytes(bytes, &mut head)?, + boolean_flag_offset: read_u32_from_bytes(bytes, &mut head)?, + }; + if list.file_type != StorageFileType::FlagInfo as u8 { + return Err(AconfigStorageError::BytesParseFail(anyhow!( + "binary file is not a flag info file" + ))); + } + Ok(list) + } +} + +/// bit field for flag info +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FlagInfoBit { + IsSticky = 0, + IsReadWrite = 1, + HasOverride = 2, +} + +/// Flag info node struct +#[derive(PartialEq, Clone)] +pub struct FlagInfoNode { + pub attributes: u8, +} + +/// Implement debug print trait for node +impl fmt::Debug for FlagInfoNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "sticky: {}, readwrite: {}, override: {}", + self.attributes & (FlagInfoBit::IsSticky as u8), + self.attributes & (FlagInfoBit::IsReadWrite as u8), + self.attributes & (FlagInfoBit::HasOverride as u8), + )?; + Ok(()) + } +} + +impl FlagInfoNode { + /// Serialize to bytes + pub fn as_bytes(&self) -> Vec { + let mut result = Vec::new(); + result.extend_from_slice(&self.attributes.to_le_bytes()); + result + } + + /// Deserialize from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut head = 0; + let node = Self { attributes: read_u8_from_bytes(bytes, &mut head)? }; + Ok(node) + } +} + +/// Flag info list struct +#[derive(PartialEq)] +pub struct FlagInfoList { + pub header: FlagInfoHeader, + pub nodes: Vec, +} + +/// Implement debug print trait for flag info list +impl fmt::Debug for FlagInfoList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Header:")?; + write!(f, "{:?}", self.header)?; + writeln!(f, "Nodes:")?; + for node in self.nodes.iter() { + write!(f, "{:?}", node)?; + } + Ok(()) + } +} + +impl FlagInfoList { + /// Serialize to bytes + pub fn as_bytes(&self) -> Vec { + [ + self.header.as_bytes(), + self.nodes.iter().map(|v| v.as_bytes()).collect::>().concat(), + ] + .concat() + } + + /// Deserialize from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + let header = FlagInfoHeader::from_bytes(bytes)?; + let num_flags = header.num_flags; + let mut head = header.as_bytes().len(); + let nodes = (0..num_flags) + .map(|_| { + let node = FlagInfoNode::from_bytes(&bytes[head..])?; + head += node.as_bytes().len(); + Ok(node) + }) + .collect::, AconfigStorageError>>() + .map_err(|errmsg| { + AconfigStorageError::BytesParseFail(anyhow!( + "fail to parse flag info list: {}", + errmsg + )) + })?; + let list = Self { header, nodes }; + Ok(list) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::create_test_flag_info_list; + + #[test] + // this test point locks down the value list serialization + fn test_serialization() { + let flag_info_list = create_test_flag_info_list(); + + let header: &FlagInfoHeader = &flag_info_list.header; + let reinterpreted_header = FlagInfoHeader::from_bytes(&header.as_bytes()); + assert!(reinterpreted_header.is_ok()); + assert_eq!(header, &reinterpreted_header.unwrap()); + + let nodes: &Vec = &flag_info_list.nodes; + for node in nodes.iter() { + let reinterpreted_node = FlagInfoNode::from_bytes(&node.as_bytes()).unwrap(); + assert_eq!(node, &reinterpreted_node); + } + + let flag_info_bytes = flag_info_list.as_bytes(); + let reinterpreted_info_list = FlagInfoList::from_bytes(&flag_info_bytes); + assert!(reinterpreted_info_list.is_ok()); + assert_eq!(&flag_info_list, &reinterpreted_info_list.unwrap()); + assert_eq!(flag_info_bytes.len() as u32, header.file_size); + } + + #[test] + // this test point locks down that version number should be at the top of serialized + // bytes + fn test_version_number() { + let flag_info_list = create_test_flag_info_list(); + let bytes = &flag_info_list.as_bytes(); + let mut head = 0; + let version = read_u32_from_bytes(bytes, &mut head).unwrap(); + assert_eq!(version, 1234) + } + + #[test] + // this test point locks down file type check + fn test_file_type_check() { + let mut flag_info_list = create_test_flag_info_list(); + flag_info_list.header.file_type = 123u8; + let error = FlagInfoList::from_bytes(&flag_info_list.as_bytes()).unwrap_err(); + assert_eq!( + format!("{:?}", error), + format!("BytesParseFail(binary file is not a flag info file)") + ); + } +} diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs index 24b16a1a47..3bd34afede 100644 --- a/tools/aconfig/aconfig_storage_file/src/lib.rs +++ b/tools/aconfig/aconfig_storage_file/src/lib.rs @@ -32,6 +32,7 @@ //! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis //! please refer to the g3doc go/android-flags +pub mod flag_info; pub mod flag_table; pub mod flag_value; pub mod package_table; @@ -46,6 +47,7 @@ use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::Read; +pub use crate::flag_info::{FlagInfoHeader, FlagInfoList, FlagInfoNode}; pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode}; pub use crate::flag_value::{FlagValueHeader, FlagValueList}; pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode}; @@ -68,6 +70,7 @@ pub enum StorageFileType { PackageMap = 0, FlagMap = 1, FlagVal = 2, + FlagInfo = 3, } impl TryFrom<&str> for StorageFileType { @@ -78,6 +81,7 @@ impl TryFrom<&str> for StorageFileType { "package_map" => Ok(Self::PackageMap), "flag_map" => Ok(Self::FlagMap), "flag_val" => Ok(Self::FlagVal), + "flag_info" => Ok(Self::FlagInfo), _ => Err(anyhow!( "Invalid storage file type, valid types are package_map|flag_map|flag_val" )), @@ -93,6 +97,7 @@ impl TryFrom for StorageFileType { x if x == Self::PackageMap as u8 => Ok(Self::PackageMap), x if x == Self::FlagMap as u8 => Ok(Self::FlagMap), x if x == Self::FlagVal as u8 => Ok(Self::FlagVal), + x if x == Self::FlagInfo as u8 => Ok(Self::FlagInfo), _ => Err(anyhow!("Invalid storage file type")), } } diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs index 293d018c2e..12e00243d7 100644 --- a/tools/aconfig/aconfig_storage_file/src/main.rs +++ b/tools/aconfig/aconfig_storage_file/src/main.rs @@ -17,8 +17,8 @@ //! `aconfig-storage` is a debugging tool to parse storage files use aconfig_storage_file::{ - list_flags, read_file_to_bytes, AconfigStorageError, FlagTable, FlagValueList, PackageTable, - StorageFileType, + list_flags, read_file_to_bytes, AconfigStorageError, FlagInfoList, FlagTable, FlagValueList, + PackageTable, StorageFileType, }; use clap::{builder::ArgAction, Arg, Command}; @@ -67,6 +67,10 @@ fn print_storage_file( let flag_value = FlagValueList::from_bytes(&bytes)?; println!("{:?}", flag_value); } + StorageFileType::FlagInfo => { + let flag_info = FlagInfoList::from_bytes(&bytes)?; + println!("{:?}", flag_info); + } } Ok(()) } diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs index 586bb4c682..4641829a6b 100644 --- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs +++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use crate::flag_info::{FlagInfoHeader, FlagInfoList, FlagInfoNode}; use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode}; use crate::flag_value::{FlagValueHeader, FlagValueList}; use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode}; @@ -124,6 +125,19 @@ pub(crate) fn create_test_flag_value_list() -> FlagValueList { FlagValueList { header, booleans } } +pub(crate) fn create_test_flag_info_list() -> FlagInfoList { + let header = FlagInfoHeader { + version: 1234, + container: String::from("system"), + file_type: StorageFileType::FlagInfo as u8, + file_size: 35, + num_flags: 8, + boolean_flag_offset: 27, + }; + let nodes: Vec = vec![FlagInfoNode { attributes: 0 }; 8]; + FlagInfoList { header, nodes } +} + pub(crate) fn write_bytes_to_temp_file(bytes: &[u8]) -> Result { let mut file = NamedTempFile::new().map_err(|_| { AconfigStorageError::FileCreationFail(anyhow!("Failed to create temp file")) diff --git a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp index ea756b3962..22138317eb 100644 --- a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp +++ b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp @@ -67,18 +67,18 @@ static Result find_storage_file( static Result map_storage_file(std::string const& file) { int fd = open(file.c_str(), O_CLOEXEC | O_NOFOLLOW | O_RDONLY); if (fd == -1) { - return Error() << "failed to open " << file; + return ErrnoError() << "failed to open " << file; }; struct stat fd_stat; if (fstat(fd, &fd_stat) < 0) { - return Error() << "fstat failed"; + return ErrnoError() << "fstat failed"; } size_t file_size = fd_stat.st_size; void* const map_result = mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0); if (map_result == MAP_FAILED) { - return Error() << "mmap failed"; + return ErrnoError() << "mmap failed"; } auto mapped_file = MappedStorageFile(); diff --git a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs index 86c6a1b053..51354db760 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs @@ -91,6 +91,9 @@ pub unsafe fn get_mapped_file( StorageFileType::PackageMap => unsafe { map_file(files_location.package_map()) }, StorageFileType::FlagMap => unsafe { map_file(files_location.flag_map()) }, StorageFileType::FlagVal => unsafe { map_file(files_location.flag_val()) }, + StorageFileType::FlagInfo => { + Err(MapFileFail(anyhow!("TODO: add support for flag info file"))) + } } } 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 index 391b3050ca..e863c0e50a 100644 --- a/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp +++ b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp @@ -58,24 +58,24 @@ static Result find_storage_file( static Result map_storage_file(std::string const& file) { struct stat file_stat; if (stat(file.c_str(), &file_stat) < 0) { - return Error() << "fstat failed"; + return ErrnoError() << "stat failed"; } if ((file_stat.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) { - return Error() << "cannot map nonwriteable file"; + return ErrnoError() << "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; + return ErrnoError() << "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"; + return ErrnoError() << "mmap failed"; } auto mapped_file = MappedFlagValueFile(); @@ -95,7 +95,12 @@ Result get_mapped_flag_value_file_impl( if (!file_result.ok()) { return Error() << file_result.error(); } - return map_storage_file(*file_result); + auto mapped_result = map_storage_file(*file_result); + if (!mapped_result.ok()) { + return Error() << "failed to map " << *file_result << ": " + << mapped_result.error(); + } + return *mapped_result; } } // namespace private internal api