Coverage Report

Created: 2026-09-28 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/htslib/cram/cram_index.c
Line
Count
Source
1
/*
2
Copyright (c) 2013-2020, 2023-2024, 2026 Genome Research Ltd.
3
Author: James Bonfield <jkb@sanger.ac.uk>
4
5
Redistribution and use in source and binary forms, with or without
6
modification, are permitted provided that the following conditions are met:
7
8
   1. Redistributions of source code must retain the above copyright notice,
9
this list of conditions and the following disclaimer.
10
11
   2. Redistributions in binary form must reproduce the above copyright notice,
12
this list of conditions and the following disclaimer in the documentation
13
and/or other materials provided with the distribution.
14
15
   3. Neither the names Genome Research Ltd and Wellcome Trust Sanger
16
Institute nor the names of its contributors may be used to endorse or promote
17
products derived from this software without specific prior written permission.
18
19
THIS SOFTWARE IS PROVIDED BY GENOME RESEARCH LTD AND CONTRIBUTORS "AS IS" AND
20
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
DISCLAIMED. IN NO EVENT SHALL GENOME RESEARCH LTD OR CONTRIBUTORS BE LIABLE
23
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
*/
30
31
/*
32
 * The index is a gzipped tab-delimited text file with one line per slice.
33
 * The columns are:
34
 * 1: reference number (0 to N-1, as per BAM ref_id)
35
 * 2: reference position of 1st read in slice (1..?)
36
 * 3: number of reads in slice
37
 * 4: offset of container start (relative to end of SAM header, so 1st
38
 *    container is offset 0).
39
 * 5: slice number within container (ie which landmark).
40
 *
41
 * In memory, we hold this in a nested containment list. Each list element is
42
 * a cram_index struct. Each element in turn can contain its own list of
43
 * cram_index structs.
44
 *
45
 * Any start..end range which is entirely contained within another (and
46
 * earlier as it is sorted) range will be held within it. This ensures that
47
 * the outer list will never have containments and we can safely do a
48
 * binary search to find the first range which overlaps any given coordinate.
49
 */
50
51
#define HTS_BUILDING_LIBRARY // Enables HTSLIB_EXPORT, see htslib/hts_defs.h
52
#include <config.h>
53
54
#include <stdio.h>
55
#include <errno.h>
56
#include <assert.h>
57
#include <inttypes.h>
58
#include <stdlib.h>
59
#include <string.h>
60
#include <zlib.h>
61
#include <sys/types.h>
62
#include <sys/stat.h>
63
#include <math.h>
64
65
#include "../htslib/bgzf.h"
66
#include "../htslib/hfile.h"
67
#include "../htslib/hts_alloc.h"
68
#include "../hts_internal.h"
69
#include "cram.h"
70
#include "os.h"
71
72
#if 0
73
static void dump_index_(cram_index *e, int level) {
74
    int i, n;
75
    n = printf("%*s%d / %d .. %d, ", level*4, "", e->refid, e->start, e->end);
76
    printf("%*soffset %"PRId64" %p %p\n", MAX(0,50-n), "", e->offset, e, e->e_next);
77
    for (i = 0; i < e->nslice; i++) {
78
        dump_index_(&e->e[i], level+1);
79
    }
80
}
81
82
static void dump_index(cram_fd *fd) {
83
    int i;
84
    for (i = 0; i < fd->index_sz; i++) {
85
        dump_index_(&fd->index[i], 0);
86
    }
87
}
88
#endif
89
90
// Thread a linked list through the nested containment list.
91
// This makes navigating it and finding the "next" index entry
92
// trivial.
93
0
static cram_index *link_index_(cram_index *e, cram_index *e_last) {
94
0
    int i;
95
0
    if (e_last)
96
0
        e_last->e_next = e;
97
98
    // We don't want to link in the top-level cram_index with
99
    // offset=0 and start/end = INT_MIN/INT_MAX.
100
0
    if (e->offset)
101
0
        e_last = e;
102
103
0
    for (i = 0; i < e->nslice; i++)
104
0
        e_last = link_index_(&e->e[i], e_last);
105
106
0
    return e_last;
107
0
}
108
109
0
static void link_index(cram_fd *fd) {
110
0
    int i;
111
0
    cram_index *e_last = NULL;
112
113
0
    for (i = 0; i < fd->index_sz; i++) {
114
0
        e_last = link_index_(&fd->index[i], e_last);
115
0
    }
116
117
0
    if (e_last)
118
0
        e_last->e_next = NULL;
119
0
}
120
121
0
static int kget_int32(kstring_t *k, size_t *pos, int32_t *val_p) {
122
0
    int sign = 1;
123
0
    int32_t val = 0;
124
0
    size_t p = *pos;
125
126
0
    while (p < k->l && (k->s[p] == ' ' || k->s[p] == '\t'))
127
0
        p++;
128
129
0
    if (p < k->l && k->s[p] == '-')
130
0
        sign = -1, p++;
131
132
0
    if (p >= k->l || !(k->s[p] >= '0' && k->s[p] <= '9'))
133
0
        return -1;
134
135
0
    while (p < k->l && k->s[p] >= '0' && k->s[p] <= '9') {
136
0
        int digit = k->s[p++]-'0';
137
0
        val = val*10 + digit;
138
0
    }
139
140
0
    *pos = p;
141
0
    *val_p = sign*val;
142
143
0
    return 0;
144
0
}
145
146
0
static int kget_int64(kstring_t *k, size_t *pos, int64_t *val_p) {
147
0
    int sign = 1;
148
0
    int64_t val = 0;
149
0
    size_t p = *pos;
150
151
0
    while (p < k->l && (k->s[p] == ' ' || k->s[p] == '\t'))
152
0
        p++;
153
154
0
    if (p < k->l && k->s[p] == '-')
155
0
        sign = -1, p++;
156
157
0
    if (p >= k->l || !(k->s[p] >= '0' && k->s[p] <= '9'))
158
0
        return -1;
159
160
0
    while (p < k->l && k->s[p] >= '0' && k->s[p] <= '9') {
161
0
        int digit = k->s[p++]-'0';
162
0
        val = val*10 + digit;
163
0
    }
164
165
0
    *pos = p;
166
0
    *val_p = sign*val;
167
168
0
    return 0;
169
0
}
170
171
/*
172
 * Loads a CRAM .crai index into memory.
173
 *
174
 * Returns 0 for success
175
 *        -1 for failure
176
 */
