xref: /aosp_15_r20/build/make/tools/aconfig/aconfig_storage_read_api/src/lib.rs (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! `aconfig_storage_read_api` is a crate that defines read apis to read flags from storage
18 //! files. It provides four apis to interface with storage files:
19 //!
20 //! 1, function to get package read context
21 //! pub fn get_packager_read_context(container: &str, package: &str)
22 //! -> `Result<Option<PackageReadContext>>>`
23 //!
24 //! 2, function to get flag read context
25 //! pub fn get_flag_read_context(container: &str, package_id: u32, flag: &str)
26 //! -> `Result<Option<FlagReadContext>>>`
27 //!
28 //! 3, function to get the actual flag value given the global index (combined package and
29 //! flag index).
30 //! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
31 //!
32 //! 4, function to get storage file version without mmapping the file.
33 //! pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>
34 //!
35 //! Note these are low level apis that are expected to be only used in auto generated flag
36 //! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
37 //! please refer to the g3doc go/android-flags
38 
39 pub mod flag_info_query;
40 pub mod flag_table_query;
41 pub mod flag_value_query;
42 pub mod mapped_file;
43 pub mod package_table_query;
44 
45 pub use aconfig_storage_file::{AconfigStorageError, FlagValueType, StorageFileType};
46 pub use flag_table_query::FlagReadContext;
47 pub use mapped_file::map_file;
48 pub use package_table_query::PackageReadContext;
49 
50 use aconfig_storage_file::read_u32_from_bytes;
51 use flag_info_query::find_flag_attribute;
52 use flag_table_query::find_flag_read_context;
53 use flag_value_query::find_boolean_flag_value;
54 use package_table_query::find_package_read_context;
55 
56 use anyhow::anyhow;
57 pub use memmap2::Mmap;
58 use std::fs::File;
59 use std::io::Read;
60 
61 /// Storage file location
62 pub const STORAGE_LOCATION: &str = "/metadata/aconfig";
63 
64 /// Get read only mapped storage files.
65 ///
66 /// \input container: the flag package container
67 /// \input file_type: stoarge file type enum
68 /// \return a result of read only mapped file
69 ///
70 /// # Safety
71 ///
72 /// The memory mapped file may have undefined behavior if there are writes to this
73 /// file after being mapped. Ensure no writes can happen to this file while this
74 /// mapping stays alive.
get_mapped_storage_file( container: &str, file_type: StorageFileType, ) -> Result<Mmap, AconfigStorageError>75 pub unsafe fn get_mapped_storage_file(
76     container: &str,
77     file_type: StorageFileType,
78 ) -> Result<Mmap, AconfigStorageError> {
79     unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION, container, file_type) }
80 }
81 
82 /// Get package read context for a specific package.
83 ///
84 /// \input file: mapped package file
85 /// \input package: package name
86 ///
87 /// \return
88 /// If a package is found, it returns Ok(Some(PackageReadContext))
89 /// If a package is not found, it returns Ok(None)
90 /// If errors out, it returns an Err(errmsg)
get_package_read_context( file: &Mmap, package: &str, ) -> Result<Option<PackageReadContext>, AconfigStorageError>91 pub fn get_package_read_context(
92     file: &Mmap,
93     package: &str,
94 ) -> Result<Option<PackageReadContext>, AconfigStorageError> {
95     find_package_read_context(file, package)
96 }
97 
98 /// Get flag read context for a specific flag.
99 ///
100 /// \input file: mapped flag file
101 /// \input package_id: package id obtained from package mapping file
102 /// \input flag: flag name
103 ///
104 /// \return
105 /// If a flag is found, it returns Ok(Some(FlagReadContext))
106 /// If a flag is not found, it returns Ok(None)
107 /// If errors out, it returns an Err(errmsg)
get_flag_read_context( file: &Mmap, package_id: u32, flag: &str, ) -> Result<Option<FlagReadContext>, AconfigStorageError>108 pub fn get_flag_read_context(
109     file: &Mmap,
110     package_id: u32,
111     flag: &str,
112 ) -> Result<Option<FlagReadContext>, AconfigStorageError> {
113     find_flag_read_context(file, package_id, flag)
114 }
115 
116 /// Get the boolean flag value.
117 ///
118 /// \input file: a byte slice, can be either &Mmap or &MapMut
119 /// \input index: boolean flag offset
120 ///
121 /// \return
122 /// If the provide offset is valid, it returns the boolean flag value, otherwise it
123 /// returns the error message.
get_boolean_flag_value(file: &[u8], index: u32) -> Result<bool, AconfigStorageError>124 pub fn get_boolean_flag_value(file: &[u8], index: u32) -> Result<bool, AconfigStorageError> {
125     find_boolean_flag_value(file, index)
126 }
127 
128 /// Get storage file version number
129 ///
130 /// This function would read the first four bytes of the file and interpret it as the
131 /// version number of the file. There are unit tests in aconfig_storage_file crate to
132 /// lock down that for all storage files, the first four bytes will be the version
133 /// number of the storage file
get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>134 pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError> {
135     let mut file = File::open(file_path).map_err(|errmsg| {
136         AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
137     })?;
138     let mut buffer = [0; 4];
139     file.read(&mut buffer).map_err(|errmsg| {
140         AconfigStorageError::FileReadFail(anyhow!(
141             "Failed to read 4 bytes from file {}: {}",
142             file_path,
143             errmsg
144         ))
145     })?;
146     let mut head = 0;
147     read_u32_from_bytes(&buffer, &mut head)
148 }
149 
150 /// Get the flag attribute.
151 ///
152 /// \input file: a byte slice, can be either &Mmap or &MapMut
153 /// \input flag_type: flag value type
154 /// \input flag_index: flag index
155 ///
156 /// \return
157 /// If the provide offset is valid, it returns the flag attribute bitfiled, otherwise it
158 /// returns the error message.
get_flag_attribute( file: &[u8], flag_type: FlagValueType, flag_index: u32, ) -> Result<u8, AconfigStorageError>159 pub fn get_flag_attribute(
160     file: &[u8],
161     flag_type: FlagValueType,
162     flag_index: u32,
163 ) -> Result<u8, AconfigStorageError> {
164     find_flag_attribute(file, flag_type, flag_index)
165 }
166 
167 // *************************************** //
168 // CC INTERLOP
169 // *************************************** //
170 
171 // Exported rust data structure and methods, c++ code will be generated
172 #[cxx::bridge]
173 mod ffi {
174     // Storage file version query return for cc interlop
175     pub struct VersionNumberQueryCXX {
176         pub query_success: bool,
177         pub error_message: String,
178         pub version_number: u32,
179     }
180 
181     // Package table query return for cc interlop
182     pub struct PackageReadContextQueryCXX {
183         pub query_success: bool,
184         pub error_message: String,
185         pub package_exists: bool,
186         pub package_id: u32,
187         pub boolean_start_index: u32,
188     }
189 
190     // Flag table query return for cc interlop
191     pub struct FlagReadContextQueryCXX {
192         pub query_success: bool,
193         pub error_message: String,
194         pub flag_exists: bool,
195         pub flag_type: u16,
196         pub flag_index: u16,
197     }
198 
199     // Flag value query return for cc interlop
200     pub struct BooleanFlagValueQueryCXX {
201         pub query_success: bool,
202         pub error_message: String,
203         pub flag_value: bool,
204     }
205 
206     // Flag info query return for cc interlop
207     pub struct FlagAttributeQueryCXX {
208         pub query_success: bool,
209         pub error_message: String,
210         pub flag_attribute: u8,
211     }
212 
213     // Rust export to c++
214     extern "Rust" {
get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX215         pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX;
216 
get_package_read_context_cxx( file: &[u8], package: &str, ) -> PackageReadContextQueryCXX217         pub fn get_package_read_context_cxx(
218             file: &[u8],
219             package: &str,
220         ) -> PackageReadContextQueryCXX;
221 
get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> FlagReadContextQueryCXX222         pub fn get_flag_read_context_cxx(
223             file: &[u8],
224             package_id: u32,
225             flag: &str,
226         ) -> FlagReadContextQueryCXX;
227 
get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX228         pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX;
229 
get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> FlagAttributeQueryCXX230         pub fn get_flag_attribute_cxx(
231             file: &[u8],
232             flag_type: u16,
233             flag_index: u32,
234         ) -> FlagAttributeQueryCXX;
235     }
236 }
237 
238 /// Implement the package offset interlop return type, create from actual package offset api return type
239 impl ffi::PackageReadContextQueryCXX {
new( offset_result: Result<Option<PackageReadContext>, AconfigStorageError>, ) -> Self240     pub(crate) fn new(
241         offset_result: Result<Option<PackageReadContext>, AconfigStorageError>,
242     ) -> Self {
243         match offset_result {
244             Ok(offset_opt) => match offset_opt {
245                 Some(offset) => Self {
246                     query_success: true,
247                     error_message: String::from(""),
248                     package_exists: true,
249                     package_id: offset.package_id,
250                     boolean_start_index: offset.boolean_start_index,
251                 },
252                 None => Self {
253                     query_success: true,
254                     error_message: String::from(""),
255                     package_exists: false,
256                     package_id: 0,
257                     boolean_start_index: 0,
258                 },
259             },
260             Err(errmsg) => Self {
261                 query_success: false,
262                 error_message: format!("{:?}", errmsg),
263                 package_exists: false,
264                 package_id: 0,
265                 boolean_start_index: 0,
266             },
267         }
268     }
269 }
270 
271 /// Implement the flag offset interlop return type, create from actual flag offset api return type
272 impl ffi::FlagReadContextQueryCXX {
new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self273     pub(crate) fn new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self {
274         match offset_result {
275             Ok(offset_opt) => match offset_opt {
276                 Some(offset) => Self {
277                     query_success: true,
278                     error_message: String::from(""),
279                     flag_exists: true,
280                     flag_type: offset.flag_type as u16,
281                     flag_index: offset.flag_index,
282                 },
283                 None => Self {
284                     query_success: true,
285                     error_message: String::from(""),
286                     flag_exists: false,
287                     flag_type: 0u16,
288                     flag_index: 0u16,
289                 },
290             },
291             Err(errmsg) => Self {
292                 query_success: false,
293                 error_message: format!("{:?}", errmsg),
294                 flag_exists: false,
295                 flag_type: 0u16,
296                 flag_index: 0u16,
297             },
298         }
299     }
300 }
301 
302 /// Implement the flag value interlop return type, create from actual flag value api return type
303 impl ffi::BooleanFlagValueQueryCXX {
new(value_result: Result<bool, AconfigStorageError>) -> Self304     pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
305         match value_result {
306             Ok(value) => {
307                 Self { query_success: true, error_message: String::from(""), flag_value: value }
308             }
309             Err(errmsg) => Self {
310                 query_success: false,
311                 error_message: format!("{:?}", errmsg),
312                 flag_value: false,
313             },
314         }
315     }
316 }
317 
318 /// Implement the flag info interlop return type, create from actual flag info api return type
319 impl ffi::FlagAttributeQueryCXX {
new(info_result: Result<u8, AconfigStorageError>) -> Self320     pub(crate) fn new(info_result: Result<u8, AconfigStorageError>) -> Self {
321         match info_result {
322             Ok(info) => {
323                 Self { query_success: true, error_message: String::from(""), flag_attribute: info }
324             }
325             Err(errmsg) => Self {
326                 query_success: false,
327                 error_message: format!("{:?}", errmsg),
328                 flag_attribute: 0u8,
329             },
330         }
331     }
332 }
333 
334 /// Implement the storage version number interlop return type, create from actual version number
335 /// api return type
336 impl ffi::VersionNumberQueryCXX {
new(version_result: Result<u32, AconfigStorageError>) -> Self337     pub(crate) fn new(version_result: Result<u32, AconfigStorageError>) -> Self {
338         match version_result {
339             Ok(version) => Self {
340                 query_success: true,
341                 error_message: String::from(""),
342                 version_number: version,
343             },
344             Err(errmsg) => Self {
345                 query_success: false,
346                 error_message: format!("{:?}", errmsg),
347                 version_number: 0,
348             },
349         }
350     }
351 }
352 
353 /// Get package read context cc interlop
get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX354 pub fn get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX {
355     ffi::PackageReadContextQueryCXX::new(find_package_read_context(file, package))
356 }
357 
358 /// Get flag read context cc interlop
get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> ffi::FlagReadContextQueryCXX359 pub fn get_flag_read_context_cxx(
360     file: &[u8],
361     package_id: u32,
362     flag: &str,
363 ) -> ffi::FlagReadContextQueryCXX {
364     ffi::FlagReadContextQueryCXX::new(find_flag_read_context(file, package_id, flag))
365 }
366 
367 /// Get boolean flag value cc interlop
get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX368 pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX {
369     ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset))
370 }
371 
372 /// Get flag attribute cc interlop
get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> ffi::FlagAttributeQueryCXX373 pub fn get_flag_attribute_cxx(
374     file: &[u8],
375     flag_type: u16,
376     flag_index: u32,
377 ) -> ffi::FlagAttributeQueryCXX {
378     match FlagValueType::try_from(flag_type) {
379         Ok(value_type) => {
380             ffi::FlagAttributeQueryCXX::new(find_flag_attribute(file, value_type, flag_index))
381         }
382         Err(errmsg) => ffi::FlagAttributeQueryCXX::new(Err(errmsg)),
383     }
384 }
385 
386 /// Get storage version number cc interlop
get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX387 pub fn get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX {
388     ffi::VersionNumberQueryCXX::new(get_storage_file_version(file_path))
389 }
390 
391 #[cfg(test)]
392 mod tests {
393     use super::*;
394     use crate::mapped_file::get_mapped_file;
395     use aconfig_storage_file::{FlagInfoBit, StoredFlagType};
396     use rand::Rng;
397     use std::fs;
398 
create_test_storage_files() -> String399     fn create_test_storage_files() -> String {
400         let mut rng = rand::thread_rng();
401         let number: u32 = rng.gen();
402         let storage_dir = String::from("/tmp/") + &number.to_string();
403         if std::fs::metadata(&storage_dir).is_ok() {
404             fs::remove_dir_all(&storage_dir).unwrap();
405         }
406         let maps_dir = storage_dir.clone() + "/maps";
407         let boot_dir = storage_dir.clone() + "/boot";
408         fs::create_dir(&storage_dir).unwrap();
409         fs::create_dir(&maps_dir).unwrap();
410         fs::create_dir(&boot_dir).unwrap();
411 
412         let package_map = storage_dir.clone() + "/maps/mockup.package.map";
413         let flag_map = storage_dir.clone() + "/maps/mockup.flag.map";
414         let flag_val = storage_dir.clone() + "/boot/mockup.val";
415         let flag_info = storage_dir.clone() + "/boot/mockup.info";
416         fs::copy("./tests/data/v1/package_v1.map", &package_map).unwrap();
417         fs::copy("./tests/data/v1/flag_v1.map", &flag_map).unwrap();
418         fs::copy("./tests/data/v1/flag_v1.val", &flag_val).unwrap();
419         fs::copy("./tests/data/v1/flag_v1.info", &flag_info).unwrap();
420 
421         return storage_dir;
422     }
423 
424     #[test]
425     // this test point locks down flag package read context query
test_package_context_query()426     fn test_package_context_query() {
427         let storage_dir = create_test_storage_files();
428         let package_mapped_file = unsafe {
429             get_mapped_file(&storage_dir, "mockup", StorageFileType::PackageMap).unwrap()
430         };
431 
432         let package_context =
433             get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_1")
434                 .unwrap()
435                 .unwrap();
436         let expected_package_context =
437             PackageReadContext { package_id: 0, boolean_start_index: 0, fingerprint: 0 };
438         assert_eq!(package_context, expected_package_context);
439 
440         let package_context =
441             get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_2")
442                 .unwrap()
443                 .unwrap();
444         let expected_package_context =
445             PackageReadContext { package_id: 1, boolean_start_index: 3, fingerprint: 0 };
446         assert_eq!(package_context, expected_package_context);
447 
448         let package_context =
449             get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_4")
450                 .unwrap()
451                 .unwrap();
452         let expected_package_context =
453             PackageReadContext { package_id: 2, boolean_start_index: 6, fingerprint: 0 };
454         assert_eq!(package_context, expected_package_context);
455     }
456 
457     #[test]
458     // this test point locks down flag read context query
test_flag_context_query()459     fn test_flag_context_query() {
460         let storage_dir = create_test_storage_files();
461         let flag_mapped_file =
462             unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagMap).unwrap() };
463 
464         let baseline = vec![
465             (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
466             (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16),
467             (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16),
468             (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
469             (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16),
470             (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16),
471             (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16),
472             (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
473         ];
474         for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() {
475             let flag_context =
476                 get_flag_read_context(&flag_mapped_file, package_id, flag_name).unwrap().unwrap();
477             assert_eq!(flag_context.flag_type, flag_type);
478             assert_eq!(flag_context.flag_index, flag_index);
479         }
480     }
481 
482     #[test]
483     // this test point locks down flag value query
test_flag_value_query()484     fn test_flag_value_query() {
485         let storage_dir = create_test_storage_files();
486         let flag_value_file =
487             unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagVal).unwrap() };
488         let baseline: Vec<bool> = vec![false, true, true, false, true, true, true, true];
489         for (offset, expected_value) in baseline.into_iter().enumerate() {
490             let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
491             assert_eq!(flag_value, expected_value);
492         }
493     }
494 
495     #[test]
496     // this test point locks donw flag info query
test_flag_info_query()497     fn test_flag_info_query() {
498         let storage_dir = create_test_storage_files();
499         let flag_info_file =
500             unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagInfo).unwrap() };
501         let is_rw: Vec<bool> = vec![true, false, true, true, false, false, false, true];
502         for (offset, expected_value) in is_rw.into_iter().enumerate() {
503             let attribute =
504                 get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
505             assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value);
506             assert!((attribute & FlagInfoBit::HasServerOverride as u8) == 0u8);
507             assert!((attribute & FlagInfoBit::HasLocalOverride as u8) == 0u8);
508         }
509     }
510 
511     #[test]
512     // this test point locks down flag storage file version number query api
test_storage_version_query()513     fn test_storage_version_query() {
514         assert_eq!(get_storage_file_version("./tests/data/v1/package_v1.map").unwrap(), 1);
515         assert_eq!(get_storage_file_version("./tests/data/v1/flag_v1.map").unwrap(), 1);
516         assert_eq!(get_storage_file_version("./tests/data/v1/flag_v1.val").unwrap(), 1);
517         assert_eq!(get_storage_file_version("./tests/data/v1/flag_v1.info").unwrap(), 1);
518     }
519 }
520