Coverage Report

Created: 2026-01-09 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hdf5/src/H5FDonion_index.c
Line
Count
Source
1
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2
 * Copyright by The HDF Group.                                               *
3
 * All rights reserved.                                                      *
4
 *                                                                           *
5
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
6
 * terms governing use, modification, and redistribution, is contained in    *
7
 * the LICENSE file, which can be found at the root of the source code       *
8
 * distribution tree, or in https://www.hdfgroup.org/licenses.               *
9
 * If you do not have access to either file, you may request a copy from     *
10
 * help@hdfgroup.org.                                                        *
11
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12
13
/*
14
 * Onion Virtual File Driver (VFD)
15
 *
16
 * Purpose:     Code for the archival and revision indexes
17
 */
18
19
#include "H5FDmodule.h" /* This source code file is part of the H5FD module */
20
21
#include "H5private.h"      /* Generic Functions                        */
22
#include "H5Eprivate.h"     /* Error handling                           */
23
#include "H5FDpkg.h"        /* File drivers                             */
24
#include "H5FDonion_priv.h" /* Onion file driver internals              */
25
#include "H5MMprivate.h"    /* Memory management                        */
26
27
/* 2^n for uint64_t types -- H5_EXP2 unsafe past 32 bits */
28
0
#define U64_EXP2(n) ((uint64_t)1 << (n))
29
30
static int    H5FD__onion_archival_index_list_sort_cmp(const void *, const void *);
31
static herr_t H5FD__onion_revision_index_resize(H5FD_onion_revision_index_t *rix);
32
33
/*-----------------------------------------------------------------------------
34
 * Read and decode the revision_record information from `raw_file` at
35
 * `addr` .. `addr + size` (taken from history), and store the decoded
36
 * information in the structure at `r_out`.
37
 *-----------------------------------------------------------------------------
38
 */
39
herr_t
40
H5FD__onion_ingest_revision_record(H5FD_onion_revision_record_t *r_out, H5FD_t *raw_file,
41
                                   const H5FD_onion_history_t *history, uint64_t revision_num)
42
0
{
43
0
    unsigned char *buf       = NULL;
44
0
    herr_t         ret_value = SUCCEED;
45
0
    uint64_t       n         = 0;
46
0
    uint64_t       high      = 0;
47
0
    uint64_t       low       = 0;
48
0
    uint64_t       range     = 0;
49
0
    uint32_t       sum       = 0;
50
0
    haddr_t        addr      = 0;
51
0
    size_t         size      = 0;
52
53
0
    FUNC_ENTER_PACKAGE
54
55
0
    assert(r_out);
56
0
    assert(raw_file);
57
0
    assert(history);
58
0
    assert(history->record_locs);
59
0
    assert(history->n_revisions > 0);
60
61
0
    high  = history->n_revisions - 1;
62
0
    range = high;
63
0
    addr  = history->record_locs[high].phys_addr;
64
0
    size  = history->record_locs[high].record_size;
65
66
    /* Initialize r_out
67
     *
68
     * TODO: This function should completely initialize r_out. Relying on
69
     *       other code to some of the work while we just paste over parts
70
     *       of the struct here is completely bananas.
71
     */
72
0
    r_out->comment             = H5MM_xfree(r_out->comment);
73
0
    r_out->archival_index.list = H5MM_xfree(r_out->archival_index.list);
74
75
0
    if (H5FD_get_eof(raw_file, H5FD_MEM_DRAW) < (addr + size))
76
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "at least one record extends beyond EOF");
77
78
    /* recovery-open may have EOA below revision record */
79
0
    if ((H5FD_get_eoa(raw_file, H5FD_MEM_DRAW) < (addr + size)) &&
80
0
        (H5FD_set_eoa(raw_file, H5FD_MEM_DRAW, (addr + size)) < 0)) {
81
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTSET, FAIL, "can't modify EOA");
82
0
    }
83
84
    /* Perform binary search on records to find target revision by ID.
85
     * As IDs are added sequentially, they are guaranteed to be sorted.
86
     */
87
0
    while (range > 0) {
88
0
        n    = (range / 2) + low;
89
0
        addr = history->record_locs[n].phys_addr;
90
0
        size = history->record_locs[n].record_size;
91
92
0
        if (NULL == (buf = H5MM_malloc(sizeof(char) * size)))
93
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "can't allocate buffer space");
94
95
0
        if (H5FD_read(raw_file, H5FD_MEM_DRAW, addr, size, buf) < 0)
96
0
            HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "can't read revision record from file");
97
98
0
        if (H5FD__onion_revision_record_decode(buf, r_out) != size)
99
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTDECODE, FAIL, "can't decode revision record (initial)");
100
101
0
        sum = H5_checksum_fletcher32(buf, size - 4);
102
0
        if (r_out->checksum != sum)
103
0
            HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "checksum mismatch between buffer and stored");
104
105
0
        if (revision_num == r_out->revision_num)
106
0
            break;
107
108
0
        H5MM_xfree(buf);
109
0
        buf = NULL;
110
111
0
        r_out->archival_index.n_entries = 0;
112
0
        r_out->comment_size             = 0;
113
114
0
        if (r_out->revision_num < revision_num)
115
0
            low = (n == high) ? high : n + 1;
116
0
        else
117
0
            high = (n == low) ? low : n - 1;