177
0
int cram_index_load(cram_fd *fd, const char *fn, const char *fn_idx) {
178
179
0
    char *tfn_idx = NULL;
180
0
    char buf[65536];
181
0
    ssize_t len;
182
0
    kstring_t kstr = {0};
183
0
    BGZF *fp = NULL;
184
0
    cram_index *idx;
185
0
    cram_index **idx_stack = NULL, *ep, e;
186
0
    int idx_stack_alloc = 0, idx_stack_ptr = 0;
187
0
    size_t pos = 0;
188
189
    /* Check if already loaded */
190
0
    if (fd->index)
191
0
        return 0;
192
193
0
    fd->index = calloc((fd->index_sz = 1), sizeof(*fd->index));
194
0
    if (!fd->index)
195
0
        return -1;
196
197
0
    idx = &fd->index[0];
198
0
    idx->refid = -1;
199
0
    idx->start = INT_MIN;
200
0
    idx->end   = INT_MAX;
201
202
0
    idx_stack = calloc(++idx_stack_alloc, sizeof(*idx_stack));
203
0
    if (!idx_stack)
204
0
        goto fail;
205
206
0
    idx_stack[idx_stack_ptr] = idx;
207
208
    // Support pathX.cram##idx##pathY.crai
209
0
    const char *fn_delim = strstr(fn, HTS_IDX_DELIM);
210
0
    if (fn_delim && !fn_idx)
211
0
        fn_idx = fn_delim + strlen(HTS_IDX_DELIM);
212
213
0
    if (!fn_idx) {
214
0
        if (hts_idx_check_local(fn, HTS_FMT_CRAI, &tfn_idx) == 0 && hisremote(fn))
215
0
            tfn_idx = hts_idx_getfn(fn, ".crai");
216
217
0
        if (!tfn_idx) {
218
0
            hts_log_error("Could not retrieve index file for '%s'", fn);
219
0
            goto fail;
220
0
        }
221
0
        fn_idx = tfn_idx;
222
0
    }
223
224
0
    if (!(fp = bgzf_open(fn_idx, "r"))) {
225
0
        hts_log_error("Could not open index file '%s'", fn_idx);
226
0
        goto fail;
227
0
    }
228
229
    // Load the file into memory
230
0
    while ((len = bgzf_read(fp, buf, sizeof(buf))) > 0) {
231
0
        if (kputsn(buf, len, &kstr) < 0)
232
0
            goto fail;
233
0
    }
234
235
0
    if (len < 0 || kstr.l < 1)
236
0
        goto fail;
237
238
0
    int ret = bgzf_close(fp);
239
0
    fp = NULL; // Prevent double close on failure
240
0
    if (ret < 0)
241
0
        goto fail;
242
243
    // refid indexes fd->index, so bound it to the header's reference count.
244
0
    int nref = sam_hdr_nref(fd->header);
245
246
    // Parse it line at a time
247
0
    while (pos < kstr.l) {
248
        /* 1.1 layout */
249
0
        if (kget_int32(&kstr, &pos, &e.refid) == -1)
250
0
            goto fail;
251
252
0
        if (kget_int32(&kstr, &pos, &e.start) == -1)
253
0
            goto fail;
254
255
0
        if (kget_int32(&kstr, &pos, &e.end) == -1)
256
0
            goto fail;
257
258
0
        if (kget_int64(&kstr, &pos, &e.offset) == -1)
259
0
            goto fail;
260
261
0
        if (kget_int32(&kstr, &pos, &e.slice) == -1)
262
0
            goto fail;
263
264
0
        if (kget_int32(&kstr, &pos, &e.len) == -1)
265
0
            goto fail;
266
267
0
        e.end += e.start-1;
268
        //printf("%d/%d..%d-offset=%" PRIu64 ",len=%d,slice=%d\n", e.refid, e.start, e.end, e.offset, e.len, e.slice);
269
270
0
        if (e.refid < -1 || e.refid >= nref) {
271
0
            hts_log_error("Malformed index file, refid %d", e.refid);
272
0
            goto fail;
273
0
        }
274
275
0
        if (e.refid != idx->refid) {
276
0
            if (fd->index_sz < e.refid+2) {
277
0
                cram_index *new_idx;
278
0
                int new_sz = e.refid+2;
279
0
                size_t index_end = fd->index_sz * sizeof(*fd->index);
280
0
                new_idx = hts_realloc_p(fd->index, sizeof(*fd->index),
281
0
                                        new_sz);
282
0
                if (!new_idx)
283
0
                    goto fail;
284
285
0
                fd->index = new_idx;
286
0
                fd->index_sz = new_sz;
287
0
                memset(((char *)fd->index) + index_end, 0,
288
0
                       fd->index_sz * sizeof(*fd->index) - index_end);
289
0
            }
290
0
            idx = &fd->index[e.refid+1];
291
0
            if (idx->e) {
292
0
                hts_log_error("Index is not sorted");
293
0
                goto fail;
294
0
            }
295
0
            idx->refid = e.refid;
296
0
            idx->start = INT_MIN;
297
0
            idx->end   = INT_MAX;
298
0
            idx->nslice = idx->nalloc = 0;
299
0
            idx->e = NULL;
300
0
            idx_stack[(idx_stack_ptr = 0)] = idx;
301
0
        }
302
303
0
        while (!(e.start >= idx->start && e.end <= idx->end) ||
304
0
               (idx->start == 0 && idx->refid == -1)) {
305
0
            idx = idx_stack[--idx_stack_ptr];
306
0
        }
307
308
        // Now contains, so append
309
0
        if (idx->nslice+1 >= idx->nalloc) {
310
0
            cram_index *new_e;
311
0
            idx->nalloc = idx->nalloc ? idx->nalloc*2 : 16;
312
0
            new_e = hts_realloc_p(idx->e, sizeof(*idx->e), idx->nalloc);
313
0
            if (!new_e)
314
0
                goto fail;
315
316
0
            idx->e = new_e;
317
0
        }
318
319
0
        e.nalloc = e.nslice = 0; e.e = NULL;
320
0
        *(ep = &idx->e[idx->nslice++]) = e;
321
0
        idx = ep;
322
323
0
        if (++idx_stack_ptr >= idx_stack_alloc) {
324
0
            cram_index **new_stack;
325
0
            idx_stack_alloc *= 2;
326
0
            new_stack = hts_realloc_p(idx_stack, sizeof(*idx_stack), idx_stack_alloc);
327
0
            if (!new_stack)
328
0
                goto fail;
329
0
            idx_stack = new_stack;
330
0
        }
331
0
        idx_stack[idx_stack_ptr] = idx;
332
333
0
        while (pos < kstr.l && kstr.s[pos] != '\n')
334
0
            pos++;
335
0
        pos++;
336
0
    }
337
338
0
    free(idx_stack);
339
0
    free(kstr.s);
340
0
    free(tfn_idx);
341
342
    // Convert NCList to linear linked list
343
0
    link_index(fd);
344
345
    //dump_index(fd);
346
347
0
    return 0;
348
349
0
 fail:
350
0
    free(kstr.s);
351
0
    free(idx_stack);
352
0
    free(tfn_idx);
353
0
    if (fp)
354
0
        bgzf_close(fp);
355
0
    cram_index_free(fd); // Also sets fd->index = NULL
356
0
    return -1;
357
0
}
358
359
0
static void cram_index_free_recurse(cram_index *e) {
360
0
    if (e->e) {
361
0
        int i;
362
0
        for (i = 0; i < e->nslice; i++) {
363
0
            cram_index_free_recurse(&e->e[i]);
364
0
        }
365
0
        free(e->e);
366
0
    }
367
0
}
368
369
0
void cram_index_free(cram_fd *fd) {
370
0
    int i;
371
372
0
    if (!fd->index)
373
0
        return;
374
375
0
    for (i = 0; i < fd->index_sz; i++) {
376
0
        cram_index_free_recurse(&fd->index[i]);
377
0
    }
378
0
    free(fd->index);
379
380
0
    fd->index = NULL;
381
0
}
382
383
/*
384
 * Searches the index for the first slice overlapping a reference ID
385
 * and position, or one immediately preceding it if none is found in
386
 * the index to overlap this position. (Our index may have missing
387
 * entries, but we require at least one per reference.)
388
 *
389
 * If the index finds multiple slices overlapping this position we
390
 * return the first one only. Subsequent calls should specify
391
 * "from" as the last slice we checked to find the next one. Otherwise
392
 * set "from" to be NULL to find the first one.
393
 *
394
 * Refid can also be any of the special HTS_IDX_ values.
395
 * For backwards compatibility, refid -1 is equivalent to HTS_IDX_NOCOOR.
396
 *
397
 * Returns the cram_index pointer on success
398
 *         NULL on failure
399
 */
