Merge "aconfig_storage_file: add flag_info definition" into main
diff --git a/tools/aconfig/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs
index b861c1f..d4f4bc2 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 e40bbc1..293615e 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 c818d79..3f5f8de 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -18,7 +18,7 @@
 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 @@
             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 bc2da4d..ca0dec3 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 0000000..6ece543
--- /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<u8> {
+        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<Self, AconfigStorageError> {
+        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<u8> {
+        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<Self, AconfigStorageError> {
+        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<FlagInfoNode>,
+}
+
+/// 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<u8> {
+        [
+            self.header.as_bytes(),
+            self.nodes.iter().map(|v| v.as_bytes()).collect::<Vec<_>>().concat(),
+        ]
+        .concat()
+    }
+
+    /// Deserialize from bytes
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
+        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::<Result<Vec<_>, 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<FlagInfoNode> = &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 1e1e4d9..50ac69d 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::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 @@
     PackageMap = 0,
     FlagMap = 1,
     FlagVal = 2,
+    FlagInfo = 3,
 }
 
 impl TryFrom<&str> for StorageFileType {
@@ -78,6 +81,7 @@
             "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 @@
             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 293d018..12e0024 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 @@
             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 586bb4c..4641829 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 @@
     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<FlagInfoNode> = vec![FlagInfoNode { attributes: 0 }; 8];
+    FlagInfoList { header, nodes }
+}
+
 pub(crate) fn write_bytes_to_temp_file(bytes: &[u8]) -> Result<NamedTempFile, AconfigStorageError> {
     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 ea756b3..2213831 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<MappedStorageFile> 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 86c6a1b..51354db 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 @@
         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 391b305..e863c0e 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<MappedFlagValueFile> 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 @@
   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