118
0
        range = high - low;
119
0
    } /* end while 'non-leaf' binary search */
120
121
0
    if (range == 0) {
122
0
        n    = low;
123
0
        addr = history->record_locs[n].phys_addr;
124
0
        size = history->record_locs[n].record_size;
125
126
0
        if (NULL == (buf = H5MM_malloc(sizeof(char) * size)))
127
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "can't allocate buffer space");
128
129
0
        if (H5FD_read(raw_file, H5FD_MEM_DRAW, addr, size, buf) < 0)
130
0
            HGOTO_ERROR(H5E_VFL, H5E_READERROR, FAIL, "can't read revision record from file");
131
132
0
        if (H5FD__onion_revision_record_decode(buf, r_out) != size)
133
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTDECODE, FAIL, "can't decode revision record (initial)");
134
135
0
        sum = H5_checksum_fletcher32(buf, size - 4);
136
0
        if (r_out->checksum != sum)
137
0
            HGOTO_ERROR(H5E_VFL, H5E_BADVALUE, FAIL, "checksum mismatch between buffer and stored");
138
139
0
        if (revision_num != r_out->revision_num)
140
0
            HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL,
141
0
                        "could not find target revision!"); /* TODO: corrupted? */
142
0
    } /* end if revision ID at 'leaf' in binary search */
143
144
0
    if (r_out->comment_size > 0)
145
0
        if (NULL == (r_out->comment = H5MM_malloc(sizeof(char) * r_out->comment_size)))
146
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "can't allocate comment space");
147
148
0
    if (r_out->archival_index.n_entries > 0)
149
0
        if (NULL == (r_out->archival_index.list =
150
0
                         H5MM_calloc(r_out->archival_index.n_entries * sizeof(H5FD_onion_index_entry_t))))
151
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "can't allocate index entry list");
152
153
0
    if (H5FD__onion_revision_record_decode(buf, r_out) != size)
154
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTDECODE, FAIL, "can't decode revision record (final)");
155
156
0
done:
157
0
    H5MM_xfree(buf);
158
0
    if (ret_value == FAIL) {
159
0
        H5MM_xfree(r_out->comment);
160
0
        H5MM_xfree(r_out->archival_index.list);
161
0
    }
162
163
0
    FUNC_LEAVE_NOAPI(ret_value)
164
0
} /* end H5FD__onion_ingest_revision_record() */
165
166
/*-----------------------------------------------------------------------------
167
 * Function:    H5FD__onion_archival_index_is_valid
168
 *
169
 * Purpose:     Determine whether an archival index structure is valid.
170
 *
171
 *              + Verify page size (power of two).
172
 *              + Verify list exists.
173
 *              + Verify list contents:
174
 *                + Sorted by increasing logical address (no duplicates)
175
 *                + Logical addresses are multiples of page size.
176
 *
177
 * Return:      true/false
178
 *-----------------------------------------------------------------------------
179
 */
180
bool
181
H5FD__onion_archival_index_is_valid(const H5FD_onion_archival_index_t *aix)
182
0
{
183
0
    bool ret_value = true;
184
185
0
    FUNC_ENTER_PACKAGE_NOERR
186
187
0
    assert(aix);
188
189
0
    if (H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR != aix->version)
190
0
        HGOTO_DONE(false);
191
0
    if (NULL == aix->list)
192
0
        HGOTO_DONE(false);
193
194
    /* Ensure list is sorted on logical_page field */
195
0
    if (aix->n_entries > 1)
196
0
        for (uint64_t i = 1; i < aix->n_entries - 1; i++)
197
0
            if (aix->list[i + 1].logical_page <= aix->list[i].logical_page)
198
0
                HGOTO_DONE(false);
199
200
0
done:
201
0
    FUNC_LEAVE_NOAPI(ret_value)
202
0
} /* end H5FD__onion_archival_index_is_valid() */
203
204
/*-----------------------------------------------------------------------------
205
 * Function:    H5FD__onion_archival_index_find
206
 *
207
 * Purpose:     Retrieve the archival index entry by logical page ID.
208
 *
209
 *              The archival index pointer must point to a valid index entry.
210
 *              The entry out pointer-pointer cannot be null.
211
 *
212
 * Return:      Success: Positive value (1) -- entry found.
213
 *                       Entry out pointer-pointer is set to point to entry.
214
 *              Failure: Zero (0) -- entry not found.
215
 *                       Entry out pointer-pointer is unmodified.
216
 *-----------------------------------------------------------------------------
217
 */
218
int
219
H5FD__onion_archival_index_find(const H5FD_onion_archival_index_t *aix, uint64_t logical_page,
220
                                const H5FD_onion_index_entry_t **entry_out)