400
cram_index *cram_index_query(cram_fd *fd, int refid, hts_pos_t pos,
401
0
                             cram_index *from) {
402
0
    int i, j, k;
403
0
    cram_index *e;
404
405
0
    if (from) {
406
        // Continue from a previous search.
407
        // We switch to just scanning the linked list, as the nested
408
        // lists are typically short.
409
0
        if (refid == HTS_IDX_NOCOOR)
410
0
            refid = -1;
411
412
0
        e = from->e_next;
413
0
        if (e && e->refid == refid && e->start <= pos)
414
0
            return e;
415
0
        else
416
0
            return NULL;
417
0
    }
418
419
0
    switch(refid) {
420
0
    case HTS_IDX_NONE:
421
0
    case HTS_IDX_REST:
422
        // fail, or already there, dealt with elsewhere.
423
0
        return NULL;
424
425
0
    case -1:
426
0
    case HTS_IDX_NOCOOR:
427
0
        refid = -1;
428
0
        pos = 0;
429
0
        break;
430
431
0
    case HTS_IDX_START: {
432
0
        int64_t min_idx = INT64_MAX;
433
0
        for (i = 0, j = -1; i < fd->index_sz; i++) {
434
0
            if (fd->index[i].e && fd->index[i].e[0].offset < min_idx) {
435
0
                min_idx = fd->index[i].e[0].offset;
436
0
                j = i;
437
0
            }
438
0
        }
439
0
        if (j < 0)
440
0
            return NULL;
441
0
        return fd->index[j].e;
442
0
    }
443
444
0
    default:
445
0
        if (refid < HTS_IDX_NONE || refid+1 >= fd->index_sz)
446
0
            return NULL;
447
0
    }
448
449
0
    from = &fd->index[refid+1];
450
451
    // Ref with nothing aligned against it.
452
0
    if (!from->e)
453
0
        return NULL;
454
455
    // This sequence is covered by the index, so binary search to find
456
    // the optimal starting block.
457
0
    i = 0, j = fd->index[refid+1].nslice-1;
458
0
    for (k = j/2; k != i; k = (j-i)/2 + i) {
459
0
        if (from->e[k].refid > refid) {
460
0
            j = k;
461
0
            continue;
462
0
        }
463
464
0
        if (from->e[k].refid < refid) {
465
0
            i = k;
466
0
            continue;
467
0
        }
468
469
0
        if (from->e[k].start >= pos) {
470
0
            j = k;
471
0
            continue;
472
0
        }
473
474
0
        if (from->e[k].start < pos) {
475
0
            i = k;
476
0
            continue;
477
0
        }
478
0
    }
479
    // i==j or i==j-1. Check if j is better.
480
0
    if (j >= 0 && from->e[j].start < pos && from->e[j].refid == refid)
481
0
        i = j;
482
483
    /* The above found *a* bin overlapping, but not necessarily the first */
484
0
    while (i > 0 && from->e[i-1].end >= pos)
485
0
        i--;
486
487
    /* We may be one bin before the optimum, so check */
488
0
    while (i+1 < from->nslice &&
489
0
           (from->e[i].refid < refid ||
490
0
            from->e[i].end < pos))
491
0
        i++;
492
493
0
    e = &from->e[i];
494
495
0
    return e;
496
0
}
497
498
// Return the index entry for last slice on a specific reference.
499
0
cram_index *cram_index_last(cram_fd *fd, int refid, cram_index *from) {
500
0
    int slice;
501
502
0
    if (refid+1 < 0 || refid+1 >= fd->index_sz)
503
0
        return NULL;
504
505
0
    if (!from)
506
0
        from = &fd->index[refid+1];
507
508
    // Ref with nothing aligned against it.
509
0
    if (!from->e)
510
0
        return NULL;
511
512
0
    slice = fd->index[refid+1].nslice - 1;
513
514
    // e is the last entry in the nested containment list, but it may
515
    // contain further slices within it.
516
0
    cram_index *e = &from->e[slice];
517
0
    while (e->e_next)
518
0
        e = e->e_next;
519
520
0
    return e;
521
0
}
522
523
/*
524
 * Find the last container overlapping pos 'end', and the file offset of
525
 * its end (equivalent to the start offset of the container following it).
526
 */
