tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }