Coverage Report

Created: 2026-09-28 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/reftable/block.c
Line
Count
Source
1
/*
2
 * Copyright 2020 Google LLC
3
 *
4
 * Use of this source code is governed by a BSD-style
5
 * license that can be found in the LICENSE file or at
6
 * https://developers.google.com/open-source/licenses/bsd
7
 */
8
9
#include "block.h"
10
11
#include "blocksource.h"
12
#include "constants.h"
13
#include "iter.h"
14
#include "record.h"
15
#include "reftable-error.h"
16
#include "system.h"
17
18
size_t header_size(int version)
19
0
{
20
0
  switch (version) {
21
0
  case 1:
22
0
    return 24;
23
0
  case 2:
24
0
    return 28;
25
0
  }
26
0
  abort();
27
0
}
28
29
size_t footer_size(int version)
30
0
{
31
0
  switch (version) {
32
0
  case 1:
33
0
    return 68;
34
0
  case 2:
35
0
    return 72;
36
0
  }
37
0
  abort();
38
0
}
39
40
static int block_writer_register_restart(struct block_writer *w, int n,
41
           int is_restart, struct reftable_buf *key)
42
0
{
43
0
  uint32_t rlen;
44
0
  int err;
45
46
0
  rlen = w->restart_len;
47
0
  if (rlen >= MAX_RESTARTS)
48
0
    is_restart = 0;
49
50
0
  if (is_restart)
51
0
    rlen++;
52
0
  if (2 + 3 * rlen + n > w->block_size - w->next)
53
0
    return REFTABLE_ENTRY_TOO_BIG_ERROR;
54
0
  if (is_restart) {
55
0
    REFTABLE_ALLOC_GROW_OR_NULL(w->restarts, w->restart_len + 1,
56
0
              w->restart_cap);
57
0
    if (!w->restarts)
58
0
      return REFTABLE_OUT_OF_MEMORY_ERROR;
59
0
    w->restarts[w->restart_len++] = w->next;
60
0
  }
61
62
0
  w->next += n;
63
64
0
  reftable_buf_reset(&w->last_key);
65
0
  err = reftable_buf_add(&w->last_key, key->buf, key->len);
66
0
  if (err < 0)
67
0
    return err;
68
69
0
  w->entries++;
70
0
  return 0;
71
0
}
72
73
int block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *block,
74
          uint32_t block_size, uint32_t header_off, uint32_t hash_size)
75
0
{
76
0
  bw->block = block;
77
0
  bw->hash_size = hash_size;
78
0
  bw->block_size = block_size;
79
0
  bw->header_off = header_off;
80
0
  bw->block[header_off] = typ;
81
0
  bw->next = header_off + 4;
82
0
  bw->restart_interval = 16;
83
0
  bw->entries = 0;
84
0
  bw->restart_len = 0;
85
0
  bw->last_key.len = 0;
86
0
  if (!bw->zstream) {
87
0
    REFTABLE_CALLOC_ARRAY(bw->zstream, 1);
88
0
    if (!bw->zstream)
89
0
      return REFTABLE_OUT_OF_MEMORY_ERROR;
90
0
    if (deflateInit(bw->zstream, 9) != Z_OK) {
91
0
      REFTABLE_FREE_AND_NULL(bw->zstream);
92
0
      return REFTABLE_ZLIB_ERROR;
93
0
    }
94
0
  }
95
96
0
  return 0;
97
0
}
98
99
uint8_t block_writer_type(struct block_writer *bw)
100
0
{
101
0
  return bw->block[bw->header_off];
102
0
}
103
104
/*
105
 * Adds the reftable_record to the block. Returns 0 on success and
106
 * appropriate error codes on failure.
107
 */