527
0
cram_index *cram_index_query_last(cram_fd *fd, int refid, hts_pos_t end) {
528
0
    cram_index *e = NULL, *prev_e;
529
0
    do {
530
0
        prev_e = e;
531
0
        e = cram_index_query(fd, refid, end, prev_e);
532
0
    } while (e);
533
534
0
    if (!prev_e)
535
0
        return NULL;
536
0
    e = prev_e;
537
538
    // Note: offset of e and e->e_next may be the same if we're using a
539
    // multi-ref container where a single container generates multiple
540
    // index entries.
541
    //
542
    // We need to keep iterating until offset differs in order to find
543
    // the genuine file offset for the end of container.
544
0
    do {
545
0
        prev_e = e;
546
0
        e = e->e_next;
547
0
    } while (e && e->offset == prev_e->offset);
548
549
0
    return prev_e;
550
0
}
551
552
/*
553
 * Skips to a container overlapping the start coordinate listed in
554
 * cram_range.
555
 *
556
 * In theory we call cram_index_query multiple times, once per slice
557
 * overlapping the range. However slices may be absent from the index
558
 * which makes this problematic. Instead we find the left-most slice
559
 * and then read from then on, skipping decoding of slices and/or
560
 * whole containers when they don't overlap the specified cram_range.
561
 *
562
 * This function also updates the cram_fd range field.
563
 *
564
 * Returns 0 on success
565
 *        -1 on general failure
566
 *        -2 on no-data (empty chromosome)
567
 */