221
0
{
222
0
    uint64_t                  low       = 0;
223
0
    uint64_t                  high      = 0;
224
0
    uint64_t                  n         = 0;
225
0
    uint64_t                  range     = 0;
226
0
    H5FD_onion_index_entry_t *x         = NULL;
227
0
    int                       ret_value = 0;
228
229
0
    FUNC_ENTER_PACKAGE_NOERR
230
231
0
    assert(aix);
232
0
    assert(H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR == aix->version);
233
0
    assert(entry_out);
234
0
    if (aix->n_entries != 0)
235
0
        assert(aix->list);
236
237
0
    high  = aix->n_entries - 1;
238
0
    range = high;
239
240
    /* Trivial cases */
241
0
    if (aix->n_entries == 0 || logical_page > aix->list[high].logical_page ||
242
0
        logical_page < aix->list[0].logical_page)
243
0
        HGOTO_DONE(0);
244
245
    /*
246
     * Binary search on sorted list
247
     */
248
249
    /* Winnow down to first of found or one element */
250
0
    while (range > 0) {
251
0
        assert(high < aix->n_entries);
252
0
        n = low + (range / 2);
253
0
        x = &(aix->list[n]);
254
0
        if (x->logical_page == logical_page) {
255
0
            *entry_out = x; /* element found at fence */
256
0
            ret_value  = 1;
257
0
            goto done;
258
0
        }
259
0
        else if (x->logical_page < logical_page) {
260
0
            low = (n == high) ? high : n + 1;
261
0
        }
262
0
        else {
263
0
            high = (n == low) ? low : n - 1;
264
0
        }
265
0
        range = high - low;
266
0
    }
267
268
0
    assert(high == low); /* one element */
269
270
    /* n == low/high check because we may have tested it already above */
271
0
    if ((n != low || n != high) && (aix->list[low].logical_page == logical_page)) {
272
0
        *entry_out = &aix->list[low];
273
0
        ret_value  = 1;
274
0
    }
275
276
0
done:
277
0
    FUNC_LEAVE_NOAPI(ret_value)
278
0
} /* end H5FD__onion_archival_index_find() */
279
280
/*-----------------------------------------------------------------------------
281
 * Function:    H5FD__onion_revision_index_destroy
282
 *
283
 * Purpose:     Release all resources of a revision index.
284
 *
285
 * Return:      SUCCEED/FAIL
286
 *-----------------------------------------------------------------------------
287
 */
288
herr_t
289
H5FD__onion_revision_index_destroy(H5FD_onion_revision_index_t *rix)
290
0
{
291
0
    herr_t ret_value = SUCCEED;
292
293
0
    FUNC_ENTER_PACKAGE_NOERR
294
295
0
    assert(rix);
296
0
    assert(H5FD_ONION_REVISION_INDEX_VERSION_CURR == rix->version);
297
298
0
    for (size_t i = 0; 0 < rix->_hash_table_n_keys_populated && i < rix->_hash_table_size; i++) {
299
0
        H5FD_onion_revision_index_hash_chain_node_t *next = NULL;
300
0
        H5FD_onion_revision_index_hash_chain_node_t *node = rix->_hash_table[i];
301
302
0
        if (node != NULL)
303
0
            rix->_hash_table_n_keys_populated -= 1;
304
305
0
        while (node != NULL) {
306
0
            assert(H5FD_ONION_REVISION_INDEX_HASH_CHAIN_NODE_VERSION_CURR == node->version);
307
308
0
            next = node->next;
309
0
            H5MM_xfree(node);
310
0
            node = next;
311
0
        }
312
0
    }
313
0
    H5MM_xfree(rix->_hash_table);
314
0
    H5MM_xfree(rix);
315
316
0
    FUNC_LEAVE_NOAPI(ret_value)
317
0
} /* end H5FD__onion_revision_index_destroy() */
318
319
/*-----------------------------------------------------------------------------
320
 * Function:    H5FD__onion_revision_index_init
321
 *
322
 * Purpose:     Initialize a revision index structure with a default starting
323
 *              size. A new structure is allocated and populated with initial
324
 *              values.
325
 *
326
 * Return:      Success:    Pointer to newly-allocated structure
327
 *              Failure:    NULL
328
 *-----------------------------------------------------------------------------
329
 */
330
H5FD_onion_revision_index_t *
331
H5FD__onion_revision_index_init(uint32_t page_size)
332
0
{
333
0
    uint64_t                     table_size = U64_EXP2(H5FD_ONION_REVISION_INDEX_STARTING_SIZE_LOG2);
334
0
    H5FD_onion_revision_index_t *rix        = NULL;
335
0
    H5FD_onion_revision_index_t *ret_value  = NULL;
336
337
0
    FUNC_ENTER_PACKAGE
338
339
0
    assert(0 != page_size);
340
0
    assert(POWER_OF_TWO(page_size));
341
342
0
    if (NULL == (rix = H5MM_calloc(sizeof(H5FD_onion_revision_index_t))))
343
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "cannot allocate index");
344
345
0
    if (NULL ==
346
0
        (rix->_hash_table = H5MM_calloc(table_size * sizeof(H5FD_onion_revision_index_hash_chain_node_t *))))
347
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, NULL, "cannot allocate hash table");
348
349
0
    rix->version   = H5FD_ONION_REVISION_INDEX_VERSION_CURR;
350
0
    rix->n_entries = 0;
351
    /* Compute and store log2(page_size) */
352
0
    for (rix->page_size_log2 = 0; (((uint32_t)1 << rix->page_size_log2) & page_size) == 0;
353
0
         rix->page_size_log2++)
354
0
        ;
355
0
    rix->_hash_table_size             = table_size;
356
0
    rix->_hash_table_size_log2        = H5FD_ONION_REVISION_INDEX_STARTING_SIZE_LOG2;
357
0
    rix->_hash_table_n_keys_populated = 0;
358
359
0
    ret_value = rix;
360
361
0
done:
362
0
    if (NULL == ret_value)
