1 /* 2 * Copyright (C) 2024 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 use crate::storage_files::{FlagSnapshot, StorageFiles}; 18 use crate::utils::{get_files_digest, read_pb_from_file, remove_file, write_pb_to_file}; 19 use crate::AconfigdError; 20 use aconfigd_protos::{ 21 ProtoFlagOverride, ProtoFlagOverrideType, ProtoLocalFlagOverrides, ProtoOTAFlagStagingMessage, 22 ProtoPersistStorageRecord, ProtoPersistStorageRecords, ProtoRemoveOverrideType, 23 }; 24 use log::debug; 25 use std::collections::HashMap; 26 use std::path::{Path, PathBuf}; 27 28 // Storage files manager to manage all the storage files across containers 29 #[derive(Debug)] 30 pub(crate) struct StorageFilesManager { 31 pub root_dir: PathBuf, 32 pub all_storage_files: HashMap<String, StorageFiles>, 33 pub package_to_container: HashMap<String, String>, 34 } 35 36 impl StorageFilesManager { 37 /// Constructor new(root_dir: &Path) -> Self38 pub(crate) fn new(root_dir: &Path) -> Self { 39 Self { 40 root_dir: root_dir.to_path_buf(), 41 all_storage_files: HashMap::new(), 42 package_to_container: HashMap::new(), 43 } 44 } 45 46 /// Get storage files for a container get_storage_files(&mut self, container: &str) -> Option<&mut StorageFiles>47 fn get_storage_files(&mut self, container: &str) -> Option<&mut StorageFiles> { 48 self.all_storage_files.get_mut(container) 49 } 50 51 /// Add storage files based on a storage record pb entry add_storage_files_from_pb( &mut self, pb: &ProtoPersistStorageRecord, ) -> Result<(), AconfigdError>52 pub(crate) fn add_storage_files_from_pb( 53 &mut self, 54 pb: &ProtoPersistStorageRecord, 55 ) -> Result<(), AconfigdError> { 56 if self.all_storage_files.contains_key(pb.container()) { 57 debug!( 58 "Ignored request to add storage files from pb for {}, already exists", 59 pb.container() 60 ); 61 return Ok(()); 62 } 63 64 if aconfig_new_storage_flags::bluetooth_flag_value_bug_fix() { 65 // Only create storage file object if the container is active. This is to 66 // ensure that for inactive containers, the storage files object will not 67 // be created and thus their boot copy will not be produced. And thus we 68 // can prevent them from being used. 69 if PathBuf::from(pb.package_map()).exists() 70 && PathBuf::from(pb.flag_map()).exists() 71 && PathBuf::from(pb.flag_val()).exists() 72 && PathBuf::from(pb.flag_info()).exists() 73 { 74 self.all_storage_files.insert( 75 String::from(pb.container()), 76 StorageFiles::from_pb(pb, &self.root_dir)?, 77 ); 78 } 79 } else { 80 self.all_storage_files 81 .insert(String::from(pb.container()), StorageFiles::from_pb(pb, &self.root_dir)?); 82 } 83 84 Ok(()) 85 } 86 87 /// Add a new container's storage files add_storage_files_from_container( &mut self, container: &str, default_package_map: &Path, default_flag_map: &Path, default_flag_val: &Path, default_flag_info: &Path, ) -> Result<&mut StorageFiles, AconfigdError>88 fn add_storage_files_from_container( 89 &mut self, 90 container: &str, 91 default_package_map: &Path, 92 default_flag_map: &Path, 93 default_flag_val: &Path, 94 default_flag_info: &Path, 95 ) -> Result<&mut StorageFiles, AconfigdError> { 96 if self.all_storage_files.contains_key(container) { 97 debug!( 98 "Ignored request to add storage files from container {}, already exists", 99 container 100 ); 101 } 102 103 self.all_storage_files.insert( 104 String::from(container), 105 StorageFiles::from_container( 106 container, 107 default_package_map, 108 default_flag_map, 109 default_flag_val, 110 default_flag_info, 111 &self.root_dir, 112 )?, 113 ); 114 115 self.all_storage_files 116 .get_mut(container) 117 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() }) 118 } 119 120 /// Update a container's storage files in the case of container update update_container_storage_files( &mut self, container: &str, default_package_map: &Path, default_flag_map: &Path, default_flag_val: &Path, default_flag_info: &Path, ) -> Result<(), AconfigdError>121 fn update_container_storage_files( 122 &mut self, 123 container: &str, 124 default_package_map: &Path, 125 default_flag_map: &Path, 126 default_flag_val: &Path, 127 default_flag_info: &Path, 128 ) -> Result<(), AconfigdError> { 129 debug!("update {} storage files", container); 130 let mut storage_files = self 131 .get_storage_files(container) 132 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 133 134 // backup overrides 135 let server_overrides = storage_files.get_all_server_overrides()?; 136 let local_overrides = storage_files.get_all_local_overrides()?; 137 138 // recreate storage files object 139 storage_files.remove_persist_files()?; 140 self.all_storage_files.remove(container); 141 storage_files = self.add_storage_files_from_container( 142 container, 143 default_package_map, 144 default_flag_map, 145 default_flag_val, 146 default_flag_info, 147 )?; 148 149 // restage server overrides 150 debug!("restaging existing server overrides"); 151 for f in server_overrides.iter() { 152 let context = storage_files.get_package_flag_context(&f.package_name, &f.flag_name)?; 153 if context.flag_exists { 154 storage_files.stage_server_override(&context, &f.flag_value)?; 155 } 156 } 157 158 // restage local overrides 159 debug!("restaging existing local overrides"); 160 let mut new_pb = ProtoLocalFlagOverrides::new(); 161 for f in local_overrides.into_iter() { 162 let context = 163 storage_files.get_package_flag_context(f.package_name(), f.flag_name())?; 164 if context.flag_exists { 165 storage_files.stage_local_override(&context, f.flag_value())?; 166 new_pb.overrides.push(f); 167 } 168 } 169 write_pb_to_file::<ProtoLocalFlagOverrides>( 170 &new_pb, 171 &storage_files.storage_record.local_overrides, 172 )?; 173 174 Ok(()) 175 } 176 177 /// add or update a container's storage files in the case of container 178 /// update add_or_update_container_storage_files( &mut self, container: &str, default_package_map: &Path, default_flag_map: &Path, default_flag_val: &Path, default_flag_info: &Path, ) -> Result<(), AconfigdError>179 pub(crate) fn add_or_update_container_storage_files( 180 &mut self, 181 container: &str, 182 default_package_map: &Path, 183 default_flag_map: &Path, 184 default_flag_val: &Path, 185 default_flag_info: &Path, 186 ) -> Result<(), AconfigdError> { 187 match self.get_storage_files(container) { 188 Some(storage_files) => { 189 let digest = get_files_digest( 190 &[default_package_map, default_flag_map, default_flag_val, default_flag_info][..], 191 )?; 192 if storage_files.storage_record.digest != digest { 193 self.update_container_storage_files( 194 container, 195 default_package_map, 196 default_flag_map, 197 default_flag_val, 198 default_flag_info, 199 )?; 200 } else { 201 debug!("no need to update {}, computed digest matches with record", container); 202 } 203 } 204 None => { 205 self.add_storage_files_from_container( 206 container, 207 default_package_map, 208 default_flag_map, 209 default_flag_val, 210 default_flag_info, 211 )?; 212 } 213 } 214 215 Ok(()) 216 } 217 218 /// Apply all staged server and local overrides apply_all_staged_overrides( &mut self, container: &str, ) -> Result<(), AconfigdError>219 pub(crate) fn apply_all_staged_overrides( 220 &mut self, 221 container: &str, 222 ) -> Result<(), AconfigdError> { 223 let storage_files = self 224 .get_storage_files(container) 225 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 226 storage_files.apply_all_staged_overrides()?; 227 Ok(()) 228 } 229 230 /// Reset all storage files reset_all_storage(&mut self) -> Result<(), AconfigdError>231 pub(crate) fn reset_all_storage(&mut self) -> Result<(), AconfigdError> { 232 debug!("reset storage files of all containers"); 233 let all_containers = self.all_storage_files.keys().cloned().collect::<Vec<String>>(); 234 for container in all_containers { 235 let storage_files = self 236 .get_storage_files(&container) 237 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 238 239 let record = storage_files.storage_record.clone(); 240 storage_files.remove_persist_files()?; 241 self.all_storage_files.remove(&container); 242 243 self.add_storage_files_from_container( 244 &container, 245 &record.default_package_map, 246 &record.default_flag_map, 247 &record.default_flag_val, 248 &record.default_flag_info, 249 )?; 250 } 251 Ok(()) 252 } 253 254 /// Get container get_container(&mut self, package: &str) -> Result<Option<String>, AconfigdError>255 fn get_container(&mut self, package: &str) -> Result<Option<String>, AconfigdError> { 256 match self.package_to_container.get(package) { 257 Some(container) => Ok(Some(container.clone())), 258 None => { 259 for (container, storage_files) in &mut self.all_storage_files { 260 if storage_files.has_package(package)? { 261 self.package_to_container.insert(String::from(package), container.clone()); 262 return Ok(Some(container.clone())); 263 } 264 } 265 Ok(None) 266 } 267 } 268 } 269 270 /// Apply flag override override_flag_value( &mut self, package: &str, flag: &str, value: &str, override_type: ProtoFlagOverrideType, ) -> Result<(), AconfigdError>271 pub(crate) fn override_flag_value( 272 &mut self, 273 package: &str, 274 flag: &str, 275 value: &str, 276 override_type: ProtoFlagOverrideType, 277 ) -> Result<(), AconfigdError> { 278 let container = self 279 .get_container(package)? 280 .ok_or(AconfigdError::FailToFindContainer { package: package.to_string() })?; 281 282 let storage_files = self 283 .get_storage_files(&container) 284 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 285 286 let context = storage_files.get_package_flag_context(package, flag)?; 287 match override_type { 288 ProtoFlagOverrideType::SERVER_ON_REBOOT => { 289 storage_files.stage_server_override(&context, value)?; 290 } 291 ProtoFlagOverrideType::LOCAL_ON_REBOOT => { 292 storage_files.stage_local_override(&context, value)?; 293 } 294 ProtoFlagOverrideType::LOCAL_IMMEDIATE => { 295 storage_files.stage_and_apply_local_override(&context, value)?; 296 } 297 } 298 299 Ok(()) 300 } 301 302 /// Read staged ota flags get_ota_flags(&mut self) -> Result<Option<Vec<ProtoFlagOverride>>, AconfigdError>303 fn get_ota_flags(&mut self) -> Result<Option<Vec<ProtoFlagOverride>>, AconfigdError> { 304 let ota_pb_file = self.root_dir.join("flags/ota.pb"); 305 if !ota_pb_file.exists() { 306 debug!("no OTA flags staged, skip"); 307 return Ok(None); 308 } 309 310 let ota_flags_pb = read_pb_from_file::<ProtoOTAFlagStagingMessage>(&ota_pb_file)?; 311 if let Some(target_build_id) = ota_flags_pb.build_id { 312 let device_build_id = rustutils::system_properties::read("ro.build.fingerprint") 313 .map_err(|errmsg| AconfigdError::FailToReadBuildFingerPrint { errmsg })?; 314 if device_build_id == Some(target_build_id.clone()) { 315 remove_file(&ota_pb_file)?; 316 Ok(Some(ota_flags_pb.overrides)) 317 } else { 318 debug!( 319 "fingerprint mismatch between OTA flag staging {}, and device {}", 320 target_build_id, 321 device_build_id.unwrap_or(String::from("None")), 322 ); 323 Ok(None) 324 } 325 } else { 326 debug!("ill formatted OTA staged flags, build fingerprint not set"); 327 remove_file(&ota_pb_file)?; 328 return Ok(None); 329 } 330 } 331 332 /// Apply staged ota flags apply_staged_ota_flags(&mut self) -> Result<(), AconfigdError>333 pub(crate) fn apply_staged_ota_flags(&mut self) -> Result<(), AconfigdError> { 334 if let Some(flags) = self.get_ota_flags()? { 335 debug!("apply staged OTA flags"); 336 for flag in flags.iter() { 337 if let Err(errmsg) = self.override_flag_value( 338 flag.package_name(), 339 flag.flag_name(), 340 flag.flag_value(), 341 ProtoFlagOverrideType::SERVER_ON_REBOOT, 342 ) { 343 debug!( 344 "failed to apply ota flag override for {}.{}: {:?}", 345 flag.package_name(), 346 flag.flag_name(), 347 errmsg 348 ); 349 } 350 } 351 } 352 Ok(()) 353 } 354 355 /// Write persist storage records to file write_persist_storage_records_to_file( &self, file: &Path, ) -> Result<(), AconfigdError>356 pub(crate) fn write_persist_storage_records_to_file( 357 &self, 358 file: &Path, 359 ) -> Result<(), AconfigdError> { 360 debug!("writing updated storage records {}", file.display().to_string()); 361 let mut pb = ProtoPersistStorageRecords::new(); 362 pb.records = self 363 .all_storage_files 364 .values() 365 .map(|storage_files| { 366 let record = &storage_files.storage_record; 367 let mut entry = ProtoPersistStorageRecord::new(); 368 entry.set_version(record.version); 369 entry.set_container(record.container.clone()); 370 entry.set_package_map(record.default_package_map.display().to_string()); 371 entry.set_flag_map(record.default_flag_map.display().to_string()); 372 entry.set_flag_val(record.default_flag_val.display().to_string()); 373 entry.set_flag_info(record.default_flag_info.display().to_string()); 374 entry.set_digest(record.digest.clone()); 375 entry 376 }) 377 .collect(); 378 write_pb_to_file(&pb, file) 379 } 380 381 /// Remove a single local override remove_local_override( &mut self, package: &str, flag: &str, remove_override_type: ProtoRemoveOverrideType, ) -> Result<(), AconfigdError>382 pub(crate) fn remove_local_override( 383 &mut self, 384 package: &str, 385 flag: &str, 386 remove_override_type: ProtoRemoveOverrideType, 387 ) -> Result<(), AconfigdError> { 388 let container = self 389 .get_container(package)? 390 .ok_or(AconfigdError::FailToFindContainer { package: package.to_string() })?; 391 392 let storage_files = self 393 .get_storage_files(&container) 394 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 395 396 let context = storage_files.get_package_flag_context(package, flag)?; 397 let immediate = remove_override_type == ProtoRemoveOverrideType::REMOVE_LOCAL_IMMEDIATE; 398 storage_files.remove_local_override(&context, immediate) 399 } 400 401 /// Remove all local overrides remove_all_local_overrides(&mut self) -> Result<(), AconfigdError>402 pub(crate) fn remove_all_local_overrides(&mut self) -> Result<(), AconfigdError> { 403 debug!("remove all local overrides for all containers"); 404 for storage_files in self.all_storage_files.values_mut() { 405 storage_files.remove_all_local_overrides()?; 406 } 407 Ok(()) 408 } 409 410 /// Get flag snapshot get_flag_snapshot( &mut self, package: &str, flag: &str, ) -> Result<Option<FlagSnapshot>, AconfigdError>411 pub(crate) fn get_flag_snapshot( 412 &mut self, 413 package: &str, 414 flag: &str, 415 ) -> Result<Option<FlagSnapshot>, AconfigdError> { 416 match self.get_container(package)? { 417 Some(container) => { 418 let storage_files = self.get_storage_files(&container).ok_or( 419 AconfigdError::FailToGetStorageFiles { container: container.to_string() }, 420 )?; 421 422 storage_files.get_flag_snapshot(package, flag) 423 } 424 None => Ok(None), 425 } 426 } 427 428 /// List all flags in a package list_flags_in_package( &mut self, package: &str, ) -> Result<Vec<FlagSnapshot>, AconfigdError>429 pub(crate) fn list_flags_in_package( 430 &mut self, 431 package: &str, 432 ) -> Result<Vec<FlagSnapshot>, AconfigdError> { 433 let container = self 434 .get_container(package)? 435 .ok_or(AconfigdError::FailToFindContainer { package: package.to_string() })?; 436 437 let storage_files = self 438 .get_storage_files(&container) 439 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 440 441 storage_files.list_flags_in_package(package) 442 } 443 444 /// List flags in a container list_flags_in_container( &mut self, container: &str, ) -> Result<Vec<FlagSnapshot>, AconfigdError>445 pub(crate) fn list_flags_in_container( 446 &mut self, 447 container: &str, 448 ) -> Result<Vec<FlagSnapshot>, AconfigdError> { 449 let storage_files = self 450 .get_storage_files(&container) 451 .ok_or(AconfigdError::FailToGetStorageFiles { container: container.to_string() })?; 452 453 storage_files.list_all_flags() 454 } 455 456 /// List all the flags list_all_flags(&mut self) -> Result<Vec<FlagSnapshot>, AconfigdError>457 pub(crate) fn list_all_flags(&mut self) -> Result<Vec<FlagSnapshot>, AconfigdError> { 458 debug!("list all flags across containers"); 459 let mut flags = Vec::new(); 460 for storage_files in self.all_storage_files.values_mut() { 461 match storage_files.list_all_flags() { 462 Ok(f) => { 463 flags.extend(f); 464 } 465 Err(errmsg) => { 466 debug!( 467 "failed to list all flags for {}: {:?}", 468 storage_files.storage_record.container, errmsg 469 ); 470 } 471 } 472 } 473 Ok(flags) 474 } 475 } 476 477 #[cfg(test)] 478 mod tests { 479 use super::*; 480 use crate::storage_files::StorageRecord; 481 use crate::test_utils::{has_same_content, ContainerMock, StorageRootDirMock}; 482 use crate::utils::{copy_file, get_files_digest, read_pb_from_file}; 483 use aconfig_storage_file::{FlagValueSummary, StoredFlagType}; 484 use aconfigd_protos::ProtoFlagOverride; 485 486 #[test] test_add_storage_files_from_pb()487 fn test_add_storage_files_from_pb() { 488 let root_dir = StorageRootDirMock::new(); 489 let container = ContainerMock::new(); 490 491 let persist_package_map = root_dir.maps_dir.join("mockup.package.map"); 492 let persist_flag_map = root_dir.maps_dir.join("mockup.flag.map"); 493 let persist_flag_val = root_dir.flags_dir.join("mockup.val"); 494 let persist_flag_info = root_dir.flags_dir.join("mockup.info"); 495 copy_file(&container.package_map, &persist_package_map, 0o444).unwrap(); 496 copy_file(&container.flag_map, &persist_flag_map, 0o444).unwrap(); 497 copy_file(&container.flag_val, &persist_flag_val, 0o644).unwrap(); 498 copy_file(&container.flag_info, &persist_flag_info, 0o644).unwrap(); 499 500 let mut pb = ProtoPersistStorageRecord::new(); 501 pb.set_version(123); 502 pb.set_container("mockup".to_string()); 503 pb.set_package_map(container.package_map.display().to_string()); 504 pb.set_flag_map(container.flag_map.display().to_string()); 505 pb.set_flag_val(container.flag_val.display().to_string()); 506 pb.set_flag_info(container.flag_info.display().to_string()); 507 pb.set_digest(String::from("abc")); 508 509 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 510 manager.add_storage_files_from_pb(&pb); 511 assert_eq!(manager.all_storage_files.len(), 1); 512 assert_eq!( 513 manager.all_storage_files.get("mockup").unwrap(), 514 &StorageFiles::from_pb(&pb, &root_dir.tmp_dir.path()).unwrap(), 515 ); 516 517 // Ensure we can run this again, to exercise the case where the storage 518 // files already exist, for example if the storage proto is deleted. 519 manager.add_storage_files_from_pb(&pb); 520 assert_eq!(manager.all_storage_files.len(), 1); 521 assert_eq!( 522 manager.all_storage_files.get("mockup").unwrap(), 523 &StorageFiles::from_pb(&pb, &root_dir.tmp_dir.path()).unwrap(), 524 ); 525 } 526 init_storage(container: &ContainerMock, manager: &mut StorageFilesManager)527 fn init_storage(container: &ContainerMock, manager: &mut StorageFilesManager) { 528 manager 529 .add_or_update_container_storage_files( 530 &container.name, 531 &container.package_map, 532 &container.flag_map, 533 &container.flag_val, 534 &container.flag_info, 535 ) 536 .unwrap(); 537 } 538 539 #[test] test_add_storage_files_from_container()540 fn test_add_storage_files_from_container() { 541 let container = ContainerMock::new(); 542 let root_dir = StorageRootDirMock::new(); 543 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 544 init_storage(&container, &mut manager); 545 546 let storage_files = manager.get_storage_files(&container.name).unwrap(); 547 548 let expected_record = StorageRecord { 549 version: 1, 550 container: String::from("mockup"), 551 default_package_map: container.package_map.clone(), 552 default_flag_map: container.flag_map.clone(), 553 default_flag_val: container.flag_val.clone(), 554 default_flag_info: container.flag_info.clone(), 555 persist_package_map: root_dir.maps_dir.join("mockup.package.map"), 556 persist_flag_map: root_dir.maps_dir.join("mockup.flag.map"), 557 persist_flag_val: root_dir.flags_dir.join("mockup.val"), 558 persist_flag_info: root_dir.flags_dir.join("mockup.info"), 559 local_overrides: root_dir.flags_dir.join("mockup_local_overrides.pb"), 560 boot_flag_val: root_dir.boot_dir.join("mockup.val"), 561 boot_flag_info: root_dir.boot_dir.join("mockup.info"), 562 digest: get_files_digest( 563 &[ 564 container.package_map.as_path(), 565 container.flag_map.as_path(), 566 container.flag_val.as_path(), 567 container.flag_info.as_path(), 568 ][..], 569 ) 570 .unwrap(), 571 }; 572 573 let expected_storage_files = StorageFiles { 574 storage_record: expected_record, 575 package_map: None, 576 flag_map: None, 577 flag_val: None, 578 boot_flag_val: None, 579 boot_flag_info: None, 580 persist_flag_val: None, 581 persist_flag_info: None, 582 mutable_boot_flag_val: None, 583 mutable_boot_flag_info: None, 584 }; 585 586 assert_eq!(storage_files, &expected_storage_files); 587 588 assert!(has_same_content( 589 &container.package_map, 590 &storage_files.storage_record.persist_package_map 591 )); 592 assert!(has_same_content( 593 &container.flag_map, 594 &storage_files.storage_record.persist_flag_map 595 )); 596 assert!(has_same_content( 597 &container.flag_val, 598 &storage_files.storage_record.persist_flag_val 599 )); 600 assert!(has_same_content( 601 &container.flag_info, 602 &storage_files.storage_record.persist_flag_info 603 )); 604 assert!(has_same_content(&container.flag_val, &storage_files.storage_record.boot_flag_val)); 605 assert!(has_same_content( 606 &container.flag_info, 607 &storage_files.storage_record.boot_flag_info 608 )); 609 } 610 611 #[test] test_simple_update_container_storage_files()612 fn test_simple_update_container_storage_files() { 613 let container = ContainerMock::new(); 614 let root_dir = StorageRootDirMock::new(); 615 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 616 init_storage(&container, &mut manager); 617 618 // copy files over to mimic a container update 619 std::fs::copy("./tests/data/container_with_more_flags.package.map", &container.package_map) 620 .unwrap(); 621 std::fs::copy("./tests/data/container_with_more_flags.flag.map", &container.flag_map) 622 .unwrap(); 623 std::fs::copy("./tests/data/container_with_more_flags.flag.val", &container.flag_val) 624 .unwrap(); 625 std::fs::copy("./tests/data/container_with_more_flags.flag.info", &container.flag_info) 626 .unwrap(); 627 628 // update container 629 manager 630 .add_or_update_container_storage_files( 631 &container.name, 632 &container.package_map, 633 &container.flag_map, 634 &container.flag_val, 635 &container.flag_info, 636 ) 637 .unwrap(); 638 639 let storage_files = manager.get_storage_files(&container.name).unwrap(); 640 641 assert!(has_same_content( 642 &Path::new("./tests/data/container_with_more_flags.package.map"), 643 &storage_files.storage_record.persist_package_map 644 )); 645 assert!(has_same_content( 646 &Path::new("./tests/data/container_with_more_flags.flag.map"), 647 &storage_files.storage_record.persist_flag_map 648 )); 649 assert!(has_same_content( 650 &Path::new("./tests/data/container_with_more_flags.flag.val"), 651 &storage_files.storage_record.persist_flag_val 652 )); 653 assert!(has_same_content( 654 &Path::new("./tests/data/container_with_more_flags.flag.info"), 655 &storage_files.storage_record.persist_flag_info 656 )); 657 assert!(has_same_content( 658 &Path::new("./tests/data/container_with_more_flags.flag.val"), 659 &storage_files.storage_record.boot_flag_val 660 )); 661 assert!(has_same_content( 662 &Path::new("./tests/data/container_with_more_flags.flag.info"), 663 &storage_files.storage_record.boot_flag_info 664 )); 665 assert!(storage_files.storage_record.local_overrides.exists()); 666 } 667 add_example_overrides(manager: &mut StorageFilesManager)668 fn add_example_overrides(manager: &mut StorageFilesManager) { 669 manager 670 .override_flag_value( 671 "com.android.aconfig.storage.test_1", 672 "enabled_rw", 673 "false", 674 ProtoFlagOverrideType::SERVER_ON_REBOOT, 675 ) 676 .unwrap(); 677 678 manager 679 .override_flag_value( 680 "com.android.aconfig.storage.test_1", 681 "disabled_rw", 682 "false", 683 ProtoFlagOverrideType::SERVER_ON_REBOOT, 684 ) 685 .unwrap(); 686 687 manager 688 .override_flag_value( 689 "com.android.aconfig.storage.test_1", 690 "disabled_rw", 691 "true", 692 ProtoFlagOverrideType::LOCAL_ON_REBOOT, 693 ) 694 .unwrap(); 695 } 696 697 #[test] test_overrides_after_update_container_storage_files()698 fn test_overrides_after_update_container_storage_files() { 699 let container = ContainerMock::new(); 700 let root_dir = StorageRootDirMock::new(); 701 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 702 init_storage(&container, &mut manager); 703 add_example_overrides(&mut manager); 704 705 // copy files over to mimic a container update 706 std::fs::copy("./tests/data/package.map", &container.package_map).unwrap(); 707 std::fs::copy("./tests/data/flag.map", &container.flag_map).unwrap(); 708 std::fs::copy("./tests/data/flag.val", &container.flag_val).unwrap(); 709 std::fs::copy("./tests/data/flag.info", &container.flag_info).unwrap(); 710 711 // update container 712 manager 713 .add_or_update_container_storage_files( 714 &container.name, 715 &container.package_map, 716 &container.flag_map, 717 &container.flag_val, 718 &container.flag_info, 719 ) 720 .unwrap(); 721 722 // verify that server override is persisted 723 let storage_files = manager.get_storage_files(&container.name).unwrap(); 724 let server_overrides = storage_files.get_all_server_overrides().unwrap(); 725 assert_eq!(server_overrides.len(), 2); 726 assert_eq!( 727 server_overrides[0], 728 FlagValueSummary { 729 package_name: "com.android.aconfig.storage.test_1".to_string(), 730 flag_name: "disabled_rw".to_string(), 731 flag_value: "false".to_string(), 732 value_type: StoredFlagType::ReadWriteBoolean, 733 } 734 ); 735 assert_eq!( 736 server_overrides[1], 737 FlagValueSummary { 738 package_name: "com.android.aconfig.storage.test_1".to_string(), 739 flag_name: "enabled_rw".to_string(), 740 flag_value: "false".to_string(), 741 value_type: StoredFlagType::ReadWriteBoolean, 742 } 743 ); 744 745 // verify that local override is persisted 746 let local_overrides = storage_files.get_all_local_overrides().unwrap(); 747 assert_eq!(local_overrides.len(), 1); 748 let mut pb = ProtoFlagOverride::new(); 749 pb.set_package_name("com.android.aconfig.storage.test_1".to_string()); 750 pb.set_flag_name("disabled_rw".to_string()); 751 pb.set_flag_value("true".to_string()); 752 assert_eq!(local_overrides[0], pb); 753 } 754 755 #[test] test_apply_all_staged_overrides()756 fn test_apply_all_staged_overrides() { 757 let container = ContainerMock::new(); 758 let root_dir = StorageRootDirMock::new(); 759 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 760 init_storage(&container, &mut manager); 761 add_example_overrides(&mut manager); 762 manager.apply_all_staged_overrides("mockup").unwrap(); 763 764 let mut flag = 765 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "enabled_rw").unwrap(); 766 767 let mut expected_flag = FlagSnapshot { 768 container: String::from("mockup"), 769 package: String::from("com.android.aconfig.storage.test_1"), 770 flag: String::from("enabled_rw"), 771 server_value: String::from("false"), 772 local_value: String::new(), 773 boot_value: String::from("false"), 774 default_value: String::from("true"), 775 is_readwrite: true, 776 has_server_override: true, 777 has_local_override: false, 778 has_boot_local_override: false, 779 }; 780 781 assert_eq!(flag, Some(expected_flag)); 782 783 flag = 784 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "disabled_rw").unwrap(); 785 786 expected_flag = FlagSnapshot { 787 container: String::from("mockup"), 788 package: String::from("com.android.aconfig.storage.test_1"), 789 flag: String::from("disabled_rw"), 790 server_value: String::from("false"), 791 local_value: String::from("true"), 792 boot_value: String::from("true"), 793 default_value: String::from("false"), 794 is_readwrite: true, 795 has_server_override: true, 796 has_local_override: true, 797 has_boot_local_override: false, 798 }; 799 800 assert_eq!(flag, Some(expected_flag)); 801 } 802 803 #[test] test_reset_all_storage()804 fn test_reset_all_storage() { 805 let container = ContainerMock::new(); 806 let root_dir = StorageRootDirMock::new(); 807 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 808 init_storage(&container, &mut manager); 809 add_example_overrides(&mut manager); 810 manager.apply_all_staged_overrides("mockup").unwrap(); 811 812 manager.reset_all_storage().unwrap(); 813 let storage_files = manager.get_storage_files(&container.name).unwrap(); 814 assert!(has_same_content( 815 &container.flag_val, 816 &storage_files.storage_record.persist_flag_val 817 )); 818 assert!(has_same_content( 819 &container.flag_info, 820 &storage_files.storage_record.persist_flag_info 821 )); 822 assert!(has_same_content(&container.flag_val, &storage_files.storage_record.boot_flag_val)); 823 assert!(has_same_content( 824 &container.flag_info, 825 &storage_files.storage_record.boot_flag_info 826 )); 827 } 828 test_override_flag_server_on_reboot()829 fn test_override_flag_server_on_reboot() { 830 let container = ContainerMock::new(); 831 let root_dir = StorageRootDirMock::new(); 832 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 833 init_storage(&container, &mut manager); 834 manager.apply_all_staged_overrides("mockup").unwrap(); 835 836 manager 837 .override_flag_value( 838 "com.android.aconfig.storage.test_1", 839 "enabled_rw", 840 "false", 841 ProtoFlagOverrideType::SERVER_ON_REBOOT, 842 ) 843 .unwrap(); 844 845 let flag = 846 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "enabled_rw").unwrap(); 847 848 let expected_flag = FlagSnapshot { 849 container: String::from("mockup"), 850 package: String::from("com.android.aconfig.storage.test_1"), 851 flag: String::from("enabled_rw"), 852 server_value: String::from("false"), 853 local_value: String::new(), 854 boot_value: String::from("true"), 855 default_value: String::from("true"), 856 is_readwrite: true, 857 has_server_override: true, 858 has_local_override: false, 859 has_boot_local_override: false, 860 }; 861 862 assert_eq!(flag, Some(expected_flag)); 863 } 864 865 #[test] test_override_flag_local_on_reboot()866 fn test_override_flag_local_on_reboot() { 867 let container = ContainerMock::new(); 868 let root_dir = StorageRootDirMock::new(); 869 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 870 init_storage(&container, &mut manager); 871 manager.apply_all_staged_overrides("mockup").unwrap(); 872 873 manager 874 .override_flag_value( 875 "com.android.aconfig.storage.test_1", 876 "enabled_rw", 877 "false", 878 ProtoFlagOverrideType::LOCAL_ON_REBOOT, 879 ) 880 .unwrap(); 881 882 let flag = 883 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "enabled_rw").unwrap(); 884 885 let expected_flag = FlagSnapshot { 886 container: String::from("mockup"), 887 package: String::from("com.android.aconfig.storage.test_1"), 888 flag: String::from("enabled_rw"), 889 server_value: String::new(), 890 local_value: String::from("false"), 891 boot_value: String::from("true"), 892 default_value: String::from("true"), 893 is_readwrite: true, 894 has_server_override: false, 895 has_local_override: true, 896 has_boot_local_override: false, 897 }; 898 899 assert_eq!(flag, Some(expected_flag)); 900 } 901 902 #[test] test_override_flag_local_immediate()903 fn test_override_flag_local_immediate() { 904 let container = ContainerMock::new(); 905 let root_dir = StorageRootDirMock::new(); 906 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 907 init_storage(&container, &mut manager); 908 manager.apply_all_staged_overrides("mockup").unwrap(); 909 910 manager 911 .override_flag_value( 912 "com.android.aconfig.storage.test_1", 913 "enabled_rw", 914 "false", 915 ProtoFlagOverrideType::LOCAL_IMMEDIATE, 916 ) 917 .unwrap(); 918 919 let flag = 920 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "enabled_rw").unwrap(); 921 922 let expected_flag = FlagSnapshot { 923 container: String::from("mockup"), 924 package: String::from("com.android.aconfig.storage.test_1"), 925 flag: String::from("enabled_rw"), 926 server_value: String::new(), 927 local_value: String::from("false"), 928 boot_value: String::from("false"), 929 default_value: String::from("true"), 930 is_readwrite: true, 931 has_server_override: false, 932 has_local_override: true, 933 has_boot_local_override: false, 934 }; 935 936 assert_eq!(flag, Some(expected_flag)); 937 } 938 939 #[test] test_get_ota_flags()940 fn test_get_ota_flags() { 941 let root_dir = StorageRootDirMock::new(); 942 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 943 944 let mut ota_flags = ProtoOTAFlagStagingMessage::new(); 945 ota_flags.set_build_id("xyz.123".to_string()); 946 write_pb_to_file::<ProtoOTAFlagStagingMessage>( 947 &ota_flags, 948 &root_dir.flags_dir.join("ota.pb"), 949 ) 950 .unwrap(); 951 let staged_ota_flags = manager.get_ota_flags().unwrap(); 952 assert!(staged_ota_flags.is_none()); 953 assert!(root_dir.flags_dir.join("ota.pb").exists()); 954 955 let device_build_id = 956 rustutils::system_properties::read("ro.build.fingerprint").unwrap().unwrap(); 957 ota_flags.set_build_id(device_build_id); 958 let mut flag1 = ProtoFlagOverride::new(); 959 flag1.set_package_name("com.android.aconfig.storage.test_1".to_string()); 960 flag1.set_flag_name("enabled_rw".to_string()); 961 flag1.set_flag_value("false".to_string()); 962 ota_flags.overrides.push(flag1.clone()); 963 let mut flag2 = ProtoFlagOverride::new(); 964 flag2.set_package_name("com.android.aconfig.storage.test_2".to_string()); 965 flag2.set_flag_name("disabled_rw".to_string()); 966 flag2.set_flag_value("true".to_string()); 967 ota_flags.overrides.push(flag2.clone()); 968 write_pb_to_file::<ProtoOTAFlagStagingMessage>( 969 &ota_flags, 970 &root_dir.flags_dir.join("ota.pb"), 971 ) 972 .unwrap(); 973 let staged_ota_flags = manager.get_ota_flags().unwrap().unwrap(); 974 assert_eq!(staged_ota_flags.len(), 2); 975 assert_eq!(staged_ota_flags[0], flag1); 976 assert_eq!(staged_ota_flags[1], flag2); 977 assert!(!root_dir.flags_dir.join("ota.pb").exists()); 978 } 979 980 #[test] test_apply_staged_ota_flags()981 fn test_apply_staged_ota_flags() { 982 let container = ContainerMock::new(); 983 let root_dir = StorageRootDirMock::new(); 984 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 985 init_storage(&container, &mut manager); 986 987 let mut ota_flags = ProtoOTAFlagStagingMessage::new(); 988 let device_build_id = 989 rustutils::system_properties::read("ro.build.fingerprint").unwrap().unwrap(); 990 ota_flags.set_build_id(device_build_id); 991 let mut flag1 = ProtoFlagOverride::new(); 992 flag1.set_package_name("com.android.aconfig.storage.test_1".to_string()); 993 flag1.set_flag_name("enabled_rw".to_string()); 994 flag1.set_flag_value("false".to_string()); 995 ota_flags.overrides.push(flag1.clone()); 996 let mut flag2 = ProtoFlagOverride::new(); 997 flag2.set_package_name("com.android.aconfig.storage.test_2".to_string()); 998 flag2.set_flag_name("disabled_rw".to_string()); 999 flag2.set_flag_value("true".to_string()); 1000 ota_flags.overrides.push(flag2.clone()); 1001 let mut flag3 = ProtoFlagOverride::new(); 1002 flag3.set_package_name("not_exist".to_string()); 1003 flag3.set_flag_name("not_exist".to_string()); 1004 flag3.set_flag_value("true".to_string()); 1005 ota_flags.overrides.push(flag3.clone()); 1006 write_pb_to_file::<ProtoOTAFlagStagingMessage>( 1007 &ota_flags, 1008 &root_dir.flags_dir.join("ota.pb"), 1009 ) 1010 .unwrap(); 1011 1012 manager.apply_staged_ota_flags().unwrap(); 1013 let storage_files = manager.get_storage_files(&container.name).unwrap(); 1014 let server_overrides = storage_files.get_all_server_overrides().unwrap(); 1015 assert_eq!(server_overrides.len(), 2); 1016 assert_eq!( 1017 server_overrides[0].package_name, 1018 "com.android.aconfig.storage.test_1".to_string() 1019 ); 1020 assert_eq!(server_overrides[0].flag_name, "enabled_rw".to_string()); 1021 assert_eq!(server_overrides[0].flag_value, "false".to_string()); 1022 assert_eq!( 1023 server_overrides[1].package_name, 1024 "com.android.aconfig.storage.test_2".to_string() 1025 ); 1026 assert_eq!(server_overrides[1].flag_name, "disabled_rw".to_string()); 1027 assert_eq!(server_overrides[1].flag_value, "true".to_string()); 1028 } 1029 1030 #[test] test_write_persist_storage_records_to_file()1031 fn test_write_persist_storage_records_to_file() { 1032 let container = ContainerMock::new(); 1033 let root_dir = StorageRootDirMock::new(); 1034 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 1035 init_storage(&container, &mut manager); 1036 1037 let pb_file = root_dir.tmp_dir.path().join("records.pb"); 1038 manager.write_persist_storage_records_to_file(&pb_file).unwrap(); 1039 1040 let pb = read_pb_from_file::<ProtoPersistStorageRecords>(&pb_file).unwrap(); 1041 assert_eq!(pb.records.len(), 1); 1042 1043 let mut entry = ProtoPersistStorageRecord::new(); 1044 entry.set_version(1); 1045 entry.set_container("mockup".to_string()); 1046 entry.set_package_map(container.package_map.display().to_string()); 1047 entry.set_flag_map(container.flag_map.display().to_string()); 1048 entry.set_flag_val(container.flag_val.display().to_string()); 1049 entry.set_flag_info(container.flag_info.display().to_string()); 1050 let digest = get_files_digest( 1051 &[ 1052 container.package_map.as_path(), 1053 container.flag_map.as_path(), 1054 container.flag_val.as_path(), 1055 container.flag_info.as_path(), 1056 ][..], 1057 ) 1058 .unwrap(); 1059 entry.set_digest(digest); 1060 assert_eq!(pb.records[0], entry); 1061 } 1062 1063 #[test] test_remove_local_override()1064 fn test_remove_local_override() { 1065 let container = ContainerMock::new(); 1066 let root_dir = StorageRootDirMock::new(); 1067 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 1068 init_storage(&container, &mut manager); 1069 add_example_overrides(&mut manager); 1070 manager.apply_all_staged_overrides("mockup").unwrap(); 1071 1072 manager 1073 .remove_local_override( 1074 "com.android.aconfig.storage.test_1", 1075 "disabled_rw", 1076 ProtoRemoveOverrideType::REMOVE_LOCAL_ON_REBOOT, 1077 ) 1078 .unwrap(); 1079 1080 let flag = 1081 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "disabled_rw").unwrap(); 1082 1083 let expected_flag = FlagSnapshot { 1084 container: String::from("mockup"), 1085 package: String::from("com.android.aconfig.storage.test_1"), 1086 flag: String::from("disabled_rw"), 1087 server_value: String::from("false"), 1088 local_value: String::new(), 1089 boot_value: String::from("true"), 1090 default_value: String::from("false"), 1091 is_readwrite: true, 1092 has_server_override: true, 1093 has_local_override: false, 1094 has_boot_local_override: false, 1095 }; 1096 1097 assert_eq!(flag, Some(expected_flag)); 1098 } 1099 1100 #[test] test_remove_all_local_override()1101 fn test_remove_all_local_override() { 1102 let container = ContainerMock::new(); 1103 let root_dir = StorageRootDirMock::new(); 1104 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 1105 init_storage(&container, &mut manager); 1106 1107 manager 1108 .override_flag_value( 1109 "com.android.aconfig.storage.test_1", 1110 "disabled_rw", 1111 "true", 1112 ProtoFlagOverrideType::LOCAL_ON_REBOOT, 1113 ) 1114 .unwrap(); 1115 1116 manager 1117 .override_flag_value( 1118 "com.android.aconfig.storage.test_2", 1119 "disabled_rw", 1120 "true", 1121 ProtoFlagOverrideType::LOCAL_ON_REBOOT, 1122 ) 1123 .unwrap(); 1124 manager.apply_all_staged_overrides("mockup").unwrap(); 1125 manager.remove_all_local_overrides().unwrap(); 1126 1127 let mut flag = 1128 manager.get_flag_snapshot("com.android.aconfig.storage.test_1", "disabled_rw").unwrap(); 1129 1130 let mut expected_flag = FlagSnapshot { 1131 container: String::from("mockup"), 1132 package: String::from("com.android.aconfig.storage.test_1"), 1133 flag: String::from("disabled_rw"), 1134 server_value: String::from(""), 1135 local_value: String::new(), 1136 boot_value: String::from("true"), 1137 default_value: String::from("false"), 1138 is_readwrite: true, 1139 has_server_override: false, 1140 has_local_override: false, 1141 has_boot_local_override: false, 1142 }; 1143 1144 assert_eq!(flag, Some(expected_flag)); 1145 1146 flag = 1147 manager.get_flag_snapshot("com.android.aconfig.storage.test_2", "disabled_rw").unwrap(); 1148 1149 expected_flag = FlagSnapshot { 1150 container: String::from("mockup"), 1151 package: String::from("com.android.aconfig.storage.test_2"), 1152 flag: String::from("disabled_rw"), 1153 server_value: String::from(""), 1154 local_value: String::new(), 1155 boot_value: String::from("true"), 1156 default_value: String::from("false"), 1157 is_readwrite: true, 1158 has_server_override: false, 1159 has_local_override: false, 1160 has_boot_local_override: false, 1161 }; 1162 1163 assert_eq!(flag, Some(expected_flag)); 1164 } 1165 1166 #[test] test_list_flags_in_package()1167 fn test_list_flags_in_package() { 1168 let container = ContainerMock::new(); 1169 let root_dir = StorageRootDirMock::new(); 1170 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 1171 init_storage(&container, &mut manager); 1172 add_example_overrides(&mut manager); 1173 manager.apply_all_staged_overrides("mockup").unwrap(); 1174 1175 let flags = manager.list_flags_in_package("com.android.aconfig.storage.test_1").unwrap(); 1176 1177 let mut flag = FlagSnapshot { 1178 container: String::from("mockup"), 1179 package: String::from("com.android.aconfig.storage.test_1"), 1180 flag: String::from("disabled_rw"), 1181 server_value: String::from("false"), 1182 local_value: String::from("true"), 1183 boot_value: String::from("true"), 1184 default_value: String::from("false"), 1185 is_readwrite: true, 1186 has_server_override: true, 1187 has_local_override: true, 1188 has_boot_local_override: false, 1189 }; 1190 assert_eq!(flags[0], flag); 1191 1192 flag = FlagSnapshot { 1193 container: String::from("mockup"), 1194 package: String::from("com.android.aconfig.storage.test_1"), 1195 flag: String::from("enabled_ro"), 1196 server_value: String::new(), 1197 local_value: String::new(), 1198 boot_value: String::from("true"), 1199 default_value: String::from("true"), 1200 is_readwrite: false, 1201 has_server_override: false, 1202 has_local_override: false, 1203 has_boot_local_override: false, 1204 }; 1205 assert_eq!(flags[1], flag); 1206 1207 flag = FlagSnapshot { 1208 container: String::from("mockup"), 1209 package: String::from("com.android.aconfig.storage.test_1"), 1210 flag: String::from("enabled_rw"), 1211 server_value: String::from("false"), 1212 local_value: String::new(), 1213 boot_value: String::from("false"), 1214 default_value: String::from("true"), 1215 is_readwrite: true, 1216 has_server_override: true, 1217 has_local_override: false, 1218 has_boot_local_override: false, 1219 }; 1220 assert_eq!(flags[2], flag); 1221 } 1222 1223 #[test] test_list_flags_in_container()1224 fn test_list_flags_in_container() { 1225 let container = ContainerMock::new(); 1226 let root_dir = StorageRootDirMock::new(); 1227 let mut manager = StorageFilesManager::new(&root_dir.tmp_dir.path()); 1228 init_storage(&container, &mut manager); 1229 add_example_overrides(&mut manager); 1230 manager.apply_all_staged_overrides("mockup").unwrap(); 1231 1232 let flags = manager.list_flags_in_container("mockup").unwrap(); 1233 assert_eq!(flags.len(), 8); 1234 1235 let mut flag = FlagSnapshot { 1236 container: String::from("mockup"), 1237 package: String::from("com.android.aconfig.storage.test_1"), 1238 flag: String::from("enabled_rw"), 1239 server_value: String::from("false"), 1240 local_value: String::new(), 1241 boot_value: String::from("false"), 1242 default_value: String::from("true"), 1243 is_readwrite: true, 1244 has_server_override: true, 1245 has_local_override: false, 1246 has_boot_local_override: false, 1247 }; 1248 assert_eq!(flags[2], flag); 1249 1250 flag = FlagSnapshot { 1251 container: String::from("mockup"), 1252 package: String::from("com.android.aconfig.storage.test_1"), 1253 flag: String::from("disabled_rw"), 1254 server_value: String::from("false"), 1255 local_value: String::from("true"), 1256 boot_value: String::from("true"), 1257 default_value: String::from("false"), 1258 is_readwrite: true, 1259 has_server_override: true, 1260 has_local_override: true, 1261 has_boot_local_override: false, 1262 }; 1263 assert_eq!(flags[0], flag); 1264 } 1265 } 1266