star-mesh

Define and load a binary data format for meshes
git clone git://git.meso-star.fr/star-mesh.git
Log | Files | Refs | README | LICENSE

smsh.c (12653B)


      1 /* Copyright (C) 2020-2023, 2025 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #define _POSIX_C_SOURCE 200809L /* mmap support */
     17 #define _DEFAULT_SOURCE 1 /* MAP_POPULATE support */
     18 #define _BSD_SOURCE 1 /* MAP_POPULATE for glibc < 2.19 */
     19 
     20 #include "smsh.h"
     21 #include "smsh_c.h"
     22 #include "smsh_log.h"
     23 
     24 #include <rsys/cstr.h>
     25 #include <rsys/mem_allocator.h>
     26 
     27 #include <errno.h>
     28 #include <unistd.h>
     29 #include <sys/mman.h> /* mmap */
     30 #include <sys/stat.h> /* fstat */
     31 
     32 /*******************************************************************************
     33  * Helper functions
     34  ******************************************************************************/
     35 static INLINE int
     36 is_stdin(FILE* stream)
     37 {
     38   struct stat stream_buf;
     39   struct stat stdin_buf;
     40   ASSERT(stream);
     41 
     42   CHK(fstat(fileno(stream), &stream_buf) == 0);
     43   CHK(fstat(STDIN_FILENO, &stdin_buf) == 0);
     44   return stream_buf.st_dev == stdin_buf.st_dev;
     45 }
     46 
     47 static INLINE res_T
     48 check_smsh_create_args(const struct smsh_create_args* args)
     49 {
     50   /* Nothing to check. Only return RES_BAD_ARG if args is NULL */
     51   return args ? RES_OK : RES_BAD_ARG;
     52 }
     53 
     54 static INLINE res_T
     55 check_smsh_load_args(const struct smsh_load_args* args)
     56 {
     57   if(!args || !args->path) return RES_BAD_ARG;
     58   return RES_OK;
     59 }
     60 
     61 static INLINE res_T
     62 check_smsh_load_stream_args
     63   (struct smsh* smsh,
     64    const struct smsh_load_stream_args* args)
     65 {
     66   if(!args || !args->stream || !args->name) return RES_BAD_ARG;
     67   if(args->memory_mapping && is_stdin(args->stream)) {
     68     log_err(smsh,
     69       "%s: unable to use memory mapping on data loaded from stdin\n",
     70       args->name);
     71     return RES_BAD_ARG;
     72   }
     73   return RES_OK;
     74 }
     75 
     76 static void
     77 reset_smsh(struct smsh* smsh)
     78 {
     79   ASSERT(smsh);
     80   smsh->nnodes = 0;
     81   smsh->ncells = 0;
     82   smsh->pagesize = 0;
     83   if(smsh->nodes) {
     84     if(!smsh->map_len_nodes) {
     85       MEM_RM(smsh->allocator, smsh->nodes);
     86     } else if(smsh->nodes != MAP_FAILED) {
     87       munmap(smsh->nodes, smsh->map_len_nodes);
     88     }
     89   }
     90   if(smsh->cells) {
     91     if(!smsh->map_len_cells) {
     92       MEM_RM(smsh->allocator, smsh->cells);
     93     } else if(smsh->nodes != MAP_FAILED) {
     94       munmap(smsh->cells, smsh->map_len_cells);
     95     }
     96   }
     97   smsh->nodes = NULL;
     98   smsh->cells = NULL;
     99   smsh->map_len_nodes = 0;
    100   smsh->map_len_cells = 0;
    101 }
    102 
    103 static res_T
    104 map_data
    105   (struct smsh* smsh,
    106    const char* name,
    107    const int fd, /* File descriptor */
    108    const size_t filesz, /* Overall filesize */
    109    const char* data_name,
    110    const off_t offset, /* Offset of the data into file */
    111    const size_t map_len,
    112    void** out_map) /* Lenght of the data to map */
    113 {
    114   void* map = NULL;
    115   res_T res = RES_OK;
    116   ASSERT(smsh && name && filesz && data_name && map_len && out_map);
    117   ASSERT(IS_ALIGNED((size_t)offset, (size_t)smsh->pagesize));
    118 
    119   if((size_t)offset + map_len > filesz) {
    120     log_err(smsh, "%s: the %s to load exceed the file size.\n",
    121       name, data_name);
    122     res = RES_IO_ERR;
    123     goto error;
    124   }
    125 
    126   map = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE|MAP_POPULATE, fd, offset);
    127   if(map == MAP_FAILED) {
    128     log_err(smsh, "%s: could not map the %s -- %s.\n",
    129       name, data_name, strerror(errno));
    130     res = RES_IO_ERR;
    131     goto error;
    132   }
    133 
    134 exit:
    135   *out_map = map;
    136   return res;
    137 error:
    138   if(map == MAP_FAILED) map = NULL;
    139   goto exit;
    140 }
    141 
    142 static res_T
    143 map_file(struct smsh* smsh, FILE* stream, const char* name)
    144 {
    145   off_t pos_offset;
    146   off_t ids_offset;
    147   size_t filesz;
    148   res_T res = RES_OK;
    149   ASSERT(smsh && stream && name);
    150 
    151   /* Compute the length in bytes of the data to map */
    152   smsh->map_len_nodes = smsh->nnodes * sizeof(double) * smsh->dnode;
    153   smsh->map_len_cells = smsh->ncells * sizeof(uint64_t) * smsh->dcell;
    154   smsh->map_len_nodes = ALIGN_SIZE(smsh->map_len_nodes, (size_t)smsh->pagesize);
    155   smsh->map_len_cells = ALIGN_SIZE(smsh->map_len_cells, (size_t)smsh->pagesize);
    156 
    157   /* Find the offsets of the positions/indices data into the stream */
    158   pos_offset = (off_t)ALIGN_SIZE((uint64_t)ftell(stream), smsh->pagesize);
    159   ids_offset = (off_t)((size_t)pos_offset + smsh->map_len_nodes);
    160 
    161   /* Retrieve the overall filesize */
    162   fseek(stream, 0, SEEK_END);
    163   filesz = (size_t)ftell(stream);
    164 
    165   /* Map the nodes */
    166   res = map_data(smsh, name, fileno(stream), filesz, "nodes",
    167     pos_offset, smsh->map_len_nodes, (void**)&smsh->nodes);
    168   if(res != RES_OK) goto error;
    169 
    170   /* Map the cells */
    171   res = map_data(smsh, name, fileno(stream), filesz, "cells",
    172     ids_offset, smsh->map_len_cells, (void**)&smsh->cells);
    173   if(res != RES_OK) goto error;
    174 
    175 exit:
    176   return res;
    177 error:
    178   goto exit;
    179 }
    180 
    181 static res_T
    182 read_padding(FILE* stream, const size_t padding)
    183 {
    184   char chunk[1024];
    185   size_t remaining_nbytes = padding;
    186 
    187   while(remaining_nbytes) {
    188     const size_t nbytes = MMIN(sizeof(chunk), remaining_nbytes);
    189     if(fread(chunk, 1, nbytes, stream) != nbytes) return RES_IO_ERR;
    190     remaining_nbytes -= nbytes;
    191   }
    192   return RES_OK;
    193 }
    194 
    195 static res_T
    196 load_file(struct smsh* smsh, FILE* stream, const char* name)
    197 {
    198   size_t ncoords;
    199   size_t nindices;
    200   size_t padding_bytes;
    201   size_t sizeof_nodes;
    202   size_t sizeof_cells;
    203   size_t sizeof_header;
    204   res_T res = RES_OK;
    205   ASSERT(smsh && stream && name);
    206 
    207   ncoords = smsh->nnodes * smsh->dnode;
    208   nindices = smsh->ncells * smsh->dcell;
    209   sizeof_nodes = ncoords * sizeof(*smsh->nodes);
    210   sizeof_cells = nindices * sizeof(*smsh->cells);
    211   sizeof_header =
    212     sizeof(smsh->pagesize)
    213   + sizeof(smsh->nnodes)
    214   + sizeof(smsh->ncells)
    215   + sizeof(smsh->dnode)
    216   + sizeof(smsh->dcell);
    217 
    218   /* Allocate the memory space where the loaded data will be stored */
    219   smsh->nodes = MEM_CALLOC(smsh->allocator, ncoords, sizeof(*smsh->nodes));
    220   if(!smsh->nodes) { res = RES_MEM_ERR; goto error; }
    221   smsh->cells = MEM_CALLOC(smsh->allocator, nindices, sizeof(*smsh->cells));
    222   if(!smsh->cells) { res = RES_MEM_ERR; goto error; }
    223 
    224   padding_bytes = ALIGN_SIZE(sizeof_header, smsh->pagesize) - sizeof_header;
    225   if((res = read_padding(stream, padding_bytes)) != RES_OK) goto error;
    226 
    227   /* Load the nodes */
    228   if(fread(smsh->nodes, sizeof(*smsh->nodes), ncoords, stream) != ncoords) {
    229     res = RES_IO_ERR;
    230     goto error;
    231   }
    232 
    233   padding_bytes = ALIGN_SIZE(sizeof_nodes, smsh->pagesize) - sizeof_nodes;
    234   if((res = read_padding(stream, padding_bytes)) != RES_OK) goto error;
    235 
    236   /* Load the cells */
    237   if(fread(smsh->cells, sizeof(*smsh->cells), nindices, stream) != nindices) {
    238     res = RES_IO_ERR;
    239     goto error;
    240   }
    241 
    242   padding_bytes = ALIGN_SIZE(sizeof_cells, smsh->pagesize) - sizeof_cells;
    243   if((res = read_padding(stream, padding_bytes)) != RES_OK) goto error;
    244 
    245 exit:
    246   return res;
    247 error:
    248   log_err(smsh, "%s: error while loading data -- %s.\n",
    249     name, res_to_cstr(res));
    250   goto exit;
    251 }
    252 
    253 static res_T
    254 load_stream(struct smsh* smsh, const struct smsh_load_stream_args* args)
    255 {
    256   res_T res = RES_OK;
    257   ASSERT(smsh && check_smsh_load_stream_args(smsh, args) == RES_OK);
    258 
    259   reset_smsh(smsh);
    260 
    261   /* Read file header */
    262   if(fread(&smsh->pagesize, sizeof(&smsh->pagesize), 1, args->stream) != 1) {
    263     if(ferror(args->stream)) {
    264       log_err(smsh, "%s: could not read the pagesize.\n", args->name);
    265     }
    266     res = RES_IO_ERR;
    267     goto error;
    268   }
    269 
    270   #define READ(Var, N, Name) {                                                 \
    271     if(fread((Var), sizeof(*(Var)), (N), args->stream) != (N)) {               \
    272       log_err(smsh, "%s: could not read the %s.\n", args->name, (Name));       \
    273       res = RES_IO_ERR;                                                        \
    274       goto error;                                                              \
    275     }                                                                          \
    276   } (void)0
    277   READ(&smsh->nnodes, 1, "number of nodes");
    278   READ(&smsh->ncells, 1, "number of cells");
    279   READ(&smsh->dnode, 1, "node dimension");
    280   READ(&smsh->dcell, 1, "cell dimension");
    281   #undef READ
    282 
    283   if(!IS_ALIGNED(smsh->pagesize, smsh->pagesize_os)) {
    284     log_err(smsh,
    285       "%s: invalid page size %li. The page size attribute must be aligned on "
    286       "the page size of the operating system (%lu).\n",
    287       args->name, smsh->pagesize, (unsigned long)smsh->pagesize_os);
    288     res = RES_BAD_ARG;
    289     goto error;
    290   }
    291 
    292   if(args->memory_mapping) {
    293     res = map_file(smsh, args->stream, args->name);
    294     if(res != RES_OK) goto error;
    295   } else {
    296     res = load_file(smsh, args->stream, args->name);
    297     if(res != RES_OK) goto error;
    298   }
    299 
    300 exit:
    301   return res;
    302 error:
    303   reset_smsh(smsh);
    304   goto exit;
    305 }
    306 
    307 static void
    308 release_smsh(ref_T* ref)
    309 {
    310   struct smsh* smsh;
    311   ASSERT(ref);
    312   smsh = CONTAINER_OF(ref, struct smsh, ref);
    313   reset_smsh(smsh);
    314   if(smsh->logger == &smsh->logger__) logger_release(&smsh->logger__);
    315   MEM_RM(smsh->allocator, smsh);
    316 }
    317 
    318 /*******************************************************************************
    319  * Exported functions
    320  ******************************************************************************/
    321 res_T
    322 smsh_create
    323   (const struct smsh_create_args* args,
    324    struct smsh** out_smsh)
    325 {
    326   struct smsh* smsh = NULL;
    327   struct mem_allocator* allocator = NULL;
    328   res_T res = RES_OK;
    329 
    330   if(!out_smsh) { res = RES_BAD_ARG; goto error; }
    331   res = check_smsh_create_args(args);
    332   if(res != RES_OK) goto error;
    333 
    334   allocator = args->allocator ? args->allocator : &mem_default_allocator;
    335   smsh = MEM_CALLOC(allocator, 1, sizeof(*smsh));
    336   if(!smsh) {
    337     if(args->verbose) {
    338       #define ERR_STR "Could not allocate the Star-Mesh device.\n"
    339       if(args->logger) {
    340         logger_print(args->logger, LOG_ERROR, ERR_STR);
    341       } else {
    342         fprintf(stderr, MSG_ERROR_PREFIX ERR_STR);
    343       }
    344       #undef ERR_STR
    345     }
    346     res = RES_MEM_ERR;
    347     goto error;
    348   }
    349   ref_init(&smsh->ref);
    350   smsh->allocator = allocator;
    351   smsh->verbose = args->verbose;
    352   smsh->pagesize_os = (size_t)sysconf(_SC_PAGESIZE);
    353   if(args->logger) {
    354     smsh->logger = args->logger;
    355   } else {
    356     setup_log_default(smsh);
    357   }
    358 
    359 exit:
    360   if(out_smsh) *out_smsh = smsh;
    361   return res;
    362 error:
    363   if(smsh) {
    364     SMSH(ref_put(smsh));
    365     smsh = NULL;
    366   }
    367   goto exit;
    368 }
    369 
    370 res_T
    371 smsh_ref_get(struct smsh* smsh)
    372 {
    373   if(!smsh) return RES_BAD_ARG;
    374   ref_get(&smsh->ref);
    375   return RES_OK;
    376 }
    377 
    378 res_T
    379 smsh_ref_put(struct smsh* smsh)
    380 {
    381   if(!smsh) return RES_BAD_ARG;
    382   ref_put(&smsh->ref, release_smsh);
    383   return RES_OK;
    384 }
    385 
    386 
    387 res_T
    388 smsh_load(struct smsh* smsh, const struct smsh_load_args* args)
    389 {
    390   struct smsh_load_stream_args stream_args = SMSH_LOAD_STREAM_ARGS_NULL;
    391   FILE* file = NULL;
    392   res_T res = RES_OK;
    393 
    394   if(!smsh) { res = RES_BAD_ARG; goto error; }
    395   res = check_smsh_load_args(args);
    396   if(res != RES_OK) goto error;
    397 
    398   file = fopen(args->path, "r");
    399   if(!file) {
    400     log_err(smsh, "%s: error opening file `%s'.\n", FUNC_NAME, args->path);
    401     res = RES_IO_ERR;
    402     goto error;
    403   }
    404 
    405   stream_args.stream = file;
    406   stream_args.name = args->path;
    407   stream_args.memory_mapping = args->memory_mapping;
    408   res = load_stream(smsh, &stream_args);
    409   if(res != RES_OK) goto error;
    410 
    411 exit:
    412   if(file) fclose(file);
    413   return res;
    414 error:
    415   goto exit;
    416 }
    417 
    418 res_T
    419 smsh_load_stream(struct smsh* smsh, const struct smsh_load_stream_args* args)
    420 {
    421   res_T res = RES_OK;
    422   if(!smsh) return RES_BAD_ARG;
    423   res = check_smsh_load_stream_args(smsh, args);
    424   if(res != RES_OK) return res;
    425   return load_stream(smsh, args);
    426 }
    427 
    428 res_T
    429 smsh_get_desc(const struct smsh* smsh, struct smsh_desc* desc)
    430 {
    431   if(!smsh || !desc) return RES_BAD_ARG;
    432   desc->nodes = smsh->nodes;
    433   desc->cells = smsh->cells;
    434   desc->nnodes = smsh->nnodes;
    435   desc->ncells = smsh->ncells;
    436   desc->dnode = smsh->dnode;
    437   desc->dcell = smsh->dcell;
    438   return RES_OK;
    439 }
    440 
    441 res_T
    442 smsh_desc_compute_hash(const struct smsh_desc* desc, hash256_T hash)
    443 {
    444   struct sha256_ctx ctx;
    445 
    446   if(!desc || !hash) return RES_BAD_ARG;
    447 
    448   #define HASH(Var, Nb) \
    449     sha256_ctx_update(&ctx, (const char*)(Var), sizeof(*Var)*(Nb));
    450 
    451   sha256_ctx_init(&ctx);
    452   HASH(desc->nodes, desc->nnodes*desc->dnode);
    453   HASH(desc->cells, desc->ncells*desc->dcell);
    454   HASH(&desc->nnodes, 1);
    455   HASH(&desc->ncells, 1);
    456   HASH(&desc->dnode, 1);
    457   HASH(&desc->dcell, 1);
    458   sha256_ctx_finalize(&ctx, hash);
    459 
    460   #undef HASH
    461 
    462   return RES_OK;
    463 }