363
0
        H5MM_xfree(rix);
364
365
0
    FUNC_LEAVE_NOAPI(ret_value)
366
0
} /* end H5FD__onion_revision_index_init() */
367
368
/*-----------------------------------------------------------------------------
369
 * Function:    H5FD__onion_revision_index_resize()
370
 *
371
 * Purpose:     Replace the hash table in the revision index.
372
 *
373
 *              Doubles the available number of keys, re-hashes table contents,
374
 *              and updates relevant components in the index structure.
375
 *
376
 *              Fails if unable to allocate space for larger hash table.
377
 *
378
 * Return:      SUCCEED/FAIL
379
 *-----------------------------------------------------------------------------
380
 */
381
static herr_t
382
H5FD__onion_revision_index_resize(H5FD_onion_revision_index_t *rix)
383
0
{
384
0
    H5FD_onion_revision_index_hash_chain_node_t **new_table = NULL;
385
386
0
    uint64_t new_size_log2        = rix->_hash_table_size_log2 + 1;
387
0
    uint64_t new_size             = U64_EXP2(new_size_log2);
388
0
    uint64_t new_n_keys_populated = 0;
389
0
    herr_t   ret_value            = SUCCEED;
390
391
0
    FUNC_ENTER_PACKAGE
392
393
0
    assert(rix);
394
0
    assert(H5FD_ONION_REVISION_INDEX_VERSION_CURR == rix->version);
395
0
    assert(rix->_hash_table);
396
397
0
    if (NULL == (new_table = H5MM_calloc(new_size * sizeof(H5FD_onion_revision_index_hash_chain_node_t *))))
398
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "cannot allocate new hash table");
399
400
0
    for (uint64_t i = 0; i < rix->_hash_table_size; i++) {
401
0
        while (rix->_hash_table[i] != NULL) {
402
0
            H5FD_onion_revision_index_hash_chain_node_t *node = NULL;
403
0
            uint64_t                                     key  = 0;
404
405
            /* Pop entry off of bucket stack and re-hash */
406
0
            node                = rix->_hash_table[i];
407
0
            rix->_hash_table[i] = node->next;
408
0
            node->next          = NULL;
409
0
            key                 = node->entry_data.logical_page & (new_size - 1);
410
411
0
            if (NULL == new_table[key]) {
412
0
                new_table[key] = node;
413
0
                new_n_keys_populated++;
414
0
            }
415
0
            else {
416
0
                node->next   = new_table[i];
417
0
                new_table[i] = node;
418
0
            }
419
0
        }
420
0
    }
421
422
0
    H5MM_xfree(rix->_hash_table);
423
0
    rix->_hash_table_size             = new_size;
424
0
    rix->_hash_table_size_log2        = new_size_log2;
425
0
    rix->_hash_table_n_keys_populated = new_n_keys_populated;
426
0
    rix->_hash_table                  = new_table;
427
428
0
done:
429
0
    FUNC_LEAVE_NOAPI(ret_value)
430
0
} /* end H5FD__onion_revision_index_resize() */
431
432
/*-----------------------------------------------------------------------------
433
 * Function:    H5FD__onion_revision_index_insert()
434
 *
435
 * Purpose:     Add an entry to the revision index, or update an existing
436
 *              entry. Must be used to update entries as well as add --
437
 *              checksum value will change.
438
 *
439
 *              Entry data is copied into separate memory region; user pointer
440
 *              can be safley reused or discarded after operation.
441
 *
442
 * Return:      SUCCEED/FAIL
443
 *-----------------------------------------------------------------------------
444
 */
445
herr_t
446
H5FD__onion_revision_index_insert(H5FD_onion_revision_index_t *rix, const H5FD_onion_index_entry_t *entry)
447
0
{
448
0
    uint64_t                                      key         = 0;
449
0
    H5FD_onion_revision_index_hash_chain_node_t  *node        = NULL;
450
0
    H5FD_onion_revision_index_hash_chain_node_t **append_dest = NULL;
451
0
    herr_t                                        ret_value   = SUCCEED;
452
453
0
    FUNC_ENTER_PACKAGE
454
455
0
    assert(rix);
456
0
    assert(H5FD_ONION_REVISION_INDEX_VERSION_CURR == rix->version);
457
0
    assert(entry);
458
459
    /* Resize and re-hash table if necessary */
460
0
    if (rix->n_entries >= (rix->_hash_table_size * 2) ||
461
0
        rix->_hash_table_n_keys_populated >= (rix->_hash_table_size / 2)) {
462
0
        if (H5FD__onion_revision_index_resize(rix) < 0)
463
0
            HGOTO_ERROR(H5E_VFL, H5E_NONE_MINOR, FAIL, "unable to resize and hash table");
464
0
    }
465
466
0
    key = entry->logical_page & (rix->_hash_table_size - 1);
467
0
    assert(key < rix->_hash_table_size);
468
469
0
    if (NULL == rix->_hash_table[key]) {
470
        /* Key maps to empty bucket */
471
472
0
        append_dest = &rix->_hash_table[key];
473
0
        rix->_hash_table_n_keys_populated++;
474
0
    }
475
0
    else {
476
        /* Key maps to populated bucket */
477
478
0
        for (node = rix->_hash_table[key]; node != NULL; node = node->next) {
479
0
            append_dest = &node->next; /* look for bucket tail */
480
0
            if (entry->logical_page == node->entry_data.logical_page) {
481
0
                if (entry->phys_addr != node->entry_data.phys_addr) {
482
0
                    HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, FAIL, "physical address mismatch");
483
0
                }
484
0
                H5MM_memcpy(&node->entry_data, entry, sizeof(H5FD_onion_index_entry_t));
485
0
                append_dest = NULL; /* Node updated, do not append */
486
0
                break;
487
0
            }
488
0
        }
489
0
    }
