Skip to main content

outlook_pst/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    cell::RefMut,
5    fmt::Debug,
6    fs::{File, OpenOptions},
7    io::{self, BufWriter, Read, Seek, SeekFrom, Write},
8    mem,
9    path::Path,
10    rc::Rc,
11    sync::Mutex,
12};
13use thiserror::Error;
14use tracing::{error, instrument, warn};
15
16pub mod ltp;
17pub mod messaging;
18pub mod ndb;
19
20mod block_sig;
21mod crc;
22mod encode;
23
24use ltp::{heap::*, prop_context::*, table_context::*, tree::*};
25use messaging::{folder::*, message::*, named_prop::*, search::*, store::*};
26use ndb::{
27    block::*, block_id::*, block_ref::*, byte_index::*, header::*, node_id::*, page::*,
28    read_write::*, root::*, *,
29};
30
31#[derive(Error, Debug)]
32pub enum PstError {
33    #[error("Opened read-only")]
34    OpenedReadOnly,
35    #[error("Cannot write to file: {0}")]
36    NoWriteAccess(String),
37    #[error("I/O error: {0:?}")]
38    Io(#[from] io::Error),
39    #[error("I/O error: {0}")]
40    BorrowedIo(String),
41    #[error("Failed to lock file")]
42    LockError,
43    #[error("Integer conversion failed")]
44    IntegerConversion,
45    #[error("Node Database error: {0}")]
46    NodeDatabaseError(#[from] NdbError),
47    #[error("AllocationMapPage not found: {0}")]
48    AllocationMapPageNotFound(usize),
49    #[error("Invalid BTree page: offset: 0x{0:X}")]
50    InvalidBTreePage(u64),
51}
52
53impl From<&PstError> for io::Error {
54    fn from(err: &PstError) -> Self {
55        match err {
56            PstError::NoWriteAccess(path) => {
57                Self::new(io::ErrorKind::PermissionDenied, path.as_str())
58            }
59            err => Self::other(format!("{err:?}")),
60        }
61    }
62}
63
64impl From<PstError> for io::Error {
65    fn from(err: PstError) -> Self {
66        match err {
67            PstError::NoWriteAccess(path) => {
68                Self::new(io::ErrorKind::PermissionDenied, path.as_str())
69            }
70            PstError::Io(err) => err,
71            err => Self::other(err),
72        }
73    }
74}
75
76impl From<&io::Error> for PstError {
77    fn from(err: &io::Error) -> Self {
78        Self::BorrowedIo(format!("{err:?}"))
79    }
80}
81
82type PstResult<T> = std::result::Result<T, PstError>;
83
84/// The methods on this trait and the [`PstFileInner`] struct are not public, PST modifications
85/// have to go through `pub fn` methods on the [`PstFileLockGuard`] type which encapsulates a `dyn`
86/// reference to this trait.
87trait PstFileLock<Pst>
88where
89    Pst: PstFile,
90{
91    fn start_write(&mut self) -> io::Result<()>;
92    fn finish_write(&mut self) -> io::Result<()>;
93
94    fn block_cache(&self) -> RefMut<'_, RootBTreePageCache<<Pst as PstFile>::BlockBTree>>;
95    fn node_cache(&self) -> RefMut<'_, RootBTreePageCache<<Pst as PstFile>::NodeBTree>>;
96}
97
98/// This is the public interface for writing to a PST.
99pub struct PstFileLockGuard<'a, Pst>
100where
101    Pst: PstFile,
102{
103    pst: &'a mut dyn PstFileLock<Pst>,
104}
105
106impl<'a, Pst> PstFileLockGuard<'a, Pst>
107where
108    Pst: PstFile,
109{
110    fn new(pst: &'a mut dyn PstFileLock<Pst>) -> io::Result<Self> {
111        pst.start_write()?;
112        Ok(Self { pst })
113    }
114
115    /// Explicitly flush pending updates to the PST file. This will still happen implicitly when
116    /// the [`PstFileLockGuard`] is dropped, but this allows you to handle errors.
117    #[instrument(skip_all)]
118    pub fn flush(&mut self) -> io::Result<()> {
119        self.pst.finish_write().inspect_err(|err| {
120            error!(
121                name: "PstFinishWriteFailed",
122                ?err,
123                "PstFileLock::finish_write failed"
124            );
125        })?;
126
127        Ok(())
128    }
129}
130
131impl<Pst> Drop for PstFileLockGuard<'_, Pst>
132where
133    Pst: PstFile,
134{
135    #[instrument(skip_all)]
136    fn drop(&mut self) {
137        if let Err(err) = self.flush() {
138            error!(
139                name: "PstFileLockGuardFlushFailed",
140                ?err,
141                "Writing to the PST file failed"
142            );
143        }
144    }
145}
146
147pub trait PstReader: Read + Seek {}
148
149impl<T> PstReader for T where T: Read + Seek {}
150
151/// [PST File](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/6b57253b-0853-47bb-99bb-d4b8f78105f0)
152pub trait PstFile: Sized {
153    type BlockId: BlockId<Index = Self::BTreeKey> + BlockIdReadWrite;
154    type PageId: BlockId<Index = Self::BTreeKey> + BlockIdReadWrite;
155    type ByteIndex: ByteIndex + ByteIndexReadWrite;
156    type BlockRef: BlockRef<Block = Self::BlockId, Index = Self::ByteIndex> + BlockRefReadWrite;
157    type PageRef: BlockRef<Block = Self::PageId, Index = Self::ByteIndex> + BlockRefReadWrite;
158    type Root: Root<Self>;
159    type Header: Header<Self>;
160    type PageTrailer: PageTrailer<BlockId = Self::PageId> + PageTrailerReadWrite;
161    type BTreeKey: BTreeEntryKey;
162    type NodeBTreeEntry: NodeBTreeEntry<Block = Self::BlockId> + BTreeEntry<Key = Self::BTreeKey>;
163    type NodeBTree: NodeBTree<Self, Self::NodeBTreeEntry>;
164    type BlockBTreeEntry: BlockBTreeEntry<Block = Self::BlockRef> + BTreeEntry<Key = Self::BTreeKey>;
165    type BlockBTree: BlockBTree<Self, Self::BlockBTreeEntry>;
166    type BlockTrailer: BlockTrailer<BlockId = Self::BlockId>;
167    type AllocationMapPage: AllocationMapPage<Self>;
168    type AllocationPageMapPage: AllocationPageMapPage<Self>;
169    type FreeMapPage: FreeMapPage<Self>;
170    type FreePageMapPage: FreePageMapPage<Self>;
171    type DensityListPage: DensityListPage<Self>;
172    type DataTreeEntry: IntermediateTreeEntry + IntermediateDataTreeEntry<Self>;
173    type DataTreeBlock: IntermediateTreeBlock<
174        Header = DataTreeBlockHeader,
175        Entry = Self::DataTreeEntry,
176        Trailer = Self::BlockTrailer,
177    >;
178    type DataBlock: Block<Trailer = Self::BlockTrailer>;
179    type SubNodeTreeBlockHeader: IntermediateTreeHeader;
180    type SubNodeTreeBlock: IntermediateTreeBlock<
181        Header = Self::SubNodeTreeBlockHeader,
182        Entry = IntermediateSubNodeTreeEntry<Self::BlockId>,
183        Trailer = Self::BlockTrailer,
184    >;
185    type SubNodeBlock: IntermediateTreeBlock<
186        Header = Self::SubNodeTreeBlockHeader,
187        Entry = LeafSubNodeTreeEntry<Self::BlockId>,
188        Trailer = Self::BlockTrailer,
189    >;
190    type TableContext: TableContext;
191    type PropertyContext: PropertyContext;
192    type HeapNode: HeapNode;
193    type PropertyTree: HeapTree<Key = PropertyTreeRecordKey, Value = PropertyTreeRecordValue>;
194    type Store: Store;
195    type Folder: Folder;
196    type Message: Message;
197    type NamedPropertyMap: NamedPropertyMap;
198    type SearchUpdateQueue: SearchUpdateQueue;
199
200    fn header(&self) -> &Self::Header;
201    fn density_list(&self) -> Result<&dyn DensityListPage<Self>, &io::Error>;
202    fn reader(&self) -> &Mutex<Box<dyn PstReader>>;
203    fn lock(&mut self) -> io::Result<PstFileLockGuard<'_, Self>>;
204
205    fn read_node(&self, node: NodeId) -> io::Result<Self::NodeBTreeEntry>;
206    fn read_block(&self, block: Self::BlockId) -> io::Result<Vec<u8>>;
207}
208
209struct PstFileInner<Pst>
210where
211    Pst: PstFile,
212{
213    reader: Mutex<Box<dyn PstReader>>,
214    writer: PstResult<Mutex<BufWriter<File>>>,
215    header: Pst::Header,
216    density_list: io::Result<Pst::DensityListPage>,
217    node_cache: NodeBTreePageCache<Pst>,
218    block_cache: BlockBTreePageCache<Pst>,
219}
220
221pub struct UnicodePstFile {
222    inner: PstFileInner<Self>,
223}
224
225impl UnicodePstFile {
226    pub fn read_from(reader: Box<dyn PstReader>) -> io::Result<Self> {
227        let inner = PstFileInner::read_from(reader)?;
228        Ok(Self { inner })
229    }
230
231    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
232        let inner = PstFileInner::open(path)?;
233        Ok(Self { inner })
234    }
235}
236
237impl PstFileLock<UnicodePstFile> for UnicodePstFile {
238    fn start_write(&mut self) -> io::Result<()> {
239        self.inner.start_write()
240    }
241
242    fn finish_write(&mut self) -> io::Result<()> {
243        self.inner.finish_write()
244    }
245
246    fn block_cache(&self) -> RefMut<'_, RootBTreePageCache<<Self as PstFile>::BlockBTree>> {
247        self.inner.block_cache.borrow_mut()
248    }
249
250    fn node_cache(&self) -> RefMut<'_, RootBTreePageCache<<Self as PstFile>::NodeBTree>> {
251        self.inner.node_cache.borrow_mut()
252    }
253}
254
255impl PstFile for UnicodePstFile {
256    type BlockId = UnicodeBlockId;
257    type PageId = UnicodePageId;
258    type ByteIndex = UnicodeByteIndex;
259    type BlockRef = UnicodeBlockRef;
260    type PageRef = UnicodePageRef;
261    type Root = UnicodeRoot;
262    type Header = UnicodeHeader;
263    type PageTrailer = UnicodePageTrailer;
264    type BTreeKey = u64;
265    type NodeBTreeEntry = UnicodeNodeBTreeEntry;
266    type NodeBTree = UnicodeNodeBTree;
267    type BlockBTreeEntry = UnicodeBlockBTreeEntry;
268    type BlockBTree = UnicodeBlockBTree;
269    type BlockTrailer = UnicodeBlockTrailer;
270    type AllocationMapPage = UnicodeMapPage<{ PageType::AllocationMap as u8 }>;
271    type AllocationPageMapPage = UnicodeMapPage<{ PageType::AllocationPageMap as u8 }>;
272    type FreeMapPage = UnicodeMapPage<{ PageType::FreeMap as u8 }>;
273    type FreePageMapPage = UnicodeMapPage<{ PageType::FreePageMap as u8 }>;
274    type DensityListPage = UnicodeDensityListPage;
275    type DataTreeEntry = UnicodeDataTreeEntry;
276    type DataTreeBlock = UnicodeDataTreeBlock;
277    type DataBlock = UnicodeDataBlock;
278    type SubNodeTreeBlockHeader = UnicodeSubNodeTreeBlockHeader;
279    type SubNodeTreeBlock = UnicodeIntermediateSubNodeTreeBlock;
280    type SubNodeBlock = UnicodeLeafSubNodeTreeBlock;
281    type HeapNode = UnicodeHeapNode;
282    type PropertyTree = UnicodeHeapTree<PropertyTreeRecordKey, PropertyTreeRecordValue>;
283    type TableContext = UnicodeTableContext;
284    type PropertyContext = UnicodePropertyContext;
285    type Store = UnicodeStore;
286    type Folder = UnicodeFolder;
287    type Message = UnicodeMessage;
288    type NamedPropertyMap = UnicodeNamedPropertyMap;
289    type SearchUpdateQueue = UnicodeSearchUpdateQueue;
290
291    fn header(&self) -> &Self::Header {
292        &self.inner.header
293    }
294
295    fn density_list(&self) -> Result<&dyn DensityListPage<Self>, &io::Error> {
296        self.inner.density_list.as_ref().map(|dl| dl as _)
297    }
298
299    fn reader(&self) -> &Mutex<Box<dyn PstReader>> {
300        &self.inner.reader
301    }
302
303    fn lock(&mut self) -> io::Result<PstFileLockGuard<'_, Self>> {
304        PstFileLockGuard::new(self)
305    }
306
307    fn read_node(&self, node: NodeId) -> io::Result<UnicodeNodeBTreeEntry> {
308        self.inner.read_node(node)
309    }
310
311    fn read_block(&self, block: UnicodeBlockId) -> io::Result<Vec<u8>> {
312        self.inner.read_block(block)
313    }
314}
315
316pub struct AnsiPstFile {
317    inner: PstFileInner<Self>,
318}
319
320impl AnsiPstFile {
321    pub fn read_from(reader: Box<dyn PstReader>) -> io::Result<Self> {
322        let inner = PstFileInner::read_from(reader)?;
323        Ok(Self { inner })
324    }
325
326    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
327        let inner = PstFileInner::open(path)?;
328        Ok(Self { inner })
329    }
330}
331
332impl PstFileLock<AnsiPstFile> for AnsiPstFile {
333    fn start_write(&mut self) -> io::Result<()> {
334        self.inner.start_write()
335    }
336
337    fn finish_write(&mut self) -> io::Result<()> {
338        self.inner.finish_write()
339    }
340
341    fn block_cache(&self) -> RefMut<'_, RootBTreePageCache<<Self as PstFile>::BlockBTree>> {
342        self.inner.block_cache.borrow_mut()
343    }
344
345    fn node_cache(&self) -> RefMut<'_, RootBTreePageCache<<Self as PstFile>::NodeBTree>> {
346        self.inner.node_cache.borrow_mut()
347    }
348}
349
350impl PstFile for AnsiPstFile {
351    type BlockId = AnsiBlockId;
352    type PageId = AnsiPageId;
353    type ByteIndex = AnsiByteIndex;
354    type BlockRef = AnsiBlockRef;
355    type PageRef = AnsiPageRef;
356    type Root = AnsiRoot;
357    type Header = AnsiHeader;
358    type PageTrailer = AnsiPageTrailer;
359    type BTreeKey = u32;
360    type NodeBTreeEntry = AnsiNodeBTreeEntry;
361    type NodeBTree = AnsiNodeBTree;
362    type BlockBTreeEntry = AnsiBlockBTreeEntry;
363    type BlockBTree = AnsiBlockBTree;
364    type BlockTrailer = AnsiBlockTrailer;
365    type AllocationMapPage = AnsiMapPage<{ PageType::AllocationMap as u8 }>;
366    type AllocationPageMapPage = AnsiMapPage<{ PageType::AllocationPageMap as u8 }>;
367    type FreeMapPage = AnsiMapPage<{ PageType::FreeMap as u8 }>;
368    type FreePageMapPage = AnsiMapPage<{ PageType::FreePageMap as u8 }>;
369    type DensityListPage = AnsiDensityListPage;
370    type DataTreeEntry = AnsiDataTreeEntry;
371    type DataTreeBlock = AnsiDataTreeBlock;
372    type DataBlock = AnsiDataBlock;
373    type SubNodeTreeBlockHeader = AnsiSubNodeTreeBlockHeader;
374    type SubNodeTreeBlock = AnsiIntermediateSubNodeTreeBlock;
375    type SubNodeBlock = AnsiLeafSubNodeTreeBlock;
376    type HeapNode = AnsiHeapNode;
377    type PropertyTree = AnsiHeapTree<PropertyTreeRecordKey, PropertyTreeRecordValue>;
378    type TableContext = AnsiTableContext;
379    type PropertyContext = AnsiPropertyContext;
380    type Store = AnsiStore;
381    type Folder = AnsiFolder;
382    type Message = AnsiMessage;
383    type NamedPropertyMap = AnsiNamedPropertyMap;
384    type SearchUpdateQueue = AnsiSearchUpdateQueue;
385
386    fn header(&self) -> &Self::Header {
387        &self.inner.header
388    }
389
390    fn density_list(&self) -> Result<&dyn DensityListPage<Self>, &io::Error> {
391        self.inner.density_list.as_ref().map(|dl| dl as _)
392    }
393
394    fn reader(&self) -> &Mutex<Box<dyn PstReader>> {
395        &self.inner.reader
396    }
397
398    fn lock(&mut self) -> io::Result<PstFileLockGuard<'_, Self>> {
399        PstFileLockGuard::new(self)
400    }
401
402    fn read_node(&self, node: NodeId) -> io::Result<AnsiNodeBTreeEntry> {
403        self.inner.read_node(node)
404    }
405
406    fn read_block(&self, block: AnsiBlockId) -> io::Result<Vec<u8>> {
407        self.inner.read_block(block)
408    }
409}
410
411const AMAP_FIRST_OFFSET: u64 = 0x4400;
412const AMAP_DATA_SIZE: u64 = size_of::<MapBits>() as u64 * 8 * 64;
413
414const PMAP_FIRST_OFFSET: u64 = AMAP_FIRST_OFFSET + PAGE_SIZE as u64;
415const PMAP_PAGE_COUNT: u64 = 8;
416const PMAP_DATA_SIZE: u64 = AMAP_DATA_SIZE * PMAP_PAGE_COUNT;
417
418const FMAP_FIRST_SIZE: u64 = 128;
419const FMAP_FIRST_DATA_SIZE: u64 = AMAP_DATA_SIZE * FMAP_FIRST_SIZE;
420const FMAP_FIRST_OFFSET: u64 = AMAP_FIRST_OFFSET + FMAP_FIRST_DATA_SIZE + (2 * PAGE_SIZE) as u64;
421const FMAP_PAGE_COUNT: u64 = size_of::<MapBits>() as u64;
422const FMAP_DATA_SIZE: u64 = AMAP_DATA_SIZE * FMAP_PAGE_COUNT;
423
424const FPMAP_FIRST_SIZE: u64 = 128 * 64;
425const FPMAP_FIRST_DATA_SIZE: u64 = AMAP_DATA_SIZE * FPMAP_FIRST_SIZE;
426const FPMAP_FIRST_OFFSET: u64 = AMAP_FIRST_OFFSET + FPMAP_FIRST_DATA_SIZE + (3 * PAGE_SIZE) as u64;
427const FPMAP_PAGE_COUNT: u64 = size_of::<MapBits>() as u64 * 64;
428const FPMAP_DATA_SIZE: u64 = AMAP_DATA_SIZE * FPMAP_PAGE_COUNT;
429
430struct AllocationMapPageInfo<Pst>
431where
432    Pst: PstFile,
433    <Pst as PstFile>::AllocationMapPage: AllocationMapPageReadWrite<Pst>,
434{
435    amap_page: <Pst as PstFile>::AllocationMapPage,
436    free_space: u64,
437}
438
439impl<Pst> AllocationMapPageInfo<Pst>
440where
441    Pst: PstFile,
442    <Pst as PstFile>::AllocationMapPage: AllocationMapPageReadWrite<Pst>,
443{
444    fn max_free_slots(&self) -> u8 {
445        u8::try_from(self.amap_page.find_free_bits(0xFF).len()).unwrap_or(0xFF)
446    }
447}
448
449type PstFileReadWriteBTree<Pst, BTree> = RootBTreePage<
450    Pst,
451    <BTree as RootBTree>::Entry,
452    <BTree as RootBTree>::IntermediatePage,
453    <BTree as RootBTree>::LeafPage,
454>;
455
456type PstFileReadWriteNodeBTree<Pst> = PstFileReadWriteBTree<Pst, <Pst as PstFile>::NodeBTree>;
457
458type PstFileReadWriteBlockBTree<Pst> = PstFileReadWriteBTree<Pst, <Pst as PstFile>::BlockBTree>;
459
460impl<Pst> PstFileInner<Pst>
461where
462    Pst: PstFile + PstFileLock<Pst>,
463    <Pst as PstFile>::BlockId: BlockId<Index = <Pst as PstFile>::BTreeKey>
464        + From<<<Pst as PstFile>::ByteIndex as ByteIndex>::Index>
465        + Debug,
466    <Pst as PstFile>::PageId: From<<<Pst as PstFile>::ByteIndex as ByteIndex>::Index> + Debug,
467    <Pst as PstFile>::ByteIndex: ByteIndex<Index: TryFrom<u64>> + Debug,
468    <Pst as PstFile>::BlockRef: Debug,
469    <Pst as PstFile>::PageRef: Debug,
470    <Pst as PstFile>::Root: RootReadWrite<Pst>,
471    <Pst as PstFile>::Header: HeaderReadWrite<Pst>,
472    <Pst as PstFile>::DensityListPage: DensityListPageReadWrite<Pst>,
473    <Pst as PstFile>::PageTrailer: PageTrailerReadWrite,
474    <Pst as PstFile>::BTreeKey: BTreePageKeyReadWrite,
475    <Pst as PstFile>::NodeBTreeEntry: NodeBTreeEntryReadWrite,
476    <Pst as PstFile>::NodeBTree: NodeBTreeReadWrite<Pst, <Pst as PstFile>::NodeBTreeEntry>,
477    <<Pst as PstFile>::NodeBTree as RootBTree>::IntermediatePage:
478        RootBTreeIntermediatePageReadWrite<
479            Pst,
480            <Pst as PstFile>::NodeBTreeEntry,
481            <<Pst as PstFile>::NodeBTree as RootBTree>::LeafPage,
482        >,
483    <<<Pst as PstFile>::NodeBTree as RootBTree>::IntermediatePage as BTreePage>::Entry:
484        BTreePageEntryReadWrite,
485    <<Pst as PstFile>::NodeBTree as RootBTree>::LeafPage: RootBTreeLeafPageReadWrite<Pst>,
486    <Pst as PstFile>::BlockBTreeEntry: BlockBTreeEntryReadWrite,
487    <Pst as PstFile>::BlockBTree: BlockBTreeReadWrite<Pst, <Pst as PstFile>::BlockBTreeEntry>,
488    <<Pst as PstFile>::BlockBTree as RootBTree>::IntermediatePage:
489        RootBTreeIntermediatePageReadWrite<
490            Pst,
491            <Pst as PstFile>::BlockBTreeEntry,
492            <<Pst as PstFile>::BlockBTree as RootBTree>::LeafPage,
493        >,
494    <<<Pst as PstFile>::BlockBTree as RootBTree>::IntermediatePage as BTreePage>::Entry:
495        BTreePageEntryReadWrite,
496    <<Pst as PstFile>::BlockBTree as RootBTree>::LeafPage: RootBTreeLeafPageReadWrite<Pst>,
497    <Pst as PstFile>::BlockTrailer: BlockTrailerReadWrite,
498    <Pst as PstFile>::AllocationMapPage: AllocationMapPageReadWrite<Pst>,
499    <Pst as PstFile>::AllocationPageMapPage: AllocationPageMapPageReadWrite<Pst>,
500    <Pst as PstFile>::FreeMapPage: FreeMapPageReadWrite<Pst>,
501    <Pst as PstFile>::FreePageMapPage: FreePageMapPageReadWrite<Pst>,
502    <Pst as PstFile>::DensityListPage: DensityListPageReadWrite<Pst>,
503    <Pst as PstFile>::DataTreeBlock: IntermediateTreeBlockReadWrite,
504    <Pst as PstFile>::DataTreeEntry:
505        IntermediateTreeEntryReadWrite + From<<Pst as PstFile>::BlockId>,
506    <Pst as PstFile>::DataBlock: BlockReadWrite + Clone,
507    <Pst as PstFile>::SubNodeTreeBlockHeader: SubNodeTreeBlockHeaderReadWrite,
508    <Pst as PstFile>::SubNodeTreeBlock: IntermediateTreeBlockReadWrite,
509    <<Pst as PstFile>::SubNodeTreeBlock as IntermediateTreeBlock>::Entry:
510        IntermediateTreeEntryReadWrite,
511    <Pst as PstFile>::SubNodeBlock: IntermediateTreeBlockReadWrite,
512    <<Pst as PstFile>::SubNodeBlock as IntermediateTreeBlock>::Entry:
513        IntermediateTreeEntryReadWrite,
514{
515    fn read_from(mut reader: Box<dyn PstReader>) -> io::Result<Self> {
516        let header = <<Pst as PstFile>::Header as HeaderReadWrite<Pst>>::read(&mut reader)?;
517        let density_list =
518            <<Pst as PstFile>::DensityListPage as DensityListPageReadWrite<Pst>>::read(&mut reader);
519        Ok(Self {
520            reader: Mutex::new(Box::new(reader)),
521            writer: Err(PstError::OpenedReadOnly),
522            header,
523            density_list,
524            node_cache: Default::default(),
525            block_cache: Default::default(),
526        })
527    }
528
529    fn open(path: impl AsRef<Path>) -> io::Result<Self> {
530        let reader = Box::new(File::open(&path)?);
531        let writer = OpenOptions::new()
532            .write(true)
533            .open(&path)
534            .map(BufWriter::new)
535            .map(Mutex::new)
536            .map_err(|_| PstError::NoWriteAccess(path.as_ref().display().to_string()));
537        Ok(Self {
538            writer,
539            ..Self::read_from(reader)?
540        })
541    }
542
543    /// Begin a transaction by rebuilding the allocation map if needed and initializing the density
544    /// list, then set [`AmapStatus::Invalid`] in the header till the transaction is finished.
545    ///
546    /// See also [Transactional Semantics](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/bc5a92df-7fc1-4dc2-9c7c-5677237dd73a).
547    fn start_write(&mut self) -> io::Result<()> {
548        self.rebuild_allocation_map()?;
549        self.ensure_density_list()?;
550
551        let header = {
552            self.header.update_unique();
553
554            let root = self.header.root_mut();
555            root.set_amap_status(AmapStatus::Invalid);
556            self.header.clone()
557        };
558
559        let mut writer = self
560            .writer
561            .as_ref()?
562            .lock()
563            .map_err(|_| PstError::LockError)?;
564        let writer = &mut *writer;
565        writer.seek(SeekFrom::Start(0))?;
566        header.write(writer)?;
567        writer.flush()
568    }
569
570    /// Complete a transaction by writing the header and density list to the file, and setting
571    /// [`AmapStatus::Valid2`].
572    ///
573    /// See also [Transactional Semantics](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/bc5a92df-7fc1-4dc2-9c7c-5677237dd73a).
574    #[instrument(skip_all)]
575    fn finish_write(&mut self) -> io::Result<()> {
576        // Reset AmapStatus::Valid2 to complete the transaction and then rewrite the updated
577        // density list.
578        let header = {
579            self.header.update_unique();
580            let root = self.header.root_mut();
581            root.set_amap_status(AmapStatus::Valid2);
582            self.header.clone()
583        };
584
585        self.update_density_list_page_id()?;
586        let density_list = {
587            self.density_list.as_ref().ok().and_then(|dl| {
588                <<Pst as PstFile>::DensityListPage as DensityListPageReadWrite<Pst>>::new(
589                    dl.backfill_complete(),
590                    dl.current_page(),
591                    dl.entries(),
592                    *dl.trailer(),
593                )
594                .ok()
595            })
596        };
597
598        let mut writer = self
599            .writer
600            .as_ref()?
601            .lock()
602            .map_err(|_| PstError::LockError)?;
603        let writer = &mut *writer;
604        writer.seek(SeekFrom::Start(0))?;
605        header.write(writer)?;
606        writer.flush()?;
607
608        if let Some(density_list) = density_list {
609            density_list.write(writer)?;
610            writer.flush()?;
611        }
612
613        Ok(())
614    }
615
616    /// [Crash Recovery and AMap Rebuilding](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/d9bcc1fd-c66a-41b3-b6d7-ed09d2a25ced)
617    fn rebuild_allocation_map(&mut self) -> io::Result<()> {
618        let root = self.header.root();
619        if AmapStatus::Invalid != root.amap_is_valid() {
620            return Ok(());
621        }
622
623        let num_amap_pages = root.file_eof_index().index().into() - AMAP_FIRST_OFFSET;
624        let num_amap_pages = num_amap_pages.div_ceil(AMAP_DATA_SIZE);
625
626        let mut amap_pages: Vec<_> = (0..num_amap_pages)
627            .map(|index| {
628                let has_pmap_page = index % 8 == 0;
629                let has_fmap_page = has_pmap_page
630                    && index >= FMAP_FIRST_SIZE
631                    && (index - FMAP_FIRST_SIZE) % FMAP_PAGE_COUNT == 0;
632                let has_fpmap_page = has_pmap_page
633                    && index >= FPMAP_FIRST_SIZE
634                    && (index - FPMAP_FIRST_SIZE) % FPMAP_PAGE_COUNT == 0;
635
636                let index =
637                    <<<Pst as PstFile>::ByteIndex as ByteIndex>::Index as TryFrom<u64>>::try_from(
638                        index * AMAP_DATA_SIZE + AMAP_FIRST_OFFSET,
639                    )
640                    .map_err(|_| PstError::IntegerConversion)?;
641                let block_id = <Pst as PstFile>::PageId::from(index);
642
643                let trailer = <<Pst as PstFile>::PageTrailer as PageTrailerReadWrite>::new(
644                    PageType::AllocationMap,
645                    0,
646                    block_id,
647                    0,
648                );
649
650                let mut map_bits = [0; mem::size_of::<MapBits>()];
651                let mut reserved = 1;
652                if has_pmap_page {
653                    reserved += 1;
654                }
655                if has_fmap_page {
656                    reserved += 1;
657                }
658                if has_fpmap_page {
659                    reserved += 1;
660                }
661
662                let free_space = AMAP_DATA_SIZE - (reserved * PAGE_SIZE) as u64;
663
664                let reserved = &[0xFF; 4][..reserved];
665                map_bits[..reserved.len()].copy_from_slice(reserved);
666
667                let amap_page =
668                    <<Pst as PstFile>::AllocationMapPage as AllocationMapPageReadWrite<Pst>>::new(
669                        map_bits, trailer,
670                    )?;
671                Ok(AllocationMapPageInfo::<Pst> {
672                    amap_page,
673                    free_space,
674                })
675            })
676            .collect::<PstResult<Vec<_>>>()?;
677
678        {
679            let mut reader = self.reader.lock().map_err(|_| PstError::LockError)?;
680            let reader = &mut *reader;
681
682            let node_btree =
683                <Pst::NodeBTree as RootBTreeReadWrite>::read(reader, *root.node_btree())?;
684
685            Self::mark_node_btree_allocations(
686                reader,
687                root.node_btree().index(),
688                &node_btree,
689                &mut amap_pages,
690            )?;
691
692            let block_btree =
693                <Pst::BlockBTree as RootBTreeReadWrite>::read(reader, *root.block_btree())?;
694
695            Self::mark_block_btree_allocations(
696                reader,
697                root.block_btree().index(),
698                &block_btree,
699                &mut amap_pages,
700            )?;
701        }
702
703        let free_bytes =
704            <<<Pst as PstFile>::ByteIndex as ByteIndex>::Index as TryFrom<u64>>::try_from(
705                amap_pages.iter().map(|page| page.free_space).sum(),
706            )
707            .map_err(|_| PstError::IntegerConversion)?;
708        let free_bytes = <<Pst as PstFile>::ByteIndex as ByteIndexReadWrite>::new(free_bytes);
709
710        let mut first_fmap = [0; FMAP_FIRST_SIZE as usize];
711        for (entry, free_space) in first_fmap
712            .iter_mut()
713            .zip(amap_pages.iter().map(|page| page.max_free_slots()))
714        {
715            *entry = free_space;
716        }
717
718        let pmap_pages: Vec<_> = (0..=(num_amap_pages / 8))
719            .map(|index| {
720                let index =
721                    <<<Pst as PstFile>::ByteIndex as ByteIndex>::Index as TryFrom<u64>>::try_from(
722                        index * PMAP_DATA_SIZE + PMAP_FIRST_OFFSET,
723                    )
724                    .map_err(|_| PstError::IntegerConversion)?;
725                let block_id = <Pst as PstFile>::PageId::from(index);
726
727                let trailer = <<Pst as PstFile>::PageTrailer as PageTrailerReadWrite>::new(
728                    PageType::AllocationPageMap,
729                    0,
730                    block_id,
731                    0,
732                );
733
734                let map_bits = [0xFF; mem::size_of::<MapBits>()];
735
736                let pmap_page =
737                    <<Pst as PstFile>::AllocationPageMapPage as AllocationPageMapPageReadWrite<
738                        Pst,
739                    >>::new(map_bits, trailer)?;
740                Ok(pmap_page)
741            })
742            .collect::<PstResult<Vec<_>>>()?;
743
744        let fmap_pages: Vec<_> = (0..(num_amap_pages.max(FMAP_FIRST_SIZE) - FMAP_FIRST_SIZE)
745            .div_ceil(FMAP_PAGE_COUNT))
746            .map(|index| {
747                let amap_index =
748                    FMAP_FIRST_SIZE as usize + (index as usize * mem::size_of::<MapBits>());
749                let index =
750                    <<<Pst as PstFile>::ByteIndex as ByteIndex>::Index as TryFrom<u64>>::try_from(
751                        index * FMAP_DATA_SIZE + FMAP_FIRST_OFFSET,
752                    )
753                    .map_err(|_| PstError::IntegerConversion)?;
754                let block_id = <Pst as PstFile>::PageId::from(index);
755
756                let trailer = <<Pst as PstFile>::PageTrailer as PageTrailerReadWrite>::new(
757                    PageType::FreeMap,
758                    0,
759                    block_id,
760                    0,
761                );
762
763                let mut map_bits = [0; mem::size_of::<MapBits>()];
764                for (entry, free_space) in map_bits.iter_mut().zip(
765                    amap_pages
766                        .iter()
767                        .skip(amap_index)
768                        .map(|page| page.max_free_slots()),
769                ) {
770                    *entry = free_space;
771                }
772
773                let fmap_page = <<Pst as PstFile>::FreeMapPage as FreeMapPageReadWrite<Pst>>::new(
774                    map_bits, trailer,
775                )?;
776                Ok(fmap_page)
777            })
778            .collect::<PstResult<Vec<_>>>()?;
779
780        let fpmap_pages: Vec<_> = (0..(num_amap_pages.max(FPMAP_FIRST_SIZE) - FPMAP_FIRST_SIZE)
781            .div_ceil(FPMAP_PAGE_COUNT))
782            .map(|index| {
783                let index =
784                    <<<Pst as PstFile>::ByteIndex as ByteIndex>::Index as TryFrom<u64>>::try_from(
785                        index * FPMAP_DATA_SIZE + FPMAP_FIRST_OFFSET,
786                    )
787                    .map_err(|_| PstError::IntegerConversion)?;
788                let block_id = <Pst as PstFile>::PageId::from(index);
789
790                let trailer = <<Pst as PstFile>::PageTrailer as PageTrailerReadWrite>::new(
791                    PageType::FreePageMap,
792                    0,
793                    block_id,
794                    0,
795                );
796
797                let map_bits = [0xFF; mem::size_of::<MapBits>()];
798
799                let fpmap_page = <<Pst as PstFile>::FreePageMapPage as FreePageMapPageReadWrite<
800                    Pst,
801                >>::new(map_bits, trailer)?;
802                Ok(fpmap_page)
803            })
804            .collect::<PstResult<Vec<_>>>()?;
805
806        {
807            let mut writer = self
808                .writer
809                .as_ref()?
810                .lock()
811                .map_err(|_| PstError::LockError)?;
812            let writer = &mut *writer;
813
814            for page in amap_pages.into_iter().map(|info| info.amap_page) {
815                writer.seek(SeekFrom::Start(page.trailer().block_id().into_u64()))?;
816                <Pst::AllocationMapPage as AllocationMapPageReadWrite<Pst>>::write(&page, writer)?;
817            }
818
819            for page in pmap_pages.into_iter() {
820                writer.seek(SeekFrom::Start(page.trailer().block_id().into_u64()))?;
821                <Pst::AllocationPageMapPage as AllocationPageMapPageReadWrite<Pst>>::write(
822                    &page, writer,
823                )?;
824            }
825
826            for page in fmap_pages.into_iter() {
827                writer.seek(SeekFrom::Start(page.trailer().block_id().into_u64()))?;
828                <Pst::FreeMapPage as FreeMapPageReadWrite<Pst>>::write(&page, writer)?;
829            }
830
831            for page in fpmap_pages.into_iter() {
832                writer.seek(SeekFrom::Start(page.trailer().block_id().into_u64()))?;
833                <Pst::FreePageMapPage as FreePageMapPageReadWrite<Pst>>::write(&page, writer)?;
834            }
835
836            writer.flush()?;
837        }
838
839        let header = {
840            <<Pst as PstFile>::Header as HeaderReadWrite<Pst>>::first_free_map(&mut self.header)
841                .copy_from_slice(&first_fmap);
842            self.header.update_unique();
843
844            let root = self.header.root_mut();
845            root.reset_free_size(free_bytes)?;
846            root.set_amap_status(AmapStatus::Valid2);
847
848            self.header.clone()
849        };
850
851        let mut writer = self
852            .writer
853            .as_ref()?
854            .lock()
855            .map_err(|_| PstError::LockError)?;
856        let writer = &mut *writer;
857        writer.seek(SeekFrom::Start(0))?;
858        header.write(writer)?;
859        writer.flush()
860    }
861
862    /// Recursively mark all of the pages in the [`Node BTree`](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/7d759bcb-7864-480c-8746-f6af913ab085).
863    /// as allocated. This does not include any blocks referenced in the nodes or the sub-trees in
864    /// those blocks, blocks will be marked by [`Self::mark_block_btree_allocations`].
865    ///
866    /// See also [Crash Recovery and AMap Rebuilding](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/d9bcc1fd-c66a-41b3-b6d7-ed09d2a25ced).
867    #[instrument(skip_all)]
868    fn mark_node_btree_allocations<R: PstReader>(
869        reader: &mut R,
870        page_index: Pst::ByteIndex,
871        node_btree: &PstFileReadWriteNodeBTree<Pst>,
872        amap_pages: &mut Vec<AllocationMapPageInfo<Pst>>,
873    ) -> io::Result<()> {
874        Self::mark_page_allocation(page_index.index().into(), amap_pages)?;
875
876        if let RootBTreePage::Intermediate(page, ..) = node_btree {
877            let level = page.level();
878            for entry in page.entries() {
879                let block = entry.block();
880                let node_btree = <Pst::NodeBTree as RootBTreeReadWrite>::read(reader, block)?;
881                match &node_btree {
882                    RootBTreePage::Intermediate(page, ..) if page.level() + 1 != level => {
883                        error!(
884                            name: "PstUnexpectedBTreeIntermediatePage",
885                            block = ?block.block(),
886                            index = ?block.index(),
887                            parent = level,
888                            child = page.level(),
889                            "Possible NBT page cycle detected, expected child == parent - 1"
890                        );
891                        return Err(PstError::InvalidBTreePage(block.index().index().into()).into());
892                    }
893                    RootBTreePage::Leaf(_) if level != 1 => {
894                        error!(
895                            name: "PstUnexpectedBTreeLeafPage",
896                            block = ?block.block(),
897                            index = ?block.index(),
898                            parent = level,
899                            child = page.level(),
900                            "Corrupted NBT intermediate page detected, unexpected child leaf page"
901                        );
902                        return Err(PstError::InvalidBTreePage(block.index().index().into()).into());
903                    }
904                    _ => (),
905                }
906                Self::mark_node_btree_allocations(reader, block.index(), &node_btree, amap_pages)?;
907            }
908        }
909
910        Ok(())
911    }
912
913    /// Recursively mark all of the pages and blocks in the [`Block BTree`](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/7d759bcb-7864-480c-8746-f6af913ab085).
914    ///
915    /// See also [Crash Recovery and AMap Rebuilding](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/d9bcc1fd-c66a-41b3-b6d7-ed09d2a25ced).
916    #[instrument(skip_all)]
917    fn mark_block_btree_allocations<R: PstReader>(
918        reader: &mut R,
919        page_index: Pst::ByteIndex,
920        block_btree: &PstFileReadWriteBlockBTree<Pst>,
921        amap_pages: &mut Vec<AllocationMapPageInfo<Pst>>,
922    ) -> io::Result<()> {
923        Self::mark_page_allocation(page_index.index().into(), amap_pages)?;
924
925        match block_btree {
926            RootBTreePage::Intermediate(page, ..) => {
927                let level = page.level();
928                for entry in page.entries() {
929                    let block = entry.block();
930                    let block_btree = <Pst::BlockBTree as RootBTreeReadWrite>::read(reader, block)?;
931                    match &block_btree {
932                        RootBTreePage::Intermediate(page, ..) if page.level() + 1 != level => {
933                            error!(
934                                name: "PstUnexpectedBTreeIntermediatePage",
935                                block = ?block.block(),
936                                index = ?block.index(),
937                                parent = level,
938                                child = page.level(),
939                                "Possible BBT page cycle detected, expected child == parent - 1"
940                            );
941                            return Err(
942                                PstError::InvalidBTreePage(block.index().index().into()).into()
943                            );
944                        }
945                        RootBTreePage::Leaf(_) if level != 1 => {
946                            error!(
947                                name: "PstUnexpectedBTreeLeafPage",
948                                block = ?block.block(),
949                                index = ?block.index(),
950                                parent = level,
951                                child = page.level(),
952                                "Corrupted BBT intermediate page detected, unexpected child leaf page"
953                            );
954                            return Err(
955                                PstError::InvalidBTreePage(block.index().index().into()).into()
956                            );
957                        }
958                        _ => (),
959                    }
960                    Self::mark_block_btree_allocations(
961                        reader,
962                        block.index(),
963                        &block_btree,
964                        amap_pages,
965                    )?;
966                }
967            }
968            RootBTreePage::Leaf(page) => {
969                for entry in page.entries() {
970                    Self::mark_block_allocation(
971                        entry.block().index().index().into(),
972                        entry.size(),
973                        amap_pages,
974                    )?;
975                }
976            }
977        }
978        Ok(())
979    }
980
981    /// Mark a page at the given file offset as allocated.
982    fn mark_page_allocation(
983        index: u64,
984        amap_pages: &mut [AllocationMapPageInfo<Pst>],
985    ) -> io::Result<()> {
986        let index = index - AMAP_FIRST_OFFSET;
987        let amap_index =
988            usize::try_from(index / AMAP_DATA_SIZE).map_err(|_| PstError::IntegerConversion)?;
989        let entry = amap_pages
990            .get_mut(amap_index)
991            .ok_or(PstError::AllocationMapPageNotFound(amap_index))?;
992        entry.free_space -= PAGE_SIZE as u64;
993
994        let bytes = entry.amap_page.map_bits_mut();
995
996        let bit_index = usize::try_from((index % AMAP_DATA_SIZE) / 64)
997            .map_err(|_| PstError::IntegerConversion)?;
998        let byte_index = bit_index / 8;
999        let bit_index = bit_index % 8;
1000
1001        if bit_index == 0 {
1002            bytes[byte_index] = 0xFF;
1003        } else {
1004            let mask = 0x80_u8 >> bit_index;
1005            let mask = mask | (mask - 1);
1006            bytes[byte_index] |= mask;
1007            bytes[byte_index + 1] |= !mask;
1008        }
1009
1010        Ok(())
1011    }
1012
1013    /// Mark a block at the given file offset and size as allocated.
1014    fn mark_block_allocation(
1015        index: u64,
1016        size: u16,
1017        amap_pages: &mut [AllocationMapPageInfo<Pst>],
1018    ) -> io::Result<()> {
1019        let index = index - AMAP_FIRST_OFFSET;
1020        let amap_index =
1021            usize::try_from(index / AMAP_DATA_SIZE).map_err(|_| PstError::IntegerConversion)?;
1022        let entry = amap_pages
1023            .get_mut(amap_index)
1024            .ok_or(PstError::AllocationMapPageNotFound(amap_index))?;
1025        let size = u64::from(block_size(
1026            size + <<Pst as PstFile>::BlockTrailer as BlockTrailerReadWrite>::SIZE,
1027        ));
1028        entry.free_space -= size;
1029
1030        let bytes = entry.amap_page.map_bits_mut();
1031
1032        let bit_start = usize::try_from((index % AMAP_DATA_SIZE) / 64)
1033            .map_err(|_| PstError::IntegerConversion)?;
1034        let bit_end =
1035            bit_start + usize::try_from(size / 64).map_err(|_| PstError::IntegerConversion)?;
1036        let byte_start = bit_start / 8;
1037        let bit_start = bit_start % 8;
1038        let byte_end = bit_end / 8;
1039        let bit_end = bit_end % 8;
1040
1041        if byte_start == byte_end {
1042            // The allocation fits in a single byte
1043            if bit_end > bit_start {
1044                let mask_start = 0x80_u8 >> bit_start;
1045                let mask_start = mask_start | (mask_start - 1);
1046                let mask_end = 0x80_u8 >> bit_end;
1047                let mask_end = !(mask_end | (mask_end - 1));
1048                let mask = mask_start & mask_end;
1049                bytes[byte_start] |= mask;
1050            }
1051            return Ok(());
1052        }
1053
1054        let byte_start = if bit_start == 0 {
1055            byte_start
1056        } else {
1057            let mask_start = 0x80_u8 >> bit_start;
1058            let mask_start = mask_start | (mask_start - 1);
1059            bytes[byte_start] |= mask_start;
1060            byte_start + 1
1061        };
1062
1063        if bit_end != 0 {
1064            let mask_end = 0x80_u8 >> bit_end;
1065            let mask_end = !(mask_end | (mask_end - 1));
1066            bytes[byte_end] |= mask_end;
1067        };
1068
1069        if byte_end > byte_start {
1070            for byte in bytes[byte_start..byte_end].iter_mut() {
1071                *byte = 0xFF;
1072            }
1073        }
1074
1075        Ok(())
1076    }
1077
1078    /// Initialize the density list at the beginning of a transaction if it is missing, corrupt, or
1079    /// the page ID doesn't match the next page ID in the header.
1080    fn ensure_density_list(&mut self) -> PstResult<()> {
1081        if let Ok(density_list) = self.density_list.as_ref() {
1082            if density_list.trailer().block_id() == self.header.next_page() {
1083                return Ok(());
1084            }
1085        }
1086
1087        let current_page = u32::try_from(
1088            (self.header.root().amap_last_index().index().into() - AMAP_FIRST_OFFSET)
1089                / AMAP_DATA_SIZE,
1090        )
1091        .map_err(|_| PstError::IntegerConversion)?;
1092        let block_id = self.header.next_page();
1093        let signature = PageType::DensityList
1094            .signature(ndb::page::DENSITY_LIST_FILE_OFFSET, block_id.into_u64());
1095        let trailer = <<Pst as PstFile>::PageTrailer as PageTrailerReadWrite>::new(
1096            PageType::DensityList,
1097            signature,
1098            block_id,
1099            0,
1100        );
1101        let density_list =
1102            <<Pst as PstFile>::DensityListPage as DensityListPageReadWrite<Pst>>::new(
1103                false,
1104                current_page,
1105                &[],
1106                trailer,
1107            )?;
1108
1109        self.density_list = Ok(density_list);
1110        Ok(())
1111    }
1112
1113    /// Similar to [`Self::ensure_density_list`], but instead of resetting the density list, it
1114    /// assumes that it's already initialized and only updates the page ID if it doesn't match.
1115    fn update_density_list_page_id(&mut self) -> PstResult<()> {
1116        let Ok(density_list) = self.density_list.as_ref() else {
1117            return Ok(());
1118        };
1119
1120        let next_page = self.header.next_page();
1121        if density_list.trailer().block_id() == next_page {
1122            return Ok(());
1123        }
1124
1125        let signature = PageType::DensityList
1126            .signature(ndb::page::DENSITY_LIST_FILE_OFFSET, next_page.into_u64());
1127        let trailer = <<Pst as PstFile>::PageTrailer as PageTrailerReadWrite>::new(
1128            PageType::DensityList,
1129            signature,
1130            next_page,
1131            0,
1132        );
1133
1134        let density_list =
1135            <<Pst as PstFile>::DensityListPage as DensityListPageReadWrite<Pst>>::new(
1136                density_list.backfill_complete(),
1137                density_list.current_page(),
1138                density_list.entries(),
1139                trailer,
1140            )?;
1141
1142        self.density_list = Ok(density_list);
1143        Ok(())
1144    }
1145
1146    fn read_node(&self, node: NodeId) -> io::Result<<Pst as PstFile>::NodeBTreeEntry> {
1147        let node_btree = *self.header.root().node_btree();
1148        let mut reader = self.reader.lock().map_err(|_| PstError::LockError)?;
1149        let reader = &mut *reader;
1150        let node_btree =
1151            <<Pst as PstFile>::NodeBTree as RootBTreeReadWrite>::read(reader, node_btree)?;
1152        let mut page_cache = self.node_cache.borrow_mut();
1153        let node_id: <Pst as PstFile>::BTreeKey = u32::from(node).into();
1154        let node = node_btree.find_entry(reader, node_id, &mut page_cache)?;
1155        Ok(node)
1156    }
1157
1158    fn read_block(&self, block: <Pst as PstFile>::BlockId) -> io::Result<Vec<u8>> {
1159        let encoding = self.header.crypt_method();
1160        let block_btree = *self.header.root().block_btree();
1161        let mut reader = self.reader.lock().map_err(|_| PstError::LockError)?;
1162        let reader = &mut *reader;
1163        let block_btree =
1164            <<Pst as PstFile>::BlockBTree as RootBTreeReadWrite>::read(reader, block_btree)?;
1165        let mut page_cache = self.block_cache.borrow_mut();
1166        let block = block_btree.find_entry(reader, block.search_key(), &mut page_cache)?;
1167        let block = DataTree::<Pst>::read(reader, encoding, &block)?;
1168        let mut block_cache = Default::default();
1169        let mut data = vec![];
1170        let _ = block
1171            .reader(
1172                reader,
1173                encoding,
1174                &block_btree,
1175                &mut page_cache,
1176                &mut block_cache,
1177            )?
1178            .read_to_end(&mut data)?;
1179        Ok(data)
1180    }
1181}
1182
1183pub fn open_store(path: impl AsRef<Path>) -> io::Result<Rc<dyn Store>> {
1184    Ok(if let Ok(pst_file) = UnicodePstFile::open(path.as_ref()) {
1185        UnicodeStore::read(Rc::new(pst_file))?
1186    } else {
1187        let pst_file = AnsiPstFile::open(path.as_ref())?;
1188        AnsiStore::read(Rc::new(pst_file))?
1189    })
1190}