568
0
int cram_seek_to_refpos(cram_fd *fd, cram_range *r) {
569
0
    int ret = 0;
570
0
    cram_index *e;
571
572
0
    if (r->refid == HTS_IDX_NONE) {
573
0
        ret = -2; goto err;
574
0
    }
575
576
    // Ideally use an index, so see if we have one.
577
0
    if ((e = cram_index_query(fd, r->refid, r->start, NULL))) {
578
0
        if (0 != cram_seek(fd, e->offset, SEEK_SET)) {
579
0
            ret = -1; goto err;
580
0
        }
581
0
    } else {
582
        // Absent from index, but this most likely means it simply has no data.
583
0
        ret = -2; goto err;
584
0
    }
585
586
0
    pthread_mutex_lock(&fd->range_lock);
587
0
    fd->range = *r;
588
0
    if (r->refid == HTS_IDX_NOCOOR) {
589
0
        fd->range.refid = -1;
590
0
        fd->range.start = 0;
591
0
    } else if (r->refid == HTS_IDX_START || r->refid == HTS_IDX_REST) {
592
0
        fd->range.refid = -2; // special case in cram_next_slice
593
0
    }
594
0
    pthread_mutex_unlock(&fd->range_lock);
595
596
0
    if (fd->ctr) {
597
0
        cram_free_container(fd->ctr);
598
0
        if (fd->ctr_mt && fd->ctr_mt != fd->ctr)
599
0
            cram_free_container(fd->ctr_mt);
600
0
        fd->ctr = NULL;
601
0
        fd->ctr_mt = NULL;
602
0
        fd->ooc = 0;
603
0
        fd->eof = 0;
604
0
    }
605
606
0
    return 0;
607
608
0
 err:
609
    // It's unlikely fd->range will be accessed after EOF or error,
610
    // but this maintains identical behaviour to the previous code.
611
0
    pthread_mutex_lock(&fd->range_lock);
612
0
    fd->range = *r;
613
0
    pthread_mutex_unlock(&fd->range_lock);
614
0
    return ret;
615
0
}
616
617
618
/*
619
 * A specialised form of cram_index_build (below) that deals with slices
620
 * having multiple references in this (ref_id -2). In this scenario we
621
 * decode the slice to look at the RI data series instead.
622
 *
623
 * Returns 0 on success
624
 *        -1 on read failure
625
 *        -2 on wrong sort order
626
 *        -4 on write failure
627
 */