490
491
    /* Add new entry to bucket chain */
492
0
    if (append_dest != NULL) {
493
0
        if (NULL == (node = H5MM_malloc(sizeof(H5FD_onion_revision_index_hash_chain_node_t))))
494
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "cannot allocate new ash chain node");
495
0
        node->version = H5FD_ONION_REVISION_INDEX_HASH_CHAIN_NODE_VERSION_CURR;
496
0
        node->next    = NULL;
497
0
        H5MM_memcpy(&node->entry_data, entry, sizeof(H5FD_onion_index_entry_t));
498
0
        *append_dest = node;
499
0
        rix->n_entries++;
500
0
    }
501
502
0
done:
503
0
    FUNC_LEAVE_NOAPI(ret_value)
504
0
} /* end H5FD__onion_revision_index_insert() */
505
506
/*-----------------------------------------------------------------------------
507
 * Function:    H5FD__onion_revision_index_find()
508
 *
509
 *
510
 * Purpose:     Get pointer to revision index entry with the given page number,
511
 *              if it exists in the index.
512
 *
513
 * Return:      Success: Positive value (1) -- entry found.
514
 *                       Entry out pointer-pointer is set to point to entry.
515
 *              Failure: Zero (0) -- entry not found.
516
 *                       Entry out pointer-pointer is unmodified.
517
 *-----------------------------------------------------------------------------
518
 */
519
int
520
H5FD__onion_revision_index_find(const H5FD_onion_revision_index_t *rix, uint64_t logical_page,
521
                                const H5FD_onion_index_entry_t **entry_out)
522
0
{
523
0
    uint64_t key       = 0;
524
0
    int      ret_value = 0;
525
526
0
    FUNC_ENTER_PACKAGE_NOERR
527
528
0
    assert(rix);
529
0
    assert(H5FD_ONION_REVISION_INDEX_VERSION_CURR == rix->version);
530
0
    assert(rix->_hash_table);
531
0
    assert(entry_out);
532
533
0
    key = logical_page & (rix->_hash_table_size - 1);
534
0
    assert(key < rix->_hash_table_size);
535
536
0
    if (rix->_hash_table[key] != NULL) {
537
0
        H5FD_onion_revision_index_hash_chain_node_t *node = NULL;
538
539
0
        for (node = rix->_hash_table[key]; node != NULL; node = node->next) {
540
0
            if (logical_page == node->entry_data.logical_page) {
541
0
                *entry_out = &node->entry_data;
542
0
                ret_value  = 1;
543
0
                break;
544
0
            }
545
0
        }
546
0
    }
547
548
0
    FUNC_LEAVE_NOAPI(ret_value)
549
0
} /* end H5FD__onion_revision_index_find() */
550
551
/*-----------------------------------------------------------------------------
552
 * Function:    H5FD__onion_revision_record_decode
553
 *
554
 * Purpose:     Attempt to read a buffer and store it as a revision record
555
 *              structure.
556
 *
557
 *              Implementation must correspond with
558
 *              H5FD__onion_revision_record_encode().
559
 *
560
 *              MUST BE CALLED TWICE:
561
 *              On the first call, n_entries and comment_size in the
562
 *              destination structure must all all be zero, and their
563
 *              respective variable-length components (index_entry_list,
564
 *              comment) must all be NULL.
565
 *
566
 *              If the buffer is well-formed, the destination structure is
567
 *              tentatively populated with fixed-size values, and the number of
568
 *              bytes read are returned.
569
 *
570
 *              Prior to the second call, the user must allocate space for the
571
 *              variable-length components, in accordance with the associated
572
 *              indicators (array of index-entry structures for
573
 *              index_entry_list, of size n_entries; character arrays for
574
 *              comment, allocated with the *_size number of bytes -- space
575
 *              for NULL-terminator is included in _size).
576
 *
577
 *              Then the decode operation is called a second time, and all
578
 *              components will be populated (and again number of bytes read is
579
 *              returned).
580
 *
581
 * Return:      Success:    Number of bytes read from buffer
582
 *              Failure:    0
583
 *-----------------------------------------------------------------------------
584
 */
585
size_t H5_ATTR_NO_OPTIMIZE
586
H5FD__onion_revision_record_decode(unsigned char *buf, H5FD_onion_revision_record_t *record)
587
0
{
588
0
    uint32_t       ui32         = 0;
589
0
    uint32_t       page_size    = 0;
590
0
    uint32_t       sum          = 0;
591
0
    uint64_t       ui64         = 0;
592
0
    uint64_t       n_entries    = 0;
593
0
    uint32_t       comment_size = 0;
594
0
    uint8_t       *ui8p         = NULL;
595
0
    unsigned char *ptr          = NULL;
596
0
    size_t         ret_value    = 0;
597
598
0
    FUNC_ENTER_PACKAGE
599
600
0
    assert(buf != NULL);
601
0
    assert(record != NULL);
602
0
    assert(H5FD_ONION_REVISION_RECORD_VERSION_CURR == record->version);
603
0
    assert(H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR == record->archival_index.version);
604
605
0
    if (strncmp((const char *)buf, H5FD_ONION_REVISION_RECORD_SIGNATURE, 4))
606
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "invalid signature");
607
608
0
    if (H5FD_ONION_REVISION_RECORD_VERSION_CURR != buf[4])