108
int block_writer_add(struct block_writer *w, struct reftable_record *rec)
109
0
{
110
0
  struct reftable_buf empty = REFTABLE_BUF_INIT;
111
0
  struct reftable_buf last =
112
0
    w->entries % w->restart_interval == 0 ? empty : w->last_key;
113
0
  struct string_view out = {
114
0
    .buf = w->block + w->next,
115
0
    .len = w->block_size - w->next,
116
0
  };
117
0
  struct string_view start = out;
118
0
  int is_restart = 0;
119
0
  int n = 0;
120
0
  int err;
121
122
0
  err = reftable_record_key(rec, &w->scratch);
123
0
  if (err < 0)
124
0
    goto done;
125
126
0
  if (!w->scratch.len) {
127
0
    err = REFTABLE_API_ERROR;
128
0
    goto done;
129
0
  }
130
131
0
  n = reftable_encode_key(&is_restart, out, last, w->scratch,
132
0
        reftable_record_val_type(rec));
133
0
  if (n < 0) {
134
0
    err = n;
135
0
    goto done;
136
0
  }
137
0
  string_view_consume(&out, n);
138
139
0
  n = reftable_record_encode(rec, out, w->hash_size);
140
0
  if (n < 0) {
141
0
    err = n;
142
0
    goto done;
143
0
  }
144
0
  string_view_consume(&out, n);
145
146
0
  err = block_writer_register_restart(w, start.len - out.len, is_restart,
147
0
              &w->scratch);
148
0
done:
149
0
  return err;
150
0
}
151
152
int block_writer_finish(struct block_writer *w)
153
0
{
154
0
  for (uint32_t i = 0; i < w->restart_len; i++) {
155
0
    reftable_put_be24(w->block + w->next, w->restarts[i]);
156
0
    w->next += 3;
157
0
  }
158
159
0
  reftable_put_be16(w->block + w->next, w->restart_len);
160
0
  w->next += 2;
161
0
  reftable_put_be24(w->block + 1 + w->header_off, w->next);
162
163
  /*
164
   * Log records are stored zlib-compressed. Note that the compression
165
   * also spans over the restart points we have just written.
166
   */
167
0
  if (block_writer_type(w) == REFTABLE_BLOCK_TYPE_LOG) {
168
0
    int block_header_skip = 4 + w->header_off;
169
0
    uLongf src_len = w->next - block_header_skip, compressed_len;
170
0
    int ret;
171
172
0
    ret = deflateReset(w->zstream);
173
0
    if (ret != Z_OK)
174
0
      return REFTABLE_ZLIB_ERROR;
175
176
    /*
177
     * Precompute the upper bound of how many bytes the compressed
178
     * data may end up with. Combined with `Z_FINISH`, `deflate()`
179
     * is guaranteed to return `Z_STREAM_END`.
180
     */
181
0
    compressed_len = deflateBound(w->zstream, src_len);
182
0
    REFTABLE_ALLOC_GROW_OR_NULL(w->compressed, compressed_len,
183
0
              w->compressed_cap);
184
0
    if (!w->compressed) {
185
0
      ret = REFTABLE_OUT_OF_MEMORY_ERROR;
186
0
      return ret;
187
0
    }
188
189
0
    w->zstream->next_out = w->compressed;
190
0
    w->zstream->avail_out = compressed_len;
191
0
    w->zstream->next_in = w->block + block_header_skip;
192
0
    w->zstream->avail_in = src_len;
193
194
    /*
195
     * We want to perform all decompression in a single step, which
196
     * is why we can pass Z_FINISH here. As we have precomputed the
197
     * deflated buffer's size via `deflateBound()` this function is
198
     * guaranteed to succeed according to the zlib documentation.
199
     */
200
0
    ret = deflate(w->zstream, Z_FINISH);
201
0
    if (ret != Z_STREAM_END)
202
0
      return REFTABLE_ZLIB_ERROR;
203
204
    /*
205
     * Overwrite the uncompressed data we have already written and
206
     * adjust the `next` pointer to point right after the
207
     * compressed data.
208
     */
209
0
    memcpy(w->block + block_header_skip, w->compressed,
210
0
           w->zstream->total_out);
211
0
    w->next = w->zstream->total_out + block_header_skip;
212
0
  }
213
214
0
  return w->next;
215
0
}
216
217
static int read_block(struct reftable_block_source *source,
218
          struct reftable_block_data *dest, uint64_t off,
219
          uint32_t sz)
220
0
{
221
0
  size_t size = block_source_size(source);
222
0
  block_source_release_data(dest);
223
0
  if (off >= size)
224
0
    return 0;
225
0
  if (off + sz > size)
226
0
    sz = size - off;
227
0
  return block_source_read_data(source, dest, off, sz);
228
0
}
229
230
int reftable_block_init(struct reftable_block *block,
231
      struct reftable_block_source *source,
232
      uint32_t offset, uint32_t header_size,
233
      uint32_t table_block_size, uint32_t hash_size,
234
      uint8_t want_type)