628
static int cram_index_build_multiref(cram_fd *fd,
629
                                     cram_container *c,
630
                                     cram_slice *s,
631
                                     BGZF *fp,
632
                                     off_t cpos,
633
                                     int32_t landmark,
634
0
                                     int sz) {
635
0
    int i, ref = -2;
636
0
    int64_t ref_start = 0, ref_end;
637
0
    char buf[1024];
638
639
0
    if (fd->mode != 'w') {
640
0
        if (0 != cram_decode_slice(fd, c, s, fd->header))
641
0
            return -1;
642
0
    }
643
644
0
    ref_end = INT_MIN;
645
646
0
    int32_t last_ref = -9;
647
0
    int32_t last_pos = -9;
648
0
    for (i = 0; i < s->hdr->num_records; i++) {
649
0
        if (s->crecs[i].ref_id == last_ref && s->crecs[i].apos < last_pos) {
650
0
            hts_log_error("CRAM file is not sorted by chromosome / position");
651
0
            return -2;
652
0
        }
653
0
        last_ref = s->crecs[i].ref_id;
654
0
        last_pos = s->crecs[i].apos;
655
656
0
        if (s->crecs[i].ref_id == ref) {
657
0
            if (ref_end < s->crecs[i].aend)
658
0
                ref_end = s->crecs[i].aend;
659
0
            continue;
660
0
        }
661
662
0
        if (ref != -2) {
663
0
            snprintf(buf, sizeof(buf),
664
0
                     "%d\t%"PRId64"\t%"PRId64"\t%"PRId64"\t%d\t%d\n",
665
0
                     ref, ref_start, ref_end - ref_start + 1,
666
0
                     (int64_t)cpos, landmark, sz);
667
0
            if (bgzf_write(fp, buf, strlen(buf)) < 0)
668
0
                return -4;
669
0
        }
670
671
0
        ref = s->crecs[i].ref_id;
672
0
        ref_start = s->crecs[i].apos;
673
0
        ref_end   = s->crecs[i].aend;
674
0
    }
675
676
0
    if (ref != -2) {
677
0
        snprintf(buf, sizeof(buf),
678
0
                 "%d\t%"PRId64"\t%"PRId64"\t%"PRId64"\t%d\t%d\n",
679
0
                 ref, ref_start, ref_end - ref_start + 1,
680
0
                 (int64_t)cpos, landmark, sz);
681
0
        if (bgzf_write(fp, buf, strlen(buf)) < 0)
682
0
            return -4;
683
0
    }
684
685
0
    return 0;
686
0
}
687
688
/*
689
 * Adds a single slice to the index.
690
 */
691
int cram_index_slice(cram_fd *fd,
692
                     cram_container *c,
693
                     cram_slice *s,
694
                     BGZF *fp,
695
                     off_t cpos,
696
                     off_t spos, // relative to cpos
697
0
                     off_t sz) {
698
0
    int ret;
699
0
    char buf[1024];
700
701
0
    if (sz > INT_MAX) {
702
0
        hts_log_error("CRAM slice is too big (%"PRId64" bytes)",
703
0
                      (int64_t) sz);
704
0
        return -1;
705
0
    }
706
707
0
    if (s->hdr->ref_seq_id == -2) {
708
0
        ret = cram_index_build_multiref(fd, c, s, fp, cpos, spos, sz);
709
0
    } else {
710
0
        snprintf(buf, sizeof(buf),
711
0
                 "%d\t%"PRId64"\t%"PRId64"\t%"PRId64"\t%d\t%d\n",
712
0
                 s->hdr->ref_seq_id, s->hdr->ref_seq_start,
713
0
                 s->hdr->ref_seq_span, (int64_t)cpos, (int)spos, (int)sz);
714
0
        ret = (bgzf_write(fp, buf, strlen(buf)) >= 0)? 0 : -4;
715
0
    }
716
717
0
    return ret;
718
0
}
719
720
/*
721
 * Adds a single container to the index.
722
 */
723
static
724
int cram_index_container(cram_fd *fd,
725
                         cram_container *c,
726
                         BGZF *fp,
727
0
                         off_t cpos) {
728
0
    int j;
729
0
    off_t spos;
730
731
    // 2.0 format
732
0
    for (j = 0; j < c->num_landmarks; j++) {
733
0
        cram_slice *s;
734
0
        off_t sz;
735
0
        int ret;
736
737
0
        spos = htell(fd->fp);
738
0
        if (spos - cpos - (off_t) c->offset != c->landmark[j]) {
739
0
            hts_log_error("CRAM slice offset %"PRId64" does not match"
740
0
                          " landmark %d in container header (%"PRId32")",
741
0
                          (int64_t) (spos - cpos - (off_t) c->offset),
742
0
                          j, c->landmark[j]);
743
0
            return -1;
744
0
        }
745
746
0
        if (!(s = cram_read_slice(fd))) {
747
0
            return -1;
748
0
        }
749
750
0
        sz = htell(fd->fp) - spos;
751
0
        ret = cram_index_slice(fd, c, s, fp, cpos, c->landmark[j], sz);
752
753
0
        cram_free_slice(s);
754
755
0
        if (ret < 0) {
756
0
            return ret;
757
0
        }
758
0
    }
759
760
0
    return 0;
761
0
}
762
763
764
/*
765
 * Builds an index file.
766
 *
767
 * fd is a newly opened cram file that we wish to index.
768
 * fn_base is the filename of the associated CRAM file.
769
 * fn_idx is the filename of the index file to be written;
770
 * if NULL, we add ".crai" to fn_base to get the index filename.
771
 *
772
 * Returns 0 on success,
773
 *         negative on failure (-1 for read failure, -4 for write failure)
774
 */
