star-hitran

Load line-by-line data from the HITRAN database
git clone git://git.meso-star.fr/star-hitran.git
Log | Files | Refs | README | LICENSE

shtr_line_list.c (24331B)


      1 /* Copyright (C) 2022, 2025, 2026 |Méso|Star> (contact@meso-star.com)
      2  * Copyright (C) 2025, 2026 Université de Lorraine
      3  * Copyright (C) 2022 Centre National de la Recherche Scientifique
      4  * Copyright (C) 2022 Université Paul Sabatier
      5  *
      6  * This program is free software: you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation, either version 3 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     18 
     19 #define _POSIX_C_SOURCE 200809L /* fseeko */
     20 
     21 #include "shtr_c.h"
     22 #include "shtr_line_list_c.h"
     23 #include "shtr_param.h"
     24 
     25 #include <rsys/cstr.h>
     26 #include <rsys/text_reader.h>
     27 
     28 #include <stdio.h> /* fseeko */
     29 
     30 /* Maximum number of lines that can be stored in a memory block */
     31 #define NLINES_PER_BLOCK (BLOCK_SIZE/sizeof(struct line))
     32 
     33 struct stream {
     34   const char* name;
     35   FILE* fp;
     36   int intern_fp; /* Define if the stream was internally opened */
     37 };
     38 static const struct stream STREAM_NULL = {NULL, NULL, 0};
     39 
     40 /*******************************************************************************
     41  * Helper functions
     42  ******************************************************************************/
     43 static INLINE res_T
     44 check_shtr_line_list_load_args(const struct shtr_line_list_load_args* args)
     45 {
     46   if(!args) return RES_BAD_ARG;
     47 
     48   /* Source is missing */
     49   if(!args->file && !args->filename) return RES_BAD_ARG;
     50 
     51   return RES_OK;
     52 }
     53 
     54 static INLINE res_T
     55 check_shtr_line_list_read_args(const struct shtr_line_list_read_args* args)
     56 {
     57   if(!args) return RES_BAD_ARG;
     58 
     59   /* Source is missing */
     60   if(!args->file && !args->filename) return RES_BAD_ARG;
     61 
     62   /* Line range is degenerated */
     63   if(args->range[0] > args->range[1]) return RES_BAD_ARG;
     64 
     65   return RES_OK;
     66 }
     67 
     68 static INLINE res_T
     69 check_shtr_line_list_write_args(const struct shtr_line_list_write_args* args)
     70 {
     71   if(!args) return RES_BAD_ARG;
     72 
     73   /* Destination is missing */
     74   if(!args->file && !args->filename) return RES_BAD_ARG;
     75 
     76   return RES_OK;
     77 }
     78 
     79 static INLINE void
     80 stream_release(struct stream* stream)
     81 {
     82   ASSERT(stream);
     83   if(stream->intern_fp && stream->fp) CHK(fclose(stream->fp) == 0);
     84 }
     85 
     86 static res_T
     87 stream_init
     88   (struct shtr* shtr,
     89    const char* caller,
     90    const char* name, /* NULL <=> default stream name */
     91    FILE* fp, /* NULL <=> open file "name" */
     92    const char* mode, /* mode in fopen */
     93    struct stream* stream)
     94 {
     95   res_T res = RES_OK;
     96 
     97   ASSERT(shtr && caller && stream);
     98   ASSERT(fp || (name && mode));
     99 
    100   *stream = STREAM_NULL;
    101 
    102   if(fp) {
    103     stream->intern_fp = 0;
    104     stream->name = name ? name : "stream";
    105     stream->fp = fp;
    106 
    107   } else {
    108     stream->intern_fp = 1;
    109     stream->name = name;
    110     if(!(stream->fp = fopen(name, mode))) {
    111       ERROR(shtr, "%s:%s: error opening file -- %s\n",
    112         caller, name, strerror(errno));
    113       res = RES_IO_ERR;
    114       goto error;
    115     }
    116   }
    117 
    118 exit:
    119   return res;
    120 error:
    121   if(stream->intern_fp && stream->fp) CHK(fclose(stream->fp) == 0);
    122   goto exit;
    123 }
    124 
    125 static res_T
    126 size_to_off(const size_t sz, off_t* off)
    127 {
    128   ASSERT(off);
    129   if(sz != (size_t)((off_t)sz)) return RES_BAD_OP;
    130   *off = (off_t)sz;
    131   return RES_OK;
    132 }
    133 
    134 static res_T
    135 create_line_list
    136   (struct shtr* shtr,
    137    struct shtr_line_list** out_list)
    138 {
    139   struct shtr_line_list* list = NULL;
    140   res_T res = RES_OK;
    141   ASSERT(shtr && out_list);
    142 
    143   list = MEM_CALLOC(shtr->allocator, 1, sizeof(*list));
    144   if(!list) {
    145     ERROR(shtr, "Could not allocate the list of lines.\n");
    146     res = RES_MEM_ERR;
    147     goto error;
    148   }
    149   ref_init(&list->ref);
    150   SHTR(ref_get(shtr));
    151   list->shtr = shtr;
    152   darray_charp_init(shtr->allocator, &list->blocks);
    153   list->info = SHTR_LINE_LIST_INFO_NULL;
    154 
    155 exit:
    156   *out_list = list;
    157   return res;
    158 error:
    159   if(list) {
    160     SHTR(line_list_ref_put(list));
    161     list = NULL;
    162   }
    163   goto exit;
    164 }
    165 
    166 static INLINE const struct line*
    167 get_line(const struct shtr_line_list* list, const size_t i)
    168 {
    169   const size_t iblock = i / NLINES_PER_BLOCK;
    170   const size_t iline  = i % NLINES_PER_BLOCK;
    171 
    172   ASSERT(list && i < list->nlines);
    173   ASSERT(iblock < darray_charp_size_get(&list->blocks));
    174   return (struct line*)(darray_charp_cdata_get(&list->blocks))[iblock] + iline;
    175 }
    176 
    177 static res_T
    178 line_encode
    179   (const struct shtr* shtr, /* For logging */
    180    const struct txtrdr* txtrdr, /* For logging */
    181    const struct shtr_line* line,
    182    struct line* line_encoded)
    183 {
    184   union { float flt; int32_t i32; } ucast;
    185   res_T res = RES_OK;
    186   ASSERT(line && line_encoded);
    187 
    188   /* Keep the wavenumber and the intensity as it */
    189   line_encoded->wavenumber = line->wavenumber;
    190   line_encoded->intensity = line->intensity;
    191 
    192   /* Encode the lower state energy and delta air in single precision  */
    193   line_encoded->lower_state_energy = (float)line->lower_state_energy;
    194   line_encoded->delta_air = (float)line->delta_air;
    195 
    196   /* Store gamma_air as an unsigned fixed-point number (0:14), i.e., 0 bit for
    197    * the integer part and 14 bits for the fractional part */
    198   if((int64_t)line->gamma_air != 0) {
    199     WARN(shtr,
    200       "%s:%lu: invalid gamma air %g: encoding expects a value in [0,1[\n",
    201       txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr), line->gamma_air);
    202     res = RES_BAD_OP;
    203   } else {
    204     ASSERT(line->gamma_air >= 0);
    205     line_encoded->gair14_gself14_isoid4 =
    206       ((int32_t)float2fix(line->gamma_air, 0, 14) & (BIT_I32(14)-1)) << 18;
    207   }
    208 
    209   /* Store gamma_self as an unsigned fixed-point number (3:11), i.e., 3 bit for
    210    * the integer part and 11 bits for the fractional part */
    211   if((int64_t)line->gamma_self > 8) {
    212     WARN(shtr,
    213       "%s:%lu: invalid gamma self %g: encoding expects a value in [0,8[\n",
    214       txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr), line->gamma_self);
    215     res = RES_BAD_OP;
    216   } else {
    217     ASSERT(line->gamma_self >= 0);
    218     line_encoded->gair14_gself14_isoid4 |=
    219       ((int32_t)float2fix(line->gamma_self, 3, 11) & (BIT_I32(14)-1)) << 4;
    220   }
    221 
    222   /* Store the isotope id on 4 bits */
    223   ASSERT(line->isotope_id_local < 16);
    224   line_encoded->gair14_gself14_isoid4 |= line->isotope_id_local & (BIT_I32(4)-1);
    225 
    226   /* Encode n_air in single precision with its last 7 bits of matissa disabled
    227    * in order to store the molecule identifier */
    228   ucast.flt = (float)line->n_air;
    229   ucast.i32 &= ~(BIT_I32(7)-1);
    230 
    231   /* Store the molecule id on 7 bits */
    232   ASSERT(line->molecule_id < 128);
    233   ucast.i32 |= (int32_t)line->molecule_id & (BIT_I32(7)-1);
    234   line_encoded->nair25_molid7 = ucast.i32;
    235 
    236   return res;
    237 }
    238 
    239 static void
    240 line_decode(const struct line* line_encoded, struct shtr_line* line)
    241 {
    242   union { float flt; int32_t i32; } ucast;
    243   int64_t i64 = 0;
    244   ASSERT(line && line_encoded);
    245 
    246   line->wavenumber = line_encoded->wavenumber;
    247   line->intensity = line_encoded->intensity;
    248   line->lower_state_energy = line_encoded->lower_state_energy;
    249   line->delta_air = line_encoded->delta_air;
    250 
    251   /* Convert gamma_air and gamma_self from fixed-point numbers on 14 bits to
    252    * double-precision floating-point numbers. Gamma air is encode un (0:14) and
    253    * gamma self in (3:11) */
    254   i64 = (line_encoded->gair14_gself14_isoid4 >> 18) & (BIT_I32(14)-1);
    255   line->gamma_air = fix2float(i64, 0, 14);
    256   i64 = (line_encoded->gair14_gself14_isoid4 >> 4) & (BIT_I32(14)-1);
    257   line->gamma_self = fix2float(i64, 3, 11);
    258 
    259   /* Unpack the isotope ID */
    260   line->isotope_id_local = line_encoded->gair14_gself14_isoid4 & (BIT_I32(4)-1);
    261 
    262   /* Unpack the molecule ID */
    263   ucast.i32 = line_encoded->nair25_molid7;
    264   line->molecule_id = ucast.i32 & (BIT_I32(7)-1);
    265   ucast.i32 &= ~(BIT_I32(7)-1);
    266 
    267   /* Convert n_air from single precision to double precision */
    268   line->n_air = ucast.flt;
    269 }
    270 
    271 static res_T
    272 register_line
    273   (struct shtr_line_list* list,
    274    const struct txtrdr* txtrdr,
    275    const struct shtr_line* line)
    276 {
    277   struct shtr_line ln = SHTR_LINE_NULL;
    278   struct line* lines = NULL;
    279   struct line ln_encoded = LINE_NULL;
    280   size_t iblock = 0; /* Index of the block in which the line is stored */
    281   size_t iline  = 0; /* Index of the line in the block */
    282   res_T res = RES_OK;
    283 
    284   /* Pre-conditions */
    285   ASSERT(list && txtrdr && line);
    286 
    287   res = line_encode(list->shtr, txtrdr, line, &ln_encoded);
    288   switch(res) {
    289     case RES_OK: /* nothing to do */; break;
    290     case RES_BAD_OP:
    291       WARN(list->shtr, "%s:%lu: line discarded\n",
    292         txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr));
    293       res = RES_OK;
    294       goto exit;
    295     default: goto error;
    296   }
    297 
    298   /* Check if a line has been saved. If so, ensure that the lines are sorted */
    299   if(list->nlines) {
    300     const struct line* ln_encoded_prev = get_line(list, list->nlines-1);
    301     if(ln_encoded_prev->wavenumber > ln_encoded.wavenumber) {
    302       ERROR(list->shtr,
    303         "%s:%lu: lines are not sorted in ascending order wrt their wavenumber.\n",
    304         txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr));
    305       res = RES_BAD_ARG;
    306       goto error;
    307     }
    308   }
    309 
    310   iblock = list->nlines / NLINES_PER_BLOCK;
    311   iline  = list->nlines % NLINES_PER_BLOCK;
    312 
    313   /* Ensure there is sufficient space to store the line */
    314   if(iline == 0) {
    315     /* There is no more space in the last allocated block. Allocate a new one. */
    316     char* block = MEM_CALLOC(list->shtr->allocator, 1, BLOCK_SIZE);
    317     if(!block) { res = RES_MEM_ERR; goto error; }
    318 
    319     res = darray_charp_push_back(&list->blocks, &block);
    320     if(res != RES_OK) goto error;
    321   }
    322 
    323   /* Store the encoded line */
    324   lines = (struct line*)darray_charp_data_get(&list->blocks)[iblock];
    325   lines[iline] = ln_encoded;
    326   ++list->nlines;
    327 
    328   line_decode(&ln_encoded, &ln);
    329   ASSERT(ln.molecule_id == line->molecule_id);
    330   ASSERT(ln.isotope_id_local == line->isotope_id_local);
    331 
    332   #define UPDATE_INFO(Name) { \
    333     const double err = fabs(line->Name - ln.Name); \
    334     list->info.Name.range[0] = MMIN(list->info.Name.range[0], ln.Name); \
    335     list->info.Name.range[1] = MMAX(list->info.Name.range[1], ln.Name); \
    336     list->info.Name.err = MMAX(list->info.Name.err, err); \
    337   } (void) 0
    338   UPDATE_INFO(wavenumber);
    339   UPDATE_INFO(intensity);
    340   UPDATE_INFO(gamma_air);
    341   UPDATE_INFO(gamma_self);
    342   UPDATE_INFO(lower_state_energy);
    343   UPDATE_INFO(n_air);
    344   UPDATE_INFO(delta_air);
    345   #undef UPDATE_INFO
    346 
    347 exit:
    348   return res;
    349 error:
    350   goto exit;
    351 }
    352 
    353 static res_T
    354 parse_line
    355   (struct shtr_line_list* list,
    356    struct txtrdr* txtrdr,
    357    struct shtr_line* ln)
    358 {
    359   struct param_desc param = PARAM_DESC_NULL;
    360   struct shtr* shtr = NULL;
    361   char* line = NULL;
    362   char* str = NULL;
    363   char* end = NULL;
    364   char backup;
    365   int molecule_id;
    366   int isotope_id_local;
    367   res_T res = RES_OK;
    368 
    369   ASSERT(list && txtrdr && ln);
    370 
    371   line = txtrdr_get_line(txtrdr);
    372   ASSERT(line);
    373 
    374   shtr = list->shtr;
    375   param.path = txtrdr_get_name(txtrdr);
    376   param.line = txtrdr_get_line_num(txtrdr);
    377 
    378   str = end = line;
    379   backup = str[0];
    380   #define NEXT(Size) {                                                         \
    381     *end = backup;                                                             \
    382     str = end;                                                                 \
    383     end = str+(Size);                                                          \
    384     backup = *end;                                                             \
    385     *end = '\0';                                                               \
    386   } (void)0
    387   #define PARSE(Var, Size, Type, Name, Low, Upp, LowIncl, UppIncl) {           \
    388     NEXT(Size);                                                                \
    389     param.name = (Name);                                                       \
    390     param.low = (Low);                                                         \
    391     param.upp = (Upp);                                                         \
    392     param.is_low_incl = (LowIncl);                                             \
    393     param.is_upp_incl = (UppIncl);                                             \
    394     res = parse_param_##Type(shtr, str, &param, Var);                          \
    395     if(res != RES_OK) goto error;                                              \
    396   } (void)0
    397 
    398   PARSE(&molecule_id, 2, int, "molecule identifier", 0,99,1,1);
    399   ln->molecule_id = (int32_t)molecule_id;
    400 
    401   PARSE(&isotope_id_local, 1, int, "isotope local identifier", 0,9,1,1);
    402   ln->isotope_id_local = (int32_t)
    403     (isotope_id_local == 0 ? 9 : (isotope_id_local - 1));
    404 
    405   PARSE(&ln->wavenumber, 12, double, "central wavenumber", 0,INF,0,1);
    406   PARSE(&ln->intensity, 10, double, "reference intensity", 0,INF,0,1);
    407 
    408   NEXT(10); /* Skip the Enstein coef */
    409 
    410   PARSE(&ln->gamma_air, 5, double, "air broadening half-width", 0,INF,1,1);
    411   PARSE(&ln->gamma_self, 5, double, "self broadening half-width", 0,INF,1,1);
    412 
    413   /* Handle unavailable lower state energy */
    414   PARSE(&ln->lower_state_energy, 10, double, "lower state energy",-INF,INF,1,1);
    415   if(ln->lower_state_energy == -1) {
    416     WARN(shtr,
    417       "%s:%lu: the lower state energy is unavailable for this line, so it is "
    418       "ignored.\n", txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr));
    419     goto exit; /* Skip the line */
    420   }
    421   /* Check the domain validity */
    422   if(ln->lower_state_energy < 0) {
    423     ERROR(shtr,
    424       "%s:%lu: invalid lower state energy %g. It must be in [0, INF].\n",
    425       txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr),
    426       ln->lower_state_energy);
    427     res = RES_BAD_ARG;
    428     goto error;
    429   }
    430 
    431   PARSE(&ln->n_air, 4, double, "temperature-dependent exponent",-INF,INF,1,1);
    432   PARSE(&ln->delta_air, 8, double, "air-pressure wavenumber shift", -INF,INF,1,1);
    433 
    434   /* Skip the remaining values */
    435 
    436   #undef NEXT
    437   #undef PARSE
    438 
    439   /* Check the size of the remaining data to ensure that there is at least the
    440    * expected number of bytes wrt the HITRAN fileformat */
    441   *end = backup;
    442   str = end;
    443   if(strlen(str) != 93) {
    444     ERROR(list->shtr, "%s:%lu: missing data after delta air.\n",
    445       param.path, (unsigned long)param.line);
    446     res = RES_BAD_ARG;
    447     goto error;
    448   }
    449 
    450 exit:
    451   return res;
    452 error:
    453   goto exit;
    454 }
    455 
    456 static res_T
    457 load_stream
    458   (struct shtr* shtr,
    459    const struct shtr_line_list_load_args* args,
    460    struct shtr_line_list** out_lines)
    461 {
    462   struct stream stream = STREAM_NULL;
    463   struct shtr_line_list* list = NULL;
    464   struct txtrdr* txtrdr = NULL;
    465   res_T res = RES_OK;
    466 
    467   ASSERT(shtr && args && out_lines);
    468 
    469   res = create_line_list(shtr,  &list);
    470   if(res != RES_OK) goto error;
    471 
    472   res = stream_init(shtr, FUNC_NAME, args->filename, args->file, "r", &stream);
    473   if(res != RES_OK) goto error;
    474 
    475   res = txtrdr_stream(list->shtr->allocator, stream.fp, stream.name,
    476     0/*No comment char*/, &txtrdr);
    477   if(res != RES_OK) {
    478     ERROR(shtr, "%s: error creating the text reader -- %s.\n",
    479       stream.name, res_to_cstr(res));
    480     goto error;
    481   }
    482 
    483   for(;;) {
    484     struct shtr_line ln = SHTR_LINE_NULL;
    485 
    486     res = txtrdr_read_line(txtrdr);
    487     if(res != RES_OK) {
    488       ERROR(shtr, "%s: error reading the line `%lu' -- %s.\n",
    489         stream.name, (unsigned long)txtrdr_get_line_num(txtrdr),
    490         res_to_cstr(res));
    491       goto error;
    492     }
    493 
    494     if(!txtrdr_get_cline(txtrdr)) break; /* No more parsed line */
    495 
    496     res = parse_line(list, txtrdr, &ln);
    497     if(res != RES_OK) goto error;
    498 
    499     res = register_line(list, txtrdr, &ln);
    500     if(res != RES_OK) goto error;
    501   }
    502 
    503 exit:
    504   if(txtrdr) txtrdr_ref_put(txtrdr);
    505   stream_release(&stream);
    506   *out_lines = list;
    507   return res;
    508 error:
    509   if(list) {
    510     SHTR(line_list_ref_put(list));
    511     list = NULL;
    512   }
    513   goto exit;
    514 }
    515 
    516 static void
    517 hash_blocks(const struct shtr_line_list* list, struct sha256_ctx* ctx)
    518 {
    519   size_t i = 0;
    520   size_t n = 0;
    521   ASSERT(list && ctx);
    522 
    523   n = darray_charp_size_get(&list->blocks);
    524   FOR_EACH(i, 0, n) {
    525     const char* block = darray_charp_cdata_get(&list->blocks)[i];
    526     const size_t nlines = MMIN(NLINES_PER_BLOCK, list->nlines-i*NLINES_PER_BLOCK);
    527 
    528     sha256_ctx_update(ctx, block, nlines*sizeof(struct line));
    529   }
    530 }
    531 
    532 static void
    533 release_lines(ref_T * ref)
    534 {
    535   struct shtr* shtr = NULL;
    536   struct shtr_line_list* list = CONTAINER_OF(ref, struct shtr_line_list, ref);
    537   char** blocks = NULL;
    538   size_t i=0, n=0;
    539 
    540   ASSERT(ref);
    541 
    542   shtr = list->shtr;
    543 
    544   n = darray_charp_size_get(&list->blocks);
    545   blocks = darray_charp_data_get(&list->blocks);
    546   FOR_EACH(i, 0, n) { if(blocks[i]) MEM_RM(shtr->allocator, blocks[i]); }
    547 
    548   darray_charp_release(&list->blocks);
    549   MEM_RM(shtr->allocator, list);
    550   SHTR(ref_put(shtr));
    551 }
    552 
    553 /*******************************************************************************
    554  * Exported functions
    555  ******************************************************************************/
    556 res_T
    557 shtr_line_list_load
    558   (struct shtr* shtr,
    559    const struct shtr_line_list_load_args* args,
    560    struct shtr_line_list** list)
    561 {
    562   res_T res = RES_OK;
    563 
    564   if(!shtr || !list) { res = RES_BAD_ARG; goto error; }
    565   res = check_shtr_line_list_load_args(args);
    566   if(res != RES_OK) goto error;
    567 
    568   res = load_stream(shtr, args, list);
    569   if(res != RES_OK) goto error;
    570 
    571 exit:
    572   return res;
    573 error:
    574   goto exit;
    575 }
    576 
    577 res_T
    578 shtr_line_list_read
    579   (struct shtr* shtr,
    580    const struct shtr_line_list_read_args* args,
    581    struct shtr_line_list** out_list)
    582 {
    583   size_t line_range[2]={0,0}; /* Range of lines adapted to effective line ids */
    584   size_t nlines = 0; /* Ttotal number of lines in the original list */
    585   size_t nblocks = 0; /* Number of memory blocks needed to store read lines */
    586 
    587   struct shtr_line_list* list = NULL; /* The output list */
    588   char** blocks = NULL; /* Allocated list of memory blocks */
    589 
    590   /* Miscellaneous */
    591   struct stream stream = STREAM_NULL;
    592   size_t i = 0;
    593   size_t sz = 0;
    594   size_t sz_to_load = 0;
    595   off_t off;
    596   int version = 0;
    597   int err = 0;
    598   res_T res = RES_OK;
    599 
    600   if(!shtr || !out_list) { res = RES_BAD_ARG; goto error; }
    601   res = check_shtr_line_list_read_args(args);
    602   if(res != RES_OK) goto error;
    603 
    604   res = create_line_list(shtr, &list);
    605   if(res != RES_OK) goto error;
    606 
    607   res = stream_init
    608     (list->shtr, FUNC_NAME, args->filename, args->file, "r", &stream);
    609   if(res != RES_OK) goto error;
    610 
    611   #define READ(Var, Nb) {                                                      \
    612     if(fread((Var), sizeof(*(Var)), (Nb), stream.fp) != (Nb)) {                \
    613       if(feof(stream.fp)) {                                                    \
    614         res = RES_BAD_ARG;                                                     \
    615       } else if(ferror(stream.fp)) {                                           \
    616         res = RES_IO_ERR;                                                      \
    617       } else {                                                                 \
    618         res = RES_UNKNOWN_ERR;                                                 \
    619       }                                                                        \
    620       ERROR(shtr,                                                              \
    621         "%s:%s: error reading line list -- %s.\n",                             \
    622         FUNC_NAME, stream.name, res_to_cstr(res));                             \
    623       goto error;                                                              \
    624     }                                                                          \
    625   } (void)0
    626 
    627   READ(&version, 1);
    628   if(version != SHTR_LINE_LIST_VERSION) {
    629     ERROR(shtr,
    630       "%s:%s: unexpected line list version %d. "
    631       "Expecting a line list in version %d.\n",
    632       FUNC_NAME, stream.name, version, SHTR_LINE_LIST_VERSION);
    633     res = RES_BAD_ARG;
    634     goto error;
    635   }
    636 
    637   /* Informations on line parameters */
    638   READ(&list->info, 1);
    639 
    640   /* Total number of lines in the list from which the lines will be read */
    641   READ(&nlines, 1);
    642 
    643   /* Actually, there are no lines to read */
    644   if(nlines <= args->range[0]) goto exit;
    645 
    646   /* Fit the upper limit to the effective number of lines */
    647   line_range[0] = args->range[0];
    648   line_range[1] = MMIN(args->range[1], nlines-1/*inclusive bounds*/);
    649   list->nlines = line_range[1] - line_range[0] + 1/*inclusive bounds*/;
    650 
    651   /* Calculate the number of blocks needed to store the line to be read */
    652   nblocks = (list->nlines + (NLINES_PER_BLOCK-1)/*ceil*/) / NLINES_PER_BLOCK;
    653   if((res = darray_charp_resize(&list->blocks, nblocks)) != RES_OK) goto error;
    654 
    655   /* Compute the offset toward the first line to load */
    656   sz = line_range[0] * sizeof(struct line);
    657   res = size_to_off(sz, &off);
    658   if(res != RES_OK) {
    659     ERROR(shtr, "%s:%s: file is too large regarding the seek offset %zu\n",
    660       FUNC_NAME, stream.name, sz);
    661     goto error;
    662   }
    663 
    664   /* Move to the first line to load */
    665   err = fseeko(stream.fp, off, SEEK_CUR);
    666   if(err) {
    667     ERROR(shtr, "%s:%s: %s\n", FUNC_NAME, stream.name, strerror(errno));
    668     res = RES_IO_ERR;
    669     goto error;
    670   }
    671 
    672   /* Compute the overall number of bytes to load */
    673   sz_to_load = list->nlines * sizeof(struct line);
    674 
    675   /* Load line data and store them in memory blocks */
    676   blocks = darray_charp_data_get(&list->blocks);
    677   FOR_EACH(i, 0, nblocks) {
    678     blocks[i] = MEM_CALLOC(list->shtr->allocator, 1, BLOCK_SIZE);
    679     if(!blocks[i]) {
    680       ERROR(shtr, "%s:%s: error allocating memory block\n",
    681         stream.name, FUNC_NAME);
    682       res = RES_MEM_ERR;
    683       goto error;
    684     }
    685 
    686     sz = MMIN(BLOCK_SIZE, sz_to_load);
    687     READ(blocks[i], sz);
    688 
    689     sz_to_load -= sz;
    690   }
    691 
    692   #undef READ
    693 
    694 exit:
    695   stream_release(&stream);
    696   if(out_list) *out_list = list;
    697   return res;
    698 error:
    699   if(list) { SHTR(line_list_ref_put(list)); list = NULL; }
    700   goto exit;
    701 }
    702 
    703 res_T
    704 shtr_line_list_ref_get(struct shtr_line_list* list)
    705 {
    706   if(!list) return RES_BAD_ARG;
    707   ref_get(&list->ref);
    708   return RES_OK;
    709 }
    710 
    711 res_T
    712 shtr_line_list_ref_put(struct shtr_line_list* list)
    713 {
    714   if(!list) return RES_BAD_ARG;
    715   ref_put(&list->ref, release_lines);
    716   return RES_OK;
    717 }
    718 
    719 res_T
    720 shtr_line_list_get_size
    721   (const struct shtr_line_list* list,
    722    size_t* nlines)
    723 {
    724   if(!list || !nlines) return RES_BAD_ARG;
    725   *nlines = list->nlines;
    726   return RES_OK;
    727 }
    728 
    729 res_T
    730 shtr_line_list_at
    731   (struct shtr_line_list* list,
    732    const size_t i,
    733    struct shtr_line* line)
    734 {
    735   const struct line* ln_encoded = NULL;
    736 
    737   if(!list || !line || i >= list->nlines) return RES_BAD_ARG;
    738   ln_encoded = get_line(list, i);
    739   line_decode(ln_encoded, line);
    740   return RES_OK;
    741 }
    742 
    743 res_T
    744 shtr_line_list_write
    745   (const struct shtr_line_list* list,
    746    const struct shtr_line_list_write_args* args)
    747 {
    748   struct stream stream = STREAM_NULL;
    749   char* const* blocks = NULL;
    750   size_t i=0, n=0;
    751   res_T res = RES_OK;
    752 
    753   if(!list) { res = RES_BAD_ARG; goto error; }
    754   res = check_shtr_line_list_write_args(args);
    755   if(res != RES_OK) goto error;
    756 
    757   res = stream_init
    758     (list->shtr, FUNC_NAME, args->filename, args->file, "w", &stream);
    759   if(res != RES_OK) goto error;
    760 
    761   #define WRITE(Var, Nb) {                                                     \
    762     if(fwrite((Var), sizeof(*(Var)), (Nb), stream.fp) != (Nb)) {               \
    763       ERROR(list->shtr, "%s:%s: error writing line list -- %s\n",              \
    764         FUNC_NAME, stream.name, strerror(errno));                              \
    765       res = RES_IO_ERR;                                                        \
    766       goto error;                                                              \
    767     }                                                                          \
    768   } (void)0
    769 
    770   /* Version management */
    771   WRITE(&SHTR_LINE_LIST_VERSION, 1);
    772 
    773   /* Informations on line parameters */
    774   WRITE(&list->info, 1);
    775 
    776   /* Number of lines in the list */
    777   WRITE(&list->nlines, 1);
    778 
    779   /* Lines stored in memory blocks. */
    780   blocks = darray_charp_cdata_get(&list->blocks);
    781   n = darray_charp_size_get(&list->blocks);
    782   FOR_EACH(i, 0, n) { WRITE(blocks[i], BLOCK_SIZE); }
    783 
    784   #undef WRITE
    785 
    786 exit:
    787   stream_release(&stream);
    788   return res;
    789 error:
    790   goto exit;
    791 }
    792 
    793 res_T
    794 shtr_line_list_get_info
    795   (const struct shtr_line_list* list,
    796    struct shtr_line_list_info* info)
    797 {
    798   if(!list || !info) return RES_BAD_ARG;
    799   *info = list->info;
    800   return RES_OK;
    801 }
    802 
    803 res_T
    804 shtr_line_list_hash(const struct shtr_line_list* list, hash256_T hash)
    805 {
    806   struct sha256_ctx ctx;
    807   res_T res = RES_OK;
    808 
    809   if(!list || !hash) { res = RES_BAD_ARG; goto error; }
    810 
    811   sha256_ctx_init(&ctx);
    812   hash_blocks(list, &ctx);
    813   sha256_ctx_finalize(&ctx, hash);
    814 
    815 exit:
    816   return res;
    817 error:
    818   goto exit;
    819 }