no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / security / manager / ssl / data_storage / src / lib.rs
blob79963e4578883bde7e650eb47e97a555068970df
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;
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;
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};
26 use nserror::{
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;
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};
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,
55 impl From<u8> for DataType {
56     fn from(value: u8) -> Self {
57         match value {
58             nsIDataStorage::Persistent => DataType::Persistent,
59             nsIDataStorage::Private => DataType::Private,
60             _ => panic!("invalid nsIDataStorage::DataType"),
61         }
62     }
65 impl From<DataType> for u8 {
66     fn from(value: DataType) -> Self {
67         match value {
68             DataType::Persistent => nsIDataStorage::Persistent,
69             DataType::Private => nsIDataStorage::Private,
70         }
71     }
74 /// Returns the current day in days since the unix epoch, to a maximum of
75 /// u16::MAX days.
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)
82         .try_into()
83         .unwrap_or(u16::MAX)
86 /// An entry in some DataStorageTable.
87 #[derive(Clone, MallocSizeOf)]
88 struct Entry {
89     /// The number of unique days this Entry has been accessed on.
90     score: u16,
91     /// The number of days since the unix epoch this Entry was last accessed.
92     last_accessed: u16,
93     /// The key.
94     key: Vec<u8>,
95     /// The value.
96     value: Vec<u8>,
97     /// The slot index of this Entry.
98     slot_index: usize,
101 impl 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);
108         }
109         let score = parts[1]
110             .parse::<u16>()
111             .map_err(|_| NS_ERROR_ILLEGAL_INPUT)?;
112         let last_accessed = parts[2]
113             .parse::<u16>()
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);
118         }
119         let value = Vec::from(parts[3]);
120         if value.len() > value_length {
121             return Err(NS_ERROR_ILLEGAL_INPUT);
122         }
123         Ok(Entry {
124             score,
125             last_accessed,
126             key,
127             value,
128             slot_index,
129         })
130     }
132     /// Constructs an Entry given the parsed parts from the current format.
133     fn from_slot(
134         score: u16,
135         last_accessed: u16,
136         key: Vec<u8>,
137         value: Vec<u8>,
138         slot_index: usize,
139     ) -> Self {
140         Entry {
141             score,
142             last_accessed,
143             key,
144             value,
145             slot_index,
146         }
147     }
149     /// Constructs a new Entry given a key, value, and index.
150     fn new(key: Vec<u8>, value: Vec<u8>, slot_index: usize) -> Self {
151         Entry {
152             score: 1,
153             last_accessed: now_in_days(),
154             key,
155             value,
156             slot_index,
157         }
158     }
160     /// Constructs a new, empty `Entry` with the given index. Useful for clearing
161     /// slots on disk.
162     fn new_empty(slot_index: usize) -> Self {
163         Entry {
164             score: 0,
165             last_accessed: 0,
166             key: Vec::new(),
167             value: Vec::new(),
168             slot_index,
169         }
170     }
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()
178     }
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;
187             self.score += 1;
188             true
189         } else {
190             false
191         }
192     }
194     /// Clear the data stored in this Entry. Useful for clearing a single slot
195     /// on disk.
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);
200     }
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
205 /// state.
206 fn strip_zeroes(vec: &mut Vec<u8>) {
207     let mut length = vec.len();
208     while length > 0 && vec[length - 1] == 0 {
209         length -= 1;
210     }
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();
223         }
224         if entry.score == min_score {
225             min_score_entries.push(entry);
226         }
227     }
228     min_score_entries
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").
252     name: String,
253     /// The maximum permitted length of values.
254     value_length: usize,
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 {
264     fn new(
265         name: String,
266         value_length: usize,
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(),
274             name,
275             value_length,
276             maybe_profile_path,
277             write_queue: Some(create_background_task_queue(cstr!("data_storage"))?),
278         })
279     }
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
284     /// current format.
285     fn initialize(&mut self) -> Result<(), nsresult> {
286         let Some(profile_path) = self.maybe_profile_path.as_ref() else {
287             return Ok(());
288         };
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)
297         } else {
298             Ok(())
299         }
300     }
302     /// Reads the backing file, processing each slot.
303     fn read(&mut self, path: PathBuf) -> Result<(), nsresult> {
304         let f = OpenOptions::new()
305             .read(true)
306             .write(true)
307             .create(true)
308             .open(path)
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())? {
315                 slots.push(entry);
316             } else {
317                 break;
318             }
319         }
320         self.persistent_slots = slots;
321         // Then build the key -> slot index lookup table.
322         self.persistent_table = self
323             .persistent_slots
324             .iter()
325             .filter(|slot| !slot.is_empty())
326             .map(|slot| (slot.key.clone(), slot.slot_index))
327             .collect();
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)
334             }
335             _ => panic!("unknown nsIDataStorageManager::DataStorage"),
336         }
337         Ok(())
338     }
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>(
345         &mut self,
346         reader: &mut R,
347         slot_index: usize,
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),
360         };
361         let score = reader
362             .read_u16::<BigEndian>()
363             .map_err(|_| NS_ERROR_FAILURE)?;
364         checksum ^= score;
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) {
373             checksum ^= chunk
374                 .read_u16::<BigEndian>()
375                 .map_err(|_| NS_ERROR_FAILURE)?;
376         }
377         strip_zeroes(&mut key);
378         let mut value = vec![0u8; self.value_length];
379         reader
380             .read_exact(&mut value)
381             .map_err(|_| NS_ERROR_FAILURE)?;
382         for mut chunk in value.chunks(2) {
383             checksum ^= chunk
384                 .read_u16::<BigEndian>()
385                 .map_err(|_| NS_ERROR_FAILURE)?;
386         }
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)));
393         }
395         Ok(Some(Entry::from_slot(
396             score,
397             last_accessed,
398             key,
399             value,
400             slot_index,
401         )))
402     }
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) {
412                 Ok(entry) => {
413                     if self.persistent_slots.len() >= MAX_SLOTS {
414                         warn!("too many lines in old DataStorage format");
415                         break;
416                     }
417                     if !entry.is_empty() {
418                         self.persistent_slots.push(entry);
419                     } else {
420                         warn!("empty entry in old DataStorage format?");
421                     }
422                 }
423                 Err(_) => {
424                     warn!("failed to migrate a line from old DataStorage format");
425                 }
426             }
427         }
428         // Then build the key -> slot index lookup table.
429         self.persistent_table = self
430             .persistent_slots
431             .iter()
432             .filter(|slot| !slot.is_empty())
433             .map(|slot| (slot.key.clone(), slot.slot_index))
434             .collect();
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)
443             }
444             _ => panic!("unknown nsIDataStorageManager::DataStorage"),
445         }
446         Ok(())
447     }
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
452     /// evicted.
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);
461             }
462         }
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());
468         } else {
469             panic!(
470                 "put_internal should not have been given an Entry with slot_index > slots.len()"
471             );
472         }
473         if type_ == DataType::Persistent {
474             self.async_write_entry(entry)?;
475         }
476         Ok(())
477     }
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
483     }
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
490             .iter()
491             .enumerate()
492             .find(|(_, maybe_empty_entry)| maybe_empty_entry.is_empty());
493         if let Some((unoccupied_slot, _)) = maybe_unoccupied_slot {
494             return unoccupied_slot;
495         }
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 {
499             return slots.len();
500         }
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
506             .iter()
507             .min_by_key(|e| e.last_accessed)
508             .unwrap();
509         min_score_entry.slot_index
510     }
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]) {
515         match type_ {
516             DataType::Persistent => (&self.persistent_table, &self.persistent_slots),
517             DataType::Private => (&self.private_table, &self.private_slots),
518         }
519     }
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(
524         &mut self,
525         type_: DataType,
526     ) -> (&mut DataStorageTable, &mut Vec<Entry>) {
527         match type_ {
528             DataType::Persistent => (&mut self.persistent_table, &mut self.persistent_slots),
529             DataType::Private => (&mut self.private_table, &mut self.private_slots),
530         }
531     }
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])
538     }
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);
544         };
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)?;
549         }
550         Ok(value)
551     }
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);
557         }
558         if let Some(existing_entry) = self.get_entry(&key, type_) {
559             let data_changed = existing_entry.value != value;
560             if data_changed {
561                 existing_entry.value = value;
562             }
563             if (existing_entry.update_score() || data_changed) && type_ == DataType::Persistent {
564                 let entry = existing_entry.clone();
565                 self.async_write_entry(entry)?;
566             }
567             Ok(())
568         } else {
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_)
572         }
573     }
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 {
579             return Ok(());
580         };
581         let entry = &mut slots[slot_index];
582         entry.clear();
583         if type_ == DataType::Persistent {
584             let entry = entry.clone();
585             self.async_write_entry(entry)?;
586         }
587         Ok(())
588     }
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 {
597             return Ok(());
598         };
599         let Some(write_queue) = self.write_queue.clone() else {
600             return Ok(());
601         };
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);
608         })
609         .may_block(true)
610         .dispatch(write_queue.coerce())
611     }
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();
617     }
619     /// Asynchronously writes the given entry on the background serial event
620     /// target.
621     fn async_write_entry(&self, entry: Entry) -> Result<(), nsresult> {
622         self.async_write_entries(vec![entry])
623     }
625     /// Asynchronously writes the given entries on the background serial event
626     /// target.
627     fn async_write_entries(&self, entries: Vec<Entry>) -> Result<(), nsresult> {
628         let Some(mut backing_path) = self.maybe_profile_path.clone() else {
629             return Ok(());
630         };
631         let Some(write_queue) = self.write_queue.clone() else {
632             return Ok(());
633         };
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);
639         })
640         .may_block(true)
641         .dispatch(write_queue.coerce())
642     }
644     /// Drop the write queue to prevent further writes.
645     fn drop_write_queue(&mut self) {
646         let _ = self.write_queue.take();
647     }
649     /// Takes a callback that is run for each entry in each table.
650     fn for_each<F>(&self, mut f: F)
651     where
652         F: FnMut(&Entry, DataType),
653     {
654         for entry in self
655             .persistent_slots
656             .iter()
657             .filter(|entry| !entry.is_empty())
658         {
659             f(entry, DataType::Persistent);
660         }
661         for entry in self.private_slots.iter().filter(|entry| !entry.is_empty()) {
662             f(entry, DataType::Private);
663         }
664     }
666     /// Collects the memory used by this DataStorageInner.
667     fn collect_reports(
668         &self,
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,
677         };
678         unsafe {
679             callback
680                 .Callback(
681                     &nsCStr::new() as &nsACString,
682                     &nsCString::from(format!("explicit/data-storage/{}", self.name)) as &nsACString,
683                     nsIMemoryReporter::KIND_HEAP,
684                     nsIMemoryReporter::UNITS_BYTES,
685                     size as i64,
686                     &nsCStr::from("Memory used by PSM data storage cache") as &nsACString,
687                     data,
688                 )
689                 .to_result()
690         }
691     }
694 #[xpcom(implement(nsIDataStorageItem), atomic)]
695 struct DataStorageItem {
696     key: nsCString,
697     value: nsCString,
698     type_: u8,
701 impl DataStorageItem {
702     xpcom_method!(get_key => GetKey() -> nsACString);
703     fn get_key(&self) -> Result<nsCString, nsresult> {
704         Ok(self.key.clone())
705     }
707     xpcom_method!(get_value => GetValue() -> nsACString);
708     fn get_value(&self) -> Result<nsCString, nsresult> {
709         Ok(self.value.clone())
710     }
712     xpcom_method!(get_type => GetType() -> u8);
713     fn get_type(&self) -> Result<u8, nsresult> {
714         Ok(self.type_)
715     }
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)]
723 struct DataStorage {
724     ready: (Mutex<bool>, Condvar),
725     data: Mutex<DataStorageInner>,
726     size_of_op: VoidPtrToSizeFn,
727     enclosing_size_of_op: VoidPtrToSizeFn,
730 impl DataStorage {
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();
735         storage
736             .get(&Vec::from(key.as_ref()), type_.into())
737             .map(|data| nsCString::from(data))
738     }
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();
744         storage.put(
745             Vec::from(key.as_ref()),
746             Vec::from(value.as_ref()),
747             type_.into(),
748         )
749     }
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())?;
756         Ok(())
757     }
759     xpcom_method!(clear => Clear());
760     fn clear(&self) -> Result<(), nsresult> {
761         self.wait_for_ready()?;
762         let mut storage = self.data.lock().unwrap();
763         storage.clear()?;
764         Ok(())
765     }
767     xpcom_method!(is_ready => IsReady() -> bool);
768     fn is_ready(&self) -> Result<bool, nsresult> {
769         let ready = self.ready.0.lock().unwrap();
770         Ok(*ready)
771     }
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(),
783             });
784             items.push(Some(RefPtr::new(item.coerce())));
785         };
786         storage.for_each(add_item);
787         Ok(items)
788     }
790     fn indicate_ready(&self) -> Result<(), nsresult> {
791         let (ready_mutex, condvar) = &self.ready;
792         let mut ready = ready_mutex.lock().unwrap();
793         *ready = true;
794         condvar.notify_all();
795         Ok(())
796     }
798     fn wait_for_ready(&self) -> Result<(), nsresult> {
799         let (ready_mutex, condvar) = &self.ready;
800         let mut ready = ready_mutex.lock().unwrap();
801         while !*ready {
802             ready = condvar.wait(ready).unwrap();
803         }
804         Ok(())
805     }
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()
814     }
816     xpcom_method!(collect_reports => CollectReports(callback: *const nsIHandleReportCallback, data: *const nsISupports, anonymize: bool));
817     fn collect_reports(
818         &self,
819         callback: &nsIHandleReportCallback,
820         data: Option<&nsISupports>,
821         _anonymize: bool,
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)
826     }
828     xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: *const c_char, _data: *const u16));
829     unsafe fn observe(
830         &self,
831         _subject: Option<&nsISupports>,
832         topic: *const c_char,
833         _data: *const u16,
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
839         // that goes away.
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();
846         }
847         Ok(())
848     }
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.
854 fn write_entries(
855     entries: Vec<Entry>,
856     backing_path: PathBuf,
857     value_length: usize,
858     slot_length: usize,
859 ) -> Result<(), std::io::Error> {
860     let mut backing_file = OpenOptions::new()
861         .write(true)
862         .create(true)
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
866     };
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)?;
870     }
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;
882             } else {
883                 checksum ^= chunk.read_u16::<BigEndian>()?;
884             }
885         }
886         if entry.key.len() > KEY_LENGTH {
887             continue;
888         }
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;
895             } else {
896                 checksum ^= chunk.read_u16::<BigEndian>()?;
897             }
898         }
899         if entry.value.len() > value_length {
900             continue;
901         }
902         buf_writer.write_all(&entry.value)?;
903         buf_writer.fill(0);
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)?;
910     }
911     Ok(())
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();
919     unsafe {
920         directory_service
921             .Get(
922                 cstr!("ProfD").as_ptr(),
923                 &nsIFile::IID,
924                 profile_dir.void_ptr(),
925             )
926             .to_result()?;
927     }
928     let profile_dir = profile_dir.refptr().ok_or(NS_ERROR_FAILURE)?;
929     let mut profile_path = nsString::new();
930     unsafe {
931         (*profile_dir).GetPath(&mut *profile_path).to_result()?;
932     }
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(
938     basename: &str,
939     value_length: usize,
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(),
948             value_length,
949             maybe_profile_path,
950         )?),
951         size_of_op,
952         enclosing_size_of_op,
953     });
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();
958     })
959     .may_block(true)
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>() {
964         unsafe {
965             observer_service
966                 .AddObserver(
967                     data_storage.coerce(),
968                     cstr!("profile-before-change").as_ptr(),
969                     false,
970                 )
971                 .to_result()?;
972             observer_service
973                 .AddObserver(
974                     data_storage.coerce(),
975                     cstr!("xpcom-shutdown-threads").as_ptr(),
976                     false,
977                 )
978                 .to_result()?;
979             observer_service
980                 .AddObserver(
981                     data_storage.coerce(),
982                     cstr!("last-pb-context-exited").as_ptr(),
983                     false,
984                 )
985                 .to_result()?;
986         }
987     }
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"
992     )) {
993         unsafe {
994             memory_reporter_manager
995                 .RegisterStrongReporter(data_storage.coerce())
996                 .to_result()?;
997         }
998     }
1000     Ok(RefPtr::new(data_storage.coerce()))
1003 #[no_mangle]
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,
1010 ) -> nsresult {
1011     if basename.is_null() || result.is_null() {
1012         return NS_ERROR_INVALID_ARG;
1013     }
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),
1018         Err(e) => return e,
1019     }
1020     NS_OK