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