PST File Format SDK v0.4
Loading...
Searching...
No Matches
writer.h
Go to the documentation of this file.
1
12
13#ifndef PSTSDK_NDB_WRITER_H
14#define PSTSDK_NDB_WRITER_H
15
16#include <algorithm>
17#include <cstring>
18#include <map>
19#include <set>
20#include <vector>
21
22#include "pstsdk/util/errors.h"
24
25#include "pstsdk/ndb/database.h"
26
27namespace pstsdk
28{
29
31
38template<typename T>
39class db_writer
40{
41public:
42 explicit db_writer(const std::shared_ptr<database_impl<T> >& db)
43 : m_db(db), m_dirty(false) { }
44
48 std::vector<byte> read_block(block_id bid);
49
58 void write_block(block_id bid, const std::vector<byte>& payload);
59
63 void nbt_remove(node_id nid);
64
72 void delete_node(node_id nid);
73
78 struct data_ref
79 {
80 block_id data;
81 block_id sub;
82 };
83
86 data_ref node_ref(node_id nid);
87
92 data_ref subnode_ref(block_id tree, node_id sub);
93
102 bool subnode_remove(block_id tree, node_id sub);
103
106 std::vector<block_id> external_blocks(block_id bid);
107
118 bool shrink_data_tail(block_id bid, size_t new_size);
119
120
128 ulonglong wipe_free_space();
129
130
135 void commit();
136
137private:
138 void zero_extent(ulonglong address, size_t size);
139 void bbt_remove(block_id bid);
140 void bbt_set_size(block_id bid, ushort cb);
141 void bbt_set_ref_count(block_id bid, ushort count);
143 void release_block(block_id bid);
144
146 static const size_t bt_meta = disk::page<T>::page_data_size - sizeof(T);
147
148 static uint bt_count(const std::vector<byte>& page) { return page[bt_meta]; }
149 static uint bt_entry_size(const std::vector<byte>& page) { return page[bt_meta + 2]; }
150 static uint bt_level(const std::vector<byte>& page) { return page[bt_meta + 3]; }
151
153 static T bt_key(const std::vector<byte>& page, uint index);
154 static void bt_set_key(std::vector<byte>& page, uint index, T key);
156 static ulonglong bt_child(const std::vector<byte>& page, uint index);
157 static int bt_search(const std::vector<byte>& page, T key);
158
159 std::vector<byte> read_page_raw(ulonglong address);
160 void write_page_raw(ulonglong address, std::vector<byte>& page);
161
162 void bt_descend(ulonglong root, T key, std::vector<ulonglong>& path, std::vector<uint>& indices);
163 void bt_remove(ulonglong root, T key);
165 void bt_propagate_key(const std::vector<ulonglong>& path, const std::vector<uint>& indices,
166 size_t depth, T key);
168 void bt_find(ulonglong root, T key, ulonglong& address, uint& index);
169
170 void collect_data_tree(block_id bid, std::vector<block_id>& blocks);
171 void collect_subnode_tree(block_id bid, std::vector<block_id>& blocks);
172
173 void block_children(block_id bid, std::vector<block_id>& children);
174
176 void plan_release(block_id root, std::map<block_id, ushort>& remaining,
177 std::map<block_id, block_info>& info, std::vector<block_id>& order);
179 void apply_release(const std::map<block_id, ushort>& remaining,
180 const std::map<block_id, block_info>& info,
181 const std::vector<block_id>& order);
182
183 void collect_pages(ulonglong address, std::vector<ulonglong>& pages);
185 size_t logical_size(block_id bid);
186 bool looks_like_page(ulonglong address);
187
188 ulonglong nbt_root() const { return m_db->get_header().root_info.brefNBT.ib; }
189 ulonglong bbt_root() const { return m_db->get_header().root_info.brefBBT.ib; }
190
191 void stamp_header_crc();
192
193 std::shared_ptr<database_impl<T> > m_db;
194 bool m_dirty;
195};
196
198
199} // end pstsdk namespace
200
202template<typename T>
203inline std::vector<pstsdk::byte> pstsdk::db_writer<T>::read_block(block_id bid)
204{
205 block_info bi = m_db->lookup_block_info(bid);
206 std::vector<byte> buffer = m_db->read_block_data(bi);
207 buffer.resize(bi.size);
208
209 if(bi.size == 0 || !disk::bid_is_external(bi.id))
210 return buffer;
211
212 if(m_db->get_header().bCryptMethod == disk::crypt_method_permute)
213 disk::permute(&buffer[0], bi.size, false);
214 else if(m_db->get_header().bCryptMethod == disk::crypt_method_cyclic)
215 disk::cyclic(&buffer[0], bi.size, (ulong)bi.id);
216
217 return buffer;
218}
219
220template<typename T>
221inline void pstsdk::db_writer<T>::write_block(block_id bid, const std::vector<byte>& payload)
222{
223 block_info bi = m_db->lookup_block_info(bid);
224
225 if(payload.size() > bi.size)
226 throw can_not_resize("write_block cannot grow a block");
227
228 if(bi.ref_count > disk::block_unreferenced + 1)
229 throw shared_block("cannot edit a block in place while it is shared");
230
231 const size_t new_cb = payload.size();
232 const size_t old_aligned = disk::align_disk<T>(bi.size);
233 const size_t new_aligned = disk::align_disk<T>(new_cb);
234
235 std::vector<byte> buffer(new_aligned, 0);
236 if(new_cb > 0)
237 {
238 memcpy(&buffer[0], &payload[0], new_cb);
239
240 // extended and subnode blocks are stored plain; only external blocks
241 // carry the store's encoding
242 if(disk::bid_is_external(bi.id))
243 {
244 if(m_db->get_header().bCryptMethod == disk::crypt_method_permute)
245 disk::permute(&buffer[0], (ulong)new_cb, true);
246 else if(m_db->get_header().bCryptMethod == disk::crypt_method_cyclic)
247 disk::cyclic(&buffer[0], (ulong)new_cb, (ulong)bi.id);
248 }
249 }
250
251 disk::block_trailer<T>* bt = reinterpret_cast<disk::block_trailer<T>*>(
252 &buffer[0] + new_aligned - sizeof(disk::block_trailer<T>));
253 bt->cb = (ushort)new_cb;
254 bt->signature = disk::compute_signature(bi.id, bi.address);
255 bt->bid = bi.id;
256 // over the encoded bytes: read_block_data checks the CRC before
257 // read_external_block gets a chance to decode
258 bt->crc = disk::compute_crc(&buffer[0], (ulong)new_cb);
259
260 // a shrink past a slot boundary moves the trailer, so the old one has to go
261 if(new_aligned < old_aligned)
262 zero_extent(bi.address, old_aligned);
263
264 m_db->get_file().write(buffer, bi.address);
265 m_dirty = true;
266
267 if(new_cb != bi.size)
268 bbt_set_size(bid, (ushort)new_cb);
269}
270
271template<typename T>
272inline T pstsdk::db_writer<T>::bt_key(const std::vector<byte>& page, uint index)
273{
274 T key;
275 memcpy(&key, &page[index * bt_entry_size(page)], sizeof(T));
276 return key;
277}
278
279template<typename T>
280inline void pstsdk::db_writer<T>::bt_set_key(std::vector<byte>& page, uint index, T key)
281{
282 memcpy(&page[index * bt_entry_size(page)], &key, sizeof(T));
283}
284
285template<typename T>
286inline pstsdk::ulonglong pstsdk::db_writer<T>::bt_child(const std::vector<byte>& page, uint index)
287{
288 T address;
289 memcpy(&address, &page[index * bt_entry_size(page) + 2 * sizeof(T)], sizeof(T));
290 return address;
291}
292
293// Mirrors btree_node::binary_search: the last entry whose key is <= the target, or
294// -1 when the target sorts below everything on the page.
295template<typename T>
296inline int pstsdk::db_writer<T>::bt_search(const std::vector<byte>& page, T key)
297{
298 uint start = 0;
299 uint end = bt_count(page);
300 uint mid = (start + end) / 2;
301
302 while(mid < end)
303 {
304 T current = bt_key(page, mid);
305
306 if(current < key)
307 start = mid + 1;
308 else if(current == key)
309 return (int)mid;
310 else
311 end = mid;
312
313 mid = (start + end) / 2;
314 }
315
316 return (int)mid - 1;
317}
318
319template<typename T>
320inline std::vector<pstsdk::byte> pstsdk::db_writer<T>::read_page_raw(ulonglong address)
321{
322 std::vector<byte> page(disk::page_size);
323 m_db->get_file().read(page, address);
324 return page;
325}
326
327template<typename T>
328inline void pstsdk::db_writer<T>::write_page_raw(ulonglong address, std::vector<byte>& page)
329{
330 disk::page<T>* p = reinterpret_cast<disk::page<T>*>(&page[0]);
331 p->trailer.signature = disk::compute_signature((T)p->trailer.bid, (T)address);
332 p->trailer.crc = disk::compute_crc(&page[0], disk::page<T>::page_data_size);
333
334 m_db->get_file().write(page, address);
335
336 // the store caches its BTree roots forever and never invalidates them, so any
337 // page edit has to drop the cache or subsequent lookups read the old tree
338 m_db->reset_page_cache();
339 m_dirty = true;
340}
341
342template<typename T>
343inline void pstsdk::db_writer<T>::bt_descend(ulonglong root, T key,
344 std::vector<ulonglong>& path,
345 std::vector<uint>& indices)
346{
347 ulonglong address = root;
348 uint above = 0;
349 bool descending = false;
350
351 for(;;)
352 {
353 std::vector<byte> page = read_page_raw(address);
354
355 // Levels strictly decrease on the way down. A tree that says otherwise is
356 // corrupt, and following it would never terminate.
357 if(descending && bt_level(page) >= above)
358 throw database_corrupt("btree level did not decrease");
359
360 above = bt_level(page);
361 descending = true;
362
363 int position = bt_search(page, key);
364
365 if(position < 0)
366 throw key_not_found<T>(key);
367
368 path.push_back(address);
369 indices.push_back((uint)position);
370
371 if(bt_level(page) == 0)
372 {
373 if(bt_key(page, (uint)position) != key)
374 throw key_not_found<T>(key);
375 return;
376 }
377
378 address = bt_child(page, (uint)position);
379 }
380}
381
382template<typename T>
383inline void pstsdk::db_writer<T>::bt_propagate_key(const std::vector<ulonglong>& path,
384 const std::vector<uint>& indices,
385 size_t depth, T key)
386{
387 while(depth > 0)
388 {
389 size_t parent = depth - 1;
390 std::vector<byte> page = read_page_raw(path[parent]);
391 bt_set_key(page, indices[parent], key);
392 write_page_raw(path[parent], page);
393
394 // only a change to the parent's own first entry keeps rippling upward
395 if(indices[parent] != 0)
396 return;
397
398 depth = parent;
399 }
400}
401
402template<typename T>
403inline void pstsdk::db_writer<T>::bt_remove(ulonglong root, T key)
404{
405 std::vector<ulonglong> path;
406 std::vector<uint> indices;
407 bt_descend(root, key, path, indices);
408
409 // Bail before touching anything if this would cascade all the way up: the
410 // removal below zeroes pages as it climbs, so discovering it at the root would
411 // leave the tree half dismantled.
412 bool empties_root = true;
413 for(size_t depth = 0; depth < path.size() && empties_root; ++depth)
414 {
415 std::vector<byte> page = read_page_raw(path[depth]);
416 empties_root = bt_count(page) == 1;
417 }
418 if(empties_root)
419 throw database_corrupt("btree root emptied");
420
421 for(size_t depth = path.size(); depth-- > 0;)
422 {
423 std::vector<byte> page = read_page_raw(path[depth]);
424 const uint count = bt_count(page);
425 const uint entry_size = bt_entry_size(page);
426 const uint index = indices[depth];
427
428 if(index + 1 < count)
429 memmove(&page[index * entry_size], &page[(index + 1) * entry_size],
430 (count - index - 1) * entry_size);
431 memset(&page[(count - 1) * entry_size], 0, entry_size);
432 page[bt_meta] = (byte)(count - 1);
433
434 if(count > 1)
435 {
436 write_page_raw(path[depth], page);
437
438 // [MS-PST] 2.2.2.7.7.2 defines a nonleaf key as its child's first key,
439 // so dropping entry zero leaves every ancestor naming a key that moved
440 if(index == 0)
441 bt_propagate_key(path, indices, depth, bt_key(page, 0));
442
443 return;
444 }
445
446 // An empty page is not merely untidy: btree_node_nonleaf::first indexes
447 // child zero unguarded and last underflows num_values() - 1, so end() is
448 // poisoned as soon as one exists. Drop it from its parent instead.
449 if(depth == 0)
450 throw database_corrupt("btree root emptied");
451
452 zero_extent(path[depth], disk::page_size);
453 }
454}
455
456template<typename T>
457inline void pstsdk::db_writer<T>::bt_find(ulonglong root, T key, ulonglong& address, uint& index)
458{
459 std::vector<ulonglong> path;
460 std::vector<uint> indices;
461 bt_descend(root, key, path, indices);
462
463 address = path.back();
464 index = indices.back();
465}
466
467template<typename T>
469{
470 bt_remove(nbt_root(), (T)nid);
471}
472
473template<typename T>
475{
476 bt_remove(bbt_root(), (T)(bid & ~(block_id(disk::block_id_attached_bit))));
477}
478
479template<typename T>
481{
482 ulonglong address;
483 uint index;
484 bt_find(bbt_root(), (T)(bid & ~(block_id(disk::block_id_attached_bit))), address, index);
485
486 std::vector<byte> page = read_page_raw(address);
487 disk::bbt_leaf_entry<T>* entry =
488 reinterpret_cast<disk::bbt_leaf_entry<T>*>(&page[index * bt_entry_size(page)]);
489 entry->size = cb;
490 write_page_raw(address, page);
491}
492
493template<typename T>
495{
496 ulonglong address;
497 uint index;
498 bt_find(bbt_root(), (T)(bid & ~(block_id(disk::block_id_attached_bit))), address, index);
499
500 std::vector<byte> page = read_page_raw(address);
501 disk::bbt_leaf_entry<T>* entry =
502 reinterpret_cast<disk::bbt_leaf_entry<T>*>(&page[index * bt_entry_size(page)]);
503 entry->ref_count = count;
504 write_page_raw(address, page);
505}
506
507template<typename T>
508inline void pstsdk::db_writer<T>::collect_data_tree(block_id bid, std::vector<block_id>& blocks)
509{
510 if(bid == 0)
511 return;
512
513 blocks.push_back(bid);
514
515 if(disk::bid_is_external(bid))
516 return;
517
518 std::vector<byte> raw = read_block(bid);
519 const disk::extended_block<T>* xblock =
520 reinterpret_cast<const disk::extended_block<T>*>(&raw[0]);
521
522 if(xblock->block_type != disk::block_type_extended)
523 throw unexpected_block("expected an extended block in a data tree");
524
525 for(ushort i = 0; i < xblock->count; ++i)
526 collect_data_tree(xblock->bid[i], blocks);
527}
528
529template<typename T>
530inline void pstsdk::db_writer<T>::collect_subnode_tree(block_id bid, std::vector<block_id>& blocks)
531{
532 if(bid == 0)
533 return;
534
535 blocks.push_back(bid);
536
537 std::vector<byte> raw = read_block(bid);
538 const disk::sub_block<T, disk::sub_leaf_entry<T> >* sblock =
539 reinterpret_cast<const disk::sub_block<T, disk::sub_leaf_entry<T> >*>(&raw[0]);
540
541 if(sblock->block_type != disk::block_type_sub)
542 throw unexpected_block("expected a subnode block in a subnode tree");
543
544 if(sblock->level == 0)
545 {
546 for(ushort i = 0; i < sblock->count; ++i)
547 {
548 // a subnode owns a data tree and a subnode tree of its own, which is
549 // how attachments and embedded messages hang off a message
550 collect_data_tree(sblock->entry[i].data, blocks);
551 collect_subnode_tree(sblock->entry[i].sub, blocks);
552 }
553
554 return;
555 }
556
557 const disk::sub_block<T, disk::sub_nonleaf_entry<T> >* nonleaf =
558 reinterpret_cast<const disk::sub_block<T, disk::sub_nonleaf_entry<T> >*>(&raw[0]);
559
560 for(ushort i = 0; i < nonleaf->count; ++i)
561 collect_subnode_tree(nonleaf->entry[i].sub_block_bid, blocks);
562}
563
564template<typename T>
565inline std::vector<pstsdk::block_id> pstsdk::db_writer<T>::external_blocks(block_id bid)
566{
567 std::vector<block_id> tree;
568 collect_data_tree(bid, tree);
569
570 std::vector<block_id> external;
571 for(size_t i = 0; i < tree.size(); ++i)
572 if(disk::bid_is_external(tree[i]))
573 external.push_back(tree[i]);
574
575 return external;
576}
577
578
579template<typename T>
581{
582 const node_info ni = m_db->lookup_node_info(nid);
583 data_ref ref;
584 ref.data = ni.data_bid;
585 ref.sub = ni.sub_bid;
586 return ref;
587}
588
589template<typename T>
590inline typename pstsdk::db_writer<T>::data_ref
592{
593 std::vector<block_id> pending;
594 std::set<block_id> seen;
595 pending.push_back(tree);
596
597 while(!pending.empty())
598 {
599 const block_id bid = pending.back();
600 pending.pop_back();
601
602 if(bid == 0)
603 continue;
604
605 // a subnode tree that names a block twice would otherwise be walked forever
606 if(!seen.insert(bid & ~(block_id(disk::block_id_attached_bit))).second)
607 continue;
608
609 std::vector<byte> raw = read_block(bid);
610 const disk::sub_block<T, disk::sub_leaf_entry<T> >* leaf =
611 reinterpret_cast<const disk::sub_block<T, disk::sub_leaf_entry<T> >*>(&raw[0]);
612
613 if(leaf->level != 0)
614 {
615 const disk::sub_block<T, disk::sub_nonleaf_entry<T> >* nonleaf =
616 reinterpret_cast<const disk::sub_block<T, disk::sub_nonleaf_entry<T> >*>(&raw[0]);
617
618 for(ushort i = 0; i < nonleaf->count; ++i)
619 pending.push_back(nonleaf->entry[i].sub_block_bid);
620
621 continue;
622 }
623
624 for(ushort i = 0; i < leaf->count; ++i)
625 {
626 if(leaf->entry[i].nid != sub)
627 continue;
628
629 data_ref ref;
630 ref.data = leaf->entry[i].data;
631 ref.sub = leaf->entry[i].sub;
632 return ref;
633 }
634 }
635
636 throw key_not_found<node_id>(sub);
637}
638
639template<typename T>
641{
642 if(tree == 0)
643 throw key_not_found<node_id>(sub);
644
645 std::vector<byte> raw = read_block(tree);
646 disk::sub_block<T, disk::sub_leaf_entry<T> >* leaf =
647 reinterpret_cast<disk::sub_block<T, disk::sub_leaf_entry<T> >*>(&raw[0]);
648
649 if(leaf->level != 0)
650 {
651 disk::sub_block<T, disk::sub_nonleaf_entry<T> >* nonleaf =
652 reinterpret_cast<disk::sub_block<T, disk::sub_nonleaf_entry<T> >*>(&raw[0]);
653
654 // entries are ordered by nid, so the subtree that can hold sub is the
655 // last one whose key does not exceed it
656 ushort i = 0;
657 while(i + 1 < nonleaf->count && nonleaf->entry[i + 1].nid_key <= sub)
658 ++i;
659
660 if(nonleaf->count == 0 || sub < nonleaf->entry[0].nid_key)
661 throw key_not_found<node_id>(sub);
662
663 {
664 block_id child = nonleaf->entry[i].sub_block_bid;
665
666 if(!subnode_remove(child, sub))
667 return false;
668
669 if(i + 1 < nonleaf->count)
670 memmove(&nonleaf->entry[i], &nonleaf->entry[i + 1],
671 (nonleaf->count - i - 1) * sizeof(disk::sub_nonleaf_entry<T>));
672 memset(&nonleaf->entry[nonleaf->count - 1], 0, sizeof(disk::sub_nonleaf_entry<T>));
673 --nonleaf->count;
674
675 if(nonleaf->count == 0)
676 return true;
677
678 write_block(tree, raw);
679 release_block(child);
680 return false;
681 }
682 }
683
684 for(ushort i = 0; i < leaf->count; ++i)
685 {
686 if(leaf->entry[i].nid != sub)
687 continue;
688
689 const block_id data = leaf->entry[i].data;
690 const block_id owned = leaf->entry[i].sub;
691
692 if(i + 1 < leaf->count)
693 memmove(&leaf->entry[i], &leaf->entry[i + 1],
694 (leaf->count - i - 1) * sizeof(disk::sub_leaf_entry<T>));
695 memset(&leaf->entry[leaf->count - 1], 0, sizeof(disk::sub_leaf_entry<T>));
696 --leaf->count;
697
698 // the entry has to reach disk before anything it named is released, or
699 // the store is left pointing at scrubbed blocks
700 write_block(tree, raw);
701
702 std::map<block_id, ushort> remaining;
703 std::map<block_id, block_info> info;
704 std::vector<block_id> order;
705 plan_release(data, remaining, info, order);
706 plan_release(owned, remaining, info, order);
707 apply_release(remaining, info, order);
708
709 return leaf->count == 0;
710 }
711
712 throw key_not_found<node_id>(sub);
713}
714
715template<typename T>
716inline bool pstsdk::db_writer<T>::shrink_data_tail(block_id bid, size_t new_size)
717{
718 if(disk::bid_is_external(bid))
719 {
720 if(new_size == 0)
721 return true;
722
723 std::vector<byte> payload = read_block(bid);
724 payload.resize(new_size);
725 write_block(bid, payload);
726 return false;
727 }
728
729 std::vector<byte> raw = read_block(bid);
730 disk::extended_block<T>* xblock = reinterpret_cast<disk::extended_block<T>*>(&raw[0]);
731
732 if(xblock->block_type != disk::block_type_extended)
733 throw unexpected_block("expected an extended block in a data tree");
734
735 if(xblock->count == 0)
736 return true;
737
738 const ushort last = (ushort)(xblock->count - 1);
739 const block_id child = xblock->bid[last];
740 const size_t was = logical_size(child);
741
742 if(!shrink_data_tail(child, new_size))
743 {
744 xblock->total_size -= (ulong)(was - new_size);
745 write_block(bid, raw);
746 return false;
747 }
748
749 // The child emptied. If it was the only one the whole tree goes, and the
750 // caller releases the root rather than this dismantling it halfway.
751 if(xblock->count == 1)
752 return true;
753
754 xblock->total_size -= (ulong)was;
755 xblock->bid[last] = 0;
756 --xblock->count;
757
758 write_block(bid, raw);
759 release_block(child);
760 return false;
761}
762
763// A block's BBT size is its own length. For an extended block that is the bid
764// array, not the bytes it stands for, which is what the parent's total_size
765// counts.
766template<typename T>
768{
769 if(disk::bid_is_external(bid))
770 return m_db->lookup_block_info(bid).size;
771
772 std::vector<byte> raw = read_block(bid);
773 return reinterpret_cast<const disk::extended_block<T>*>(&raw[0])->total_size;
774}
775
776template<typename T>
777inline void pstsdk::db_writer<T>::block_children(block_id bid, std::vector<block_id>& children)
778{
779 if(disk::bid_is_external(bid))
780 return;
781
782 std::vector<byte> raw = read_block(bid);
783
784 if(raw[0] == disk::block_type_extended)
785 {
786 const disk::extended_block<T>* xblock =
787 reinterpret_cast<const disk::extended_block<T>*>(&raw[0]);
788
789 for(ushort i = 0; i < xblock->count; ++i)
790 children.push_back(xblock->bid[i]);
791
792 return;
793 }
794
795 if(raw[0] != disk::block_type_sub)
796 throw unexpected_block("unknown internal block type");
797
798 const disk::sub_block<T, disk::sub_leaf_entry<T> >* leaf =
799 reinterpret_cast<const disk::sub_block<T, disk::sub_leaf_entry<T> >*>(&raw[0]);
800
801 if(leaf->level == 0)
802 {
803 for(ushort i = 0; i < leaf->count; ++i)
804 {
805 children.push_back(leaf->entry[i].data);
806 children.push_back(leaf->entry[i].sub);
807 }
808
809 return;
810 }
811
812 const disk::sub_block<T, disk::sub_nonleaf_entry<T> >* nonleaf =
813 reinterpret_cast<const disk::sub_block<T, disk::sub_nonleaf_entry<T> >*>(&raw[0]);
814
815 for(ushort i = 0; i < nonleaf->count; ++i)
816 children.push_back(nonleaf->entry[i].sub_block_bid);
817}
818
819// A block that keeps a reference keeps its children with it, so the walk stops at
820// anything that survives. Descending past one and freeing what it points at would
821// pull the data out from under whoever still owns it. Real stores do share
822// extended and subnode blocks, so this is not hypothetical.
823template<typename T>
825 std::map<block_id, ushort>& remaining,
826 std::map<block_id, block_info>& info,
827 std::vector<block_id>& order)
828{
829 std::vector<block_id> pending;
830 pending.push_back(root);
831
832 while(!pending.empty())
833 {
834 const block_id raw = pending.back();
835 pending.pop_back();
836
837 if(raw == 0)
838 continue;
839
840 const block_id bid = raw & ~(block_id(disk::block_id_attached_bit));
841
842 if(remaining.find(bid) == remaining.end())
843 {
844 const block_info bi = m_db->lookup_block_info(bid);
845 info[bid] = bi;
846 remaining[bid] = bi.ref_count;
847 order.push_back(bid);
848 }
849
850 if(remaining[bid] <= disk::block_unreferenced)
851 throw database_corrupt("block released more often than it is referenced");
852
853 --remaining[bid];
854
855 if(remaining[bid] > disk::block_unreferenced)
856 continue;
857
858 block_children(bid, pending);
859 }
860}
861
862template<typename T>
863inline void pstsdk::db_writer<T>::apply_release(const std::map<block_id, ushort>& remaining,
864 const std::map<block_id, block_info>& info,
865 const std::vector<block_id>& order)
866{
867 for(size_t i = 0; i < order.size(); ++i)
868 {
869 const block_id bid = order[i];
870 const ushort count = remaining.find(bid)->second;
871
872 if(count > disk::block_unreferenced)
873 {
874 bbt_set_ref_count(bid, count);
875 continue;
876 }
877
878 const block_info& bi = info.find(bid)->second;
879 const ulonglong address = bi.address;
880 const size_t extent = disk::align_disk<T>(bi.size);
881
882 bbt_remove(bid);
883 zero_extent(address, extent);
884 }
885}
886
887template<typename T>
889{
890 std::map<block_id, ushort> remaining;
891 std::map<block_id, block_info> info;
892 std::vector<block_id> order;
893
894 plan_release(bid, remaining, info, order);
895 apply_release(remaining, info, order);
896}
897
898template<typename T>
900{
901 const node_info ni = m_db->lookup_node_info(nid);
902
903 std::map<block_id, ushort> remaining;
904 std::map<block_id, block_info> info;
905 std::vector<block_id> order;
906
907 plan_release(ni.data_bid, remaining, info, order);
908 plan_release(ni.sub_bid, remaining, info, order);
909
910 nbt_remove(nid);
911 apply_release(remaining, info, order);
912}
913
914template<typename T>
915inline void pstsdk::db_writer<T>::collect_pages(ulonglong address, std::vector<ulonglong>& pages)
916{
917 pages.push_back(address);
918
919 std::vector<byte> page = read_page_raw(address);
920 if(bt_level(page) == 0)
921 return;
922
923 for(uint i = 0; i < bt_count(page); ++i)
924 collect_pages(bt_child(page, i), pages);
925}
926
927// Rather than working out where every kind of allocation map page is supposed to
928// sit, ask the bytes. A page carries its type twice and a CRC over its contents,
929// so anything that validates is a real page of some kind and is left alone. That
930// covers the AMaps, the DList and the deprecated PMap and FMap pages without
931// this code having to know their spacing.
932template<typename T>
934{
935 std::vector<byte> page(disk::page_size);
936
937 try { m_db->get_file().read(page, address); }
938 catch(std::out_of_range&) { return false; }
939
940 const disk::page<T>* p = reinterpret_cast<const disk::page<T>*>(&page[0]);
941
942 if(p->trailer.page_type != p->trailer.page_type_repeat)
943 return false;
944
945 switch(p->trailer.page_type)
946 {
954 break;
955 default:
956 return false;
957 }
958
959 return p->trailer.crc == disk::compute_crc(&page[0], disk::page<T>::page_data_size);
960}
961
962template<typename T>
964{
965 const ulonglong eof = m_db->get_header().root_info.ibFileEof;
967
968 std::vector<std::pair<ulonglong, ulonglong> > live;
969
970 // the header, the DList and everything reserved ahead of the first AMap
971 live.push_back(std::make_pair((ulonglong)0, first));
972
973 std::vector<ulonglong> pages;
974 collect_pages(nbt_root(), pages);
975 collect_pages(bbt_root(), pages);
976 for(size_t i = 0; i < pages.size(); ++i)
977 live.push_back(std::make_pair(pages[i], pages[i] + disk::page_size));
978
979 std::shared_ptr<bbt_page> root = m_db->read_bbt_root();
980 for(const_blockinfo_iterator i = root->begin(); i != root->end(); ++i)
981 live.push_back(std::make_pair((*i).address,
982 (*i).address + disk::align_disk<T>((*i).size)));
983
984 std::sort(live.begin(), live.end());
985
986 std::vector<std::pair<ulonglong, ulonglong> > keep;
987 ulonglong at = 0;
988
989 for(size_t i = 0; i <= live.size(); ++i)
990 {
991 const ulonglong stop = i < live.size() ? live[i].first : eof;
992
993 ulonglong page = at < first ? first : at;
994 if((page - first) % disk::page_size)
995 page += disk::page_size - ((page - first) % disk::page_size);
996
997 for(; page + disk::page_size <= stop; page += disk::page_size)
998 if(looks_like_page(page))
999 keep.push_back(std::make_pair(page, page + disk::page_size));
1000
1001 if(i < live.size() && live[i].second > at)
1002 at = live[i].second;
1003 }
1004
1005 live.insert(live.end(), keep.begin(), keep.end());
1006 std::sort(live.begin(), live.end());
1007
1008 ulonglong wiped = 0;
1009 at = 0;
1010
1011 for(size_t i = 0; i < live.size(); ++i)
1012 {
1013 if(live[i].first > at)
1014 {
1015 zero_extent(at, (size_t)(live[i].first - at));
1016 wiped += live[i].first - at;
1017 }
1018
1019 if(live[i].second > at)
1020 at = live[i].second;
1021 }
1022
1023 if(eof > at)
1024 {
1025 zero_extent(at, (size_t)(eof - at));
1026 wiped += eof - at;
1027 }
1028
1029 return wiped;
1030}
1031
1032template<typename T>
1033inline void pstsdk::db_writer<T>::zero_extent(ulonglong address, size_t size)
1034{
1035 if(size == 0)
1036 return;
1037
1038 const size_t chunk = 1024 * 1024;
1039 std::vector<byte> zeroes(size < chunk ? size : chunk, 0);
1040
1041 for(ulonglong at = address; at < address + size; at += zeroes.size())
1042 {
1043 const ulonglong left = address + size - at;
1044 if(left < zeroes.size())
1045 zeroes.resize((size_t)left);
1046
1047 m_db->get_file().write(zeroes, at);
1048 }
1049
1050 m_dirty = true;
1051}
1052
1053template<typename T>
1054inline void pstsdk::db_writer<T>::commit()
1055{
1056 if(!m_dirty)
1057 return;
1058
1059 disk::header<T>& h = m_db->get_header();
1060 h.root_info.fAMapValid = disk::invalid_amap;
1061 ++h.dwUnique;
1062 stamp_header_crc();
1063
1064 std::vector<byte> buffer(sizeof(disk::header<T>));
1065 memcpy(&buffer[0], &h, sizeof(disk::header<T>));
1066 m_db->get_file().write(buffer, 0);
1067
1068 m_db->reset_page_cache();
1069 m_dirty = false;
1070}
1071
1072template<>
1074{
1075 disk::header<ulong>& h = m_db->get_header();
1076 h.dwCRCPartial = disk::compute_crc(
1077 reinterpret_cast<byte*>(&h) + disk::header_crc_locations<ulong>::start,
1078 disk::header_crc_locations<ulong>::length);
1079}
1080
1081template<>
1083{
1084 disk::header<ulonglong>& h = m_db->get_header();
1085 h.dwCRCPartial = disk::compute_crc(
1086 reinterpret_cast<byte*>(&h) + disk::header_crc_locations<ulonglong>::partial_start,
1087 disk::header_crc_locations<ulonglong>::partial_length);
1088 h.dwCRCFull = disk::compute_crc(
1089 reinterpret_cast<byte*>(&h) + disk::header_crc_locations<ulonglong>::full_start,
1090 disk::header_crc_locations<ulonglong>::full_length);
1091}
1093
1094#endif
Contains references to other bth_node allocations.
Definition heap.h:364
const_iterator begin() const
Returns a STL style iterator positioned at the first entry.
Definition btree.h:85
Database implementation.
The exceptions used by pstsdk.
bool bid_is_external(T bid)
Determines if a block is external or not.
Definition disk.h:1009
@ block_type_sub
A subnode block type.
Definition disk.h:964
@ block_type_extended
An extended block type.
Definition disk.h:963
@ invalid_amap
The AMaps are stale and must be rebuilt before use.
Definition disk.h:120
@ crypt_method_permute
The permute method is used in this file.
Definition disk.h:99
@ crypt_method_cyclic
The cyclic method is used in this file.
Definition disk.h:100
const size_t page_size
Size of all pages in the file in bytes, including the page trailer.
Definition disk.h:539
const size_t first_amap_page_location
The location of the first AMap page in the file.
Definition disk.h:630
const ushort block_unreferenced
The value of bbt_leaf_entry::ref_count for an unreferenced block.
Definition disk.h:819
@ page_type_amap
An AMap (Allocation Map) page.
Definition disk.h:552
@ page_type_pmap
Definition disk.h:551
@ page_type_fmap
Definition disk.h:550
@ page_type_fpmap
Definition disk.h:553
@ page_type_nbt
A NBT (Nodes BTree) page.
Definition disk.h:549
@ page_type_bbt
A BBT (Blocks BTree) page.
Definition disk.h:548
@ page_type_dlist
A DList (Density List) page.
Definition disk.h:554
void permute(void *pdata, ulong cb, bool encrypt)
Modifies the data block in place, according to the permute method.
Definition disk.h:1667
void cyclic(void *pdata, ulong cb, ulong key)
Modifies the data block in place, according to the cyclic method.
Definition disk.h:1680
boost::uint64_t ulonglong
Definition primitives.h:70
boost::uint8_t byte
Definition primitives.h:72
boost::uint32_t uint
Definition primitives.h:67
ulong node_id
Definition primitives.h:86
boost::uint32_t ulong
Definition primitives.h:68
ulonglong block_id
Definition primitives.h:87
boost::uint16_t ushort
Definition primitives.h:73
ushort compute_signature(T id, T address)
Calculate the signature of an item.
ulong compute_crc(const void *pdata, ulong cb)
Compute the CRC of a block of data.
Definition disk.h:1656
const uint block_id_attached_bit
The attached bit indicates a block is referenced in memory This is an implementation detail,...
Definition disk.h:990
Contains the definition of all in memory representations of disk structures.
Definition disk.h:19
const_btree_node_iter< block_id, block_info > const_blockinfo_iterator
Primitive structures defined by MS-PST and MAPI.
static const size_t page_data_size
Amount of usable space in a page.
Definition disk.h:612