775
0
int cram_index_build(cram_fd *fd, const char *fn_base, const char *fn_idx) {
776
0
    cram_container *c = NULL;
777
0
    off_t cpos, hpos;
778
0
    BGZF *fp = NULL;
779
0
    kstring_t fn_idx_str = {0};
780
0
    int64_t last_ref = -9, last_start = -9;
781
0
    int ret = -1;
782
783
    // Useful for cram_index_build_multiref
784
0
    cram_set_option(fd, CRAM_OPT_REQUIRED_FIELDS, SAM_RNAME | SAM_POS | SAM_CIGAR);
785
786
0
    if (! fn_idx) {
787
0
        kputs(fn_base, &fn_idx_str);
788
0
        kputs(".crai", &fn_idx_str);
789
0
        fn_idx = fn_idx_str.s;
790
0
    }
791
792
0
    if (!(fp = bgzf_open(fn_idx, "wg"))) {
793
0
        perror(fn_idx);
794
0
        free(fn_idx_str.s);
795
0
        return -4;
796
0
    }
797
798
0
    free(fn_idx_str.s);
799
800
0
    cpos = htell(fd->fp);
801
0
    while ((c = cram_read_container(fd))) {
802
0
        if (fd->err) {
803
0
            perror("Cram container read");
804
0
            return -1;
805
0
        }
806
807
0
        hpos = htell(fd->fp);
808
809
0
        if (!(c->comp_hdr_block = cram_read_block(fd)))
810
0
            goto err;
811
0
        if (c->comp_hdr_block->content_type != COMPRESSION_HEADER) {
812
0
            hts_log_error("Expected a compression header block at pos %lld",
813
0
                          (long long)hpos);
814
0
            goto err;
815
0
        }
816
817
0
        c->comp_hdr = cram_decode_compression_header(fd, c->comp_hdr_block);
818
0
        if (!c->comp_hdr)
819
0
            goto err;
820
821
0
        if (c->ref_seq_id == last_ref && c->ref_seq_start < last_start) {
822
0
            hts_log_error("CRAM file is not sorted by chromosome / position");
823
0
            ret = -2;
824
0
            goto err;
825
0
        }
826
0
        last_ref = c->ref_seq_id;
827
0
        last_start = c->ref_seq_start;
828
829
0
        if (cram_index_container(fd, c, fp, cpos) < 0)
830
0
            goto err;
831
832
0
        off_t next_cpos = htell(fd->fp);
833
0
        if (next_cpos != hpos + c->length) {
834
0
            hts_log_error("Length %"PRId32" in container header at offset %lld does not match block lengths (%lld)",
835
0
                          c->length, (long long) cpos, (long long) next_cpos - hpos);
836
0
            goto err;
837
0
        }
838
0
        cpos = next_cpos;
839
840
0
        cram_free_container(c);
841
0
    }
842
0
    if (fd->err) {
843
0
        bgzf_close(fp);
844
0
        return -1;
845
0
    }
846
847
0
    return (bgzf_close(fp) >= 0)? 0 : -4;
848
849
0
 err:
850
0
    if (fp)
851
0
        bgzf_close(fp);
852
0
    if (c)
853
0
        cram_free_container(c);
854
0
    return ret;
855
0
}
856
857
// internal recursive step
858
static int64_t cram_num_containers_between_(cram_index *e, int64_t *last_pos,
859
                                            int64_t nct,
860
                                            off_t cstart, off_t cend,
861
0
                                            int64_t *first, int64_t *last) {
862
0
    int64_t nc = 0, i;
863
864
0
    if (e->offset) {
865
0
        if (e->offset != *last_pos) {
866
0
            if (e->offset >= cstart && (!cend || e->offset <= cend)) {
867
0
                if (first && *first < 0)
868
0
                    *first = nct;
869
0
                if (last)
870
0
                    *last = nct;
871
0
            }
872
0
            nc++;
873
0
        }
874
        // else a new multi-ref in same container
875
0
        *last_pos = e->offset;
876
0
    }
877
878
0
    for (i = 0; i < e->nslice; i++)
879
0
        nc += cram_num_containers_between_(&e->e[i], last_pos, nc + nct,
880
0
                                           cstart, cend, first, last);
881
882
0
    return nc;
883
0
}
884
885
/*! Returns the number of containers in the CRAM file within given offsets.
886
 *
887
 * The cstart and cend offsets are the locations of the start of containers
888
 * as returned by index_container_offset.
889
 *
890
 * If non-NULL, first and last will hold the inclusive range of container
891
 * numbers, counting from zero.
892
 *
893
 * @return
894
 * Returns the number of containers, equivalent to *last-*first+1.
895
 */
896
int64_t cram_num_containers_between(cram_fd *fd,
897
                                    off_t cstart, off_t cend,
898
0
                                    int64_t *first, int64_t *last) {
899
0
    int64_t nc = 0, i;
900
0
    int64_t last_pos = -99;
901
0
    int64_t l_first = -1, l_last = -1;
902
903
0
    for (i = 0; i < fd->index_sz; i++) {
904
0
        int j = i+1 == fd->index_sz ? 0 : i+1; // maps "*" to end
905
0
        nc += cram_num_containers_between_(&fd->index[j], &last_pos, nc,
906
0
                                           cstart, cend, &l_first, &l_last);
907
0
    }
908
909
0
    if (first)
910
0
        *first = l_first;
911
0
    if (last)
912
0
        *last = l_last;
913
914
0
    return l_last - l_first + 1;
915
0
}
916
917
/*
918
 * Queries the total number of distinct containers in the index.
919
 * Note there may be more containers in the file than in the index, as we
920
 * are not required to have an index entry for every one.
921
 */
