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/. */
5 extern crate byteorder;
8 extern crate firefox_on_glean;
12 extern crate malloc_size_of_derive;
13 extern crate moz_task;
15 extern crate thin_vec;
16 extern crate wr_malloc_size_of;
20 use wr_malloc_size_of as malloc_size_of;
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};
27 nsresult, NS_ERROR_FAILURE, NS_ERROR_ILLEGAL_INPUT, NS_ERROR_INVALID_ARG,
28 NS_ERROR_NOT_AVAILABLE, NS_OK,
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,
36 use xpcom::{xpcom_method, RefPtr, XpCom};
38 use std::collections::HashMap;
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};
47 /// Helper type for turning the nsIDataStorage::DataType "enum" into a rust
49 #[derive(Copy, Clone, Eq, PartialEq)]
55 impl From<u8> for DataType {
56 fn from(value: u8) -> Self {
58 nsIDataStorage::Persistent => DataType::Persistent,
59 nsIDataStorage::Private => DataType::Private,
60 _ => panic!("invalid nsIDataStorage::DataType"),
65 impl From<DataType> for u8 {
66 fn from(value: DataType) -> Self {
68 DataType::Persistent => nsIDataStorage::Persistent,
69 DataType::Private => nsIDataStorage::Private,
74 /// Returns the current day in days since the unix epoch, to a maximum of
76 fn now_in_days() -> u16 {
77 const SECONDS_PER_DAY: u64 = 60 * 60 * 24;
78 let now = SystemTime::now()
79 .duration_since(UNIX_EPOCH)
80 .unwrap_or(Duration::ZERO);
81 (now.as_secs() / SECONDS_PER_DAY)
86 /// An entry in some DataStorageTable.
87 #[derive(Clone, MallocSizeOf)]
89 /// The number of unique days this Entry has been accessed on.
91 /// The number of days since the unix epoch this Entry was last accessed.
97 /// The slot index of this Entry.
102 /// Constructs an Entry given a line of text from the old DataStorage format.
103 fn from_old_line(line: &str, slot_index: usize, value_length: usize) -> Result<Self, nsresult> {
104 // the old format is <key>\t<score>\t<last accessed>\t<value>
105 let parts: Vec<&str> = line.split('\t').collect();
106 if parts.len() != 4 {
107 return Err(NS_ERROR_ILLEGAL_INPUT);
111 .map_err(|_| NS_ERROR_ILLEGAL_INPUT)?;
112 let last_accessed = parts[2]
114 .map_err(|_| NS_ERROR_ILLEGAL_INPUT)?;
115 let key = Vec::from(parts[0]);
116 if key.len() > KEY_LENGTH {
117 return Err(NS_ERROR_ILLEGAL_INPUT);
119 let value = Vec::from(parts[3]);
120 if value.len() > value_length {
121 return Err(NS_ERROR_ILLEGAL_INPUT);
132 /// Constructs an Entry given the parsed parts from the current format.
149 /// Constructs a new Entry given a key, value, and index.
150 fn new(key: Vec<u8>, value: Vec<u8>, slot_index: usize) -> Self {
153 last_accessed: now_in_days(),
160 /// Constructs a new, empty `Entry` with the given index. Useful for clearing
162 fn new_empty(slot_index: usize) -> Self {
172 /// Returns whether or not this is an empty `Entry` (an empty `Entry` has
173 /// been created with `Entry::new_empty()` or cleared with
174 /// `Entry::clear()`, has 0 `score` and `last_accessed`, and has an empty
175 /// `key` and `value`.
176 fn is_empty(&self) -> bool {
177 self.score == 0 && self.last_accessed == 0 && self.key.is_empty() && self.value.is_empty()
180 /// If this Entry was last accessed on a day different from today,
181 /// increments its score (as well as its last accessed day).
182 /// Returns `true` if the score did in fact change, and `false` otherwise.
183 fn update_score(&mut self) -> bool {
184 let now_in_days = now_in_days();
185 if self.last_accessed != now_in_days {
186 self.last_accessed = now_in_days;
194 /// Clear the data stored in this Entry. Useful for clearing a single slot
196 fn clear(&mut self) {
197 // Note: it's important that this preserves slot_index - the writer
198 // needs it to know where to write out the zeroed Entry
199 *self = Self::new_empty(self.slot_index);
203 /// Strips all trailing 0 bytes from the end of the given vec.
204 /// Useful for converting 0-padded keys and values to their original, non-padded
206 fn strip_zeroes(vec: &mut Vec<u8>) {
207 let mut length = vec.len();
208 while length > 0 && vec[length - 1] == 0 {
211 vec.truncate(length);
214 /// Given a slice of entries, returns a Vec<Entry> consisting of each Entry
215 /// with score equal to the minimum score among all entries.
216 fn get_entries_with_minimum_score(entries: &[Entry]) -> Vec<&Entry> {
217 let mut min_score = u16::MAX;
218 let mut min_score_entries = Vec::new();
219 for entry in entries.iter() {
220 if entry.score < min_score {
221 min_score = entry.score;
222 min_score_entries.clear();
224 if entry.score == min_score {
225 min_score_entries.push(entry);
231 const MAX_SLOTS: usize = 2048;
232 const KEY_LENGTH: usize = 256;
234 /// Helper type to map between an entry key and the slot it is stored on.
235 type DataStorageTable = HashMap<Vec<u8>, usize>;
237 /// The main structure of this implementation. Keeps track of persistent
238 /// and private entries.
239 #[derive(MallocSizeOf)]
240 struct DataStorageInner {
241 /// The key to slot index mapping table for persistent data.
242 persistent_table: DataStorageTable,
243 /// The persistent entries that are stored on disk.
244 persistent_slots: Vec<Entry>,
245 /// The key to slot index mapping table for private, temporary data.
246 private_table: DataStorageTable,
247 /// The private, temporary entries that are not stored on disk.
248 /// This data is cleared upon observing "last-pb-context-exited", and is
249 /// forgotten when the program shuts down.
250 private_slots: Vec<Entry>,
251 /// The name of the table (e.g. "SiteSecurityServiceState").
253 /// The maximum permitted length of values.
255 /// A PathBuf holding the location of the profile directory, if available.
256 maybe_profile_path: Option<PathBuf>,
257 /// A serial event target to post tasks to, to write out changed persistent
258 /// data in the background.
259 #[ignore_malloc_size_of = "not implemented for nsISerialEventTarget"]
260 write_queue: Option<RefPtr<nsISerialEventTarget>>,
263 impl DataStorageInner {
267 maybe_profile_path: Option<PathBuf>,
268 ) -> Result<Self, nsresult> {
269 Ok(DataStorageInner {
270 persistent_table: DataStorageTable::new(),
271 persistent_slots: Vec::new(),
272 private_table: DataStorageTable::new(),
273 private_slots: Vec::new(),
277 write_queue: Some(create_background_task_queue(cstr!("data_storage"))?),
281 /// Initializes the DataStorageInner. If the profile directory is not
282 /// present, does nothing. If the backing file is available, processes it.
283 /// Otherwise, if the old backing file is available, migrates it to the
285 fn initialize(&mut self) -> Result<(), nsresult> {
286 let Some(profile_path) = self.maybe_profile_path.as_ref() else {
289 let mut backing_path = profile_path.clone();
290 backing_path.push(format!("{}.bin", &self.name));
291 let mut old_backing_path = profile_path.clone();
292 old_backing_path.push(format!("{}.txt", &self.name));
293 if backing_path.exists() {
294 self.read(backing_path)
295 } else if old_backing_path.exists() {
296 self.read_old_format(old_backing_path)
302 /// Reads the backing file, processing each slot.
303 fn read(&mut self, path: PathBuf) -> Result<(), nsresult> {
304 let f = OpenOptions::new()
309 .map_err(|_| NS_ERROR_FAILURE)?;
310 let mut backing_file = BufReader::new(f);
311 let mut slots = Vec::new();
312 // First read each entry into the persistent slots list.
313 while slots.len() < MAX_SLOTS {
314 if let Some(entry) = self.process_slot(&mut backing_file, slots.len())? {
320 self.persistent_slots = slots;
321 // Then build the key -> slot index lookup table.
322 self.persistent_table = self
325 .filter(|slot| !slot.is_empty())
326 .map(|slot| (slot.key.clone(), slot.slot_index))
328 let num_entries = self.persistent_table.len() as i64;
329 match self.name.as_str() {
330 "AlternateServices" => data_storage::alternate_services.set(num_entries),
331 "ClientAuthRememberList" => data_storage::client_auth_remember_list.set(num_entries),
332 "SiteSecurityServiceState" => {
333 data_storage::site_security_service_state.set(num_entries)
335 _ => panic!("unknown nsIDataStorageManager::DataStorage"),
340 /// Processes a slot (via a reader) by reading its metadata, key, and
341 /// value. If the checksum fails or if the score or last accessed fields
342 /// are 0, this is an empty slot. Otherwise, un-0-pads the key and value,
343 /// creates a new Entry, and puts it in the persistent table.
344 fn process_slot<R: Read>(
348 ) -> Result<Option<Entry>, nsresult> {
349 // Format is [checksum][score][last accessed][key][value], where
350 // checksum is 2 bytes big-endian, score and last accessed are 2 bytes
351 // big-endian, key is KEY_LENGTH bytes (currently 256), and value is
352 // self.value_length bytes (1024 for most instances, but 24 for
353 // SiteSecurityServiceState - see DataStorageManager::Get).
354 let mut checksum = match reader.read_u16::<BigEndian>() {
355 Ok(checksum) => checksum,
356 // The file may be shorter than expected due to unoccupied slots.
357 // Every slot after the last read slot is unoccupied.
358 Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
359 Err(_) => return Err(NS_ERROR_FAILURE),
362 .read_u16::<BigEndian>()
363 .map_err(|_| NS_ERROR_FAILURE)?;
365 let last_accessed = reader
366 .read_u16::<BigEndian>()
367 .map_err(|_| NS_ERROR_FAILURE)?;
368 checksum ^= last_accessed;
370 let mut key = vec![0u8; KEY_LENGTH];
371 reader.read_exact(&mut key).map_err(|_| NS_ERROR_FAILURE)?;
372 for mut chunk in key.chunks(2) {
374 .read_u16::<BigEndian>()
375 .map_err(|_| NS_ERROR_FAILURE)?;
377 strip_zeroes(&mut key);
378 let mut value = vec![0u8; self.value_length];
380 .read_exact(&mut value)
381 .map_err(|_| NS_ERROR_FAILURE)?;
382 for mut chunk in value.chunks(2) {
384 .read_u16::<BigEndian>()
385 .map_err(|_| NS_ERROR_FAILURE)?;
387 strip_zeroes(&mut value);
389 // If this slot is incomplete, corrupted, or empty, treat it as empty.
390 if checksum != 0 || score == 0 || last_accessed == 0 {
391 // This slot is empty.
392 return Ok(Some(Entry::new_empty(slot_index)));
395 Ok(Some(Entry::from_slot(
404 /// Migrates from the old format to the current format.
405 fn read_old_format(&mut self, path: PathBuf) -> Result<(), nsresult> {
406 let file = File::open(path).map_err(|_| NS_ERROR_FAILURE)?;
407 let reader = BufReader::new(file);
408 // First read each line in the old file into the persistent slots list.
409 // The old format was limited to 1024 lines, so only expect that many.
410 for line in reader.lines().flatten().take(1024) {
411 match Entry::from_old_line(&line, self.persistent_slots.len(), self.value_length) {
413 if self.persistent_slots.len() >= MAX_SLOTS {
414 warn!("too many lines in old DataStorage format");
417 if !entry.is_empty() {
418 self.persistent_slots.push(entry);
420 warn!("empty entry in old DataStorage format?");
424 warn!("failed to migrate a line from old DataStorage format");
428 // Then build the key -> slot index lookup table.
429 self.persistent_table = self
432 .filter(|slot| !slot.is_empty())
433 .map(|slot| (slot.key.clone(), slot.slot_index))
435 // Finally, write out the migrated data to the new backing file.
436 self.async_write_entries(self.persistent_slots.clone())?;
437 let num_entries = self.persistent_table.len() as i64;
438 match self.name.as_str() {
439 "AlternateServices" => data_storage::alternate_services.set(num_entries),
440 "ClientAuthRememberList" => data_storage::client_auth_remember_list.set(num_entries),
441 "SiteSecurityServiceState" => {
442 data_storage::site_security_service_state.set(num_entries)
444 _ => panic!("unknown nsIDataStorageManager::DataStorage"),
449 /// Given an `Entry` and `DataType`, this function updates the internal
450 /// list of slots and the mapping from keys to slot indices. If the slot
451 /// assigned to the `Entry` is already occupied, the existing `Entry` is
453 /// After updating internal state, if the type of this entry is persistent,
454 /// this function dispatches an event to asynchronously write the data out.
455 fn put_internal(&mut self, entry: Entry, type_: DataType) -> Result<(), nsresult> {
456 let (table, slots) = self.get_table_and_slots_for_type_mut(type_);
457 if entry.slot_index < slots.len() {
458 let entry_to_evict = &slots[entry.slot_index];
459 if !entry_to_evict.is_empty() {
460 table.remove(&entry_to_evict.key);
463 let _ = table.insert(entry.key.clone(), entry.slot_index);
464 if entry.slot_index < slots.len() {
465 slots[entry.slot_index] = entry.clone();
466 } else if entry.slot_index == slots.len() {
467 slots.push(entry.clone());
470 "put_internal should not have been given an Entry with slot_index > slots.len()"
473 if type_ == DataType::Persistent {
474 self.async_write_entry(entry)?;
479 /// Returns the total length of each slot on disk.
480 fn slot_length(&self) -> usize {
481 // Checksum is 2 bytes, and score and last accessed are 2 bytes each.
482 2 + 2 + 2 + KEY_LENGTH + self.value_length
485 /// Gets the next free slot index, or determines a slot to evict (but
486 /// doesn't actually perform the eviction - the caller must do that).
487 fn get_free_slot_or_slot_to_evict(&self, type_: DataType) -> usize {
488 let (_, slots) = self.get_table_and_slots_for_type(type_);
489 let maybe_unoccupied_slot = slots
492 .find(|(_, maybe_empty_entry)| maybe_empty_entry.is_empty());
493 if let Some((unoccupied_slot, _)) = maybe_unoccupied_slot {
494 return unoccupied_slot;
496 // If `slots` isn't full, the next free slot index is one more than the
497 // current last index.
498 if slots.len() < MAX_SLOTS {
501 // If there isn't an unoccupied slot, evict the entry with the lowest score.
502 let min_score_entries = get_entries_with_minimum_score(&slots);
503 // `min_score_entry` is the oldest Entry with the minimum score.
504 // There must be at least one such Entry, so unwrap it or abort.
505 let min_score_entry = min_score_entries
507 .min_by_key(|e| e.last_accessed)
509 min_score_entry.slot_index
512 /// Helper function to get a handle on the slot list and key to slot index
513 /// mapping for the given `DataType`.
514 fn get_table_and_slots_for_type(&self, type_: DataType) -> (&DataStorageTable, &[Entry]) {
516 DataType::Persistent => (&self.persistent_table, &self.persistent_slots),
517 DataType::Private => (&self.private_table, &self.private_slots),
521 /// Helper function to get a mutable handle on the slot list and key to
522 /// slot index mapping for the given `DataType`.
523 fn get_table_and_slots_for_type_mut(
526 ) -> (&mut DataStorageTable, &mut Vec<Entry>) {
528 DataType::Persistent => (&mut self.persistent_table, &mut self.persistent_slots),
529 DataType::Private => (&mut self.private_table, &mut self.private_slots),
533 /// Helper function to look up an `Entry` by its key and type.
534 fn get_entry(&mut self, key: &[u8], type_: DataType) -> Option<&mut Entry> {
535 let (table, slots) = self.get_table_and_slots_for_type_mut(type_);
536 let slot_index = table.get(key)?;
537 Some(&mut slots[*slot_index])
540 /// Gets a value by key, if available. Updates the Entry's score when appropriate.
541 fn get(&mut self, key: &[u8], type_: DataType) -> Result<Vec<u8>, nsresult> {
542 let Some(entry) = self.get_entry(key, type_) else {
543 return Err(NS_ERROR_NOT_AVAILABLE);
545 let value = entry.value.clone();
546 if entry.update_score() && type_ == DataType::Persistent {
547 let entry = entry.clone();
548 self.async_write_entry(entry)?;
553 /// Inserts or updates a value by key. Updates the Entry's score if applicable.
554 fn put(&mut self, key: Vec<u8>, value: Vec<u8>, type_: DataType) -> Result<(), nsresult> {
555 if key.len() > KEY_LENGTH || value.len() > self.value_length {
556 return Err(NS_ERROR_INVALID_ARG);
558 if let Some(existing_entry) = self.get_entry(&key, type_) {
559 let data_changed = existing_entry.value != value;
561 existing_entry.value = value;
563 if (existing_entry.update_score() || data_changed) && type_ == DataType::Persistent {
564 let entry = existing_entry.clone();
565 self.async_write_entry(entry)?;
569 let slot_index = self.get_free_slot_or_slot_to_evict(type_);
570 let entry = Entry::new(key.clone(), value, slot_index);
571 self.put_internal(entry, type_)
575 /// Removes an Entry by key, if it is present.
576 fn remove(&mut self, key: &Vec<u8>, type_: DataType) -> Result<(), nsresult> {
577 let (table, slots) = self.get_table_and_slots_for_type_mut(type_);
578 let Some(slot_index) = table.remove(key) else {
581 let entry = &mut slots[slot_index];
583 if type_ == DataType::Persistent {
584 let entry = entry.clone();
585 self.async_write_entry(entry)?;
590 /// Clears all tables and the backing persistent file.
591 fn clear(&mut self) -> Result<(), nsresult> {
592 self.persistent_table.clear();
593 self.private_table.clear();
594 self.persistent_slots.clear();
595 self.private_slots.clear();
596 let Some(profile_path) = self.maybe_profile_path.clone() else {
599 let Some(write_queue) = self.write_queue.clone() else {
602 let name = self.name.clone();
603 RunnableBuilder::new("data_storage::remove_backing_files", move || {
604 let old_backing_path = profile_path.join(format!("{name}.txt"));
605 let _ = std::fs::remove_file(old_backing_path);
606 let backing_path = profile_path.join(format!("{name}.bin"));
607 let _ = std::fs::remove_file(backing_path);
610 .dispatch(write_queue.coerce())
613 /// Clears only data in the private table.
614 fn clear_private_data(&mut self) {
615 self.private_table.clear();
616 self.private_slots.clear();
619 /// Asynchronously writes the given entry on the background serial event
621 fn async_write_entry(&self, entry: Entry) -> Result<(), nsresult> {
622 self.async_write_entries(vec![entry])
625 /// Asynchronously writes the given entries on the background serial event
627 fn async_write_entries(&self, entries: Vec<Entry>) -> Result<(), nsresult> {
628 let Some(mut backing_path) = self.maybe_profile_path.clone() else {
631 let Some(write_queue) = self.write_queue.clone() else {
634 backing_path.push(format!("{}.bin", &self.name));
635 let value_length = self.value_length;
636 let slot_length = self.slot_length();
637 RunnableBuilder::new("data_storage::write_entries", move || {
638 let _ = write_entries(entries, backing_path, value_length, slot_length);
641 .dispatch(write_queue.coerce())
644 /// Drop the write queue to prevent further writes.
645 fn drop_write_queue(&mut self) {
646 let _ = self.write_queue.take();
649 /// Takes a callback that is run for each entry in each table.
650 fn for_each<F>(&self, mut f: F)
652 F: FnMut(&Entry, DataType),
657 .filter(|entry| !entry.is_empty())
659 f(entry, DataType::Persistent);
661 for entry in self.private_slots.iter().filter(|entry| !entry.is_empty()) {
662 f(entry, DataType::Private);
666 /// Collects the memory used by this DataStorageInner.
669 ops: &mut MallocSizeOfOps,
670 callback: &nsIHandleReportCallback,
671 data: Option<&nsISupports>,
672 ) -> Result<(), nsresult> {
673 let size = self.size_of(ops);
674 let data = match data {
675 Some(data) => data as *const nsISupports,
676 None => std::ptr::null() as *const nsISupports,
681 &nsCStr::new() as &nsACString,
682 &nsCString::from(format!("explicit/data-storage/{}", self.name)) as &nsACString,
683 nsIMemoryReporter::KIND_HEAP,
684 nsIMemoryReporter::UNITS_BYTES,
686 &nsCStr::from("Memory used by PSM data storage cache") as &nsACString,
694 #[xpcom(implement(nsIDataStorageItem), atomic)]
695 struct DataStorageItem {
701 impl DataStorageItem {
702 xpcom_method!(get_key => GetKey() -> nsACString);
703 fn get_key(&self) -> Result<nsCString, nsresult> {
707 xpcom_method!(get_value => GetValue() -> nsACString);
708 fn get_value(&self) -> Result<nsCString, nsresult> {
709 Ok(self.value.clone())
712 xpcom_method!(get_type => GetType() -> u8);
713 fn get_type(&self) -> Result<u8, nsresult> {
718 type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
720 /// Helper struct that coordinates xpcom access to the DataStorageInner that
721 /// actually holds the data.
722 #[xpcom(implement(nsIDataStorage, nsIMemoryReporter, nsIObserver), atomic)]
724 ready: (Mutex<bool>, Condvar),
725 data: Mutex<DataStorageInner>,
726 size_of_op: VoidPtrToSizeFn,
727 enclosing_size_of_op: VoidPtrToSizeFn,
731 xpcom_method!(get => Get(key: *const nsACString, type_: u8) -> nsACString);
732 fn get(&self, key: &nsACString, type_: u8) -> Result<nsCString, nsresult> {
733 self.wait_for_ready()?;
734 let mut storage = self.data.lock().unwrap();
736 .get(&Vec::from(key.as_ref()), type_.into())
737 .map(|data| nsCString::from(data))
740 xpcom_method!(put => Put(key: *const nsACString, value: *const nsACString, type_: u8));
741 fn put(&self, key: &nsACString, value: &nsACString, type_: u8) -> Result<(), nsresult> {
742 self.wait_for_ready()?;
743 let mut storage = self.data.lock().unwrap();
745 Vec::from(key.as_ref()),
746 Vec::from(value.as_ref()),
751 xpcom_method!(remove => Remove(key: *const nsACString, type_: u8));
752 fn remove(&self, key: &nsACString, type_: u8) -> Result<(), nsresult> {
753 self.wait_for_ready()?;
754 let mut storage = self.data.lock().unwrap();
755 storage.remove(&Vec::from(key.as_ref()), type_.into())?;
759 xpcom_method!(clear => Clear());
760 fn clear(&self) -> Result<(), nsresult> {
761 self.wait_for_ready()?;
762 let mut storage = self.data.lock().unwrap();
767 xpcom_method!(is_ready => IsReady() -> bool);
768 fn is_ready(&self) -> Result<bool, nsresult> {
769 let ready = self.ready.0.lock().unwrap();
773 xpcom_method!(get_all => GetAll() -> ThinVec<Option<RefPtr<nsIDataStorageItem>>>);
774 fn get_all(&self) -> Result<ThinVec<Option<RefPtr<nsIDataStorageItem>>>, nsresult> {
775 self.wait_for_ready()?;
776 let storage = self.data.lock().unwrap();
777 let mut items = ThinVec::new();
778 let add_item = |entry: &Entry, data_type: DataType| {
779 let item = DataStorageItem::allocate(InitDataStorageItem {
780 key: entry.key.clone().into(),
781 value: entry.value.clone().into(),
782 type_: data_type.into(),
784 items.push(Some(RefPtr::new(item.coerce())));
786 storage.for_each(add_item);
790 fn indicate_ready(&self) -> Result<(), nsresult> {
791 let (ready_mutex, condvar) = &self.ready;
792 let mut ready = ready_mutex.lock().unwrap();
794 condvar.notify_all();
798 fn wait_for_ready(&self) -> Result<(), nsresult> {
799 let (ready_mutex, condvar) = &self.ready;
800 let mut ready = ready_mutex.lock().unwrap();
802 ready = condvar.wait(ready).unwrap();
807 fn initialize(&self) -> Result<(), nsresult> {
808 let mut storage = self.data.lock().unwrap();
809 // If this fails, the implementation is "ready", but it probably won't
810 // store any data persistently. This is expected in cases where there
811 // is no profile directory.
812 let _ = storage.initialize();
813 self.indicate_ready()
816 xpcom_method!(collect_reports => CollectReports(callback: *const nsIHandleReportCallback, data: *const nsISupports, anonymize: bool));
819 callback: &nsIHandleReportCallback,
820 data: Option<&nsISupports>,
822 ) -> Result<(), nsresult> {
823 let storage = self.data.lock().unwrap();
824 let mut ops = MallocSizeOfOps::new(self.size_of_op, Some(self.enclosing_size_of_op));
825 storage.collect_reports(&mut ops, callback, data)
828 xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: *const c_char, _data: *const u16));
831 _subject: Option<&nsISupports>,
832 topic: *const c_char,
834 ) -> Result<(), nsresult> {
835 let mut storage = self.data.lock().unwrap();
836 let topic = CStr::from_ptr(topic);
837 // Observe shutdown - prevent any further writes.
838 // The backing file is in the profile directory, so stop writing when
840 // "xpcom-shutdown-threads" is a backstop for situations where the
841 // "profile-before-change" notification is not emitted.
842 if topic == cstr!("profile-before-change") || topic == cstr!("xpcom-shutdown-threads") {
843 storage.drop_write_queue();
844 } else if topic == cstr!("last-pb-context-exited") {
845 storage.clear_private_data();
851 /// Given some entries, the path of the backing file, and metadata about Entry
852 /// length, writes an Entry to the backing file in the appropriate slot.
853 /// Creates the backing file if it does not exist.
856 backing_path: PathBuf,
859 ) -> Result<(), std::io::Error> {
860 let mut backing_file = OpenOptions::new()
863 .open(backing_path)?;
864 let Some(max_slot_index) = entries.iter().map(|entry| entry.slot_index).max() else {
865 return Ok(()); // can only happen if entries is empty
867 let necessary_len = ((max_slot_index + 1) * slot_length) as u64;
868 if backing_file.metadata()?.len() < necessary_len {
869 backing_file.set_len(necessary_len)?;
871 let mut buf = vec![0u8; slot_length];
872 for entry in entries {
873 let mut buf_writer = buf.as_mut_slice();
874 buf_writer.write_u16::<BigEndian>(0)?; // set checksum to 0 for now
875 let mut checksum = entry.score;
876 buf_writer.write_u16::<BigEndian>(entry.score)?;
877 checksum ^= entry.last_accessed;
878 buf_writer.write_u16::<BigEndian>(entry.last_accessed)?;
879 for mut chunk in entry.key.chunks(2) {
880 if chunk.len() == 1 {
881 checksum ^= (chunk[0] as u16) << 8;
883 checksum ^= chunk.read_u16::<BigEndian>()?;
886 if entry.key.len() > KEY_LENGTH {
889 buf_writer.write_all(&entry.key)?;
890 let (key_remainder, mut buf_writer) = buf_writer.split_at_mut(KEY_LENGTH - entry.key.len());
891 key_remainder.fill(0);
892 for mut chunk in entry.value.chunks(2) {
893 if chunk.len() == 1 {
894 checksum ^= (chunk[0] as u16) << 8;
896 checksum ^= chunk.read_u16::<BigEndian>()?;
899 if entry.value.len() > value_length {
902 buf_writer.write_all(&entry.value)?;
905 backing_file.seek(SeekFrom::Start((entry.slot_index * slot_length) as u64))?;
906 backing_file.write_all(&buf)?;
907 backing_file.flush()?;
908 backing_file.seek(SeekFrom::Start((entry.slot_index * slot_length) as u64))?;
909 backing_file.write_u16::<BigEndian>(checksum)?;
914 /// Uses the xpcom directory service to try to obtain the profile directory.
915 fn get_profile_path() -> Result<PathBuf, nsresult> {
916 let directory_service: RefPtr<nsIProperties> =
917 xpcom::components::Directory::service().map_err(|_| NS_ERROR_FAILURE)?;
918 let mut profile_dir = xpcom::GetterAddrefs::<nsIFile>::new();
922 cstr!("ProfD").as_ptr(),
924 profile_dir.void_ptr(),
928 let profile_dir = profile_dir.refptr().ok_or(NS_ERROR_FAILURE)?;
929 let mut profile_path = nsString::new();
931 (*profile_dir).GetPath(&mut *profile_path).to_result()?;
933 let profile_path = String::from_utf16(profile_path.as_ref()).map_err(|_| NS_ERROR_FAILURE)?;
934 Ok(PathBuf::from(profile_path))
937 fn make_data_storage_internal(
940 size_of_op: VoidPtrToSizeFn,
941 enclosing_size_of_op: VoidPtrToSizeFn,
942 ) -> Result<RefPtr<nsIDataStorage>, nsresult> {
943 let maybe_profile_path = get_profile_path().ok();
944 let data_storage = DataStorage::allocate(InitDataStorage {
945 ready: (Mutex::new(false), Condvar::new()),
946 data: Mutex::new(DataStorageInner::new(
947 basename.to_string(),
952 enclosing_size_of_op,
954 // Initialize the DataStorage on a background thread.
955 let data_storage_for_background_initialization = data_storage.clone();
956 RunnableBuilder::new("data_storage::initialize", move || {
957 let _ = data_storage_for_background_initialization.initialize();
960 .dispatch_background_task()?;
962 // Observe shutdown and when the last private browsing context exits.
963 if let Ok(observer_service) = xpcom::components::Observer::service::<nsIObserverService>() {
967 data_storage.coerce(),
968 cstr!("profile-before-change").as_ptr(),
974 data_storage.coerce(),
975 cstr!("xpcom-shutdown-threads").as_ptr(),
981 data_storage.coerce(),
982 cstr!("last-pb-context-exited").as_ptr(),
989 // Register the DataStorage as a memory reporter.
990 if let Some(memory_reporter_manager) = xpcom::get_service::<nsIMemoryReporterManager>(cstr!(
991 "@mozilla.org/memory-reporter-manager;1"
994 memory_reporter_manager
995 .RegisterStrongReporter(data_storage.coerce())
1000 Ok(RefPtr::new(data_storage.coerce()))
1004 pub unsafe extern "C" fn make_data_storage(
1005 basename: *const nsAString,
1006 value_length: usize,
1007 size_of_op: VoidPtrToSizeFn,
1008 enclosing_size_of_op: VoidPtrToSizeFn,
1009 result: *mut *const xpcom::interfaces::nsIDataStorage,
1011 if basename.is_null() || result.is_null() {
1012 return NS_ERROR_INVALID_ARG;
1014 let basename = &*basename;
1015 let basename = basename.to_string();
1016 match make_data_storage_internal(&basename, value_length, size_of_op, enclosing_size_of_op) {
1017 Ok(val) => val.forget(&mut *result),