609
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "invalid record version");
610
611
0
    ptr = buf + 8;
612
613
0
    H5MM_memcpy(&ui64, ptr, 8);
614
0
    ui8p = (uint8_t *)&ui64;
615
0
    UINT64DECODE(ui8p, record->revision_num);
616
0
    ptr += 8;
617
618
0
    H5MM_memcpy(&ui64, ptr, 8);
619
0
    ui8p = (uint8_t *)&ui64;
620
0
    UINT64DECODE(ui8p, record->parent_revision_num);
621
0
    ptr += 8;
622
623
0
    H5MM_memcpy(record->time_of_creation, ptr, 16);
624
0
    ptr += 16;
625
626
0
    H5MM_memcpy(&ui64, ptr, 8);
627
0
    ui8p = (uint8_t *)&ui64;
628
0
    UINT64DECODE(ui8p, record->logical_eof);
629
0
    ptr += 8;
630
631
0
    H5MM_memcpy(&ui32, ptr, 4);
632
0
    ui8p = (uint8_t *)&ui32;
633
0
    UINT32DECODE(ui8p, page_size);
634
0
    ptr += 4;
635
636
0
    if (page_size == 0)
637
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "page size is zero");
638
0
    if (!POWER_OF_TWO(page_size))
639
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "page size not power of two");
640
641
0
    for (record->archival_index.page_size_log2 = 0;
642
0
         (((uint32_t)1 << record->archival_index.page_size_log2) & page_size) == 0;
643
0
         record->archival_index.page_size_log2++)
644
0
        ;
645
646
0
    H5MM_memcpy(&ui64, ptr, 8);
647
0
    ui8p = (uint8_t *)&ui64;
648
0
    UINT64DECODE(ui8p, n_entries);
649
0
    ptr += 8;
650
651
0
    H5MM_memcpy(&ui32, ptr, 4);
652
0
    ui8p = (uint8_t *)&ui32;
653
0
    UINT32DECODE(ui8p, comment_size);
654
0
    ptr += 4;
655
656
0
    if (record->archival_index.n_entries == 0) {
657
0
        record->archival_index.n_entries = n_entries;
658
0
        ptr += H5FD_ONION_ENCODED_SIZE_INDEX_ENTRY * n_entries;
659
0
    }
660
0
    else if (n_entries != record->archival_index.n_entries) {
661
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "n_entries in archival index does not match decoded");
662
0
    }
663
0
    else {
664
0
        H5FD_onion_index_entry_t *entry = NULL;
665
666
0
        if (record->archival_index.list == NULL)
667
0
            HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "no archival index entry list");
668
669
0
        for (size_t i = 0; i < n_entries; i++) {
670
0
            entry = &record->archival_index.list[i];
671
672
0
            H5MM_memcpy(&ui64, ptr, 8);
673
0
            ui8p = (uint8_t *)&ui64;
674
0
            UINT64DECODE(ui8p, entry->logical_page);
675
0
            ptr += 8;
676
677
            /* logical_page actually encoded as address; check and convert */
678
0
            if (entry->logical_page & (page_size - 1))
679
0
                HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "logical address does not align with page size");
680
681
0
            entry->logical_page = entry->logical_page >> record->archival_index.page_size_log2;
682
683
0
            H5MM_memcpy(&ui64, ptr, 8);
684
0
            ui8p = (uint8_t *)&ui64;
685
0
            UINT64DECODE(ui8p, entry->phys_addr);
686
0
            ptr += 8;
687
688
0
            H5MM_memcpy(&ui32, ptr, 4);
689
0
            ui8p = (uint8_t *)&ui32;
690
0
            UINT32DECODE(ui8p, sum);
691
0
            ptr += 4;
692
693
0
            ui32 = H5_checksum_fletcher32((ptr - 20), 16);
694
0
            if (ui32 != sum)
695
0
                HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "index entry checksum mismatch");
696
0
        }
697
0
    }
698
699
0
    if (record->comment_size == 0) {
700
0
        if (record->comment != NULL)
701
0
            HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "comment pointer prematurely allocated");
702
0
        record->comment_size = comment_size;
703
0
    }
704
0
    else {
705
0
        if (record->comment == NULL)
706
0
            HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "no comment pointer");
707
0
        H5MM_memcpy(record->comment, ptr, comment_size);
708
0
    }
709
0
    ptr += comment_size;
710
711
0
    sum = H5_checksum_fletcher32(buf, (size_t)(ptr - buf));
712
713
0
    H5MM_memcpy(&ui32, ptr, 4);
714
0
    ui8p = (uint8_t *)&ui32;
715
0
    UINT32DECODE(ui8p, record->checksum);
716
0
    ptr += 4;
717
718
0
    if (sum != record->checksum)
719
0
        HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, 0, "checksum mismatch");
720
721
0
    ret_value = (size_t)(ptr - buf);
