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 package_table_query::PackageReadContext;
48
49 use aconfig_storage_file::{read_u32_from_bytes, FILE_VERSION};
50 use flag_info_query::find_flag_attribute;
51 use flag_table_query::find_flag_read_context;
52 use flag_value_query::find_boolean_flag_value;
53 use package_table_query::find_package_read_context;
54
55 use anyhow::anyhow;
56 pub use memmap2::Mmap;
57 use std::fs::File;
58 use std::io::Read;
59
60 /// Storage file location
61 pub const STORAGE_LOCATION: &str = "/metadata/aconfig";
62
63 /// Get read only mapped storage files.
64 ///
65 /// \input container: the flag package container
66 /// \input file_type: stoarge file type enum
67 /// \return a result of read only mapped file
68 ///
69 /// # Safety
70 ///
71 /// The memory mapped file may have undefined behavior if there are writes to this
72 /// file after being mapped. Ensure no writes can happen to this file while this
73 /// mapping stays alive.
get_mapped_storage_file( container: &str, file_type: StorageFileType, ) -> Result<Mmap, AconfigStorageError>74 pub unsafe fn get_mapped_storage_file(
75 container: &str,
76 file_type: StorageFileType,
77 ) -> Result<Mmap, AconfigStorageError> {
78 unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION, container, file_type) }
79 }
80
81 /// Get package read context for a specific package.
82 ///
83 /// \input file: mapped package file
84 /// \input package: package name
85 ///
86 /// \return
87 /// If a package is found, it returns Ok(Some(PackageReadContext))
88 /// If a package is not found, it returns Ok(None)
89 /// If errors out, it returns an Err(errmsg)
get_package_read_context( file: &Mmap, package: &str, ) -> Result<Option<PackageReadContext>, AconfigStorageError>90 pub fn get_package_read_context(
91 file: &Mmap,
92 package: &str,
93 ) -> Result<Option<PackageReadContext>, AconfigStorageError> {
94 find_package_read_context(file, package)
95 }
96
97 /// Get flag read context for a specific flag.
98 ///
99 /// \input file: mapped flag file
100 /// \input package_id: package id obtained from package mapping file
101 /// \input flag: flag name
102 ///
103 /// \return
104 /// If a flag is found, it returns Ok(Some(FlagReadContext))
105 /// If a flag is not found, it returns Ok(None)
106 /// If errors out, it returns an Err(errmsg)
get_flag_read_context( file: &Mmap, package_id: u32, flag: &str, ) -> Result<Option<FlagReadContext>, AconfigStorageError>107 pub fn get_flag_read_context(
108 file: &Mmap,
109 package_id: u32,
110 flag: &str,
111 ) -> Result<Option<FlagReadContext>, AconfigStorageError> {
112 find_flag_read_context(file, package_id, flag)
113 }
114
115 /// Get the boolean flag value.
116 ///
117 /// \input file: mapped flag file
118 /// \input index: boolean flag offset
119 ///
120 /// \return
121 /// If the provide offset is valid, it returns the boolean flag value, otherwise it
122 /// returns the error message.
get_boolean_flag_value(file: &Mmap, index: u32) -> Result<bool, AconfigStorageError>123 pub fn get_boolean_flag_value(file: &Mmap, index: u32) -> Result<bool, AconfigStorageError> {
124 find_boolean_flag_value(file, index)
125 }
126
127 /// Get storage file version number
128 ///
129 /// This function would read the first four bytes of the file and interpret it as the
130 /// version number of the file. There are unit tests in aconfig_storage_file crate to
131 /// lock down that for all storage files, the first four bytes will be the version
132 /// number of the storage file
get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>133 pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError> {
134 let mut file = File::open(file_path).map_err(|errmsg| {
135 AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
136 })?;
137 let mut buffer = [0; 4];
138 file.read(&mut buffer).map_err(|errmsg| {
139 AconfigStorageError::FileReadFail(anyhow!(
140 "Failed to read 4 bytes from file {}: {}",
141 file_path,
142 errmsg
143 ))
144 })?;
145 let mut head = 0;
146 read_u32_from_bytes(&buffer, &mut head)
147 }
148
149 /// Get the flag attribute.
150 ///
151 /// \input file: mapped flag info file
152 /// \input flag_type: flag value type
153 /// \input flag_index: flag index
154 ///
155 /// \return
156 /// If the provide offset is valid, it returns the flag attribute bitfiled, otherwise it
157 /// returns the error message.
get_flag_attribute( file: &Mmap, flag_type: FlagValueType, flag_index: u32, ) -> Result<u8, AconfigStorageError>158 pub fn get_flag_attribute(
159 file: &Mmap,
160 flag_type: FlagValueType,
161 flag_index: u32,
162 ) -> Result<u8, AconfigStorageError> {
163 find_flag_attribute(file, flag_type, flag_index)
164 }
165
166 // *************************************** //
167 // CC INTERLOP
168 // *************************************** //
169
170 // Exported rust data structure and methods, c++ code will be generated
171 #[cxx::bridge]
172 mod ffi {
173 // Storage file version query return for cc interlop
174 pub struct VersionNumberQueryCXX {
175 pub query_success: bool,
176 pub error_message: String,
177 pub version_number: u32,
178 }
179
180 // Package table query return for cc interlop
181 pub struct PackageReadContextQueryCXX {
182 pub query_success: bool,
183 pub error_message: String,
184 pub package_exists: bool,
185 pub package_id: u32,
186 pub boolean_start_index: u32,
187 }
188
189 // Flag table query return for cc interlop
190 pub struct FlagReadContextQueryCXX {
191 pub query_success: bool,
192 pub error_message: String,
193 pub flag_exists: bool,
194 pub flag_type: u16,
195 pub flag_index: u16,
196 }
197
198 // Flag value query return for cc interlop
199 pub struct BooleanFlagValueQueryCXX {
200 pub query_success: bool,
201 pub error_message: String,
202 pub flag_value: bool,
203 }
204
205 // Flag info query return for cc interlop
206 pub struct FlagAttributeQueryCXX {
207 pub query_success: bool,
208 pub error_message: String,
209 pub flag_attribute: u8,
210 }
211
212 // Rust export to c++
213 extern "Rust" {
get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX214 pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX;
215
get_package_read_context_cxx( file: &[u8], package: &str, ) -> PackageReadContextQueryCXX216 pub fn get_package_read_context_cxx(
217 file: &[u8],
218 package: &str,
219 ) -> PackageReadContextQueryCXX;
220
get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> FlagReadContextQueryCXX221 pub fn get_flag_read_context_cxx(
222 file: &[u8],
223 package_id: u32,
224 flag: &str,
225 ) -> FlagReadContextQueryCXX;
226
get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX227 pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX;
228
get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> FlagAttributeQueryCXX229 pub fn get_flag_attribute_cxx(
230 file: &[u8],
231 flag_type: u16,
232 flag_index: u32,
233 ) -> FlagAttributeQueryCXX;
234 }
235 }
236
237 /// Implement the package offset interlop return type, create from actual package offset api return type
238 impl ffi::PackageReadContextQueryCXX {
new( offset_result: Result<Option<PackageReadContext>, AconfigStorageError>, ) -> Self239 pub(crate) fn new(
240 offset_result: Result<Option<PackageReadContext>, AconfigStorageError>,
241 ) -> Self {
242 match offset_result {
243 Ok(offset_opt) => match offset_opt {
244 Some(offset) => Self {
245 query_success: true,
246 error_message: String::from(""),
247 package_exists: true,
248 package_id: offset.package_id,
249 boolean_start_index: offset.boolean_start_index,
250 },
251 None => Self {
252 query_success: true,
253 error_message: String::from(""),
254 package_exists: false,
255 package_id: 0,
256 boolean_start_index: 0,
257 },
258 },
259 Err(errmsg) => Self {
260 query_success: false,
261 error_message: format!("{:?}", errmsg),
262 package_exists: false,
263 package_id: 0,
264 boolean_start_index: 0,
265 },
266 }
267 }
268 }
269
270 /// Implement the flag offset interlop return type, create from actual flag offset api return type
271 impl ffi::FlagReadContextQueryCXX {
new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self272 pub(crate) fn new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self {
273 match offset_result {
274 Ok(offset_opt) => match offset_opt {
275 Some(offset) => Self {
276 query_success: true,
277 error_message: String::from(""),
278 flag_exists: true,
279 flag_type: offset.flag_type as u16,
280 flag_index: offset.flag_index,
281 },
282 None => Self {
283 query_success: true,
284 error_message: String::from(""),
285 flag_exists: false,
286 flag_type: 0u16,
287 flag_index: 0u16,
288 },
289 },
290 Err(errmsg) => Self {
291 query_success: false,
292 error_message: format!("{:?}", errmsg),
293 flag_exists: false,
294 flag_type: 0u16,
295 flag_index: 0u16,
296 },
297 }
298 }
299 }
300
301 /// Implement the flag value interlop return type, create from actual flag value api return type
302 impl ffi::BooleanFlagValueQueryCXX {
new(value_result: Result<bool, AconfigStorageError>) -> Self303 pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
304 match value_result {
305 Ok(value) => {
306 Self { query_success: true, error_message: String::from(""), flag_value: value }
307 }
308 Err(errmsg) => Self {
309 query_success: false,
310 error_message: format!("{:?}", errmsg),
311 flag_value: false,
312 },
313 }
314 }
315 }
316
317 /// Implement the flag info interlop return type, create from actual flag info api return type
318 impl ffi::FlagAttributeQueryCXX {
new(info_result: Result<u8, AconfigStorageError>) -> Self319 pub(crate) fn new(info_result: Result<u8, AconfigStorageError>) -> Self {
320 match info_result {
321 Ok(info) => {
322 Self { query_success: true, error_message: String::from(""), flag_attribute: info }
323 }
324 Err(errmsg) => Self {
325 query_success: false,
326 error_message: format!("{:?}", errmsg),
327 flag_attribute: 0u8,
328 },
329 }
330 }
331 }
332
333 /// Implement the storage version number interlop return type, create from actual version number
334 /// api return type
335 impl ffi::VersionNumberQueryCXX {
new(version_result: Result<u32, AconfigStorageError>) -> Self336 pub(crate) fn new(version_result: Result<u32, AconfigStorageError>) -> Self {
337 match version_result {
338 Ok(version) => Self {
339 query_success: true,
340 error_message: String::from(""),
341 version_number: version,
342 },
343 Err(errmsg) => Self {
344 query_success: false,
345 error_message: format!("{:?}", errmsg),
346 version_number: 0,
347 },
348 }
349 }
350 }
351
352 /// Get package read context cc interlop
get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX353 pub fn get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX {
354 ffi::PackageReadContextQueryCXX::new(find_package_read_context(file, package))
355 }
356
357 /// Get flag read context cc interlop
get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> ffi::FlagReadContextQueryCXX358 pub fn get_flag_read_context_cxx(
359 file: &[u8],
360 package_id: u32,
361 flag: &str,
362 ) -> ffi::FlagReadContextQueryCXX {
363 ffi::FlagReadContextQueryCXX::new(find_flag_read_context(file, package_id, flag))
364 }
365
366 /// Get boolean flag value cc interlop
get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX367 pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX {
368 ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset))
369 }
370
371 /// Get flag attribute cc interlop
get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> ffi::FlagAttributeQueryCXX372 pub fn get_flag_attribute_cxx(
373 file: &[u8],
374 flag_type: u16,
375 flag_index: u32,
376 ) -> ffi::FlagAttributeQueryCXX {
377 match FlagValueType::try_from(flag_type) {
378 Ok(value_type) => {
379 ffi::FlagAttributeQueryCXX::new(find_flag_attribute(file, value_type, flag_index))
380 }
381 Err(errmsg) => ffi::FlagAttributeQueryCXX::new(Err(errmsg)),
382 }
383 }
384
385 /// Get storage version number cc interlop
get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX386 pub fn get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX {
387 ffi::VersionNumberQueryCXX::new(get_storage_file_version(file_path))
388 }
389
390 #[cfg(test)]
391 mod tests {
392 use super::*;
393 use crate::mapped_file::get_mapped_file;
394 use aconfig_storage_file::{FlagInfoBit, StoredFlagType};
395 use rand::Rng;
396 use std::fs;
397
create_test_storage_files() -> String398 fn create_test_storage_files() -> String {
399 let mut rng = rand::thread_rng();
400 let number: u32 = rng.gen();
401 let storage_dir = String::from("/tmp/") + &number.to_string();
402 if std::fs::metadata(&storage_dir).is_ok() {
403 fs::remove_dir_all(&storage_dir).unwrap();
404 }
405 let maps_dir = storage_dir.clone() + "/maps";
406 let boot_dir = storage_dir.clone() + "/boot";
407 fs::create_dir(&storage_dir).unwrap();
408 fs::create_dir(&maps_dir).unwrap();
409 fs::create_dir(&boot_dir).unwrap();
410
411 let package_map = storage_dir.clone() + "/maps/mockup.package.map";
412 let flag_map = storage_dir.clone() + "/maps/mockup.flag.map";
413 let flag_val = storage_dir.clone() + "/boot/mockup.val";
414 let flag_info = storage_dir.clone() + "/boot/mockup.info";
415 fs::copy("./tests/package.map", &package_map).unwrap();
416 fs::copy("./tests/flag.map", &flag_map).unwrap();
417 fs::copy("./tests/flag.val", &flag_val).unwrap();
418 fs::copy("./tests/flag.info", &flag_info).unwrap();
419
420 return storage_dir;
421 }
422
423 #[test]
424 // this test point locks down flag package read context query
test_package_context_query()425 fn test_package_context_query() {
426 let storage_dir = create_test_storage_files();
427 let package_mapped_file = unsafe {
428 get_mapped_file(&storage_dir, "mockup", StorageFileType::PackageMap).unwrap()
429 };
430
431 let package_context =
432 get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_1")
433 .unwrap()
434 .unwrap();
435 let expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 };
436 assert_eq!(package_context, expected_package_context);
437
438 let package_context =
439 get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_2")
440 .unwrap()
441 .unwrap();
442 let expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 };
443 assert_eq!(package_context, expected_package_context);
444
445 let package_context =
446 get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_4")
447 .unwrap()
448 .unwrap();
449 let expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 };
450 assert_eq!(package_context, expected_package_context);
451 }
452
453 #[test]
454 // this test point locks down flag read context query
test_flag_context_query()455 fn test_flag_context_query() {
456 let storage_dir = create_test_storage_files();
457 let flag_mapped_file =
458 unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagMap).unwrap() };
459
460 let baseline = vec![
461 (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
462 (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16),
463 (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16),
464 (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
465 (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16),
466 (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16),
467 (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16),
468 (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
469 ];
470 for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() {
471 let flag_context =
472 get_flag_read_context(&flag_mapped_file, package_id, flag_name).unwrap().unwrap();
473 assert_eq!(flag_context.flag_type, flag_type);
474 assert_eq!(flag_context.flag_index, flag_index);
475 }
476 }
477
478 #[test]
479 // this test point locks down flag value query
test_flag_value_query()480 fn test_flag_value_query() {
481 let storage_dir = create_test_storage_files();
482 let flag_value_file =
483 unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagVal).unwrap() };
484 let baseline: Vec<bool> = vec![false, true, true, false, true, true, true, true];
485 for (offset, expected_value) in baseline.into_iter().enumerate() {
486 let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
487 assert_eq!(flag_value, expected_value);
488 }
489 }
490
491 #[test]
492 // this test point locks donw flag info query
test_flag_info_query()493 fn test_flag_info_query() {
494 let storage_dir = create_test_storage_files();
495 let flag_info_file =
496 unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagInfo).unwrap() };
497 let is_rw: Vec<bool> = vec![true, false, true, true, false, false, false, true];
498 for (offset, expected_value) in is_rw.into_iter().enumerate() {
499 let attribute =
500 get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
501 assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value);
502 assert!((attribute & FlagInfoBit::HasServerOverride as u8) == 0u8);
503 assert!((attribute & FlagInfoBit::HasLocalOverride as u8) == 0u8);
504 }
505 }
506
507 #[test]
508 // this test point locks down flag storage file version number query api
test_storage_version_query()509 fn test_storage_version_query() {
510 assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 1);
511 assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 1);
512 assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 1);
513 assert_eq!(get_storage_file_version("./tests/flag.info").unwrap(), 1);
514 }
515 }
516