lib.rs (38444B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 extern crate byteorder; 6 #[macro_use] 7 extern crate cstr; 8 extern crate firefox_on_glean; 9 #[macro_use] 10 extern crate log; 11 #[macro_use] 12 extern crate malloc_size_of_derive; 13 extern crate moz_task; 14 extern crate nserror; 15 extern crate thin_vec; 16 extern crate wr_malloc_size_of; 17 #[macro_use] 18 extern crate xpcom; 19 20 use wr_malloc_size_of as malloc_size_of; 21 22 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 23 use firefox_on_glean::metrics::data_storage; 24 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 25 use moz_task::{create_background_task_queue, RunnableBuilder}; 26 use nserror::{ 27 nsresult, NS_ERROR_FAILURE, NS_ERROR_ILLEGAL_INPUT, NS_ERROR_INVALID_ARG, 28 NS_ERROR_NOT_AVAILABLE, NS_OK, 29 }; 30 use nsstring::{nsACString, nsAString, nsCStr, nsCString, nsString}; 31 use thin_vec::ThinVec; 32 use xpcom::interfaces::{ 33 nsIDataStorage, nsIDataStorageItem, nsIFile, nsIHandleReportCallback, nsIMemoryReporter, 34 nsIMemoryReporterManager, nsIObserverService, nsIProperties, nsISerialEventTarget, nsISupports, 35 }; 36 use xpcom::{xpcom_method, RefPtr, XpCom}; 37 38 use std::collections::HashMap; 39 use std::ffi::CStr; 40 use std::fs::{File, OpenOptions}; 41 use std::io::{BufRead, BufReader, ErrorKind, Read, Seek, SeekFrom, Write}; 42 use std::os::raw::{c_char, c_void}; 43 use std::path::PathBuf; 44 use std::sync::{Condvar, Mutex}; 45 use std::time::{Duration, SystemTime, UNIX_EPOCH}; 46 47 /// Helper type for turning the nsIDataStorage::DataType "enum" into a rust 48 /// enum. 49 #[derive(Copy, Clone, Eq, PartialEq)] 50 enum DataType { 51 Persistent, 52 Private, 53 Temporary, 54 } 55 56 impl From<u8> for DataType { 57 fn from(value: u8) -> Self { 58 match value { 59 nsIDataStorage::Persistent => DataType::Persistent, 60 nsIDataStorage::Private => DataType::Private, 61 nsIDataStorage::Temporary => DataType::Temporary, 62 _ => panic!("invalid nsIDataStorage::DataType"), 63 } 64 } 65 } 66 67 impl From<DataType> for u8 { 68 fn from(value: DataType) -> Self { 69 match value { 70 DataType::Persistent => nsIDataStorage::Persistent, 71 DataType::Private => nsIDataStorage::Private, 72 DataType::Temporary => nsIDataStorage::Temporary, 73 } 74 } 75 } 76 77 /// Returns the current day in days since the unix epoch, to a maximum of 78 /// u16::MAX days. 79 fn now_in_days() -> u16 { 80 const SECONDS_PER_DAY: u64 = 60 * 60 * 24; 81 let now = SystemTime::now() 82 .duration_since(UNIX_EPOCH) 83 .unwrap_or(Duration::ZERO); 84 (now.as_secs() / SECONDS_PER_DAY) 85 .try_into() 86 .unwrap_or(u16::MAX) 87 } 88 89 /// An entry in some DataStorageTable. 90 #[derive(Clone, MallocSizeOf)] 91 struct Entry { 92 /// The number of unique days this Entry has been accessed on. 93 score: u16, 94 /// The number of days since the unix epoch this Entry was last accessed. 95 last_accessed: u16, 96 /// The key. 97 key: Vec<u8>, 98 /// The value. 99 value: Vec<u8>, 100 /// The slot index of this Entry. 101 slot_index: usize, 102 } 103 104 impl Entry { 105 /// Constructs an Entry given a line of text from the old DataStorage format. 106 fn from_old_line(line: &str, slot_index: usize, value_length: usize) -> Result<Self, nsresult> { 107 // the old format is <key>\t<score>\t<last accessed>\t<value> 108 let parts: Vec<&str> = line.split('\t').collect(); 109 if parts.len() != 4 { 110 return Err(NS_ERROR_ILLEGAL_INPUT); 111 } 112 let score = parts[1] 113 .parse::<u16>() 114 .map_err(|_| NS_ERROR_ILLEGAL_INPUT)?; 115 let last_accessed = parts[2] 116 .parse::<u16>() 117 .map_err(|_| NS_ERROR_ILLEGAL_INPUT)?; 118 let key = Vec::from(parts[0]); 119 if key.len() > KEY_LENGTH { 120 return Err(NS_ERROR_ILLEGAL_INPUT); 121 } 122 let value = Vec::from(parts[3]); 123 if value.len() > value_length { 124 return Err(NS_ERROR_ILLEGAL_INPUT); 125 } 126 Ok(Entry { 127 score, 128 last_accessed, 129 key, 130 value, 131 slot_index, 132 }) 133 } 134 135 /// Constructs an Entry given the parsed parts from the current format. 136 fn from_slot( 137 score: u16, 138 last_accessed: u16, 139 key: Vec<u8>, 140 value: Vec<u8>, 141 slot_index: usize, 142 ) -> Self { 143 Entry { 144 score, 145 last_accessed, 146 key, 147 value, 148 slot_index, 149 } 150 } 151 152 /// Constructs a new Entry given a key, value, and index. 153 fn new(key: Vec<u8>, value: Vec<u8>, slot_index: usize) -> Self { 154 Entry { 155 score: 1, 156 last_accessed: now_in_days(), 157 key, 158 value, 159 slot_index, 160 } 161 } 162 163 /// Constructs a new, empty `Entry` with the given index. Useful for clearing 164 /// slots on disk. 165 fn new_empty(slot_index: usize) -> Self { 166 Entry { 167 score: 0, 168 last_accessed: 0, 169 key: Vec::new(), 170 value: Vec::new(), 171 slot_index, 172 } 173 } 174 175 /// Returns whether or not this is an empty `Entry` (an empty `Entry` has 176 /// been created with `Entry::new_empty()` or cleared with 177 /// `Entry::clear()`, has 0 `score` and `last_accessed`, and has an empty 178 /// `key` and `value`. 179 fn is_empty(&self) -> bool { 180 self.score == 0 && self.last_accessed == 0 && self.key.is_empty() && self.value.is_empty() 181 } 182 183 /// If this Entry was last accessed on a day different from today, 184 /// increments its score (as well as its last accessed day). 185 /// Returns `true` if the score did in fact change, and `false` otherwise. 186 fn update_score(&mut self) -> bool { 187 let now_in_days = now_in_days(); 188 if self.last_accessed != now_in_days { 189 self.last_accessed = now_in_days; 190 self.score += 1; 191 true 192 } else { 193 false 194 } 195 } 196 197 /// Clear the data stored in this Entry. Useful for clearing a single slot 198 /// on disk. 199 fn clear(&mut self) { 200 // Note: it's important that this preserves slot_index - the writer 201 // needs it to know where to write out the zeroed Entry 202 *self = Self::new_empty(self.slot_index); 203 } 204 } 205 206 /// Strips all trailing 0 bytes from the end of the given vec. 207 /// Useful for converting 0-padded keys and values to their original, non-padded 208 /// state. 209 fn strip_zeroes(vec: &mut Vec<u8>) { 210 let mut length = vec.len(); 211 while length > 0 && vec[length - 1] == 0 { 212 length -= 1; 213 } 214 vec.truncate(length); 215 } 216 217 /// Given a slice of entries, returns a Vec<Entry> consisting of each Entry 218 /// with score equal to the minimum score among all entries. 219 fn get_entries_with_minimum_score(entries: &[Entry]) -> Vec<&Entry> { 220 let mut min_score = u16::MAX; 221 let mut min_score_entries = Vec::new(); 222 for entry in entries.iter() { 223 if entry.score < min_score { 224 min_score = entry.score; 225 min_score_entries.clear(); 226 } 227 if entry.score == min_score { 228 min_score_entries.push(entry); 229 } 230 } 231 min_score_entries 232 } 233 234 const MAX_SLOTS: usize = 2048; 235 const KEY_LENGTH: usize = 256; 236 237 /// Helper type to map between an entry key and the slot it is stored on. 238 type DataStorageTable = HashMap<Vec<u8>, usize>; 239 240 /// The main structure of this implementation. Keeps track of persistent 241 /// and private entries. 242 #[derive(MallocSizeOf)] 243 struct DataStorageInner { 244 /// The key to slot index mapping table for persistent data. 245 persistent_table: DataStorageTable, 246 /// The persistent entries that are stored on disk. 247 persistent_slots: Vec<Entry>, 248 /// The key to slot index mapping table for private, temporary data. 249 private_table: DataStorageTable, 250 /// The private, temporary entries that are not stored on disk. 251 /// This data is cleared upon observing "last-pb-context-exited", and is 252 /// forgotten when the program shuts down. 253 private_slots: Vec<Entry>, 254 /// The key to slot index mapping table for temporary data. 255 temporary_table: DataStorageTable, 256 /// The temporary entries that are not stored on disk. 257 temporary_slots: Vec<Entry>, 258 /// The name of the table (e.g. "SiteSecurityServiceState"). 259 name: String, 260 /// The maximum permitted length of values. 261 value_length: usize, 262 /// A PathBuf holding the location of the profile directory, if available. 263 maybe_profile_path: Option<PathBuf>, 264 /// A serial event target to post tasks to, to write out changed persistent 265 /// data in the background. 266 #[ignore_malloc_size_of = "not implemented for nsISerialEventTarget"] 267 write_queue: Option<RefPtr<nsISerialEventTarget>>, 268 } 269 270 impl DataStorageInner { 271 fn new( 272 name: String, 273 value_length: usize, 274 maybe_profile_path: Option<PathBuf>, 275 ) -> Result<Self, nsresult> { 276 Ok(DataStorageInner { 277 persistent_table: DataStorageTable::new(), 278 persistent_slots: Vec::new(), 279 private_table: DataStorageTable::new(), 280 private_slots: Vec::new(), 281 temporary_table: DataStorageTable::new(), 282 temporary_slots: Vec::new(), 283 name, 284 value_length, 285 maybe_profile_path, 286 write_queue: Some(create_background_task_queue(cstr!("data_storage"))?), 287 }) 288 } 289 290 /// Initializes the DataStorageInner. If the profile directory is not 291 /// present, does nothing. If the backing file is available, processes it. 292 /// Otherwise, if the old backing file is available, migrates it to the 293 /// current format. 294 fn initialize(&mut self) -> Result<(), nsresult> { 295 let Some(profile_path) = self.maybe_profile_path.as_ref() else { 296 return Ok(()); 297 }; 298 let mut backing_path = profile_path.clone(); 299 backing_path.push(format!("{}.bin", &self.name)); 300 let mut old_backing_path = profile_path.clone(); 301 old_backing_path.push(format!("{}.txt", &self.name)); 302 if backing_path.exists() { 303 self.read(backing_path) 304 } else if old_backing_path.exists() { 305 self.read_old_format(old_backing_path) 306 } else { 307 Ok(()) 308 } 309 } 310 311 /// Reads the backing file, processing each slot. 312 fn read(&mut self, path: PathBuf) -> Result<(), nsresult> { 313 let f = OpenOptions::new() 314 .read(true) 315 .write(true) 316 .create(true) 317 .open(path) 318 .map_err(|_| NS_ERROR_FAILURE)?; 319 let mut backing_file = BufReader::new(f); 320 let mut slots = Vec::new(); 321 // First read each entry into the persistent slots list. 322 while slots.len() < MAX_SLOTS { 323 if let Some(entry) = self.process_slot(&mut backing_file, slots.len())? { 324 slots.push(entry); 325 } else { 326 break; 327 } 328 } 329 self.persistent_slots = slots; 330 // Then build the key -> slot index lookup table. 331 self.persistent_table = self 332 .persistent_slots 333 .iter() 334 .filter(|slot| !slot.is_empty()) 335 .map(|slot| (slot.key.clone(), slot.slot_index)) 336 .collect(); 337 let num_entries = self.persistent_table.len() as i64; 338 match self.name.as_str() { 339 "AlternateServices" => data_storage::alternate_services.set(num_entries), 340 "ClientAuthRememberList" => data_storage::client_auth_remember_list.set(num_entries), 341 "SiteSecurityServiceState" => { 342 data_storage::site_security_service_state.set(num_entries) 343 } 344 _ => panic!("unknown nsIDataStorageManager::DataStorage"), 345 } 346 Ok(()) 347 } 348 349 /// Processes a slot (via a reader) by reading its metadata, key, and 350 /// value. If the checksum fails or if the score or last accessed fields 351 /// are 0, this is an empty slot. Otherwise, un-0-pads the key and value, 352 /// creates a new Entry, and puts it in the persistent table. 353 fn process_slot<R: Read>( 354 &mut self, 355 reader: &mut R, 356 slot_index: usize, 357 ) -> Result<Option<Entry>, nsresult> { 358 // Format is [checksum][score][last accessed][key][value], where 359 // checksum is 2 bytes big-endian, score and last accessed are 2 bytes 360 // big-endian, key is KEY_LENGTH bytes (currently 256), and value is 361 // self.value_length bytes (1024 for most instances, but 24 for 362 // SiteSecurityServiceState - see DataStorageManager::Get). 363 let mut checksum = match reader.read_u16::<BigEndian>() { 364 Ok(checksum) => checksum, 365 // The file may be shorter than expected due to unoccupied slots. 366 // Every slot after the last read slot is unoccupied. 367 Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None), 368 Err(_) => return Err(NS_ERROR_FAILURE), 369 }; 370 let score = reader 371 .read_u16::<BigEndian>() 372 .map_err(|_| NS_ERROR_FAILURE)?; 373 checksum ^= score; 374 let last_accessed = reader 375 .read_u16::<BigEndian>() 376 .map_err(|_| NS_ERROR_FAILURE)?; 377 checksum ^= last_accessed; 378 379 let mut key = vec![0u8; KEY_LENGTH]; 380 reader.read_exact(&mut key).map_err(|_| NS_ERROR_FAILURE)?; 381 for mut chunk in key.chunks(2) { 382 checksum ^= chunk 383 .read_u16::<BigEndian>() 384 .map_err(|_| NS_ERROR_FAILURE)?; 385 } 386 strip_zeroes(&mut key); 387 let mut value = vec![0u8; self.value_length]; 388 reader 389 .read_exact(&mut value) 390 .map_err(|_| NS_ERROR_FAILURE)?; 391 for mut chunk in value.chunks(2) { 392 checksum ^= chunk 393 .read_u16::<BigEndian>() 394 .map_err(|_| NS_ERROR_FAILURE)?; 395 } 396 strip_zeroes(&mut value); 397 398 // If this slot is incomplete, corrupted, or empty, treat it as empty. 399 if checksum != 0 || score == 0 || last_accessed == 0 { 400 // This slot is empty. 401 return Ok(Some(Entry::new_empty(slot_index))); 402 } 403 404 Ok(Some(Entry::from_slot( 405 score, 406 last_accessed, 407 key, 408 value, 409 slot_index, 410 ))) 411 } 412 413 /// Migrates from the old format to the current format. 414 fn read_old_format(&mut self, path: PathBuf) -> Result<(), nsresult> { 415 let file = File::open(path).map_err(|_| NS_ERROR_FAILURE)?; 416 let reader = BufReader::new(file); 417 // First read each line in the old file into the persistent slots list. 418 // The old format was limited to 1024 lines, so only expect that many. 419 for line in reader.lines().flatten().take(1024) { 420 match Entry::from_old_line(&line, self.persistent_slots.len(), self.value_length) { 421 Ok(entry) => { 422 if self.persistent_slots.len() >= MAX_SLOTS { 423 warn!("too many lines in old DataStorage format"); 424 break; 425 } 426 if !entry.is_empty() { 427 self.persistent_slots.push(entry); 428 } else { 429 warn!("empty entry in old DataStorage format?"); 430 } 431 } 432 Err(_) => { 433 warn!("failed to migrate a line from old DataStorage format"); 434 } 435 } 436 } 437 // Then build the key -> slot index lookup table. 438 self.persistent_table = self 439 .persistent_slots 440 .iter() 441 .filter(|slot| !slot.is_empty()) 442 .map(|slot| (slot.key.clone(), slot.slot_index)) 443 .collect(); 444 // Finally, write out the migrated data to the new backing file. 445 self.async_write_entries(self.persistent_slots.clone())?; 446 let num_entries = self.persistent_table.len() as i64; 447 match self.name.as_str() { 448 "AlternateServices" => data_storage::alternate_services.set(num_entries), 449 "ClientAuthRememberList" => data_storage::client_auth_remember_list.set(num_entries), 450 "SiteSecurityServiceState" => { 451 data_storage::site_security_service_state.set(num_entries) 452 } 453 _ => panic!("unknown nsIDataStorageManager::DataStorage"), 454 } 455 Ok(()) 456 } 457 458 /// Given an `Entry` and `DataType`, this function updates the internal 459 /// list of slots and the mapping from keys to slot indices. If the slot 460 /// assigned to the `Entry` is already occupied, the existing `Entry` is 461 /// evicted. 462 /// After updating internal state, if the type of this entry is persistent, 463 /// this function dispatches an event to asynchronously write the data out. 464 fn put_internal(&mut self, entry: Entry, type_: DataType) -> Result<(), nsresult> { 465 let (table, slots) = self.get_table_and_slots_for_type_mut(type_); 466 if entry.slot_index < slots.len() { 467 let entry_to_evict = &slots[entry.slot_index]; 468 if !entry_to_evict.is_empty() { 469 table.remove(&entry_to_evict.key); 470 } 471 } 472 let _ = table.insert(entry.key.clone(), entry.slot_index); 473 if entry.slot_index < slots.len() { 474 slots[entry.slot_index] = entry.clone(); 475 } else if entry.slot_index == slots.len() { 476 slots.push(entry.clone()); 477 } else { 478 panic!( 479 "put_internal should not have been given an Entry with slot_index > slots.len()" 480 ); 481 } 482 if type_ == DataType::Persistent { 483 self.async_write_entry(entry)?; 484 } 485 Ok(()) 486 } 487 488 /// Returns the total length of each slot on disk. 489 fn slot_length(&self) -> usize { 490 // Checksum is 2 bytes, and score and last accessed are 2 bytes each. 491 2 + 2 + 2 + KEY_LENGTH + self.value_length 492 } 493 494 /// Gets the next free slot index, or determines a slot to evict (but 495 /// doesn't actually perform the eviction - the caller must do that). 496 fn get_free_slot_or_slot_to_evict(&self, type_: DataType) -> usize { 497 let (_, slots) = self.get_table_and_slots_for_type(type_); 498 let maybe_unoccupied_slot = slots 499 .iter() 500 .enumerate() 501 .find(|(_, maybe_empty_entry)| maybe_empty_entry.is_empty()); 502 if let Some((unoccupied_slot, _)) = maybe_unoccupied_slot { 503 return unoccupied_slot; 504 } 505 // If `slots` isn't full, the next free slot index is one more than the 506 // current last index. 507 if slots.len() < MAX_SLOTS { 508 return slots.len(); 509 } 510 // If there isn't an unoccupied slot, evict the entry with the lowest score. 511 let min_score_entries = get_entries_with_minimum_score(&slots); 512 // `min_score_entry` is the oldest Entry with the minimum score. 513 // There must be at least one such Entry, so unwrap it or abort. 514 let min_score_entry = min_score_entries 515 .iter() 516 .min_by_key(|e| e.last_accessed) 517 .unwrap(); 518 min_score_entry.slot_index 519 } 520 521 /// Helper function to get a handle on the slot list and key to slot index 522 /// mapping for the given `DataType`. 523 fn get_table_and_slots_for_type(&self, type_: DataType) -> (&DataStorageTable, &[Entry]) { 524 match type_ { 525 DataType::Persistent => (&self.persistent_table, &self.persistent_slots), 526 DataType::Private => (&self.private_table, &self.private_slots), 527 DataType::Temporary => (&self.temporary_table, &self.temporary_slots), 528 } 529 } 530 531 /// Helper function to get a mutable handle on the slot list and key to 532 /// slot index mapping for the given `DataType`. 533 fn get_table_and_slots_for_type_mut( 534 &mut self, 535 type_: DataType, 536 ) -> (&mut DataStorageTable, &mut Vec<Entry>) { 537 match type_ { 538 DataType::Persistent => (&mut self.persistent_table, &mut self.persistent_slots), 539 DataType::Private => (&mut self.private_table, &mut self.private_slots), 540 DataType::Temporary => (&mut self.temporary_table, &mut self.temporary_slots), 541 } 542 } 543 544 /// Helper function to look up an `Entry` by its key and type. 545 fn get_entry(&mut self, key: &[u8], type_: DataType) -> Option<&mut Entry> { 546 let (table, slots) = self.get_table_and_slots_for_type_mut(type_); 547 let slot_index = table.get(key)?; 548 Some(&mut slots[*slot_index]) 549 } 550 551 /// Gets a value by key, if available. Updates the Entry's score when appropriate. 552 fn get(&mut self, key: &[u8], type_: DataType) -> Result<Vec<u8>, nsresult> { 553 let Some(entry) = self.get_entry(key, type_) else { 554 return Err(NS_ERROR_NOT_AVAILABLE); 555 }; 556 let value = entry.value.clone(); 557 if entry.update_score() && type_ == DataType::Persistent { 558 let entry = entry.clone(); 559 self.async_write_entry(entry)?; 560 } 561 Ok(value) 562 } 563 564 /// Inserts or updates a value by key. Updates the Entry's score if applicable. 565 fn put(&mut self, key: Vec<u8>, value: Vec<u8>, type_: DataType) -> Result<(), nsresult> { 566 if key.len() > KEY_LENGTH || value.len() > self.value_length { 567 return Err(NS_ERROR_INVALID_ARG); 568 } 569 if let Some(existing_entry) = self.get_entry(&key, type_) { 570 let data_changed = existing_entry.value != value; 571 if data_changed { 572 existing_entry.value = value; 573 } 574 if (existing_entry.update_score() || data_changed) && type_ == DataType::Persistent { 575 let entry = existing_entry.clone(); 576 self.async_write_entry(entry)?; 577 } 578 Ok(()) 579 } else { 580 let slot_index = self.get_free_slot_or_slot_to_evict(type_); 581 let entry = Entry::new(key.clone(), value, slot_index); 582 self.put_internal(entry, type_) 583 } 584 } 585 586 /// Removes an Entry by key, if it is present. 587 fn remove(&mut self, key: &Vec<u8>, type_: DataType) -> Result<(), nsresult> { 588 let (table, slots) = self.get_table_and_slots_for_type_mut(type_); 589 let Some(slot_index) = table.remove(key) else { 590 return Ok(()); 591 }; 592 let entry = &mut slots[slot_index]; 593 entry.clear(); 594 if type_ == DataType::Persistent { 595 let entry = entry.clone(); 596 self.async_write_entry(entry)?; 597 } 598 Ok(()) 599 } 600 601 /// Clears all tables and the backing persistent file. 602 fn clear(&mut self) -> Result<(), nsresult> { 603 self.persistent_table.clear(); 604 self.private_table.clear(); 605 self.temporary_table.clear(); 606 self.persistent_slots.clear(); 607 self.private_slots.clear(); 608 self.temporary_slots.clear(); 609 let Some(profile_path) = self.maybe_profile_path.clone() else { 610 return Ok(()); 611 }; 612 let Some(write_queue) = self.write_queue.clone() else { 613 return Ok(()); 614 }; 615 let name = self.name.clone(); 616 RunnableBuilder::new("data_storage::remove_backing_files", move || { 617 let old_backing_path = profile_path.join(format!("{name}.txt")); 618 let _ = std::fs::remove_file(old_backing_path); 619 let backing_path = profile_path.join(format!("{name}.bin")); 620 let _ = std::fs::remove_file(backing_path); 621 }) 622 .may_block(true) 623 .dispatch(write_queue.coerce()) 624 } 625 626 /// Clears only data in the private table. 627 fn clear_private_data(&mut self) { 628 self.private_table.clear(); 629 self.private_slots.clear(); 630 } 631 632 /// Asynchronously writes the given entry on the background serial event 633 /// target. 634 fn async_write_entry(&self, entry: Entry) -> Result<(), nsresult> { 635 self.async_write_entries(vec![entry]) 636 } 637 638 /// Asynchronously writes the given entries on the background serial event 639 /// target. 640 fn async_write_entries(&self, entries: Vec<Entry>) -> Result<(), nsresult> { 641 let Some(mut backing_path) = self.maybe_profile_path.clone() else { 642 return Ok(()); 643 }; 644 let Some(write_queue) = self.write_queue.clone() else { 645 return Ok(()); 646 }; 647 backing_path.push(format!("{}.bin", &self.name)); 648 let value_length = self.value_length; 649 let slot_length = self.slot_length(); 650 RunnableBuilder::new("data_storage::write_entries", move || { 651 let _ = write_entries(entries, backing_path, value_length, slot_length); 652 }) 653 .may_block(true) 654 .dispatch(write_queue.coerce()) 655 } 656 657 /// Drop the write queue to prevent further writes. 658 fn drop_write_queue(&mut self) { 659 let _ = self.write_queue.take(); 660 } 661 662 /// Takes a callback that is run for each entry in each table. 663 fn for_each<F>(&self, mut f: F) 664 where 665 F: FnMut(&Entry, DataType), 666 { 667 for entry in self 668 .persistent_slots 669 .iter() 670 .filter(|entry| !entry.is_empty()) 671 { 672 f(entry, DataType::Persistent); 673 } 674 for entry in self.private_slots.iter().filter(|entry| !entry.is_empty()) { 675 f(entry, DataType::Private); 676 } 677 for entry in self 678 .temporary_slots 679 .iter() 680 .filter(|entry| !entry.is_empty()) 681 { 682 f(entry, DataType::Temporary); 683 } 684 } 685 686 /// Collects the memory used by this DataStorageInner. 687 fn collect_reports( 688 &self, 689 ops: &mut MallocSizeOfOps, 690 callback: &nsIHandleReportCallback, 691 data: Option<&nsISupports>, 692 ) -> Result<(), nsresult> { 693 let size = self.size_of(ops); 694 let data = match data { 695 Some(data) => data as *const nsISupports, 696 None => std::ptr::null() as *const nsISupports, 697 }; 698 unsafe { 699 callback 700 .Callback( 701 &nsCStr::new() as &nsACString, 702 &nsCString::from(format!("explicit/data-storage/{}", self.name)) as &nsACString, 703 nsIMemoryReporter::KIND_HEAP, 704 nsIMemoryReporter::UNITS_BYTES, 705 size as i64, 706 &nsCStr::from("Memory used by PSM data storage cache") as &nsACString, 707 data, 708 ) 709 .to_result() 710 } 711 } 712 } 713 714 #[xpcom(implement(nsIDataStorageItem), atomic)] 715 struct DataStorageItem { 716 key: nsCString, 717 value: nsCString, 718 type_: u8, 719 } 720 721 impl DataStorageItem { 722 xpcom_method!(get_key => GetKey() -> nsACString); 723 fn get_key(&self) -> Result<nsCString, nsresult> { 724 Ok(self.key.clone()) 725 } 726 727 xpcom_method!(get_value => GetValue() -> nsACString); 728 fn get_value(&self) -> Result<nsCString, nsresult> { 729 Ok(self.value.clone()) 730 } 731 732 xpcom_method!(get_type => GetType() -> u8); 733 fn get_type(&self) -> Result<u8, nsresult> { 734 Ok(self.type_) 735 } 736 } 737 738 type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; 739 740 /// Helper struct that coordinates xpcom access to the DataStorageInner that 741 /// actually holds the data. 742 #[xpcom(implement(nsIDataStorage, nsIMemoryReporter, nsIObserver), atomic)] 743 struct DataStorage { 744 ready: (Mutex<bool>, Condvar), 745 data: Mutex<DataStorageInner>, 746 size_of_op: VoidPtrToSizeFn, 747 enclosing_size_of_op: VoidPtrToSizeFn, 748 } 749 750 impl DataStorage { 751 xpcom_method!(get => Get(key: *const nsACString, type_: u8) -> nsACString); 752 fn get(&self, key: &nsACString, type_: u8) -> Result<nsCString, nsresult> { 753 self.wait_for_ready()?; 754 let mut storage = self.data.lock().unwrap(); 755 storage 756 .get(&Vec::from(key.as_ref()), type_.into()) 757 .map(|data| nsCString::from(data)) 758 } 759 760 xpcom_method!(put => Put(key: *const nsACString, value: *const nsACString, type_: u8)); 761 fn put(&self, key: &nsACString, value: &nsACString, type_: u8) -> Result<(), nsresult> { 762 self.wait_for_ready()?; 763 let mut storage = self.data.lock().unwrap(); 764 storage.put( 765 Vec::from(key.as_ref()), 766 Vec::from(value.as_ref()), 767 type_.into(), 768 ) 769 } 770 771 xpcom_method!(remove => Remove(key: *const nsACString, type_: u8)); 772 fn remove(&self, key: &nsACString, type_: u8) -> Result<(), nsresult> { 773 self.wait_for_ready()?; 774 let mut storage = self.data.lock().unwrap(); 775 storage.remove(&Vec::from(key.as_ref()), type_.into())?; 776 Ok(()) 777 } 778 779 xpcom_method!(clear => Clear()); 780 fn clear(&self) -> Result<(), nsresult> { 781 self.wait_for_ready()?; 782 let mut storage = self.data.lock().unwrap(); 783 storage.clear()?; 784 Ok(()) 785 } 786 787 xpcom_method!(is_ready => IsReady() -> bool); 788 fn is_ready(&self) -> Result<bool, nsresult> { 789 let ready = self.ready.0.lock().unwrap(); 790 Ok(*ready) 791 } 792 793 xpcom_method!(get_all => GetAll() -> ThinVec<Option<RefPtr<nsIDataStorageItem>>>); 794 fn get_all(&self) -> Result<ThinVec<Option<RefPtr<nsIDataStorageItem>>>, nsresult> { 795 self.wait_for_ready()?; 796 let storage = self.data.lock().unwrap(); 797 let mut items = ThinVec::new(); 798 let add_item = |entry: &Entry, data_type: DataType| { 799 let item = DataStorageItem::allocate(InitDataStorageItem { 800 key: entry.key.clone().into(), 801 value: entry.value.clone().into(), 802 type_: data_type.into(), 803 }); 804 items.push(Some(RefPtr::new(item.coerce()))); 805 }; 806 storage.for_each(add_item); 807 Ok(items) 808 } 809 810 fn indicate_ready(&self) -> Result<(), nsresult> { 811 let (ready_mutex, condvar) = &self.ready; 812 let mut ready = ready_mutex.lock().unwrap(); 813 *ready = true; 814 condvar.notify_all(); 815 Ok(()) 816 } 817 818 fn wait_for_ready(&self) -> Result<(), nsresult> { 819 let (ready_mutex, condvar) = &self.ready; 820 let mut ready = ready_mutex.lock().unwrap(); 821 while !*ready { 822 ready = condvar.wait(ready).unwrap(); 823 } 824 Ok(()) 825 } 826 827 fn initialize(&self) -> Result<(), nsresult> { 828 let mut storage = self.data.lock().unwrap(); 829 // If this fails, the implementation is "ready", but it probably won't 830 // store any data persistently. This is expected in cases where there 831 // is no profile directory. 832 let _ = storage.initialize(); 833 self.indicate_ready() 834 } 835 836 xpcom_method!(collect_reports => CollectReports(callback: *const nsIHandleReportCallback, data: *const nsISupports, anonymize: bool)); 837 fn collect_reports( 838 &self, 839 callback: &nsIHandleReportCallback, 840 data: Option<&nsISupports>, 841 _anonymize: bool, 842 ) -> Result<(), nsresult> { 843 let storage = self.data.lock().unwrap(); 844 let mut ops = MallocSizeOfOps::new(self.size_of_op, Some(self.enclosing_size_of_op)); 845 storage.collect_reports(&mut ops, callback, data) 846 } 847 848 xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: *const c_char, _data: *const u16)); 849 unsafe fn observe( 850 &self, 851 _subject: Option<&nsISupports>, 852 topic: *const c_char, 853 _data: *const u16, 854 ) -> Result<(), nsresult> { 855 let mut storage = self.data.lock().unwrap(); 856 let topic = CStr::from_ptr(topic); 857 // Observe shutdown - prevent any further writes. 858 // The backing file is in the profile directory, so stop writing when 859 // that goes away. 860 // "xpcom-shutdown-threads" is a backstop for situations where the 861 // "profile-before-change" notification is not emitted. 862 if topic == cstr!("profile-before-change") || topic == cstr!("xpcom-shutdown-threads") { 863 storage.drop_write_queue(); 864 } else if topic == cstr!("last-pb-context-exited") { 865 storage.clear_private_data(); 866 } 867 Ok(()) 868 } 869 } 870 871 /// Given some entries, the path of the backing file, and metadata about Entry 872 /// length, writes an Entry to the backing file in the appropriate slot. 873 /// Creates the backing file if it does not exist. 874 fn write_entries( 875 entries: Vec<Entry>, 876 backing_path: PathBuf, 877 value_length: usize, 878 slot_length: usize, 879 ) -> Result<(), std::io::Error> { 880 let mut backing_file = OpenOptions::new() 881 .write(true) 882 .create(true) 883 .open(backing_path)?; 884 let Some(max_slot_index) = entries.iter().map(|entry| entry.slot_index).max() else { 885 return Ok(()); // can only happen if entries is empty 886 }; 887 let necessary_len = ((max_slot_index + 1) * slot_length) as u64; 888 if backing_file.metadata()?.len() < necessary_len { 889 backing_file.set_len(necessary_len)?; 890 } 891 let mut buf = vec![0u8; slot_length]; 892 for entry in entries { 893 let mut buf_writer = buf.as_mut_slice(); 894 buf_writer.write_u16::<BigEndian>(0)?; // set checksum to 0 for now 895 let mut checksum = entry.score; 896 buf_writer.write_u16::<BigEndian>(entry.score)?; 897 checksum ^= entry.last_accessed; 898 buf_writer.write_u16::<BigEndian>(entry.last_accessed)?; 899 for mut chunk in entry.key.chunks(2) { 900 if chunk.len() == 1 { 901 checksum ^= (chunk[0] as u16) << 8; 902 } else { 903 checksum ^= chunk.read_u16::<BigEndian>()?; 904 } 905 } 906 if entry.key.len() > KEY_LENGTH { 907 continue; 908 } 909 buf_writer.write_all(&entry.key)?; 910 let (key_remainder, mut buf_writer) = buf_writer.split_at_mut(KEY_LENGTH - entry.key.len()); 911 key_remainder.fill(0); 912 for mut chunk in entry.value.chunks(2) { 913 if chunk.len() == 1 { 914 checksum ^= (chunk[0] as u16) << 8; 915 } else { 916 checksum ^= chunk.read_u16::<BigEndian>()?; 917 } 918 } 919 if entry.value.len() > value_length { 920 continue; 921 } 922 buf_writer.write_all(&entry.value)?; 923 buf_writer.fill(0); 924 925 backing_file.seek(SeekFrom::Start((entry.slot_index * slot_length) as u64))?; 926 backing_file.write_all(&buf)?; 927 backing_file.flush()?; 928 backing_file.seek(SeekFrom::Start((entry.slot_index * slot_length) as u64))?; 929 backing_file.write_u16::<BigEndian>(checksum)?; 930 } 931 Ok(()) 932 } 933 934 /// Uses the xpcom directory service to try to obtain the profile directory. 935 fn get_profile_path() -> Result<PathBuf, nsresult> { 936 let directory_service: RefPtr<nsIProperties> = 937 xpcom::components::Directory::service().map_err(|_| NS_ERROR_FAILURE)?; 938 let mut profile_dir = xpcom::GetterAddrefs::<nsIFile>::new(); 939 unsafe { 940 directory_service 941 .Get( 942 cstr!("ProfD").as_ptr(), 943 &nsIFile::IID, 944 profile_dir.void_ptr(), 945 ) 946 .to_result()?; 947 } 948 let profile_dir = profile_dir.refptr().ok_or(NS_ERROR_FAILURE)?; 949 let mut profile_path = nsString::new(); 950 unsafe { 951 (*profile_dir).GetPath(&mut *profile_path).to_result()?; 952 } 953 let profile_path = String::from_utf16(profile_path.as_ref()).map_err(|_| NS_ERROR_FAILURE)?; 954 Ok(PathBuf::from(profile_path)) 955 } 956 957 fn make_data_storage_internal( 958 basename: &str, 959 value_length: usize, 960 size_of_op: VoidPtrToSizeFn, 961 enclosing_size_of_op: VoidPtrToSizeFn, 962 ) -> Result<RefPtr<nsIDataStorage>, nsresult> { 963 let maybe_profile_path = get_profile_path().ok(); 964 let data_storage = DataStorage::allocate(InitDataStorage { 965 ready: (Mutex::new(false), Condvar::new()), 966 data: Mutex::new(DataStorageInner::new( 967 basename.to_string(), 968 value_length, 969 maybe_profile_path, 970 )?), 971 size_of_op, 972 enclosing_size_of_op, 973 }); 974 // Initialize the DataStorage on a background thread. 975 let data_storage_for_background_initialization = data_storage.clone(); 976 RunnableBuilder::new("data_storage::initialize", move || { 977 let _ = data_storage_for_background_initialization.initialize(); 978 }) 979 .may_block(true) 980 .dispatch_background_task()?; 981 982 // Observe shutdown and when the last private browsing context exits. 983 if let Ok(observer_service) = xpcom::components::Observer::service::<nsIObserverService>() { 984 unsafe { 985 observer_service 986 .AddObserver( 987 data_storage.coerce(), 988 cstr!("profile-before-change").as_ptr(), 989 false, 990 ) 991 .to_result()?; 992 observer_service 993 .AddObserver( 994 data_storage.coerce(), 995 cstr!("xpcom-shutdown-threads").as_ptr(), 996 false, 997 ) 998 .to_result()?; 999 observer_service 1000 .AddObserver( 1001 data_storage.coerce(), 1002 cstr!("last-pb-context-exited").as_ptr(), 1003 false, 1004 ) 1005 .to_result()?; 1006 } 1007 } 1008 1009 // Register the DataStorage as a memory reporter. 1010 if let Some(memory_reporter_manager) = xpcom::get_service::<nsIMemoryReporterManager>(cstr!( 1011 "@mozilla.org/memory-reporter-manager;1" 1012 )) { 1013 unsafe { 1014 memory_reporter_manager 1015 .RegisterStrongReporter(data_storage.coerce()) 1016 .to_result()?; 1017 } 1018 } 1019 1020 Ok(RefPtr::new(data_storage.coerce())) 1021 } 1022 1023 #[no_mangle] 1024 pub unsafe extern "C" fn make_data_storage( 1025 basename: *const nsAString, 1026 value_length: usize, 1027 size_of_op: VoidPtrToSizeFn, 1028 enclosing_size_of_op: VoidPtrToSizeFn, 1029 result: *mut *const xpcom::interfaces::nsIDataStorage, 1030 ) -> nsresult { 1031 if basename.is_null() || result.is_null() { 1032 return NS_ERROR_INVALID_ARG; 1033 } 1034 let basename = &*basename; 1035 let basename = basename.to_string(); 1036 match make_data_storage_internal(&basename, value_length, size_of_op, enclosing_size_of_op) { 1037 Ok(val) => val.forget(&mut *result), 1038 Err(e) => return e, 1039 } 1040 NS_OK 1041 }