722
723
0
done:
724
0
    FUNC_LEAVE_NOAPI(ret_value)
725
0
} /* end H5FD__onion_revision_record_decode() */
726
727
/*-----------------------------------------------------------------------------
728
 * Function:    H5FD__onion_revision_record_encode
729
 *
730
 * Purpose:     Write revision-record structure to the given buffer.
731
 *              All multi-byte elements are stored in little-endian word order.
732
 *
733
 *              Implementation must correspond with
734
 *              H5FD__onion_revision_record_decode().
735
 *
736
 *              The destination buffer must be sufficiently large to hold the
737
 *              encoded contents.
738
 *              (Hint: `sizeof(revision-record-struct) + comment-size +
739
 *              sizeof(index-entry-struct) * n_entries)`
740
 *              guarantees ample/excess space.)
741
 *
742
 * Return:      Number of bytes written to buffer.
743
 *              The checksum of the generated buffer contents (excluding the
744
 *              checksum itself) is stored in the pointer `checksum`).
745
 *-----------------------------------------------------------------------------
746
 */
747
size_t
748
H5FD__onion_revision_record_encode(H5FD_onion_revision_record_t *record, unsigned char *buf,
749
                                   uint32_t *checksum)
750
0
{
751
0
    unsigned char *ptr       = buf;                       /* original pointer */
752
0
    uint32_t       vers_u32  = (uint32_t)record->version; /* pad out unused bytes */
753
0
    uint32_t       page_size = 0;
754
755
0
    FUNC_ENTER_PACKAGE_NOERR
756
757
0
    assert(checksum != NULL);
758
0
    assert(buf != NULL);
759
0
    assert(record != NULL);
760
0
    assert(vers_u32 < 0x100);
761
0
    assert(H5FD_ONION_REVISION_RECORD_VERSION_CURR == record->version);
762
0
    assert(H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR == record->archival_index.version);
763
764
0
    page_size = (uint32_t)(1 << record->archival_index.page_size_log2);
765
766
0
    H5MM_memcpy(ptr, H5FD_ONION_REVISION_RECORD_SIGNATURE, 4);
767
0
    ptr += 4;
768
0
    UINT32ENCODE(ptr, vers_u32);
769
0
    UINT64ENCODE(ptr, record->revision_num);
770
0
    UINT64ENCODE(ptr, record->parent_revision_num);
771
0
    H5MM_memcpy(ptr, record->time_of_creation, 16);
772
0
    ptr += 16;
773
0
    UINT64ENCODE(ptr, record->logical_eof);
774
0
    UINT32ENCODE(ptr, page_size);
775
0
    UINT64ENCODE(ptr, record->archival_index.n_entries);
776
0
    UINT32ENCODE(ptr, record->comment_size);
777
778
0
    if (record->archival_index.n_entries > 0) {
779
0
        uint64_t page_size_log2 = record->archival_index.page_size_log2;
780
781
0
        assert(record->archival_index.list != NULL);
782
0
        for (uint64_t i = 0; i < record->archival_index.n_entries; i++) {
783
0
            uint32_t                  sum       = 0;
784
0
            H5FD_onion_index_entry_t *entry     = NULL;
785
0
            uint64_t                  logi_addr = 0;
786
787
0
            entry     = &record->archival_index.list[i];
788
0
            logi_addr = entry->logical_page << page_size_log2;
789
790
0
            UINT64ENCODE(ptr, logi_addr);
791
0
            UINT64ENCODE(ptr, entry->phys_addr);
792
0
            sum = H5_checksum_fletcher32((ptr - 16), 16);
793
0
            UINT32ENCODE(ptr, sum);
794
0
        }
795
0
    }
796
797
0
    if (record->comment_size > 0) {
798
0
        assert(record->comment != NULL && *record->comment != '\0');
799
0
        H5MM_memcpy(ptr, record->comment, record->comment_size);
800
0
        ptr += record->comment_size;
801
0
    }
802
803
0
    *checksum = H5_checksum_fletcher32(buf, (size_t)(ptr - buf));
804
0
    UINT32ENCODE(ptr, *checksum);
805
806
0
    FUNC_LEAVE_NOAPI((size_t)(ptr - buf))
807
0
} /* end H5FD__onion_revision_record_encode() */
808
809
/*-----------------------------------------------------------------------------
810
 * Callback for comparisons in sorting archival index entries by logical_page.
811
 *-----------------------------------------------------------------------------
812
 */
813
static int
814
H5FD__onion_archival_index_list_sort_cmp(const void *_a, const void *_b)
815
0
{
816
0
    const H5FD_onion_index_entry_t *a = (const H5FD_onion_index_entry_t *)_a;
817
0
    const H5FD_onion_index_entry_t *b = (const H5FD_onion_index_entry_t *)_b;
818
819
0
    if (a->logical_page < b->logical_page)
820
0
        return -1;
821
0
    else if (a->logical_page > b->logical_page)
822
0
        return 1;
823
0
    return 0;
824
0
} /* end H5FD__onion_archival_index_list_sort_cmp() */
825
826
/*-----------------------------------------------------------------------------
827
 * Function:    H5FD__onion_merge_revision_index_into_archival_index
828
 *
829
 * Purpose:     Merge index entries from revision index into archival index.
830
 *
831
 *              If successful, the archival index is expanded 'behind the
832
 *              scenes' and new entries from the revision index are inserted.
833
 *              The archival index remains sorted in ascending order of logical
834
 *              address.
835
 *
836
 *              The conversion to archival index changes logical pages in
837
 *              revision index entries to their logical addresses in-file.
838
 *
839
 * Return:      SUCCEED/FAIL
840
 *-----------------------------------------------------------------------------
841
 */