235
0
{
236
0
  uint32_t guess_block_size = table_block_size ?
237
0
    table_block_size : DEFAULT_BLOCK_SIZE;
238
0
  uint32_t full_block_size = table_block_size;
239
0
  uint16_t restart_count;
240
0
  uint32_t restart_off;
241
0
  uint32_t block_size;
242
0
  uint8_t block_type;
243
0
  int err;
244
245
0
  err = read_block(source, &block->block_data, offset, guess_block_size);
246
0
  if (err < 0)
247
0
    goto done;
248
249
0
  block_type = block->block_data.data[header_size];
250
0
  if (!reftable_is_block_type(block_type)) {
251
0
    err = REFTABLE_FORMAT_ERROR;
252
0
    goto done;
253
0
  }
254
0
  if (want_type != REFTABLE_BLOCK_TYPE_ANY && block_type != want_type) {
255
0
    err = 1;
256
0
    goto done;
257
0
  }
258
259
0
  block_size = reftable_get_be24(block->block_data.data + header_size + 1);
260
0
  if (block_size > guess_block_size) {
261
0
    err = read_block(source, &block->block_data, offset, block_size);
262
0
    if (err < 0)
263
0
      goto done;
264
0
  }
265
266
  /*
267
   * Verify that the block size covers at least the table header, block
268
   * header and the 2 byte restart counter.
269
   */
270
0
  if (block_size < header_size + 4 + 2) {
271
0
    err = REFTABLE_FORMAT_ERROR;
272
0
    goto done;
273
0
  }
274
275
0
  if (block_type == REFTABLE_BLOCK_TYPE_LOG) {
276
0
    uint32_t block_header_skip = 4 + header_size;
277
0
    uLong dst_len = block_size - block_header_skip;
278
0
    uLong src_len = block->block_data.len - block_header_skip;
279
280
    /* Log blocks specify the *uncompressed* size in their header. */
281
0
    REFTABLE_ALLOC_GROW_OR_NULL(block->uncompressed_data, block_size,
282
0
              block->uncompressed_cap);
283
0
    if (!block->uncompressed_data) {
284
0
      err = REFTABLE_OUT_OF_MEMORY_ERROR;
285
0
      goto done;
286
0
    }
287
288
    /* Copy over the block header verbatim. It's not compressed. */
289
0
    memcpy(block->uncompressed_data, block->block_data.data, block_header_skip);
290
291
0
    if (!block->zstream) {
292
0
      REFTABLE_CALLOC_ARRAY(block->zstream, 1);
293
0
      if (!block->zstream) {
294
0
        err = REFTABLE_OUT_OF_MEMORY_ERROR;
295
0
        goto done;
296
0
      }
297
298
0
      err = inflateInit(block->zstream);
299
0
    } else {
300
0
      err = inflateReset(block->zstream);
301
0
    }
302
0
    if (err != Z_OK) {
303
0
      err = REFTABLE_ZLIB_ERROR;
304
0
      goto done;
305
0
    }
306
307
0
    block->zstream->next_in = block->block_data.data + block_header_skip;
308
0
    block->zstream->avail_in = src_len;
309
0
    block->zstream->next_out = block->uncompressed_data + block_header_skip;
310
0
    block->zstream->avail_out = dst_len;
311
312
    /*
313
     * We know both input as well as output size, and we know that
314
     * the sizes should never be bigger than `uInt_MAX` because
315
     * blocks can at most be 16MB large. We can thus use `Z_FINISH`
316
     * here to instruct zlib to inflate the data in one go, which
317
     * is more efficient than using `Z_NO_FLUSH`.
318
     */
319
0
    err = inflate(block->zstream, Z_FINISH);
320
0
    if (err != Z_STREAM_END) {
321
0
      err = REFTABLE_ZLIB_ERROR;
322
0
      goto done;
323
0
    }
324
0
    err = 0;
325
326
0
    if (block->zstream->total_out + block_header_skip != block_size) {
327
0
      err = REFTABLE_FORMAT_ERROR;
328
0
      goto done;
329
0
    }
330
331
    /* We're done with the input data. */
332
0
    block_source_release_data(&block->block_data);
333
0
    block->block_data.data = block->uncompressed_data;
334
0
    block->block_data.len = block_size;
335
0
    full_block_size = src_len + block_header_skip - block->zstream->avail_in;
336
0
  } else if (full_block_size == 0) {
337
0
    full_block_size = block_size;
338
0
  } else if (block_size < full_block_size && block_size < block->block_data.len &&
339
0
       block->block_data.data[block_size] != 0) {
340
    /* If the block is smaller than the full block size, it is
341
       padded (data followed by '\0') or the next block is
342
       unaligned. */
343
0
    full_block_size = block_size;
344
0
  }
345
346
  /*
347
   * Ensure that we have sufficient data available now to satisfy the
348
   * claimed block size.
349
   */
350
0
  if (block_size > block->block_data.len) {
351
0
    err = REFTABLE_FORMAT_ERROR;
352
0
    goto done;
353
0
  }
354
355
0
  restart_count = reftable_get_be16(block->block_data.data + block_size - 2);
356
0
  restart_off = block_size - 2 - 3 * restart_count;
357
0
  if (restart_off < header_size + 4 || restart_off > block_size - 2) {
358
0
    err = REFTABLE_FORMAT_ERROR;
359
0
    goto done;
360
0
  }
361
362
0
  block->block_type = block_type;
363
0
  block->hash_size = hash_size;
364
0
  block->restart_off = restart_off;
365
0
  block->full_block_size = full_block_size;
366
0
  block->header_off = header_size;
367
0
  block->restart_count = restart_count;
368
369
0
  err = 0;
370
371
0
done:
372
0
  if (err < 0)
373
0
    reftable_block_release(block);
374
0
  return err;
375
0
}
376
377
void reftable_block_release(struct reftable_block *block)
378
0
{
379
0
  inflateEnd(block->zstream);
380
0
  reftable_free(block->zstream);
381
0
  reftable_free(block->uncompressed_data);
382
0
  block_source_release_data(&block->block_data);
383
0
  memset(block, 0, sizeof(*block));
384
0
}
385
386
uint8_t reftable_block_type(const struct reftable_block *b)
387
0
{
388
0
  return b->block_data.data[b->header_off];
389
0
}
390
391
int reftable_block_first_key(const struct reftable_block *block, struct reftable_buf *key)
392
0
{
393
0
  int off = block->header_off + 4, n;
394
0
  struct string_view in = {
395
0
    .buf = block->block_data.data + off,
396
0
    .len = block->restart_off - off,
397
0
  };
398
0
  uint8_t extra = 0;
399
400
0
  reftable_buf_reset(key);
401
402
0
  n = reftable_decode_key(key, &extra, in);
403
0
  if (n < 0)
404
0
    return n;
405
0
  if (!key->len)
406
0
    return REFTABLE_FORMAT_ERROR;
407
408
0
  return 0;
409
0
}
410
411
static uint32_t block_restart_offset(const struct reftable_block *b, size_t idx)
412
0
{
413
0
  return reftable_get_be24(b->block_data.data + b->restart_off + 3 * idx);
414
0
}
415
416
void block_iter_init(struct block_iter *it, const struct reftable_block *block)
417
0
{
418
0
  it->block = block;
419
0
  block_iter_seek_start(it);
420
0
}
421
422
void block_iter_seek_start(struct block_iter *it)
423
0
{
424
0
  reftable_buf_reset(&it->last_key);
425
0
  it->next_off = it->block->header_off + 4;
426
0
}
427
428
struct restart_needle_less_args {
429
  int error;
430
  struct reftable_buf needle;
431
  const struct reftable_block *block;
432
};
433
434
static int restart_needle_less(size_t idx, void *_args)
435
0
{
436
0
  struct restart_needle_less_args *args = _args;
437
0
  uint32_t off = block_restart_offset(args->block, idx);
438
0
  struct string_view in = {
439
0
    .buf = args->block->block_data.data + off,
440
0
    .len = args->block->restart_off - off,
441
0
  };
442
0
  uint64_t prefix_len, suffix_len;
443
0
  uint8_t extra;
444
0
  int n;
445
446
  /*
447
   * The restart offset must point to a record, which is stored before
448
   * the restart table. Verify that this is the case.
449
   */
450
0
  if (off >= args->block->restart_off) {
451
0
    args->error = 1;
452
0
    return -1;
453
0
  }
454
455
  /*
456
   * Records at restart points are stored without prefix compression, so
457
   * there is no need to fully decode the record key here. This removes
458
   * the need for allocating memory.
459
   */
460
0
  n = reftable_decode_keylen(in, &prefix_len, &suffix_len, &extra);
461
0
  if (n < 0 || prefix_len) {
462
0
    args->error = 1;
463
0
    return -1;
464
0
  }
465
466
0
  string_view_consume(&in, n);
467
0
  if (suffix_len > in.len) {
468
0
    args->error = 1;
469
0
    return -1;
470
0
  }
471
472
0
  n = memcmp(args->needle.buf, in.buf,
473
0
       args->needle.len < suffix_len ? args->needle.len : suffix_len);
474
0
  if (n)
475
0
    return n < 0;
476
0
  return args->needle.len < suffix_len;
477
0
}
478
479
int block_iter_next(struct block_iter *it, struct reftable_record *rec)
480
0
{
481
0
  struct string_view in = {
482
0
    .buf = (unsigned char *) it->block->block_data.data + it->next_off,
483
0
    .len = it->block->restart_off - it->next_off,
484
0
  };
485
0
  struct string_view start = in;
486
0
  uint8_t extra = 0;
487
0
  int n = 0;
488
489
0
  if (it->next_off >= it->block->restart_off)
490
0
    return 1;
491
492
0
  n = reftable_decode_key(&it->last_key, &extra, in);
493
0
  if (n < 0)
494
0
    return -1;
495
0
  if (!it->last_key.len)
496
0
    return REFTABLE_FORMAT_ERROR;
497
498
0
  string_view_consume(&in, n);
499
0
  n = reftable_record_decode(rec, it->last_key, extra, in, it->block->hash_size,
500
0
           &it->scratch);
501
0
  if (n < 0)
502
0
    return -1;
503
0
  string_view_consume(&in, n);
504
505
0
  it->next_off += start.len - in.len;
506
0
  return 0;
507
0
}
508
509
void block_iter_reset(struct block_iter *it)
510
0
{
511
0
  reftable_buf_reset(&it->last_key);
512
0
  it->next_off = 0;
513
0
  it->block = NULL;
514
0
}
515
516
void block_iter_close(struct block_iter *it)
517
0
{
518
0
  reftable_buf_release(&it->last_key);
519
0
  reftable_buf_release(&it->scratch);
520
0
}
521
522
int block_iter_seek_key(struct block_iter *it, struct reftable_buf *want)
523
0
{
524
0
  struct restart_needle_less_args args = {
525
0
    .needle = *want,
526
0
    .block = it->block,
527
0
  };
528
0
  struct reftable_record rec;
529
0
  int err = 0;
530
0
  size_t i;
531
532
0
  err = reftable_record_init(&rec, reftable_block_type(it->block));
533
0
  if (err < 0)
534
0
    goto done;
535
536
  /*
537
   * Perform a binary search over the block's restart points, which
538
   * avoids doing a linear scan over the whole block. Like this, we
539
   * identify the section of the block that should contain our key.
540
   *
541
   * Note that we explicitly search for the first restart point _greater_
542
   * than the sought-after record, not _greater or equal_ to it. In case
543
   * the sought-after record is located directly at the restart point we
544
   * would otherwise start doing the linear search at the preceding
545
   * restart point. While that works alright, we would end up scanning
546
   * too many record.
547
   */
548
0
  i = binsearch(it->block->restart_count, &restart_needle_less, &args);
549
0
  if (args.error) {
550
0
    err = REFTABLE_FORMAT_ERROR;
551
0
    goto done;
552
0
  }
553
554
  /*
555
   * Now there are multiple cases:
556
   *
557
   *   - `i == 0`: The wanted record is smaller than the record found at
558
   *     the first restart point. As the first restart point is the first
559
   *     record in the block, our wanted record cannot be located in this
560
   *     block at all. We still need to position the iterator so that the
561
   *     next call to `block_iter_next()` will yield an end-of-iterator
562
   *     signal.
563
   *
564
   *   - `i == restart_count`: The wanted record was not found at any of
565
   *     the restart points. As there is no restart point at the end of
566
   *     the section the record may thus be contained in the last block.
567
   *
568
   *   - `i > 0`: The wanted record must be contained in the section
569
   *     before the found restart point. We thus do a linear search
570
   *     starting from the preceding restart point.
571
   */
572
0
  if (i > 0)
573
0
    it->next_off = block_restart_offset(it->block, i - 1);
574
0
  else
575
0
    it->next_off = it->block->header_off + 4;
576
577
  /*
578
   * We're looking for the last entry less than the wanted key so that
579
   * the next call to `block_reader_next()` would yield the wanted
580
   * record. We thus don't want to position our iterator at the sought
581
   * after record, but one before. To do so, we have to go one entry too
582
   * far and then back up.
583
   */
584
0
  while (1) {
585
0
    size_t prev_off = it->next_off;
586
587
0
    err = block_iter_next(it, &rec);
588
0
    if (err < 0)
589
0
      goto done;
590
0
    if (err > 0) {
591
0
      it->next_off = prev_off;
592
0
      err = 0;
593
0
      goto done;
594
0
    }
595
596
0
    err = reftable_record_key(&rec, &it->last_key);
597
0
    if (err < 0)
598
0
      goto done;
599
600
    /*
601
     * Check whether the current key is greater or equal to the
602
     * sought-after key. In case it is greater we know that the
603
     * record does not exist in the block and can thus abort early.
604
     * In case it is equal to the sought-after key we have found
605
     * the desired record.
606
     *
607
     * Note that we store the next record's key record directly in
608
     * `last_key` without restoring the key of the preceding record
609
     * in case we need to go one record back. This is safe to do as
610
     * `block_iter_next()` would return the ref whose key is equal
611
     * to `last_key` now, and naturally all keys share a prefix
612
     * with themselves.
613
     */
614
0
    if (reftable_buf_cmp(&it->last_key, want) >= 0) {
615
0
      it->next_off = prev_off;
616
0
      goto done;
617
0
    }
618
0
  }
619
620
0
done:
621
0
  reftable_record_release(&rec);
622
0
  return err;
623
0
}
624
625
static int block_iter_seek_void(void *it, struct reftable_record *want)
626
0
{
627
0
  struct reftable_buf buf = REFTABLE_BUF_INIT;
628
0
  struct block_iter *bi = it;
629
0
  int err;
630
631
0
  if (bi->block->block_type != want->type)
632
0
    return REFTABLE_API_ERROR;
633
634
0
  err = reftable_record_key(want, &buf);
635
0
  if (err < 0)
636
0
    goto out;
637
638
0
  err = block_iter_seek_key(it, &buf);
639
0
  if (err < 0)
640
0
    goto out;
641
642
0
  err = 0;
643
644
0
out:
645
0
  reftable_buf_release(&buf);
646
0
  return err;
647
0
}
648
649
static int block_iter_next_void(void *it, struct reftable_record *rec)
650
0
{
651
0
  return block_iter_next(it, rec);
652
0
}
653
654
static void block_iter_close_void(void *it)
655
0
{
656
0
  block_iter_close(it);
657
0
}
658
659
static struct reftable_iterator_vtable block_iter_vtable = {
660
  .seek = &block_iter_seek_void,
661
  .next = &block_iter_next_void,
662
  .close = &block_iter_close_void,
663
};
664
665
int reftable_block_init_iterator(const struct reftable_block *b,
666
         struct reftable_iterator *it)
667
0
{
668
0
  struct block_iter *bi;
669
670
0
  REFTABLE_CALLOC_ARRAY(bi, 1);
671
0
  block_iter_init(bi, b);
672
673
0
  assert(!it->ops);
674
0
  it->iter_arg = bi;
675
0
  it->ops = &block_iter_vtable;
676
677
0
  return 0;
678
0
}
679
680
void block_writer_release(struct block_writer *bw)
681
0
{
682
0
  deflateEnd(bw->zstream);
683
0
  REFTABLE_FREE_AND_NULL(bw->zstream);
684
0
  REFTABLE_FREE_AND_NULL(bw->restarts);
685
0
  REFTABLE_FREE_AND_NULL(bw->compressed);
686
0
  reftable_buf_release(&bw->scratch);
687
0
  reftable_buf_release(&bw->last_key);
688
  /* the block is not owned. */
689
0
}