922
0
int64_t cram_num_containers(cram_fd *fd) {
923
0
    return cram_num_containers_between(fd, 0, 0, NULL, NULL);
924
0
}
925
926
927
/*! Returns the byte offset for the start of the n^th container.
928
 *
929
 * The index must have previously been loaded, otherwise <0 is returned.
930
 */
931
static cram_index *cram_container_num2offset_(cram_index *e, int num,
932
0
                                              int64_t *last_pos, int *nc) {
933
0
    if (e->offset) {
934
0
        if (e->offset != *last_pos) {
935
0
            if (*nc == num)
936
0
                return e;
937
0
            (*nc)++;
938
0
        }
939
        // else a new multi-ref in same container
940
0
        *last_pos = e->offset;
941
0
    }
942
943
0
    int i;
944
0
    for (i = 0; i < e->nslice; i++) {
945
0
        cram_index *tmp = cram_container_num2offset_(&e->e[i], num,
946
0
                                                     last_pos, nc);
947
0
        if (tmp)
948
0
            return tmp;
949
0
    }
950
951
952
0
    return NULL;
953
0
}
954
955
0
off_t cram_container_num2offset(cram_fd *fd, int64_t num) {
956
0
    int nc = 0, i;
957
0
    int64_t last_pos = -9;
958
0
    cram_index *e = NULL;
959
960
0
    for (i = 0; i < fd->index_sz; i++) {
961
0
        int j = i+1 == fd->index_sz ? 0 : i+1; // maps "*" to end
962
0
        if (!fd->index[j].nslice)
963
0
            continue;
964
0
        if ((e = cram_container_num2offset_(&fd->index[j], num,
965
0
                                            &last_pos, &nc)))
966
0
            break;
967
0
    }
968
969
0
    return e ? e->offset : -1;
970
0
}
971
972
973
/*! Returns the container number for the first container at offset >= pos.
974
 *
975
 * The index must have previously been loaded, otherwise <0 is returned.
976
 */
977
static cram_index *cram_container_offset2num_(cram_index *e, off_t pos,
978
0
                                              int64_t *last_pos, int *nc) {
979
0
    if (e->offset) {
980
0
        if (e->offset != *last_pos) {
981
0
            if (e->offset >= pos)
982
0
                return e;
983
0
            (*nc)++;
984
0
        }
985
        // else a new multi-ref in same container
986
0
        *last_pos = e->offset;
987
0
    }
988
989
0
    int i;
990
0
    for (i = 0; i < e->nslice; i++) {
991
0
        cram_index *tmp = cram_container_offset2num_(&e->e[i], pos,
992
0
                                                     last_pos, nc);
993
0
        if (tmp)
994
0
            return tmp;
995
0
    }
996
997
998
0
    return NULL;
999
0
}
1000
1001
0
int64_t cram_container_offset2num(cram_fd *fd, off_t pos) {
1002
0
    int nc = 0, i;
1003
0
    int64_t last_pos = -9;
1004
0
    cram_index *e = NULL;
1005
1006
0
    for (i = 0; i < fd->index_sz; i++) {
1007
0
        int j = i+1 == fd->index_sz ? 0 : i+1; // maps "*" to end
1008
0
        if (!fd->index[j].nslice)
1009
0
            continue;
1010
0
        if ((e = cram_container_offset2num_(&fd->index[j], pos,
1011
0
                                            &last_pos, &nc)))
1012
0
            break;
1013
0
    }
1014
1015
0
    return e ? nc : -1;
1016
0
}
1017
1018
/*!
1019
 * Returns the file offsets of CRAM containers covering a specific region
1020
 * query.  Note both offsets are the START of the container.
1021
 *
1022
 * first will point to the start of the first overlapping container
1023
 * last will point to the start of the last overlapping container
1024
 *
1025
 * Returns 0 on success
1026
 *        <0 on failure
1027
 */
1028
int cram_index_extents(cram_fd *fd, int refid, hts_pos_t start, hts_pos_t end,
1029
0
                       off_t *first, off_t *last) {
1030
0
    cram_index *ci;
1031
1032
0
    if (first) {
1033
0
        if (!(ci = cram_index_query(fd, refid, start, NULL)))
1034
0
            return -1;
1035
0
        *first = ci->offset;
1036
0
    }
1037
1038
0
    if (last) {
1039
0
        if (!(ci = cram_index_query_last(fd, refid, end)))
1040
0
            return -1;
1041
0
        *last = ci->offset;
1042
0
    }
1043
1044
0
    return 0;
1045
0
}