842
herr_t
843
H5FD__onion_merge_revision_index_into_archival_index(const H5FD_onion_revision_index_t *rix,
844
                                                     H5FD_onion_archival_index_t       *aix)
845
0
{
846
0
    uint64_t                    n_kept    = 0;
847
0
    H5FD_onion_index_entry_t   *kept_list = NULL;
848
0
    H5FD_onion_archival_index_t new_aix   = {
849
0
        H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR, 0, /* page_size_log2 tbd */
850
0
        0,                                         /* n_entries */
851
0
        NULL,                                      /* list pointer (allocated later) */
852
0
    };
853
0
    herr_t ret_value = SUCCEED;
854
855
0
    FUNC_ENTER_PACKAGE
856
857
0
    assert(rix);
858
0
    assert(aix);
859
0
    assert(H5FD_ONION_REVISION_INDEX_VERSION_CURR == rix->version);
860
0
    assert(H5FD_ONION_ARCHIVAL_INDEX_VERSION_CURR == aix->version);
861
0
    assert(aix->page_size_log2 == rix->page_size_log2);
862
863
    /* If the revision index is empty there is nothing to archive */
864
0
    if (rix->n_entries == 0)
865
0
        goto done;
866
867
    /* Add all revision index entries to new archival list */
868
0
    new_aix.page_size_log2 = aix->page_size_log2;
869
870
0
    if (NULL == (new_aix.list = H5MM_calloc(rix->n_entries * sizeof(H5FD_onion_index_entry_t))))
871
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "unable to allocate new archival index list");
872
873
0
    for (uint64_t i = 0; i < rix->_hash_table_size; i++) {
874
0
        const H5FD_onion_revision_index_hash_chain_node_t *node = NULL;
875
876
0
        for (node = rix->_hash_table[i]; node != NULL; node = node->next) {
877
0
            H5MM_memcpy(&new_aix.list[new_aix.n_entries], &node->entry_data,
878
0
                        sizeof(H5FD_onion_index_entry_t));
879
0
            new_aix.n_entries++;
880
0
        }
881
0
    }
882
883
    /* Sort the new archival list */
884
0
    qsort(new_aix.list, new_aix.n_entries, sizeof(H5FD_onion_index_entry_t),
885
0
          H5FD__onion_archival_index_list_sort_cmp);
886
887
    /* Add the old archival index entries to a 'kept' list containing the
888
     * old archival list entries that are not also included in the revision
889
     * list.
890
     *
891
     * Note that kept_list will be NULL if there are no entries in the passed-in
892
     * archival list.
893
     */
894
0
    if (aix->n_entries > 0)
895
0
        if (NULL == (kept_list = H5MM_calloc(aix->n_entries * sizeof(H5FD_onion_index_entry_t))))
896
0
            HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "unable to allocate larger archival index list");
897
898
0
    for (uint64_t i = 0; i < aix->n_entries; i++) {
899
0
        const H5FD_onion_index_entry_t *entry = NULL;
900
901
        /* Add only if page not already added from revision index */
902
0
        if (H5FD__onion_archival_index_find(&new_aix, aix->list[i].logical_page, &entry) == 0) {
903
0
            H5MM_memcpy(&kept_list[n_kept], &aix->list[i], sizeof(H5FD_onion_index_entry_t));
904
0
            n_kept++;
905
0
        }
906
0
    }
907
908
    /* Destroy the old archival list and replace with a list big enough to hold
909
     * the revision list entries and the kept list entries
910
     */
911
0
    H5MM_xfree(aix->list);
912
0
    if (NULL == (aix->list = H5MM_calloc((new_aix.n_entries + n_kept) * sizeof(H5FD_onion_index_entry_t))))
913
0
        HGOTO_ERROR(H5E_VFL, H5E_CANTALLOC, FAIL, "unable to allocate exact-size archival index list");
914
915
    /* Copy (new) revision list entries to replacement list */
916
0
    H5MM_memcpy(aix->list, new_aix.list, sizeof(H5FD_onion_index_entry_t) * new_aix.n_entries);
917
0
    aix->n_entries = new_aix.n_entries;
918
919
    /* Copy (old) kept archival list entries to replacement list */
920
0
    if (n_kept > 0) {
921
0
        H5MM_memcpy(&aix->list[aix->n_entries], kept_list, sizeof(H5FD_onion_index_entry_t) * n_kept);
922
0
        aix->n_entries += n_kept;
923
0
    }
924
925
    /* Sort this list */
926
0
    qsort(aix->list, aix->n_entries, sizeof(H5FD_onion_index_entry_t),
927
0
          H5FD__onion_archival_index_list_sort_cmp);
928
929
0
done:
930
    /* Free the temporary lists */
931
0
    H5MM_xfree(kept_list);
932
0
    H5MM_xfree(new_aix.list);
933
934
0
    FUNC_LEAVE_NOAPI(ret_value)
935
0
} /* end H5FD__onion_merge_revision_index_into_archival_index() */