LCOV - code coverage report
Current view: top level - cmdline - import.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 128 146 87.7 %
Date: 2026-04-29 15:04:44 Functions: 9 10 90.0 %

          Line data    Source code
       1             : // SPDX-License-Identifier: GPL-3.0-or-later
       2             : // Copyright (C) 2013 Andrea Mazzoleni
       3             : 
       4             : #include "portable.h"
       5             : 
       6             : #include "support.h"
       7             : #include "import.h"
       8             : 
       9             : /****************************************************************************/
      10             : /* import */
      11             : 
      12             : /**
      13             :  * Compare the hash of two import blocks.
      14             :  */
      15        5460 : int import_block_hash_compare(const void* void_arg, const void* void_data)
      16             : {
      17        5460 :         const unsigned char* arg = void_arg;
      18        5460 :         const struct snapraid_import_block* block = void_data;
      19             : 
      20        5460 :         return memcmp(arg, block->hash, BLOCK_HASH_SIZE);
      21             : }
      22             : 
      23           0 : int import_block_prevhash_compare(const void* void_arg, const void* void_data)
      24             : {
      25           0 :         const unsigned char* arg = void_arg;
      26           0 :         const struct snapraid_import_block* block = void_data;
      27             : 
      28           0 :         return memcmp(arg, block->prevhash, BLOCK_HASH_SIZE);
      29             : }
      30             : 
      31             : /**
      32             :  * Compute the hash of the hash for an import block.
      33             :  * We just use the first 32 bit of the hash itself.
      34             :  */
      35      700555 : static inline tommy_uint32_t import_block_hash(const unsigned char* hash)
      36             : {
      37             :         /* the hash data is not aligned, and we cannot access it with a direct cast */
      38      700555 :         return hash[0] | ((uint32_t)hash[1] << 8) | ((uint32_t)hash[2] << 16) | ((uint32_t)hash[3] << 24);
      39             : }
      40             : 
      41        1771 : static void import_file(struct snapraid_state* state, const char* path, uint64_t size)
      42             : {
      43             :         struct snapraid_import_file* file;
      44             :         block_off_t i;
      45             :         data_off_t offset;
      46             :         void* buffer;
      47             :         int ret;
      48             :         int f;
      49             :         int flags;
      50        1771 :         unsigned block_size = state->block_size;
      51             :         struct advise_struct advise;
      52             : 
      53        1771 :         file = malloc_nofail(sizeof(struct snapraid_import_file));
      54        1771 :         file->path = strdup_nofail(path);
      55        1771 :         file->size = size;
      56        1771 :         file->blockmax = (size + block_size - 1) / block_size;
      57        1771 :         file->blockimp = malloc_nofail(file->blockmax * sizeof(struct snapraid_import_block));
      58        1771 :         file->is_runtime = 1;
      59             : 
      60        1771 :         buffer = malloc_nofail(block_size);
      61             : 
      62        1771 :         advise_init(&advise, state->file_mode);
      63             : 
      64             :         /* open for read */
      65        1771 :         flags = O_RDONLY | O_BINARY | advise_flags(&advise);
      66        1771 :         f = open(path, flags);
      67        1771 :         if (f == -1) {
      68             :                 /* LCOV_EXCL_START */
      69             :                 log_fatal(errno, "Error opening file '%s'. %s.\n", path, strerror(errno));
      70             :                 exit(EXIT_FAILURE);
      71             :                 /* LCOV_EXCL_STOP */
      72             :         }
      73             : 
      74        1771 :         ret = advise_open(&advise, f);
      75        1771 :         if (ret != 0) {
      76             :                 /* LCOV_EXCL_START */
      77             :                 log_fatal(errno, "Error advising file '%s'. %s.\n", path, strerror(errno));
      78             :                 exit(EXIT_FAILURE);
      79             :                 /* LCOV_EXCL_STOP */
      80             :         }
      81             : 
      82        1771 :         offset = 0;
      83        6304 :         for (i = 0; i < file->blockmax; ++i) {
      84        4533 :                 struct snapraid_import_block* block = &file->blockimp[i];
      85        4533 :                 unsigned read_size = block_size;
      86        4533 :                 if (read_size > size)
      87        1767 :                         read_size = size;
      88             : 
      89        4533 :                 ret = read(f, buffer, read_size);
      90        4533 :                 if (ret < 0 || (unsigned)ret != read_size) {
      91             :                         /* LCOV_EXCL_START */
      92             :                         log_fatal(errno, "Error reading file '%s'. %s.\n", path, strerror(errno));
      93             :                         exit(EXIT_FAILURE);
      94             :                         /* LCOV_EXCL_STOP */
      95             :                 }
      96             : 
      97        4533 :                 block->file = file;
      98        4533 :                 block->offset = offset;
      99        4533 :                 block->size = read_size;
     100             : 
     101        4533 :                 memhash(state->hash, state->hashseed, block->hash, buffer, read_size);
     102        4533 :                 tommy_hashdyn_insert(&state->importset, &block->nodeset, block, import_block_hash(block->hash));
     103             : 
     104             :                 /* if we are in a rehash state */
     105        4533 :                 if (state->prevhash != HASH_UNDEFINED) {
     106             :                         /* compute also the previous hash */
     107           0 :                         memhash(state->prevhash, state->prevhashseed, block->prevhash, buffer, read_size);
     108           0 :                         tommy_hashdyn_insert(&state->previmportset, &block->prevnodeset, block, import_block_hash(block->prevhash));
     109             :                 }
     110             : 
     111        4533 :                 offset += read_size;
     112        4533 :                 size -= read_size;
     113             :         }
     114             : 
     115        1771 :         ret = close(f);
     116        1771 :         if (ret != 0) {
     117             :                 /* LCOV_EXCL_START */
     118             :                 log_fatal(errno, "Error closing file '%s'. %s.\n", path, strerror(errno));
     119             :                 exit(EXIT_FAILURE);
     120             :                 /* LCOV_EXCL_STOP */
     121             :         }
     122             : 
     123        1771 :         tommy_list_insert_tail(&state->importlist, &file->nodelist, file);
     124             : 
     125        1771 :         free(buffer);
     126        1771 : }
     127             : 
     128        1629 : static void import_dealloc(struct snapraid_state* state, const char* dir, struct snapraid_dealloc* dealloc)
     129             : {
     130             :         char path[PATH_MAX];
     131             :         struct snapraid_import_file* file;
     132             :         block_off_t i;
     133             :         data_off_t offset;
     134             :         data_off_t size;
     135        1629 :         unsigned block_size = state->block_size;
     136             : 
     137        1629 :         pathcpy(path, sizeof(path), dir);
     138        1629 :         pathcat(path, sizeof(path), dealloc->sub);
     139        1629 :         size = dealloc->size;
     140             : 
     141        1629 :         file = malloc_nofail(sizeof(struct snapraid_import_file));
     142        1629 :         file->path = strdup_nofail(path);
     143        1629 :         file->size = size;
     144        1629 :         file->blockmax = (size + block_size - 1) / block_size;
     145        1629 :         file->blockimp = malloc_nofail(file->blockmax * sizeof(struct snapraid_import_block));
     146        1629 :         file->is_runtime = 0;
     147             : 
     148        1629 :         offset = 0;
     149        3257 :         for (i = 0; i < file->blockmax; ++i) {
     150        1628 :                 struct snapraid_import_block* block = &file->blockimp[i];
     151        1628 :                 unsigned read_size = block_size;
     152        1628 :                 if (read_size > size)
     153        1628 :                         read_size = size;
     154             : 
     155        1628 :                 block->file = file;
     156        1628 :                 block->offset = offset;
     157        1628 :                 block->size = read_size;
     158             : 
     159        1628 :                 memcpy(block->hash, dealloc->blockhash + i * BLOCK_HASH_SIZE, BLOCK_HASH_SIZE);
     160        1628 :                 hash_invalid_set(block->prevhash);
     161             : 
     162             :                 /* do not insert invalid hashes */
     163        1628 :                 if (!hash_is_invalid(block->hash))
     164        1628 :                         tommy_hashdyn_insert(&state->importset, &block->nodeset, block, import_block_hash(block->hash));
     165             :                 /* do not insert prevhash */
     166             : 
     167        1628 :                 offset += read_size;
     168        1628 :                 size -= read_size;
     169             :         }
     170             : 
     171        1629 :         tommy_list_insert_tail(&state->importlist, &file->nodelist, file);
     172        1629 : }
     173             : 
     174        3400 : void import_file_free(struct snapraid_import_file* file)
     175             : {
     176        3400 :         free(file->path);
     177        3400 :         free(file->blockimp);
     178        3400 :         free(file);
     179        3400 : }
     180             : 
     181      694394 : int state_import_fetch(struct snapraid_state* state, int rehash, struct snapraid_block* missing_block, unsigned char* buffer)
     182             : {
     183             :         struct snapraid_import_block* block;
     184             :         int ret;
     185             :         int f;
     186      694394 :         const unsigned char* hash = missing_block->hash;
     187      694394 :         unsigned block_size = state->block_size;
     188             :         unsigned read_size;
     189             :         unsigned char buffer_hash[HASH_MAX];
     190             :         const char* path;
     191             : 
     192      694394 :         if (rehash) {
     193           0 :                 block = tommy_hashdyn_search(&state->previmportset, import_block_prevhash_compare, hash, import_block_hash(hash));
     194             :         } else {
     195      694394 :                 block = tommy_hashdyn_search(&state->importset, import_block_hash_compare, hash, import_block_hash(hash));
     196             :         }
     197      694394 :         if (!block)
     198      688934 :                 return -1;
     199             : 
     200        5460 :         path = block->file->path;
     201        5460 :         read_size = block->size;
     202             : 
     203        5460 :         f = open(path, O_RDONLY | O_BINARY);
     204        5460 :         if (f == -1) {
     205             :                 /* if no file, just skip it */
     206           0 :                 if (errno == ENOENT && !block->file->is_runtime) {
     207           0 :                         log_error(EUSER, "WARNING! Unexpected missing deallocated file '%s'.\n", path);
     208           0 :                         return -1;
     209             :                 }
     210             : 
     211             :                 /* LCOV_EXCL_START */
     212             :                 if (errno == ENOENT) {
     213             :                         log_fatal(errno, "DANGER! file '%s' disappeared.\n", path);
     214             :                         log_fatal(errno, "If you moved it, please rerun the same command.\n");
     215             :                 } else {
     216             :                         log_fatal(errno, "Error opening file '%s'. %s.\n", path, strerror(errno));
     217             :                 }
     218             :                 exit(EXIT_FAILURE);
     219             :                 /* LCOV_EXCL_STOP */
     220             :         }
     221             : 
     222        5460 :         ret = pread(f, buffer, read_size, block->offset);
     223        5460 :         if (ret < 0 || (unsigned)ret != read_size) {
     224             :                 /* LCOV_EXCL_START */
     225             :                 log_fatal(errno, "Error reading file '%s'. %s.\n", path, strerror(errno));
     226             :                 exit(EXIT_FAILURE);
     227             :                 /* LCOV_EXCL_STOP */
     228             :         }
     229             : 
     230        5460 :         ret = close(f);
     231        5460 :         if (ret != 0) {
     232             :                 /* LCOV_EXCL_START */
     233             :                 log_fatal(errno, "Error closing file '%s'. %s.\n", path, strerror(errno));
     234             :                 exit(EXIT_FAILURE);
     235             :                 /* LCOV_EXCL_STOP */
     236             :         }
     237             : 
     238        5460 :         if (read_size != block_size) {
     239             :                 /* fill the remaining with 0 */
     240        2694 :                 memset(buffer + read_size, 0, block_size - read_size);
     241             :         }
     242             : 
     243             :         /* recheck the hash */
     244        5460 :         if (rehash)
     245           0 :                 memhash(state->prevhash, state->prevhashseed, buffer_hash, buffer, read_size);
     246             :         else
     247        5460 :                 memhash(state->hash, state->hashseed, buffer_hash, buffer, read_size);
     248             : 
     249        5460 :         if (memcmp(buffer_hash, hash, BLOCK_HASH_SIZE) != 0) {
     250             :                 /* if hash mismatch, skip it */
     251           0 :                 if (!block->file->is_runtime) {
     252           0 :                         log_error(EUSER, "WARNING! Unexpected hash mismatch from deallocated file '%s'.\n", path);
     253           0 :                         return -1;
     254             :                 }
     255             : 
     256             :                 /* LCOV_EXCL_START */
     257             :                 log_fatal(EUSER, "Mismatch in data reading file '%s'.\n", path);
     258             :                 log_fatal(EUSER, "Please don't change imported files while running.\n");
     259             :                 exit(EXIT_FAILURE);
     260             :                 /* LCOV_EXCL_STOP */
     261             :         }
     262             : 
     263        5460 :         return 0;
     264             : }
     265             : 
     266           1 : static void import_dir(struct snapraid_state* state, const char* dir)
     267             : {
     268             :         DIR* d;
     269             : 
     270           1 :         d = opendir(dir);
     271           1 :         if (!d) {
     272             :                 /* LCOV_EXCL_START */
     273             :                 log_fatal(errno, "Error opening directory '%s'. %s.\n", dir, strerror(errno));
     274             :                 exit(EXIT_FAILURE);
     275             :                 /* LCOV_EXCL_STOP */
     276             :         }
     277             : 
     278        1834 :         while (1) {
     279             :                 char path_next[PATH_MAX];
     280             :                 struct stat st;
     281             :                 const char* name;
     282             :                 struct dirent* dd;
     283             : 
     284             :                 /* clear errno to detect erroneous conditions */
     285        1835 :                 errno = 0;
     286        1835 :                 dd = readdir(d);
     287        1835 :                 if (dd == 0 && errno != 0) {
     288             :                         /* LCOV_EXCL_START */
     289             :                         log_fatal(errno, "Error reading directory '%s'. %s.\n", dir, strerror(errno));
     290             :                         exit(EXIT_FAILURE);
     291             :                         /* LCOV_EXCL_STOP */
     292             :                 }
     293        1835 :                 if (dd == 0) {
     294           1 :                         break; /* finished */
     295             :                 }
     296             : 
     297             :                 /* skip "." and ".." files */
     298        1834 :                 name = dd->d_name;
     299        1834 :                 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
     300           2 :                         continue;
     301             : 
     302        1832 :                 pathprint(path_next, sizeof(path_next), "%s%s", dir, name);
     303             : 
     304             : #if HAVE_STRUCT_DIRENT_D_STAT
     305             :                 /* convert dirent to lstat result */
     306             :                 dirent_lstat(dd, &st);
     307             : 
     308             :                 /*
     309             :                  * If the st_mode field is missing, takes care to fill it using normal lstat()
     310             :                  * at now this can happen only in Windows (with HAVE_STRUCT_DIRENT_D_STAT defined),
     311             :                  * because we use a directory reading method that doesn't read info about ReparsePoint.
     312             :                  * Note that here we cannot call here lstat_sync(), because we don't know what kind
     313             :                  * of file is it, and lstat_sync() doesn't always work
     314             :                  */
     315             :                 if (st.st_mode == 0) {
     316             :                         if (lstat(path_next, &st) != 0) {
     317             :                                 /* LCOV_EXCL_START */
     318             :                                 log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
     319             :                                 exit(EXIT_FAILURE);
     320             :                                 /* LCOV_EXCL_STOP */
     321             :                         }
     322             :                 }
     323             : #else
     324             :                 /* get lstat info about the file */
     325        1832 :                 if (lstat(path_next, &st) != 0) {
     326             :                         /* LCOV_EXCL_START */
     327             :                         log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
     328             :                         exit(EXIT_FAILURE);
     329             :                         /* LCOV_EXCL_STOP */
     330             :                 }
     331             : #endif
     332             : 
     333        1832 :                 if (S_ISREG(st.st_mode)) {
     334        1771 :                         import_file(state, path_next, st.st_size);
     335          61 :                 } else if (S_ISDIR(st.st_mode)) {
     336           0 :                         pathslash(path_next, sizeof(path_next));
     337           0 :                         import_dir(state, path_next);
     338             :                 }
     339             :         }
     340             : 
     341           1 :         if (closedir(d) != 0) {
     342             :                 /* LCOV_EXCL_START */
     343             :                 log_fatal(errno, "Error closing directory '%s'. %s.\n", dir, strerror(errno));
     344             :                 exit(EXIT_FAILURE);
     345             :                 /* LCOV_EXCL_STOP */
     346             :         }
     347           1 : }
     348             : 
     349           1 : void state_import(struct snapraid_state* state, const char* dir)
     350             : {
     351             :         char path[PATH_MAX];
     352             : 
     353           1 :         msg_progress("Importing...\n");
     354             : 
     355             :         /* if the hash is not full */
     356           1 :         if (BLOCK_HASH_SIZE != HASH_MAX) {
     357             :                 /* LCOV_EXCL_START */
     358             :                 log_fatal(EUSER, "You cannot import files when using a reduced hash.\n");
     359             :                 exit(EXIT_FAILURE);
     360             :                 /* LCOV_EXCL_STOP */
     361             :         }
     362             : 
     363             :         /* add the final slash */
     364           1 :         pathimport(path, sizeof(path), dir);
     365           1 :         pathslash(path, sizeof(path));
     366             : 
     367           1 :         import_dir(state, path);
     368           1 : }
     369             : 
     370           2 : void state_dealloc(struct snapraid_state* state, const char* dir, tommy_list* dealloclist)
     371             : {
     372             :         /* snapshot should be enabled */
     373           2 :         if (!state->snapshot)
     374           0 :                 return;
     375             : 
     376             :         /* the hash must be full */
     377           2 :         if (BLOCK_HASH_SIZE != HASH_MAX)
     378           0 :                 return;
     379             : 
     380        1631 :         for (tommy_node* i = tommy_list_head(dealloclist); i != 0; i = i->next) {
     381        1629 :                 struct snapraid_dealloc* dealloc = i->data;
     382             : 
     383        1629 :                 import_dealloc(state, dir, dealloc);
     384             :         }
     385             : }
     386             : 

Generated by: LCOV version 1.0