PST File Format SDK v0.4
Loading...
Searching...
No Matches
writer.h
Go to the documentation of this file.
1
14
15#ifndef PSTSDK_LTP_WRITER_H
16#define PSTSDK_LTP_WRITER_H
17
18#include <algorithm>
19#include <cstring>
20#include <vector>
21
22#include "pstsdk/util/errors.h"
24
25#include "pstsdk/ndb/writer.h"
26
27namespace pstsdk
28{
29
31
38template<typename T>
39class heap_writer
40{
41public:
42 heap_writer(db_writer<T>& writer, node_id nid)
43 : m_writer(writer), m_ref(writer.node_ref(nid)),
44 m_blocks(writer.external_blocks(m_ref.data)) { }
45
47 heap_writer(db_writer<T>& writer, const typename db_writer<T>::data_ref& ref)
48 : m_writer(writer), m_ref(ref), m_blocks(writer.external_blocks(ref.data)) { }
49
51 const typename db_writer<T>::data_ref& ref() const { return m_ref; }
52
54 heap_id root_id();
55
57 std::vector<byte> read_alloc(heap_id id);
58
60 void write_alloc(heap_id id, const std::vector<byte>& data);
61
69 void shrink_alloc(heap_id id, size_t size);
70
71private:
72 std::vector<byte> read_block(uint page) { return m_writer.read_block(m_blocks.at(page)); }
73 void write_block(uint page, const std::vector<byte>& data)
74 { m_writer.write_block(m_blocks.at(page), data); }
75 void locate(heap_id id, uint& page, size_t& offset, size_t& size);
76
77 db_writer<T>& m_writer;
78 typename db_writer<T>::data_ref m_ref;
79 std::vector<block_id> m_blocks;
80};
81
89template<typename T>
90void pc_set_inline(db_writer<T>& writer, node_id nid, prop_id id, ulong value);
91
102template<typename T>
103void tc_remove_row(db_writer<T>& writer, node_id nid, row_id id);
104
110template<typename T>
111void tc_remove_row(db_writer<T>& writer, const typename db_writer<T>::data_ref& table, row_id id);
112
114
115} // end pstsdk namespace
116
118template<typename T>
120{
121 std::vector<byte> first = read_block(0);
122 const disk::heap_first_header* header =
123 reinterpret_cast<const disk::heap_first_header*>(&first[0]);
124
125 if(header->signature != disk::heap_signature)
126 throw database_corrupt("invalid heap signature");
127
128 return header->root_id;
129}
130
131template<typename T>
132inline void pstsdk::heap_writer<T>::locate(heap_id id, uint& page, size_t& offset, size_t& size)
133{
134 page = get_heap_page(id);
135 const uint index = get_heap_index(id);
136
137 std::vector<byte> block = read_block(page);
138 const disk::heap_page_header* header =
139 reinterpret_cast<const disk::heap_page_header*>(&block[0]);
140 const disk::heap_page_map* map =
141 reinterpret_cast<const disk::heap_page_map*>(&block[header->page_map_offset]);
142
143 if(index >= map->num_allocs)
144 throw std::length_error("heap index past num_allocs");
145
146 offset = map->allocs[index];
147 size = map->allocs[index + 1] - map->allocs[index];
148}
149
150template<typename T>
151inline std::vector<pstsdk::byte> pstsdk::heap_writer<T>::read_alloc(heap_id id)
152{
153 uint page;
154 size_t offset;
155 size_t size;
156 locate(id, page, offset, size);
157
158 std::vector<byte> block = read_block(page);
159 return std::vector<byte>(block.begin() + offset, block.begin() + offset + size);
160}
161
162template<typename T>
163inline void pstsdk::heap_writer<T>::write_alloc(heap_id id, const std::vector<byte>& data)
164{
165 uint page;
166 size_t offset;
167 size_t size;
168 locate(id, page, offset, size);
169
170 if(data.size() != size)
171 throw can_not_resize("write_alloc cannot change an allocation's size");
172
173 std::vector<byte> block = read_block(page);
174 if(size > 0)
175 memcpy(&block[offset], &data[0], size);
176 write_block(page, block);
177}
178
179template<typename T>
180inline void pstsdk::heap_writer<T>::shrink_alloc(heap_id id, size_t size)
181{
182 uint page;
183 size_t offset;
184 size_t current;
185 locate(id, page, offset, current);
186
187 if(size > current)
188 throw can_not_resize("shrink_alloc cannot grow an allocation");
189 if(size == current)
190 return;
191
192 const size_t freed = current - size;
193 const uint index = get_heap_index(id);
194
195 std::vector<byte> block = read_block(page);
196 disk::heap_page_header* header = reinterpret_cast<disk::heap_page_header*>(&block[0]);
197 disk::heap_page_map* map =
198 reinterpret_cast<disk::heap_page_map*>(&block[header->page_map_offset]);
199
200 const size_t used = map->allocs[map->num_allocs];
201
202 memmove(&block[offset + size], &block[offset + current], used - offset - current);
203 memset(&block[used - freed], 0, freed);
204
205 for(uint i = index + 1; i <= map->num_allocs; ++i)
206 map->allocs[i] = (ushort)(map->allocs[i] - freed);
207
208 if(size == 0)
209 ++map->num_frees;
210
211 write_block(page, block);
212}
213
214namespace pstsdk
215{
216namespace detail
217{
218
224inline bool cell_is_hnid(ushort type)
225{
226 switch(type)
227 {
228 case prop_type_string:
230 case prop_type_binary:
231 case prop_type_object:
232 case prop_type_guid:
233 return true;
234 default:
235 return (type & 0x1000) != 0;
236 }
237}
238
239inline void row_heap_cells(const std::vector<byte>& header, const byte* row,
240 size_t exists_at, std::vector<heap_id>& cells,
241 std::vector<node_id>* subnodes = 0)
242{
243 const disk::tc_header* tc = reinterpret_cast<const disk::tc_header*>(&header[0]);
244
245 for(byte i = 0; i < tc->num_columns; ++i)
246 {
247 const disk::column_description& column = tc->columns[i];
248
249 if(column.size != sizeof(heapnode_id) || !cell_is_hnid(column.type))
250 continue;
251
252 if(!test_bit(row + exists_at, column.bit_offset))
253 continue;
254
255 heapnode_id hnid;
256 memcpy(&hnid, row + column.offset, sizeof(hnid));
257
258 if(hnid == 0)
259 continue;
260
261 if(is_heap_id(hnid))
262 cells.push_back(hnid);
263 else if(subnodes)
264 subnodes->push_back(hnid);
265 }
266}
267
274template<typename T>
275class matrix_writer
276{
277public:
278 matrix_writer(db_writer<T>& writer, heap_writer<T>& heap, heapnode_id matrix,
279 size_t cb_per_row)
280 : m_writer(writer), m_heap(heap), m_matrix(matrix),
281 m_cb_per_row(cb_per_row), m_inline(!is_subnode_id(matrix)), m_rows_per_block(0)
282 {
283 if(m_inline)
284 return;
285
286 m_blocks = writer.external_blocks(root());
287 if(m_blocks.empty())
288 throw database_corrupt("row matrix subnode has no blocks");
289
290 m_rows_per_block = writer.read_block(m_blocks[0]).size() / cb_per_row;
291 if(m_rows_per_block == 0)
292 throw database_corrupt("row matrix block smaller than one row");
293 }
294
295 size_t rows()
296 {
297 if(m_inline)
298 return m_heap.read_alloc(m_matrix).size() / m_cb_per_row;
299
300 return (m_blocks.size() - 1) * m_rows_per_block + last_block_rows();
301 }
302
303 std::vector<byte> read_row(size_t index)
304 {
305 std::vector<byte> block = load(index);
306 const size_t at = offset_of(index);
307 return std::vector<byte>(block.begin() + at, block.begin() + at + m_cb_per_row);
308 }
309
310 void write_row(size_t index, const std::vector<byte>& row)
311 {
312 std::vector<byte> block = load(index);
313 memcpy(&block[offset_of(index)], &row[0], m_cb_per_row);
314 store(index, block);
315 }
316
317 // true once the matrix is empty
318 bool drop_last_row()
319 {
320 if(m_inline)
321 {
322 const size_t left = rows() - 1;
323 m_heap.shrink_alloc(m_matrix, left * m_cb_per_row);
324 return left == 0;
325 }
326
327 const size_t left_in_block = last_block_rows() - 1;
328 const size_t size = m_writer.read_block(m_blocks.back()).size();
329 const size_t shrunk = left_in_block ? size - m_cb_per_row : 0;
330
331 if(!m_writer.shrink_data_tail(root(), shrunk))
332 {
333 if(shrunk == 0)
334 m_blocks.pop_back();
335 return m_blocks.empty();
336 }
337
338 m_blocks.clear();
339 return true;
340 }
341
342private:
343 // the matrix hangs off the table's own subnode tree, not the store's
344 block_id root()
345 {
346 return m_writer.subnode_ref(m_heap.ref().sub, (node_id)m_matrix).data;
347 }
348
349 size_t last_block_rows()
350 {
351 return m_writer.read_block(m_blocks.back()).size() / m_cb_per_row;
352 }
353
354 size_t block_of(size_t index) const { return m_inline ? 0 : index / m_rows_per_block; }
355 size_t offset_of(size_t index) const
356 {
357 return (m_inline ? index : index % m_rows_per_block) * m_cb_per_row;
358 }
359
360 std::vector<byte> load(size_t index)
361 {
362 return m_inline ? m_heap.read_alloc(m_matrix)
363 : m_writer.read_block(m_blocks[block_of(index)]);
364 }
365
366 void store(size_t index, const std::vector<byte>& block)
367 {
368 if(m_inline)
369 m_heap.write_alloc(m_matrix, block);
370 else
371 m_writer.write_block(m_blocks[block_of(index)], block);
372 }
373
374 db_writer<T>& m_writer;
375 heap_writer<T>& m_heap;
376 heapnode_id m_matrix;
377 size_t m_cb_per_row;
378 bool m_inline;
379 size_t m_rows_per_block;
380 std::vector<block_id> m_blocks;
381};
382
385template<typename T>
386inline void free_row_subnodes(db_writer<T>& writer,
387 const typename db_writer<T>::data_ref& table,
388 const std::vector<node_id>& subnodes)
389{
390 for(size_t i = 0; i < subnodes.size(); ++i)
391 writer.subnode_remove(table.sub, subnodes[i]);
392}
393
400struct bth_layout
401{
402 heap_id header;
403 size_t key_size;
404 size_t value_size;
405 byte levels;
406 heap_id root;
407
408 size_t leaf_stride() const { return key_size + value_size; }
409 size_t nonleaf_stride() const { return key_size + sizeof(heap_id); }
410 size_t value_offset(uint index) const { return index * leaf_stride() + key_size; }
411};
412
413template<typename T>
414inline bth_layout bth_read_layout(heap_writer<T>& heap, heap_id bth_root)
415{
416 std::vector<byte> raw = heap.read_alloc(bth_root);
417 const disk::bth_header* header = reinterpret_cast<const disk::bth_header*>(&raw[0]);
418
419 if(header->bth_signature != disk::heap_sig_bth)
420 throw database_corrupt("invalid BTH signature");
421
422 bth_layout layout;
423 layout.header = bth_root;
424 layout.key_size = header->key_size;
425 layout.value_size = header->entry_size;
426 layout.levels = header->num_levels;
427 layout.root = header->root;
428 return layout;
429}
430
432inline ulong bth_read_at(const std::vector<byte>& alloc, size_t offset, size_t width)
433{
434 ulong value = 0;
435 memcpy(&value, &alloc[offset], width);
436 return value;
437}
438
439inline void bth_write_at(std::vector<byte>& alloc, size_t offset, size_t width, ulong value)
440{
441 memcpy(&alloc[offset], &value, width);
442}
443
444template<typename T>
445inline void bth_descend(heap_writer<T>& heap, const bth_layout& layout, ulong key,
446 std::vector<heap_id>& path, std::vector<uint>& indices)
447{
448 heap_id current = layout.root;
449
450 for(byte level = layout.levels; level > 0; --level)
451 {
452 std::vector<byte> alloc = heap.read_alloc(current);
453 const uint count = (uint)(alloc.size() / layout.nonleaf_stride());
454
455 if(count == 0 || key < bth_read_at(alloc, 0, layout.key_size))
456 throw key_not_found<ulong>(key);
457
458 uint position = 0;
459 while(position + 1 < count &&
460 bth_read_at(alloc, (position + 1) * layout.nonleaf_stride(), layout.key_size) <= key)
461 ++position;
462
463 path.push_back(current);
464 indices.push_back(position);
465 current = (heap_id)bth_read_at(alloc, position * layout.nonleaf_stride() + layout.key_size,
466 sizeof(heap_id));
467 }
468
469 std::vector<byte> alloc = heap.read_alloc(current);
470 const uint count = (uint)(alloc.size() / layout.leaf_stride());
471
472 for(uint i = 0; i < count; ++i)
473 {
474 if(bth_read_at(alloc, i * layout.leaf_stride(), layout.key_size) != key)
475 continue;
476
477 path.push_back(current);
478 indices.push_back(i);
479 return;
480 }
481
482 throw key_not_found<ulong>(key);
483}
484
488template<typename T>
489inline void bth_empty(heap_writer<T>& heap, const bth_layout& layout)
490{
491 std::vector<byte> raw = heap.read_alloc(layout.header);
492 disk::bth_header* header = reinterpret_cast<disk::bth_header*>(&raw[0]);
493 header->root = 0;
494 header->num_levels = 0;
495 heap.write_alloc(layout.header, raw);
496}
497
499template<typename T>
500inline void bth_remove_key(heap_writer<T>& heap, const bth_layout& layout, ulong key)
501{
502 std::vector<heap_id> path;
503 std::vector<uint> indices;
504 bth_descend(heap, layout, key, path, indices);
505
506 const size_t stride = layout.leaf_stride();
507 std::vector<byte> alloc = heap.read_alloc(path.back());
508 const uint count = (uint)(alloc.size() / stride);
509 const uint index = indices.back();
510
511 if(index + 1 < count)
512 memmove(&alloc[index * stride], &alloc[(index + 1) * stride],
513 (count - index - 1) * stride);
514
515 heap.write_alloc(path.back(), alloc);
516 heap.shrink_alloc(path.back(), (count - 1) * stride);
517
518 if(count == 1 && path.size() == 1)
519 {
520 bth_empty(heap, layout);
521 return;
522 }
523
524 if(count > 1)
525 {
526 if(index != 0 || path.size() < 2)
527 return;
528
529 std::vector<byte> survivors = heap.read_alloc(path.back());
530 const ulong first = bth_read_at(survivors, 0, layout.key_size);
531
532 for(size_t depth = path.size() - 1; depth-- > 0;)
533 {
534 std::vector<byte> parent = heap.read_alloc(path[depth]);
535 bth_write_at(parent, indices[depth] * layout.nonleaf_stride(), layout.key_size, first);
536 heap.write_alloc(path[depth], parent);
537
538 if(indices[depth] != 0)
539 return;
540 }
541
542 return;
543 }
544
545 // the leaf is empty, so take it out of its parent, and keep going if that
546 // empties the parent too
547 const size_t nonleaf = layout.nonleaf_stride();
548 for(size_t depth = path.size() - 1; depth-- > 0;)
549 {
550 std::vector<byte> parent = heap.read_alloc(path[depth]);
551 const uint parent_count = (uint)(parent.size() / nonleaf);
552 const uint parent_index = indices[depth];
553
554 if(parent_index + 1 < parent_count)
555 memmove(&parent[parent_index * nonleaf], &parent[(parent_index + 1) * nonleaf],
556 (parent_count - parent_index - 1) * nonleaf);
557
558 heap.write_alloc(path[depth], parent);
559 heap.shrink_alloc(path[depth], (parent_count - 1) * nonleaf);
560
561 if(parent_count > 1)
562 return;
563
564 if(depth == 0)
565 bth_empty(heap, layout);
566 }
567}
568
569} // end detail namespace
570} // end pstsdk namespace
571
572template<typename T>
573inline void pstsdk::pc_set_inline(db_writer<T>& writer, node_id nid, prop_id id, ulong value)
574{
575 heap_writer<T> heap(writer, nid);
576 detail::bth_layout layout = detail::bth_read_layout(heap, heap.root_id());
577
578 std::vector<heap_id> path;
579 std::vector<uint> indices;
580
581 // the BTH works in erased four byte keys, so restore the caller's key type
582 try { detail::bth_descend(heap, layout, id, path, indices); }
583 catch(key_not_found<ulong>&) { throw key_not_found<prop_id>(id); }
584
585 std::vector<byte> alloc = heap.read_alloc(path.back());
586 const size_t value_at = layout.value_offset(indices.back());
587
588 // a prop_entry is a two byte type followed by the value, and anything four
589 // bytes or under lives in that field rather than behind it
590 const ulong type = detail::bth_read_at(alloc, value_at, sizeof(ushort));
591 if(type != prop_type_long && type != prop_type_boolean)
592 throw not_implemented("pc_set_inline only handles inline fixed width properties");
593
594 detail::bth_write_at(alloc, value_at + sizeof(ushort), sizeof(ulong), value);
595 heap.write_alloc(path.back(), alloc);
596}
597
598template<typename T>
599inline void pstsdk::tc_remove_row(db_writer<T>& writer, node_id nid, row_id id)
600{
601 tc_remove_row(writer, writer.node_ref(nid), id);
602}
603
604template<typename T>
605inline void pstsdk::tc_remove_row(db_writer<T>& writer,
606 const typename db_writer<T>::data_ref& table, row_id id)
607{
608 heap_writer<T> heap(writer, table);
609 const heap_id root = heap.root_id();
610
611 std::vector<byte> raw = heap.read_alloc(root);
612 const disk::tc_header* header = reinterpret_cast<const disk::tc_header*>(&raw[0]);
613
614 if(header->signature != disk::heap_sig_tc)
615 throw database_corrupt("not a table context");
616
617 const size_t cb_per_row = header->size_offsets[disk::tc_offsets_bitmap];
618 const size_t exists_at = header->size_offsets[disk::tc_offsets_one];
619 const heap_id row_btree = header->row_btree_id;
620 const heapnode_id matrix_id = header->row_matrix_id;
621
622 if(matrix_id == 0 || cb_per_row == 0)
623 throw key_not_found<row_id>(id);
624
625 detail::bth_layout layout = detail::bth_read_layout(heap, row_btree);
626 detail::matrix_writer<T> matrix(writer, heap, matrix_id, cb_per_row);
627
628 const size_t count = matrix.rows();
629 if(count == 0)
630 throw key_not_found<row_id>(id);
631
632 std::vector<heap_id> path;
633 std::vector<uint> indices;
634 detail::bth_descend(heap, layout, id, path, indices);
635
636 std::vector<byte> leaf = heap.read_alloc(path.back());
637 const size_t target = detail::bth_read_at(leaf, layout.value_offset(indices.back()),
638 layout.value_size);
639 const size_t last = count - 1;
640
641 if(target > last)
642 throw database_corrupt("row index points past the matrix");
643
644 // Cells wider than the row point at heap allocations or subnodes. Nothing
645 // references them once the row is gone, and they hold the cached subject and
646 // sender in clear text, so they are freed rather than merely orphaned.
647 std::vector<byte> doomed_row = matrix.read_row(target);
648 std::vector<heap_id> doomed_cells;
649 std::vector<node_id> doomed_subnodes;
650 detail::row_heap_cells(raw, &doomed_row[0], exists_at, doomed_cells, &doomed_subnodes);
651
652 // the index and the matrix have to agree about which row this is
653 row_id at_target;
654 memcpy(&at_target, &doomed_row[0], sizeof(row_id));
655 if(at_target != id)
656 throw database_corrupt("row index and row matrix disagree");
657
658 // Resolve the moved row's index entry before the swap, so a key that is not
659 // there is reported while the table is still untouched.
660 std::vector<byte> moving;
661 std::vector<heap_id> moved_path;
662 std::vector<uint> moved_indices;
663
664 if(target != last)
665 {
666 moving = matrix.read_row(last);
667 row_id moved;
668 memcpy(&moved, &moving[0], sizeof(row_id));
669 detail::bth_descend(heap, layout, moved, moved_path, moved_indices);
670 }
671
672 if(target != last)
673 {
674 // the moved row keeps its id, so the index has to learn its new position
675 matrix.write_row(target, moving);
676
677 std::vector<byte> moved_leaf = heap.read_alloc(moved_path.back());
678 detail::bth_write_at(moved_leaf, layout.value_offset(moved_indices.back()),
679 layout.value_size, (ulong)target);
680 heap.write_alloc(moved_path.back(), moved_leaf);
681 }
682
683 const bool emptied = matrix.drop_last_row();
684 detail::bth_remove_key(heap, layout, id);
685
686 if(!emptied)
687 {
688 std::vector<heap_id> kept;
689 for(size_t r = 0; r < last; ++r)
690 {
691 std::vector<byte> survivor = matrix.read_row(r);
692 detail::row_heap_cells(raw, &survivor[0], exists_at, kept);
693 }
694
695 for(size_t i = 0; i < doomed_cells.size(); ++i)
696 if(std::find(kept.begin(), kept.end(), doomed_cells[i]) == kept.end())
697 heap.shrink_alloc(doomed_cells[i], 0);
698
699 detail::free_row_subnodes(writer, table, doomed_subnodes);
700 return;
701 }
702
703 for(size_t i = 0; i < doomed_cells.size(); ++i)
704 heap.shrink_alloc(doomed_cells[i], 0);
705
706 detail::free_row_subnodes(writer, table, doomed_subnodes);
707
708 // an emptied table carries neither a matrix nor a row index root on disk,
709 // which is what one that was never populated looks like
710 detail::bth_empty(heap, layout);
711
712 std::vector<byte> header_raw = heap.read_alloc(root);
713 reinterpret_cast<disk::tc_header*>(&header_raw[0])->row_matrix_id = 0;
714 heap.write_alloc(root, header_raw);
715
716 if(is_subnode_id(matrix_id))
717 writer.subnode_remove(table.sub, (node_id)matrix_id);
718}
720
721#endif
Contains references to other bth_node allocations.
Definition heap.h:364
void first(btree_iter_impl< K, V > &iter) const
Positions the iterator at the first element on this tree.
Definition btree.h:369
The exceptions used by pstsdk.
const byte heap_signature
Signature of a heap.
Definition disk.h:1234
@ heap_sig_bth
Definition disk.h:1255
@ heap_sig_tc
Definition disk.h:1250
@ tc_offsets_one
Definition disk.h:1493
@ tc_offsets_bitmap
Definition disk.h:1494
ulong get_heap_page(heap_id id)
Get the heap page from the heap id.
Definition primitives.h:243
boost::uint32_t uint
Definition primitives.h:67
ulong row_id
Definition primitives.h:95
ulong node_id
Definition primitives.h:86
boost::uint32_t ulong
Definition primitives.h:68
ulong heap_id
Definition primitives.h:90
ulonglong block_id
Definition primitives.h:87
ulong get_heap_index(heap_id id)
Get the index from the heap id.
Definition primitives.h:251
ulong heapnode_id
Definition primitives.h:91
boost::uint16_t ushort
Definition primitives.h:73
bool is_heap_id(heapnode_id id)
Inspects a heapnode_id (also known as a HNID) to determine if it is a heap_id (HID)
Definition primitives.h:272
ushort prop_id
Definition primitives.h:93
bool is_subnode_id(heapnode_id id)
Inspects a heapnode_id (also known as a HNID) to determine if it is a node_id (NID)
Definition primitives.h:281
@ prop_type_object
Definition primitives.h:309
@ prop_type_boolean
Definition primitives.h:308
@ prop_type_guid
Definition primitives.h:318
@ prop_type_binary
Definition primitives.h:320
@ prop_type_wstring
Definition primitives.h:314
@ prop_type_string
Definition primitives.h:312
@ prop_type_long
Definition primitives.h:297
bool test_bit(const byte *pbytes, ulong bit)
Test to see if the specified bit in the buffer is set.
Definition util.h:245
Contains the definition of all in memory representations of disk structures.
Definition disk.h:19
In place edits of an open store.
Primitive structures defined by MS-PST and MAPI.