reftable/block: better grouping of functions
[alt-git.git] / reftable / block.c
blobe65453e11b2a14faeb19ca80b2f5374583e091da
1 /*
2 Copyright 2020 Google LLC
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 */
9 #include "block.h"
11 #include "blocksource.h"
12 #include "constants.h"
13 #include "record.h"
14 #include "reftable-error.h"
15 #include "system.h"
16 #include <zlib.h>
18 int header_size(int version)
20 switch (version) {
21 case 1:
22 return 24;
23 case 2:
24 return 28;
26 abort();
29 int footer_size(int version)
31 switch (version) {
32 case 1:
33 return 68;
34 case 2:
35 return 72;
37 abort();
40 static int block_writer_register_restart(struct block_writer *w, int n,
41 int is_restart, struct strbuf *key)
43 int rlen = w->restart_len;
44 if (rlen >= MAX_RESTARTS) {
45 is_restart = 0;
48 if (is_restart) {
49 rlen++;
51 if (2 + 3 * rlen + n > w->block_size - w->next)
52 return -1;
53 if (is_restart) {
54 REFTABLE_ALLOC_GROW(w->restarts, w->restart_len + 1, w->restart_cap);
55 w->restarts[w->restart_len++] = w->next;
58 w->next += n;
60 strbuf_reset(&w->last_key);
61 strbuf_addbuf(&w->last_key, key);
62 w->entries++;
63 return 0;
66 void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,
67 uint32_t block_size, uint32_t header_off, int hash_size)
69 bw->buf = buf;
70 bw->hash_size = hash_size;
71 bw->block_size = block_size;
72 bw->header_off = header_off;
73 bw->buf[header_off] = typ;
74 bw->next = header_off + 4;
75 bw->restart_interval = 16;
76 bw->entries = 0;
77 bw->restart_len = 0;
78 bw->last_key.len = 0;
81 uint8_t block_writer_type(struct block_writer *bw)
83 return bw->buf[bw->header_off];
86 /* Adds the reftable_record to the block. Returns -1 if it does not fit, 0 on
87 success. Returns REFTABLE_API_ERROR if attempting to write a record with
88 empty key. */
89 int block_writer_add(struct block_writer *w, struct reftable_record *rec)
91 struct strbuf empty = STRBUF_INIT;
92 struct strbuf last =
93 w->entries % w->restart_interval == 0 ? empty : w->last_key;
94 struct string_view out = {
95 .buf = w->buf + w->next,
96 .len = w->block_size - w->next,
99 struct string_view start = out;
101 int is_restart = 0;
102 struct strbuf key = STRBUF_INIT;
103 int n = 0;
104 int err = -1;
106 reftable_record_key(rec, &key);
107 if (!key.len) {
108 err = REFTABLE_API_ERROR;
109 goto done;
112 n = reftable_encode_key(&is_restart, out, last, key,
113 reftable_record_val_type(rec));
114 if (n < 0)
115 goto done;
116 string_view_consume(&out, n);
118 n = reftable_record_encode(rec, out, w->hash_size);
119 if (n < 0)
120 goto done;
121 string_view_consume(&out, n);
123 err = block_writer_register_restart(w, start.len - out.len, is_restart,
124 &key);
125 done:
126 strbuf_release(&key);
127 return err;
130 int block_writer_finish(struct block_writer *w)
132 int i;
133 for (i = 0; i < w->restart_len; i++) {
134 put_be24(w->buf + w->next, w->restarts[i]);
135 w->next += 3;
138 put_be16(w->buf + w->next, w->restart_len);
139 w->next += 2;
140 put_be24(w->buf + 1 + w->header_off, w->next);
142 if (block_writer_type(w) == BLOCK_TYPE_LOG) {
143 int block_header_skip = 4 + w->header_off;
144 uLongf src_len = w->next - block_header_skip;
145 uLongf dest_cap = src_len * 1.001 + 12;
146 uint8_t *compressed;
148 REFTABLE_ALLOC_ARRAY(compressed, dest_cap);
150 while (1) {
151 uLongf out_dest_len = dest_cap;
152 int zresult = compress2(compressed, &out_dest_len,
153 w->buf + block_header_skip,
154 src_len, 9);
155 if (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) {
156 dest_cap *= 2;
157 compressed =
158 reftable_realloc(compressed, dest_cap);
159 if (compressed)
160 continue;
163 if (Z_OK != zresult) {
164 reftable_free(compressed);
165 return REFTABLE_ZLIB_ERROR;
168 memcpy(w->buf + block_header_skip, compressed,
169 out_dest_len);
170 w->next = out_dest_len + block_header_skip;
171 reftable_free(compressed);
172 break;
175 return w->next;
178 int block_reader_init(struct block_reader *br, struct reftable_block *block,
179 uint32_t header_off, uint32_t table_block_size,
180 int hash_size)
182 uint32_t full_block_size = table_block_size;
183 uint8_t typ = block->data[header_off];
184 uint32_t sz = get_be24(block->data + header_off + 1);
185 int err = 0;
186 uint16_t restart_count = 0;
187 uint32_t restart_start = 0;
188 uint8_t *restart_bytes = NULL;
189 uint8_t *uncompressed = NULL;
191 if (!reftable_is_block_type(typ)) {
192 err = REFTABLE_FORMAT_ERROR;
193 goto done;
196 if (typ == BLOCK_TYPE_LOG) {
197 int block_header_skip = 4 + header_off;
198 uLongf dst_len = sz - block_header_skip; /* total size of dest
199 buffer. */
200 uLongf src_len = block->len - block_header_skip;
202 /* Log blocks specify the *uncompressed* size in their header. */
203 REFTABLE_ALLOC_ARRAY(uncompressed, sz);
205 /* Copy over the block header verbatim. It's not compressed. */
206 memcpy(uncompressed, block->data, block_header_skip);
208 /* Uncompress */
209 if (Z_OK !=
210 uncompress2(uncompressed + block_header_skip, &dst_len,
211 block->data + block_header_skip, &src_len)) {
212 err = REFTABLE_ZLIB_ERROR;
213 goto done;
216 if (dst_len + block_header_skip != sz) {
217 err = REFTABLE_FORMAT_ERROR;
218 goto done;
221 /* We're done with the input data. */
222 reftable_block_done(block);
223 block->data = uncompressed;
224 uncompressed = NULL;
225 block->len = sz;
226 block->source = malloc_block_source();
227 full_block_size = src_len + block_header_skip;
228 } else if (full_block_size == 0) {
229 full_block_size = sz;
230 } else if (sz < full_block_size && sz < block->len &&
231 block->data[sz] != 0) {
232 /* If the block is smaller than the full block size, it is
233 padded (data followed by '\0') or the next block is
234 unaligned. */
235 full_block_size = sz;
238 restart_count = get_be16(block->data + sz - 2);
239 restart_start = sz - 2 - 3 * restart_count;
240 restart_bytes = block->data + restart_start;
242 /* transfer ownership. */
243 br->block = *block;
244 block->data = NULL;
245 block->len = 0;
247 br->hash_size = hash_size;
248 br->block_len = restart_start;
249 br->full_block_size = full_block_size;
250 br->header_off = header_off;
251 br->restart_count = restart_count;
252 br->restart_bytes = restart_bytes;
254 done:
255 reftable_free(uncompressed);
256 return err;
259 uint8_t block_reader_type(struct block_reader *r)
261 return r->block.data[r->header_off];
264 int block_reader_first_key(struct block_reader *br, struct strbuf *key)
266 int off = br->header_off + 4, n;
267 struct string_view in = {
268 .buf = br->block.data + off,
269 .len = br->block_len - off,
271 uint8_t extra = 0;
273 strbuf_reset(key);
275 n = reftable_decode_key(key, &extra, in);
276 if (n < 0)
277 return n;
278 if (!key->len)
279 return REFTABLE_FORMAT_ERROR;
281 return 0;
284 static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
286 return get_be24(br->restart_bytes + 3 * i);
289 void block_iter_seek_start(struct block_iter *it, struct block_reader *br)
291 it->br = br;
292 strbuf_reset(&it->last_key);
293 it->next_off = br->header_off + 4;
296 struct restart_needle_less_args {
297 int error;
298 struct strbuf needle;
299 struct block_reader *reader;
302 static int restart_needle_less(size_t idx, void *_args)
304 struct restart_needle_less_args *args = _args;
305 uint32_t off = block_reader_restart_offset(args->reader, idx);
306 struct string_view in = {
307 .buf = args->reader->block.data + off,
308 .len = args->reader->block_len - off,
310 uint64_t prefix_len, suffix_len;
311 uint8_t extra;
312 int n;
315 * Records at restart points are stored without prefix compression, so
316 * there is no need to fully decode the record key here. This removes
317 * the need for allocating memory.
319 n = reftable_decode_keylen(in, &prefix_len, &suffix_len, &extra);
320 if (n < 0 || prefix_len) {
321 args->error = 1;
322 return -1;
325 string_view_consume(&in, n);
326 if (suffix_len > in.len) {
327 args->error = 1;
328 return -1;
331 n = memcmp(args->needle.buf, in.buf,
332 args->needle.len < suffix_len ? args->needle.len : suffix_len);
333 if (n)
334 return n < 0;
335 return args->needle.len < suffix_len;
338 void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
340 dest->br = src->br;
341 dest->next_off = src->next_off;
342 strbuf_reset(&dest->last_key);
343 strbuf_addbuf(&dest->last_key, &src->last_key);
346 int block_iter_next(struct block_iter *it, struct reftable_record *rec)
348 struct string_view in = {
349 .buf = it->br->block.data + it->next_off,
350 .len = it->br->block_len - it->next_off,
352 struct string_view start = in;
353 uint8_t extra = 0;
354 int n = 0;
356 if (it->next_off >= it->br->block_len)
357 return 1;
359 n = reftable_decode_key(&it->last_key, &extra, in);
360 if (n < 0)
361 return -1;
362 if (!it->last_key.len)
363 return REFTABLE_FORMAT_ERROR;
365 string_view_consume(&in, n);
366 n = reftable_record_decode(rec, it->last_key, extra, in, it->br->hash_size,
367 &it->scratch);
368 if (n < 0)
369 return -1;
370 string_view_consume(&in, n);
372 it->next_off += start.len - in.len;
373 return 0;
376 void block_iter_close(struct block_iter *it)
378 strbuf_release(&it->last_key);
379 strbuf_release(&it->scratch);
382 int block_iter_seek_key(struct block_iter *it, struct block_reader *br,
383 struct strbuf *want)
385 struct restart_needle_less_args args = {
386 .needle = *want,
387 .reader = br,
389 struct block_iter next = BLOCK_ITER_INIT;
390 struct reftable_record rec;
391 int err = 0;
392 size_t i;
395 * Perform a binary search over the block's restart points, which
396 * avoids doing a linear scan over the whole block. Like this, we
397 * identify the section of the block that should contain our key.
399 * Note that we explicitly search for the first restart point _greater_
400 * than the sought-after record, not _greater or equal_ to it. In case
401 * the sought-after record is located directly at the restart point we
402 * would otherwise start doing the linear search at the preceding
403 * restart point. While that works alright, we would end up scanning
404 * too many record.
406 i = binsearch(br->restart_count, &restart_needle_less, &args);
407 if (args.error) {
408 err = REFTABLE_FORMAT_ERROR;
409 goto done;
413 * Now there are multiple cases:
415 * - `i == 0`: The wanted record is smaller than the record found at
416 * the first restart point. As the first restart point is the first
417 * record in the block, our wanted record cannot be located in this
418 * block at all. We still need to position the iterator so that the
419 * next call to `block_iter_next()` will yield an end-of-iterator
420 * signal.
422 * - `i == restart_count`: The wanted record was not found at any of
423 * the restart points. As there is no restart point at the end of
424 * the section the record may thus be contained in the last block.
426 * - `i > 0`: The wanted record must be contained in the section
427 * before the found restart point. We thus do a linear search
428 * starting from the preceding restart point.
430 if (i > 0)
431 it->next_off = block_reader_restart_offset(br, i - 1);
432 else
433 it->next_off = br->header_off + 4;
434 it->br = br;
436 reftable_record_init(&rec, block_reader_type(br));
439 * We're looking for the last entry less than the wanted key so that
440 * the next call to `block_reader_next()` would yield the wanted
441 * record. We thus don't want to position our reader at the sought
442 * after record, but one before. To do so, we have to go one entry too
443 * far and then back up.
445 while (1) {
446 block_iter_copy_from(&next, it);
447 err = block_iter_next(&next, &rec);
448 if (err < 0)
449 goto done;
450 if (err > 0) {
451 err = 0;
452 goto done;
456 * Check whether the current key is greater or equal to the
457 * sought-after key. In case it is greater we know that the
458 * record does not exist in the block and can thus abort early.
459 * In case it is equal to the sought-after key we have found
460 * the desired record.
462 reftable_record_key(&rec, &it->last_key);
463 if (strbuf_cmp(&it->last_key, want) >= 0)
464 goto done;
466 block_iter_copy_from(it, &next);
469 done:
470 block_iter_close(&next);
471 reftable_record_release(&rec);
473 return err;
476 void block_writer_release(struct block_writer *bw)
478 FREE_AND_NULL(bw->restarts);
479 strbuf_release(&bw->last_key);
480 /* the block is not owned. */
483 void reftable_block_done(struct reftable_block *blockp)
485 struct reftable_block_source source = blockp->source;
486 if (blockp && source.ops)
487 source.ops->return_block(source.arg, blockp);
488 blockp->data = NULL;
489 blockp->len = 0;
490 blockp->source.ops = NULL;
491 blockp->source.arg = NULL;