LCOV - code coverage report
Current view: top level - cmdline - elem.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 667 702 95.0 %
Date: 2026-03-15 15:58:19 Functions: 72 74 97.3 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (C) 2011 Andrea Mazzoleni
       3             :  *
       4             :  * This program is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 3 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * This program is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "portable.h"
      19             : 
      20             : #include "elem.h"
      21             : #include "support.h"
      22             : #include "state.h"
      23             : #include "util.h"
      24             : 
      25             : /****************************************************************************/
      26             : /* snapraid */
      27             : 
      28             : int BLOCK_HASH_SIZE = HASH_MAX;
      29             : 
      30        1667 : struct snapraid_content* content_alloc(const char* path, uint64_t dev)
      31             : {
      32             :         struct snapraid_content* content;
      33             : 
      34        1667 :         content = malloc_nofail(sizeof(struct snapraid_content));
      35        1667 :         pathimport(content->content, sizeof(content->content), path);
      36        1667 :         content->device = dev;
      37             : 
      38        1667 :         return content;
      39             : }
      40             : 
      41        1612 : void content_free(struct snapraid_content* content)
      42             : {
      43        1612 :         free(content);
      44        1612 : }
      45             : 
      46         671 : struct snapraid_filter* filter_alloc_file(int direction, const char* root, const char* pattern)
      47             : {
      48             :         struct snapraid_filter* filter;
      49             :         char* i;
      50             :         char* first;
      51             :         char* last;
      52             :         int token_is_valid;
      53             :         int token_is_filled;
      54             : 
      55         671 :         filter = malloc_nofail(sizeof(struct snapraid_filter));
      56         671 :         pathimport(filter->pattern, sizeof(filter->pattern), pattern);
      57         671 :         pathimport(filter->root, sizeof(filter->root), root);
      58         671 :         filter->direction = direction;
      59             : 
      60             :         /* find first and last slash */
      61         671 :         first = 0;
      62         671 :         last = 0;
      63             : 
      64             :         /* reject invalid tokens, like "<empty>", ".", ".." and more dots */
      65         671 :         token_is_valid = 0;
      66         671 :         token_is_filled = 0;
      67        7618 :         for (i = filter->pattern; *i; ++i) {
      68        6948 :                 if (*i == '/') {
      69             :                         /* reject invalid tokens, but accept an empty one as first */
      70          90 :                         if (!token_is_valid && (first != 0 || token_is_filled)) {
      71           1 :                                 free(filter);
      72           1 :                                 return 0;
      73             :                         }
      74          89 :                         token_is_valid = 0;
      75          89 :                         token_is_filled = 0;
      76             : 
      77             :                         /* update slash position */
      78          89 :                         if (!first)
      79          51 :                                 first = i;
      80          89 :                         last = i;
      81        6858 :                 } else if (*i != '.') {
      82        6274 :                         token_is_valid = 1;
      83        6274 :                         token_is_filled = 1;
      84             :                 } else {
      85         584 :                         token_is_filled = 1;
      86             :                 }
      87             :         }
      88             : 
      89             :         /* reject invalid tokens, but accept an empty one as last, but not if it's the only one */
      90         670 :         if (!token_is_valid && (first == 0 || token_is_filled)) {
      91           1 :                 free(filter);
      92           1 :                 return 0;
      93             :         }
      94             : 
      95             :         /* it's a file filter */
      96         669 :         filter->is_disk = 0;
      97             : 
      98         669 :         if (first == 0) {
      99             :                 /* no slash */
     100         618 :                 filter->is_abs = 0;
     101         618 :                 filter->is_dir = 0;
     102             :         } else {
     103          51 :                 if (first == filter->pattern) {
     104             :                         /* slash at the start */
     105          38 :                         filter->is_abs = 1;
     106             :                 } else {
     107             :                         /* no slash at the start */
     108          13 :                         filter->is_abs = 0;
     109             :                 }
     110          51 :                 if (last[1] == 0) {
     111             :                         /* slash at the end */
     112          50 :                         filter->is_dir = 1;
     113          50 :                         last[0] = 0; /* clear the slash */
     114             :                 } else {
     115             :                         /* no slash at the end */
     116           1 :                         filter->is_dir = 0;
     117             :                 }
     118             :         }
     119             : 
     120         669 :         return filter;
     121             : }
     122             : 
     123          11 : struct snapraid_filter* filter_alloc_disk(int direction, const char* pattern)
     124             : {
     125             :         struct snapraid_filter* filter;
     126             : 
     127          11 :         filter = malloc_nofail(sizeof(struct snapraid_filter));
     128          11 :         pathimport(filter->pattern, sizeof(filter->pattern), pattern);
     129          11 :         filter->direction = direction;
     130             : 
     131             :         /* it's a disk filter */
     132          11 :         filter->is_disk = 1;
     133          11 :         filter->is_abs = 0;
     134          11 :         filter->is_dir = 0;
     135             : 
     136             :         /* no slash allowed in disk names */
     137          11 :         if (strchr(filter->pattern, '/') != 0) {
     138             :                 /* LCOV_EXCL_START */
     139             :                 free(filter);
     140             :                 return 0;
     141             :                 /* LCOV_EXCL_STOP */
     142             :         }
     143             : 
     144          11 :         return filter;
     145             : }
     146             : 
     147         660 : void filter_free(void* filter)
     148             : {
     149         660 :         free(filter);
     150         660 : }
     151             : 
     152      124753 : const char* filter_type(struct snapraid_filter* filter, char* out, size_t out_size)
     153             : {
     154             :         const char* direction;
     155             : 
     156      124753 :         if (filter->direction < 0)
     157      124468 :                 direction = "exclude";
     158             :         else
     159         285 :                 direction = "include";
     160             : 
     161      124753 :         if (filter->is_disk)
     162           0 :                 pathprint(out, out_size, "%s %s:", direction, filter->pattern);
     163      124753 :         else if (filter->is_dir)
     164          18 :                 pathprint(out, out_size, "%s %s/", direction, filter->pattern);
     165             :         else
     166      124735 :                 pathprint(out, out_size, "%s %s", direction, filter->pattern);
     167             : 
     168      124753 :         return out;
     169             : }
     170             : 
     171             : /**
     172             :  * Apply a filter to the specified path
     173             :  *
     174             :  * @param filter The filter to apply.
     175             :  * @param reason Assign to it the filter that caused rejection.
     176             :  * @param path The path to filter.
     177             :  * @param is_dir if the path refers to a directory, otherwise to a file.
     178             :  * @return < 0 if the path is excluded, > 0 if the path is included, 0 if the filter doesn't apply.
     179             :  */
     180     6802995 : static int filter_apply(struct snapraid_filter* filter, struct snapraid_filter** reason, const char* path, int is_dir)
     181             : {
     182     6802995 :         if (filter->root[0] != 0) { /* if it's a local filter */
     183      127913 :                 if (!path_is_root_of(filter->root, path)) /* applies it only if it matches the root */
     184       68719 :                         return 0;
     185             :                 /* advance the path to compare to make it local */
     186       59194 :                 path += strlen(filter->root);
     187             :         }
     188             : 
     189             :         /* match dirs with dirs and files with files */
     190     6734276 :         if (filter->is_dir && !is_dir)
     191      119172 :                 return 0;
     192     6615104 :         if (!filter->is_dir && is_dir)
     193        6510 :                 return 0;
     194             : 
     195     6608594 :         int ret = 0;
     196             : 
     197     6608594 :         if (filter->is_abs) {
     198             :                 /* skip initial slash, as always missing in the path */
     199        6035 :                 if (wnmatch(filter->pattern + 1, path) == 0)
     200           0 :                         ret = filter->direction;
     201             :         } else {
     202             :                 /* the path is relative, first try to match from the root */
     203     6602559 :                 if (wnmatch(filter->pattern, path) == 0) {
     204         234 :                         ret = filter->direction;
     205             :                 } else {
     206             :                         /* then try to match after all the / presents */
     207     6602325 :                         char* slash = strchr(path, '/');
     208    13013557 :                         while (slash) {
     209     6535151 :                                 if (wnmatch(filter->pattern, slash + 1) == 0) {
     210      123919 :                                         ret = filter->direction;
     211      123919 :                                         break;
     212             :                                 }
     213             : 
     214     6411232 :                                 slash = strchr(slash + 1, '/');
     215             :                         }
     216             :                 }
     217             :         }
     218             : 
     219     6608594 :         if (reason != 0 && ret < 0)
     220      124153 :                 *reason = filter;
     221             : 
     222     6608594 :         return ret;
     223             : }
     224             : 
     225             : /**
     226             :  * Apply a list of filter to a specific disk/sub path
     227             :  *
     228             :  * @param filterlist The list of filter to apply
     229             :  * @param reason Assign to it the filter that caused rejection.
     230             :  * @param disk The disk that contain the path
     231             :  * @param sub The path in the disk
     232             :  * @param is_dir If the path refers to a directory, otherwise it's a file
     233             :  * @param is_def_include If no filter apply, if by default force the inclusion. Used to include directory by default.
     234             :  * @return < 0 if the path is excluded, == 0 if the path is included.
     235             :  */
     236             : 
     237     4767601 : static int filter_element(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub, int is_dir, int is_def_include)
     238             : {
     239             :         tommy_node* i;
     240             : 
     241             :         /**
     242             :          * The rule is that the latest filter defines the behaviour of paths
     243             :          * that doesn't match any filter.
     244             :          *
     245             :          * If the latest filter is an 'exclusion' filter, the default is to include.
     246             :          * If the latest filter is an 'inclusion' filter, the default is to exclude.
     247             :          * If there are no filter, the default is to include.
     248             :          */
     249     4767601 :         int default_direction = 1;
     250             : 
     251             :         /* for each filter */
     252    11564912 :         for (i = tommy_list_head(filterlist); i != 0; i = i->next) {
     253             :                 int ret;
     254     6930144 :                 struct snapraid_filter* filter = i->data;
     255             : 
     256     6930144 :                 if (filter->is_disk) {
     257      127149 :                         if (wnmatch(filter->pattern, disk) == 0) {
     258        8680 :                                 ret = filter->direction;
     259             :                         } else {
     260      118469 :                                 ret = 0;
     261             :                         }
     262      127149 :                         if (reason != 0 && ret < 0)
     263           0 :                                 *reason = filter;
     264             :                 } else {
     265     6802995 :                         ret = filter_apply(filter, reason, sub, is_dir);
     266             :                 }
     267             : 
     268     6930144 :                 if (ret > 0) {
     269             :                         /* include the file */
     270        8680 :                         return 0;
     271     6921464 :                 } else if (ret < 0) {
     272             :                         /* exclude the file */
     273      124153 :                         return -1;
     274             :                 } else {
     275             :                         /* default is opposite of the last filter */
     276     6797311 :                         default_direction = -filter->direction;
     277     6797311 :                         if (reason != 0 && default_direction < 0)
     278     3271968 :                                 *reason = filter;
     279             :                         /* continue with the next one */
     280             :                 }
     281             :         }
     282             : 
     283             :         /* directories are always included by default, otherwise we cannot apply rules */
     284             :         /* to the contained files */
     285     4634768 :         if (is_def_include)
     286        4679 :                 return 0;
     287             : 
     288             :         /* files are excluded/included depending of the last rule processed */
     289     4630089 :         if (default_direction < 0)
     290       77759 :                 return -1;
     291             : 
     292     4552330 :         return 0;
     293             : }
     294             : 
     295     4762873 : int filter_path(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
     296             : {
     297     4762873 :         return filter_element(filterlist, reason, disk, sub, 0, 0);
     298             : }
     299             : 
     300        4679 : int filter_subdir(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
     301             : {
     302        4679 :         return filter_element(filterlist, reason, disk, sub, 1, 1);
     303             : }
     304             : 
     305          49 : int filter_emptydir(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
     306             : {
     307          49 :         return filter_element(filterlist, reason, disk, sub, 1, 0);
     308             : }
     309             : 
     310       77590 : int filter_existence(int filter_missing, const char* dir, const char* sub)
     311             : {
     312             :         char path[PATH_MAX];
     313             :         struct stat st;
     314             : 
     315       77590 :         if (!filter_missing)
     316       31984 :                 return 0;
     317             : 
     318             :         /* we directly check if in the disk the file is present or not */
     319       45606 :         pathprint(path, sizeof(path), "%s%s", dir, sub);
     320             : 
     321       45606 :         if (lstat(path, &st) != 0) {
     322             :                 /* if the file doesn't exist, we don't filter it out */
     323        5018 :                 if (errno == ENOENT)
     324        5018 :                         return 0;
     325             :                 /* LCOV_EXCL_START */
     326             :                 log_fatal(errno, "Error in stat file '%s'. %s.\n", path, strerror(errno));
     327             :                 exit(EXIT_FAILURE);
     328             :                 /* LCOV_EXCL_STOP */
     329             :         }
     330             : 
     331             :         /* the file is present, so we filter it out */
     332       40588 :         return 1;
     333             : }
     334             : 
     335       35970 : int filter_correctness(int filter_error, tommy_arrayblkof* infoarr, struct snapraid_disk* disk, struct snapraid_file* file)
     336             : {
     337             :         unsigned i;
     338             : 
     339       35970 :         if (!filter_error)
     340       13422 :                 return 0;
     341             : 
     342             :         /* check each block of the file */
     343       60656 :         for (i = 0; i < file->blockmax; ++i) {
     344       46389 :                 block_off_t parity_pos = fs_file2par_get(disk, file, i);
     345       46389 :                 snapraid_info info = info_get(infoarr, parity_pos);
     346             : 
     347             :                 /* if the file has a bad block, don't exclude it */
     348       46389 :                 if (info_get_bad(info))
     349        8281 :                         return 0;
     350             :         }
     351             : 
     352             :         /* the file is correct, so we filter it out */
     353       14267 :         return 1;
     354             : }
     355             : 
     356     3335517 : int filter_content(tommy_list* contentlist, const char* path)
     357             : {
     358             :         tommy_node* i;
     359             : 
     360    21542896 :         for (i = tommy_list_head(contentlist); i != 0; i = i->next) {
     361    18207379 :                 struct snapraid_content* content = i->data;
     362             :                 char tmp[PATH_MAX];
     363             : 
     364    18207379 :                 if (pathcmp(content->content, path) == 0)
     365           0 :                         return -1;
     366             : 
     367             :                 /* exclude also the ".tmp" copy used to save it */
     368    18207379 :                 pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
     369    18207379 :                 if (pathcmp(tmp, path) == 0)
     370           0 :                         return -1;
     371             : 
     372             :                 /* exclude also the ".lock" file */
     373    18207379 :                 pathprint(tmp, sizeof(tmp), "%s.lock", content->content);
     374    18207379 :                 if (pathcmp(tmp, path) == 0)
     375           0 :                         return -1;
     376             :         }
     377             : 
     378     3335517 :         return 0;
     379             : }
     380             : 
     381     3923858 : struct snapraid_file* file_alloc(unsigned block_size, const char* sub, data_off_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical)
     382             : {
     383             :         struct snapraid_file* file;
     384             :         block_off_t i;
     385             : 
     386     3923858 :         file = malloc_nofail(sizeof(struct snapraid_file));
     387     3923858 :         file->sub = strdup_nofail(sub);
     388     3923858 :         file->size = size;
     389     3923858 :         file->blockmax = (size + block_size - 1) / block_size;
     390     3923858 :         file->mtime_sec = mtime_sec;
     391     3923858 :         file->mtime_nsec = mtime_nsec;
     392     3923858 :         file->inode = inode;
     393     3923858 :         file->physical = physical;
     394     3923858 :         file->flag = 0;
     395     3923858 :         file->blockvec = malloc_nofail(file->blockmax * block_sizeof());
     396             : 
     397    13629556 :         for (i = 0; i < file->blockmax; ++i) {
     398     9705698 :                 struct snapraid_block* block = file_block(file, i);
     399     9705698 :                 block_state_set(block, BLOCK_STATE_CHG);
     400     9705698 :                 hash_invalid_set(block->hash);
     401             :         }
     402             : 
     403     3923858 :         return file;
     404             : }
     405             : 
     406       22637 : struct snapraid_file* file_dup(struct snapraid_file* copy)
     407             : {
     408             :         struct snapraid_file* file;
     409             :         block_off_t i;
     410             : 
     411       22637 :         file = malloc_nofail(sizeof(struct snapraid_file));
     412       22637 :         file->sub = strdup_nofail(copy->sub);
     413       22637 :         file->size = copy->size;
     414       22637 :         file->blockmax = copy->blockmax;
     415       22637 :         file->mtime_sec = copy->mtime_sec;
     416       22637 :         file->mtime_nsec = copy->mtime_nsec;
     417       22637 :         file->inode = copy->inode;
     418       22637 :         file->physical = copy->physical;
     419       22637 :         file->flag = copy->flag;
     420       22637 :         file->blockvec = malloc_nofail(file->blockmax * block_sizeof());
     421             : 
     422       79556 :         for (i = 0; i < file->blockmax; ++i) {
     423       56919 :                 struct snapraid_block* block = file_block(file, i);
     424       56919 :                 struct snapraid_block* copy_block = file_block(copy, i);
     425       56919 :                 block->state = copy_block->state;
     426       56919 :                 memcpy(block->hash, copy_block->hash, BLOCK_HASH_SIZE);
     427             :         }
     428             : 
     429       22637 :         return file;
     430             : }
     431             : 
     432     3900498 : void file_free(struct snapraid_file* file)
     433             : {
     434     3900498 :         free(file->sub);
     435     3900498 :         file->sub = 0;
     436     3900498 :         free(file->blockvec);
     437     3900498 :         file->blockvec = 0;
     438     3900498 :         free(file);
     439     3900498 : }
     440             : 
     441         155 : void file_rename(struct snapraid_file* file, const char* sub)
     442             : {
     443         155 :         free(file->sub);
     444         155 :         file->sub = strdup_nofail(sub);
     445         155 : }
     446             : 
     447       22989 : void file_copy(struct snapraid_file* src_file, struct snapraid_file* dst_file)
     448             : {
     449             :         block_off_t i;
     450             : 
     451       22989 :         if (src_file->size != dst_file->size) {
     452             :                 /* LCOV_EXCL_START */
     453             :                 log_fatal(EINTERNAL, "Internal inconsistency: Copy file with different size\n");
     454             :                 os_abort();
     455             :                 /* LCOV_EXCL_STOP */
     456             :         }
     457             : 
     458       22989 :         if (src_file->mtime_sec != dst_file->mtime_sec) {
     459             :                 /* LCOV_EXCL_START */
     460             :                 log_fatal(EINTERNAL, "Internal inconsistency: Copy file with different mtime_sec\n");
     461             :                 os_abort();
     462             :                 /* LCOV_EXCL_STOP */
     463             :         }
     464             : 
     465       22989 :         if (src_file->mtime_nsec != dst_file->mtime_nsec) {
     466             :                 /* LCOV_EXCL_START */
     467             :                 log_fatal(EINTERNAL, "Internal inconsistency: Copy file with different mtime_nsec\n");
     468             :                 os_abort();
     469             :                 /* LCOV_EXCL_STOP */
     470             :         }
     471             : 
     472       79542 :         for (i = 0; i < dst_file->blockmax; ++i) {
     473             :                 /* set a block with hash computed but without parity */
     474       56553 :                 block_state_set(file_block(dst_file, i), BLOCK_STATE_REP);
     475             : 
     476             :                 /* copy the hash */
     477       56553 :                 memcpy(file_block(dst_file, i)->hash, file_block(src_file, i)->hash, BLOCK_HASH_SIZE);
     478             :         }
     479             : 
     480       22989 :         file_flag_set(dst_file, FILE_IS_COPY);
     481       22989 : }
     482             : 
     483       46512 : const char* file_name(const struct snapraid_file* file)
     484             : {
     485       46512 :         const char* r = strrchr(file->sub, '/');
     486             : 
     487       46512 :         if (!r)
     488          78 :                 r = file->sub;
     489             :         else
     490       46434 :                 ++r;
     491       46512 :         return r;
     492             : }
     493             : 
     494     7090223 : unsigned file_block_size(struct snapraid_file* file, block_off_t file_pos, unsigned block_size)
     495             : {
     496             :         /* if it's the last block */
     497     7090223 :         if (file_pos + 1 == file->blockmax) {
     498             :                 unsigned block_remainder;
     499     2881901 :                 if (file->size == 0)
     500           0 :                         return 0;
     501     2881901 :                 block_remainder = file->size % block_size;
     502     2881901 :                 if (block_remainder == 0)
     503        3194 :                         block_remainder = block_size;
     504     2881901 :                 return block_remainder;
     505             :         }
     506             : 
     507     4208322 :         return block_size;
     508             : }
     509             : 
     510     5400944 : int file_block_is_last(struct snapraid_file* file, block_off_t file_pos)
     511             : {
     512     5400944 :         if (file_pos == 0 && file->blockmax == 0)
     513           0 :                 return 1;
     514             : 
     515     5400944 :         if (file_pos >= file->blockmax) {
     516             :                 /* LCOV_EXCL_START */
     517             :                 log_fatal(EINTERNAL, "Internal inconsistency: File block position over the max\n");
     518             :                 os_abort();
     519             :                 /* LCOV_EXCL_STOP */
     520             :         }
     521             : 
     522     5400944 :         return file_pos == file->blockmax - 1;
     523             : }
     524             : 
     525     1097278 : int file_inode_compare_to_arg(const void* void_arg, const void* void_data)
     526             : {
     527     1097278 :         const uint64_t* arg = void_arg;
     528     1097278 :         const struct snapraid_file* file = void_data;
     529             : 
     530     1097278 :         if (*arg < file->inode)
     531           0 :                 return -1;
     532     1097278 :         if (*arg > file->inode)
     533           0 :                 return 1;
     534     1097278 :         return 0;
     535             : }
     536             : 
     537           0 : int file_inode_compare(const void* void_a, const void* void_b)
     538             : {
     539           0 :         const struct snapraid_file* file_a = void_a;
     540           0 :         const struct snapraid_file* file_b = void_b;
     541             : 
     542           0 :         if (file_a->inode < file_b->inode)
     543           0 :                 return -1;
     544           0 :         if (file_a->inode > file_b->inode)
     545           0 :                 return 1;
     546           0 :         return 0;
     547             : }
     548             : 
     549      186982 : int file_path_compare(const void* void_a, const void* void_b)
     550             : {
     551      186982 :         const struct snapraid_file* file_a = void_a;
     552      186982 :         const struct snapraid_file* file_b = void_b;
     553             : 
     554      186982 :         return strcmp(file_a->sub, file_b->sub);
     555             : }
     556             : 
     557       50208 : int file_physical_compare(const void* void_a, const void* void_b)
     558             : {
     559       50208 :         const struct snapraid_file* file_a = void_a;
     560       50208 :         const struct snapraid_file* file_b = void_b;
     561             : 
     562       50208 :         if (file_a->physical < file_b->physical)
     563       31390 :                 return -1;
     564       18818 :         if (file_a->physical > file_b->physical)
     565       18816 :                 return 1;
     566           2 :         return 0;
     567             : }
     568             : 
     569      109693 : int file_path_compare_to_arg(const void* void_arg, const void* void_data)
     570             : {
     571      109693 :         const char* arg = void_arg;
     572      109693 :         const struct snapraid_file* file = void_data;
     573             : 
     574      109693 :         return strcmp(arg, file->sub);
     575             : }
     576             : 
     577       23256 : int file_name_compare(const void* void_a, const void* void_b)
     578             : {
     579       23256 :         const struct snapraid_file* file_a = void_a;
     580       23256 :         const struct snapraid_file* file_b = void_b;
     581       23256 :         const char* name_a = file_name(file_a);
     582       23256 :         const char* name_b = file_name(file_b);
     583             : 
     584       23256 :         return strcmp(name_a, name_b);
     585             : }
     586             : 
     587       23020 : int file_stamp_compare(const void* void_a, const void* void_b)
     588             : {
     589       23020 :         const struct snapraid_file* file_a = void_a;
     590       23020 :         const struct snapraid_file* file_b = void_b;
     591             : 
     592       23020 :         if (file_a->size < file_b->size)
     593           0 :                 return -1;
     594       23020 :         if (file_a->size > file_b->size)
     595           0 :                 return 1;
     596             : 
     597       23020 :         if (file_a->mtime_sec < file_b->mtime_sec)
     598           0 :                 return -1;
     599       23020 :         if (file_a->mtime_sec > file_b->mtime_sec)
     600           0 :                 return 1;
     601             : 
     602       23020 :         if (file_a->mtime_nsec < file_b->mtime_nsec)
     603           0 :                 return -1;
     604       23020 :         if (file_a->mtime_nsec > file_b->mtime_nsec)
     605           0 :                 return 1;
     606             : 
     607       23020 :         return 0;
     608             : }
     609             : 
     610       23256 : int file_namestamp_compare(const void* void_a, const void* void_b)
     611             : {
     612             :         int ret;
     613             : 
     614       23256 :         ret = file_name_compare(void_a, void_b);
     615       23256 :         if (ret != 0)
     616         236 :                 return ret;
     617             : 
     618       23020 :         return file_stamp_compare(void_a, void_b);
     619             : }
     620             : 
     621           0 : int file_pathstamp_compare(const void* void_a, const void* void_b)
     622             : {
     623             :         int ret;
     624             : 
     625           0 :         ret = file_path_compare(void_a, void_b);
     626           0 :         if (ret != 0)
     627           0 :                 return ret;
     628             : 
     629           0 :         return file_stamp_compare(void_a, void_b);
     630             : }
     631             : 
     632     3969602 : struct snapraid_extent* extent_alloc(block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos, block_off_t count)
     633             : {
     634             :         struct snapraid_extent* extent;
     635             : 
     636     3969602 :         if (count == 0) {
     637             :                 /* LCOV_EXCL_START */
     638             :                 log_fatal(EINTERNAL, "Internal inconsistency: Allocating empty extent for file '%s' at position '%u/%u'\n", file->sub, file_pos, file->blockmax);
     639             :                 os_abort();
     640             :                 /* LCOV_EXCL_STOP */
     641             :         }
     642     3969602 :         if (file_pos + count > file->blockmax) {
     643             :                 /* LCOV_EXCL_START */
     644             :                 log_fatal(EINTERNAL, "Internal inconsistency: Allocating overflowing extent for file '%s' at position '%u:%u/%u'\n", file->sub, file_pos, count, file->blockmax);
     645             :                 os_abort();
     646             :                 /* LCOV_EXCL_STOP */
     647             :         }
     648             : 
     649     3969602 :         extent = malloc_nofail(sizeof(struct snapraid_extent));
     650     3969602 :         extent->parity_pos = parity_pos;
     651     3969602 :         extent->file = file;
     652     3969602 :         extent->file_pos = file_pos;
     653     3969602 :         extent->count = count;
     654             : 
     655     3969602 :         return extent;
     656             : }
     657             : 
     658     3923667 : void extent_free(struct snapraid_extent* extent)
     659             : {
     660     3923667 :         free(extent);
     661     3923667 : }
     662             : 
     663    47203078 : int extent_parity_compare(const void* void_a, const void* void_b)
     664             : {
     665    47203078 :         const struct snapraid_extent* arg_a = void_a;
     666    47203078 :         const struct snapraid_extent* arg_b = void_b;
     667             : 
     668    47203078 :         if (arg_a->parity_pos < arg_b->parity_pos)
     669      965541 :                 return -1;
     670    46237537 :         if (arg_a->parity_pos > arg_b->parity_pos)
     671    46199345 :                 return 1;
     672             : 
     673       38192 :         return 0;
     674             : }
     675             : 
     676    47536874 : int extent_file_compare(const void* void_a, const void* void_b)
     677             : {
     678    47536874 :         const struct snapraid_extent* arg_a = void_a;
     679    47536874 :         const struct snapraid_extent* arg_b = void_b;
     680             : 
     681    47536874 :         if (arg_a->file < arg_b->file)
     682     1061226 :                 return -1;
     683    46475648 :         if (arg_a->file > arg_b->file)
     684    46404307 :                 return 1;
     685             : 
     686       71341 :         if (arg_a->file_pos < arg_b->file_pos)
     687         194 :                 return -1;
     688       71147 :         if (arg_a->file_pos > arg_b->file_pos)
     689       32955 :                 return 1;
     690             : 
     691       38192 :         return 0;
     692             : }
     693             : 
     694      126838 : struct snapraid_link* link_alloc(const char* sub, const char* linkto, unsigned link_flag)
     695             : {
     696             :         struct snapraid_link* slink;
     697             : 
     698      126838 :         slink = malloc_nofail(sizeof(struct snapraid_link));
     699      126838 :         slink->sub = strdup_nofail(sub);
     700      126838 :         slink->linkto = strdup_nofail(linkto);
     701      126838 :         slink->flag = link_flag;
     702             : 
     703      126838 :         return slink;
     704             : }
     705             : 
     706      125242 : void link_free(struct snapraid_link* slink)
     707             : {
     708      125242 :         free(slink->sub);
     709      125242 :         free(slink->linkto);
     710      125242 :         free(slink);
     711      125242 : }
     712             : 
     713       37307 : int link_name_compare_to_arg(const void* void_arg, const void* void_data)
     714             : {
     715       37307 :         const char* arg = void_arg;
     716       37307 :         const struct snapraid_link* slink = void_data;
     717             : 
     718       37307 :         return strcmp(arg, slink->sub);
     719             : }
     720             : 
     721        3337 : int link_alpha_compare(const void* void_a, const void* void_b)
     722             : {
     723        3337 :         const struct snapraid_link* slink_a = void_a;
     724        3337 :         const struct snapraid_link* slink_b = void_b;
     725             : 
     726        3337 :         return strcmp(slink_a->sub, slink_b->sub);
     727             : }
     728             : 
     729         872 : struct snapraid_dir* dir_alloc(const char* sub)
     730             : {
     731             :         struct snapraid_dir* dir;
     732             : 
     733         872 :         dir = malloc_nofail(sizeof(struct snapraid_dir));
     734         872 :         dir->sub = strdup_nofail(sub);
     735         872 :         dir->flag = 0;
     736             : 
     737         872 :         return dir;
     738             : }
     739             : 
     740         864 : void dir_free(struct snapraid_dir* dir)
     741             : {
     742         864 :         free(dir->sub);
     743         864 :         free(dir);
     744         864 : }
     745             : 
     746         285 : int dir_name_compare(const void* void_arg, const void* void_data)
     747             : {
     748         285 :         const char* arg = void_arg;
     749         285 :         const struct snapraid_dir* dir = void_data;
     750             : 
     751         285 :         return strcmp(arg, dir->sub);
     752             : }
     753             : 
     754        1771 : struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev, const char* uuid, int skip_access)
     755             : {
     756             :         struct snapraid_disk* disk;
     757             :         unsigned i;
     758             : 
     759        1771 :         disk = malloc_nofail(sizeof(struct snapraid_disk));
     760        1771 :         pathcpy(disk->name, sizeof(disk->name), name);
     761        1771 :         pathimport(disk->dir, sizeof(disk->dir), dir);
     762        1771 :         pathcpy(disk->uuid, sizeof(disk->uuid), uuid);
     763             : 
     764             :         /* ensure that the dir terminate with "/" if it isn't empty */
     765        1771 :         pathslash(disk->dir, sizeof(disk->dir));
     766             : 
     767             : #if HAVE_THREAD
     768        1771 :         disk->single_thread = 0;
     769        1771 :         thread_mutex_init(&disk->fs_mutex);
     770             : #endif
     771             : 
     772        1771 :         disk->smartctl[0] = 0;
     773        8855 :         for (i = 0; i < SMART_IGNORE_MAX; ++i)
     774        7084 :                 disk->smartignore[i] = 0;
     775        1771 :         disk->fstype[0] = 0;
     776        1771 :         disk->fslabel[0] = 0;
     777        1771 :         disk->device = dev;
     778        1771 :         disk->tick = 0;
     779        1771 :         disk->cached_blocks = 0;
     780        1771 :         disk->progress_file = 0;
     781        1771 :         disk->total_blocks = 0;
     782        1771 :         disk->free_blocks = 0;
     783        1771 :         disk->first_free_block = 0;
     784        1771 :         disk->has_volatile_inodes = 0;
     785        1771 :         disk->has_volatile_hardlinks = 0;
     786        1771 :         disk->has_unreliable_physical = 0;
     787        1771 :         disk->has_different_uuid = 0;
     788        1771 :         disk->has_unsupported_uuid = *uuid == 0; /* empty UUID means unsupported */
     789        1771 :         disk->had_empty_uuid = 0;
     790        1771 :         disk->mapping_idx = -1;
     791        1771 :         disk->skip_access = skip_access;
     792        1771 :         tommy_list_init(&disk->filelist);
     793        1771 :         tommy_list_init(&disk->deletedlist);
     794        1771 :         tommy_hashdyn_init(&disk->inodeset);
     795        1771 :         tommy_hashdyn_init(&disk->pathset);
     796        1771 :         tommy_hashdyn_init(&disk->stampset);
     797        1771 :         tommy_list_init(&disk->linklist);
     798        1771 :         tommy_hashdyn_init(&disk->linkset);
     799        1771 :         tommy_list_init(&disk->dirlist);
     800        1771 :         tommy_hashdyn_init(&disk->dirset);
     801        1771 :         tommy_tree_init(&disk->fs_parity, extent_parity_compare);
     802        1771 :         tommy_tree_init(&disk->fs_file, extent_file_compare);
     803        1771 :         disk->fs_last = 0;
     804             : 
     805        1771 :         return disk;
     806             : }
     807             : 
     808        1711 : void disk_free(struct snapraid_disk* disk)
     809             : {
     810        1711 :         tommy_list_foreach(&disk->filelist, (tommy_foreach_func*)file_free);
     811        1711 :         tommy_list_foreach(&disk->deletedlist, (tommy_foreach_func*)file_free);
     812        1711 :         tommy_tree_foreach(&disk->fs_file, (tommy_foreach_func*)extent_free);
     813        1711 :         tommy_hashdyn_done(&disk->inodeset);
     814        1711 :         tommy_hashdyn_done(&disk->pathset);
     815        1711 :         tommy_hashdyn_done(&disk->stampset);
     816        1711 :         tommy_list_foreach(&disk->linklist, (tommy_foreach_func*)link_free);
     817        1711 :         tommy_hashdyn_done(&disk->linkset);
     818        1711 :         tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free);
     819        1711 :         tommy_hashdyn_done(&disk->dirset);
     820             : 
     821             : #if HAVE_THREAD
     822        1711 :         thread_mutex_destroy(&disk->fs_mutex);
     823             : #endif
     824             : 
     825        1711 :         free(disk);
     826        1711 : }
     827             : 
     828          12 : struct snapraid_extra* extra_alloc(const char* name, const char* dir, uint64_t dev, const char* uuid)
     829             : {
     830             :         struct snapraid_extra* extra;
     831             :         unsigned i;
     832             : 
     833          12 :         extra = malloc_nofail(sizeof(struct snapraid_extra));
     834          12 :         pathcpy(extra->name, sizeof(extra->name), name);
     835          12 :         pathimport(extra->dir, sizeof(extra->dir), dir);
     836          12 :         pathcpy(extra->uuid, sizeof(extra->uuid), uuid);
     837             : 
     838             :         /* ensure that the dir terminate with "/" if it isn't empty */
     839          12 :         pathslash(extra->dir, sizeof(extra->dir));
     840             : 
     841          12 :         extra->smartctl[0] = 0;
     842          60 :         for (i = 0; i < SMART_IGNORE_MAX; ++i)
     843          48 :                 extra->smartignore[i] = 0;
     844          12 :         extra->fstype[0] = 0;
     845          12 :         extra->fslabel[0] = 0;
     846          12 :         extra->device = dev;
     847             : 
     848          12 :         return extra;
     849             : }
     850             : 
     851          12 : void extra_free(struct snapraid_extra* extra)
     852             : {
     853          12 :         free(extra);
     854          12 : }
     855             : 
     856             : 
     857    66940333 : static inline void fs_lock(struct snapraid_disk* disk)
     858             : {
     859             : #if HAVE_THREAD
     860    66940333 :         if (!disk->single_thread)
     861    45907523 :                 thread_mutex_lock(&disk->fs_mutex);
     862             : #else
     863             :         (void)disk;
     864             : #endif
     865    66940333 : }
     866             : 
     867    66940333 : static inline void fs_unlock(struct snapraid_disk* disk)
     868             : {
     869             : #if HAVE_THREAD
     870    66940333 :         if (!disk->single_thread)
     871    45907523 :                 thread_mutex_unlock(&disk->fs_mutex);
     872             : #else
     873             :         (void)disk;
     874             : #endif
     875    66940333 : }
     876             : 
     877             : struct extent_disk_empty {
     878             :         block_off_t blockmax;
     879             : };
     880             : 
     881             : /**
     882             :  * Compare the extent if inside the specified blockmax.
     883             :  */
     884          12 : static int extent_disk_empty_compare_unlock(const void* void_a, const void* void_b)
     885             : {
     886          12 :         const struct extent_disk_empty* arg_a = void_a;
     887          12 :         const struct snapraid_extent* arg_b = void_b;
     888             : 
     889             :         /* if the block is inside the specified blockmax, it's found */
     890          12 :         if (arg_a->blockmax > arg_b->parity_pos)
     891           7 :                 return 0;
     892             : 
     893             :         /* otherwise search for a smaller one */
     894           5 :         return -1;
     895             : }
     896             : 
     897         966 : int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
     898             : {
     899         966 :         struct extent_disk_empty arg = { blockmax };
     900             : 
     901             :         /* if there is an element, it's not empty */
     902             :         /* even if links and dirs have no block allocation */
     903         966 :         if (!tommy_list_empty(&disk->filelist))
     904         867 :                 return 0;
     905          99 :         if (!tommy_list_empty(&disk->linklist))
     906           0 :                 return 0;
     907          99 :         if (!tommy_list_empty(&disk->dirlist))
     908           0 :                 return 0;
     909             : 
     910          99 :         fs_lock(disk);
     911             : 
     912             :         /* search for any extent inside blockmax */
     913          99 :         if (tommy_tree_search_compare(&disk->fs_parity, extent_disk_empty_compare_unlock, &arg) != 0) {
     914           7 :                 fs_unlock(disk);
     915           7 :                 return 0;
     916             :         }
     917             : 
     918             :         /* finally, it's empty */
     919          92 :         fs_unlock(disk);
     920          92 :         return 1;
     921             : }
     922             : 
     923             : struct extent_disk_size {
     924             :         block_off_t size;
     925             : };
     926             : 
     927             : /**
     928             :  * Compare the extent by highest parity position.
     929             :  *
     930             :  * The maximum parity position is stored as size.
     931             :  */
     932       54897 : static int extent_disk_size_compare_unlock(const void* void_a, const void* void_b)
     933             : {
     934       54897 :         struct extent_disk_size* arg_a = (void*)void_a;
     935       54897 :         const struct snapraid_extent* arg_b = void_b;
     936             : 
     937             :         /* get the maximum size */
     938       54897 :         if (arg_a->size < arg_b->parity_pos + arg_b->count)
     939       54897 :                 arg_a->size = arg_b->parity_pos + arg_b->count;
     940             : 
     941             :         /* search always for a bigger one */
     942       54897 :         return 1;
     943             : }
     944             : 
     945        4693 : block_off_t fs_size(struct snapraid_disk* disk)
     946             : {
     947        4693 :         struct extent_disk_size arg = { 0 };
     948             : 
     949        4693 :         fs_lock(disk);
     950             : 
     951        4693 :         tommy_tree_search_compare(&disk->fs_parity, extent_disk_size_compare_unlock, &arg);
     952             : 
     953        4693 :         fs_unlock(disk);
     954             : 
     955        4693 :         return arg.size;
     956             : }
     957             : 
     958             : struct extent_check {
     959             :         const struct snapraid_extent* prev;
     960             :         int result;
     961             : };
     962             : 
     963     7063233 : static void extent_parity_check_foreach_unlock(void* void_arg, void* void_obj)
     964             : {
     965     7063233 :         struct extent_check* arg = void_arg;
     966     7063233 :         const struct snapraid_extent* obj = void_obj;
     967     7063233 :         const struct snapraid_extent* prev = arg->prev;
     968             : 
     969             :         /* set the next previous block */
     970     7063233 :         arg->prev = obj;
     971             : 
     972             :         /* stop reporting if too many errors */
     973     7063233 :         if (arg->result > 100) {
     974             :                 /* LCOV_EXCL_START */
     975             :                 return;
     976             :                 /* LCOV_EXCL_STOP */
     977             :         }
     978             : 
     979     7063233 :         if (obj->count == 0) {
     980             :                 /* LCOV_EXCL_START */
     981             :                 log_fatal(EINTERNAL, "Internal inconsistency: Parity count zero for file '%s' at '%u'\n",
     982             :                         obj->file->sub, obj->parity_pos);
     983             :                 ++arg->result;
     984             :                 return;
     985             :                 /* LCOV_EXCL_STOP */
     986             :         }
     987             : 
     988             :         /* check only if there is a previous block */
     989     7063233 :         if (!prev)
     990        2951 :                 return;
     991             : 
     992             :         /* check the order */
     993     7060282 :         if (prev->parity_pos >= obj->parity_pos) {
     994             :                 /* LCOV_EXCL_START */
     995             :                 log_fatal(EINTERNAL, "Internal inconsistency: Parity order for files '%s' at '%u:%u' and '%s' at '%u:%u'\n",
     996             :                         prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count);
     997             :                 ++arg->result;
     998             :                 return;
     999             :                 /* LCOV_EXCL_STOP */
    1000             :         }
    1001             : 
    1002             :         /* check that the extents don't overlap */
    1003     7060282 :         if (prev->parity_pos + prev->count > obj->parity_pos) {
    1004             :                 /* LCOV_EXCL_START */
    1005             :                 log_fatal(EINTERNAL, "Internal inconsistency: Parity overlap for files '%s' at '%u:%u' and '%s' at '%u:%u'\n",
    1006             :                         prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count);
    1007             :                 ++arg->result;
    1008             :                 return;
    1009             :                 /* LCOV_EXCL_STOP */
    1010             :         }
    1011             : }
    1012             : 
    1013     7063233 : static void extent_file_check_foreach_unlock(void* void_arg, void* void_obj)
    1014             : {
    1015     7063233 :         struct extent_check* arg = void_arg;
    1016     7063233 :         const struct snapraid_extent* obj = void_obj;
    1017     7063233 :         const struct snapraid_extent* prev = arg->prev;
    1018             : 
    1019             :         /* set the next previous block */
    1020     7063233 :         arg->prev = obj;
    1021             : 
    1022             :         /* stop reporting if too many errors */
    1023     7063233 :         if (arg->result > 100) {
    1024             :                 /* LCOV_EXCL_START */
    1025             :                 return;
    1026             :                 /* LCOV_EXCL_STOP */
    1027             :         }
    1028             : 
    1029     7063233 :         if (obj->count == 0) {
    1030             :                 /* LCOV_EXCL_START */
    1031             :                 log_fatal(EINTERNAL, "Internal inconsistency: File count zero for file '%s' at '%u'\n",
    1032             :                         obj->file->sub, obj->file_pos);
    1033             :                 ++arg->result;
    1034             :                 return;
    1035             :                 /* LCOV_EXCL_STOP */
    1036             :         }
    1037             : 
    1038             :         /* note that for deleted files, some extents may be missing */
    1039             : 
    1040             :         /* if the files are different */
    1041     7063233 :         if (!prev || prev->file != obj->file) {
    1042     7013948 :                 if (prev != 0) {
    1043     7010997 :                         if (file_flag_has(prev->file, FILE_IS_DELETED)) {
    1044             :                                 /* check that the extent doesn't overflow the file */
    1045       58267 :                                 if (prev->file_pos + prev->count > prev->file->blockmax) {
    1046             :                                         /* LCOV_EXCL_START */
    1047             :                                         log_fatal(EINTERNAL, "Internal inconsistency: Delete end for file '%s' at '%u:%u' overflowing size '%u'\n",
    1048             :                                                 prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax);
    1049             :                                         ++arg->result;
    1050             :                                         return;
    1051             :                                         /* LCOV_EXCL_STOP */
    1052             :                                 }
    1053             :                         } else {
    1054             :                                 /* check that the extent ends the file */
    1055     6952730 :                                 if (prev->file_pos + prev->count != prev->file->blockmax) {
    1056             :                                         /* LCOV_EXCL_START */
    1057             :                                         log_fatal(EINTERNAL, "Internal inconsistency: File end for file '%s' at '%u:%u' instead of size '%u'\n",
    1058             :                                                 prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax);
    1059             :                                         ++arg->result;
    1060             :                                         return;
    1061             :                                         /* LCOV_EXCL_STOP */
    1062             :                                 }
    1063             :                         }
    1064             :                 }
    1065             : 
    1066     7013948 :                 if (file_flag_has(obj->file, FILE_IS_DELETED)) {
    1067             :                         /* check that the extent doesn't overflow the file */
    1068       58344 :                         if (obj->file_pos + obj->count > obj->file->blockmax) {
    1069             :                                 /* LCOV_EXCL_START */
    1070             :                                 log_fatal(EINTERNAL, "Internal inconsistency: Delete start for file '%s' at '%u:%u' overflowing size '%u'\n",
    1071             :                                         obj->file->sub, obj->file_pos, obj->count, obj->file->blockmax);
    1072             :                                 ++arg->result;
    1073             :                                 return;
    1074             :                                 /* LCOV_EXCL_STOP */
    1075             :                         }
    1076             :                 } else {
    1077             :                         /* check that the extent starts the file */
    1078     6955604 :                         if (obj->file_pos != 0) {
    1079             :                                 /* LCOV_EXCL_START */
    1080             :                                 log_fatal(EINTERNAL, "Internal inconsistency: File start for file '%s' at '%u:%u'\n",
    1081             :                                         obj->file->sub, obj->file_pos, obj->count);
    1082             :                                 ++arg->result;
    1083             :                                 return;
    1084             :                                 /* LCOV_EXCL_STOP */
    1085             :                         }
    1086             :                 }
    1087             :         } else {
    1088             :                 /* check the order */
    1089       49285 :                 if (prev->file_pos >= obj->file_pos) {
    1090             :                         /* LCOV_EXCL_START */
    1091             :                         log_fatal(EINTERNAL, "Internal inconsistency: File order for file '%s' at '%u:%u' and at '%u:%u'\n",
    1092             :                                 prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
    1093             :                         ++arg->result;
    1094             :                         return;
    1095             :                         /* LCOV_EXCL_STOP */
    1096             :                 }
    1097             : 
    1098       49285 :                 if (file_flag_has(obj->file, FILE_IS_DELETED)) {
    1099             :                         /* check that the extents don't overlap */
    1100          27 :                         if (prev->file_pos + prev->count > obj->file_pos) {
    1101             :                                 /* LCOV_EXCL_START */
    1102             :                                 log_fatal(EINTERNAL, "Internal inconsistency: Delete sequence for file '%s' at '%u:%u' and at '%u:%u'\n",
    1103             :                                         prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
    1104             :                                 ++arg->result;
    1105             :                                 return;
    1106             :                                 /* LCOV_EXCL_STOP */
    1107             :                         }
    1108             :                 } else {
    1109             :                         /* check that the extents are sequential */
    1110       49258 :                         if (prev->file_pos + prev->count != obj->file_pos) {
    1111             :                                 /* LCOV_EXCL_START */
    1112             :                                 log_fatal(EINTERNAL, "Internal inconsistency: File sequence for file '%s' at '%u:%u' and at '%u:%u'\n",
    1113             :                                         prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
    1114             :                                 ++arg->result;
    1115             :                                 return;
    1116             :                                 /* LCOV_EXCL_STOP */
    1117             :                         }
    1118             :                 }
    1119             :         }
    1120             : }
    1121             : 
    1122        3216 : int fs_check(struct snapraid_disk* disk)
    1123             : {
    1124             :         struct extent_check arg;
    1125             : 
    1126             :         /* error count starts from 0 */
    1127        3216 :         arg.result = 0;
    1128             : 
    1129        3216 :         fs_lock(disk);
    1130             : 
    1131             :         /* check parity sequence */
    1132        3216 :         arg.prev = 0;
    1133        3216 :         tommy_tree_foreach_arg(&disk->fs_parity, extent_parity_check_foreach_unlock, &arg);
    1134             : 
    1135             :         /* check file sequence */
    1136        3216 :         arg.prev = 0;
    1137        3216 :         tommy_tree_foreach_arg(&disk->fs_file, extent_file_check_foreach_unlock, &arg);
    1138             : 
    1139        3216 :         fs_unlock(disk);
    1140             : 
    1141        3216 :         if (arg.result != 0)
    1142           0 :                 return -1;
    1143             : 
    1144        3216 :         return 0;
    1145             : }
    1146             : 
    1147             : struct extent_parity_inside {
    1148             :         block_off_t parity_pos;
    1149             : };
    1150             : 
    1151             : /**
    1152             :  * Compare the extent if containing the specified parity position.
    1153             :  */
    1154   165823214 : static int extent_parity_inside_compare_unlock(const void* void_a, const void* void_b)
    1155             : {
    1156   165823214 :         const struct extent_parity_inside* arg_a = void_a;
    1157   165823214 :         const struct snapraid_extent* arg_b = void_b;
    1158             : 
    1159   165823214 :         if (arg_a->parity_pos < arg_b->parity_pos)
    1160    59877991 :                 return -1;
    1161   105945223 :         if (arg_a->parity_pos >= arg_b->parity_pos + arg_b->count)
    1162    92969131 :                 return 1;
    1163             : 
    1164    12976092 :         return 0;
    1165             : }
    1166             : 
    1167             : /**
    1168             :  * Search the extent at the specified parity position.
    1169             :  * The search is optimized for sequential accesses.
    1170             :  * \return If not found return 0
    1171             :  */
    1172    51736178 : static struct snapraid_extent* fs_par2extent_get_unlock(struct snapraid_disk* disk, struct snapraid_extent** fs_last, block_off_t parity_pos)
    1173             : {
    1174             :         struct snapraid_extent* extent;
    1175             : 
    1176             :         /* check if the last accessed extent matches */
    1177    51736178 :         if (*fs_last
    1178    51701294 :                 && parity_pos >= (*fs_last)->parity_pos
    1179    51014685 :                 && parity_pos < (*fs_last)->parity_pos + (*fs_last)->count
    1180             :         ) {
    1181    36630889 :                 extent = *fs_last;
    1182             :         } else {
    1183    15105289 :                 struct extent_parity_inside arg = { parity_pos };
    1184    15105289 :                 extent = tommy_tree_search_compare(&disk->fs_parity, extent_parity_inside_compare_unlock, &arg);
    1185             :         }
    1186             : 
    1187    51736178 :         if (!extent)
    1188     2129197 :                 return 0;
    1189             : 
    1190             :         /* store the last accessed extent */
    1191    49606981 :         *fs_last = extent;
    1192             : 
    1193    49606981 :         return extent;
    1194             : }
    1195             : 
    1196             : struct extent_file_inside {
    1197             :         struct snapraid_file* file;
    1198             :         block_off_t file_pos;
    1199             : };
    1200             : 
    1201             : /**
    1202             :  * Compare the extent if containing the specified file position.
    1203             :  */
    1204    23526371 : static int extent_file_inside_compare_unlock(const void* void_a, const void* void_b)
    1205             : {
    1206    23526371 :         const struct extent_file_inside* arg_a = void_a;
    1207    23526371 :         const struct snapraid_extent* arg_b = void_b;
    1208             : 
    1209    23526371 :         if (arg_a->file < arg_b->file)
    1210     9893231 :                 return -1;
    1211    13633140 :         if (arg_a->file > arg_b->file)
    1212    11392783 :                 return 1;
    1213             : 
    1214     2240357 :         if (arg_a->file_pos < arg_b->file_pos)
    1215        8217 :                 return -1;
    1216     2232140 :         if (arg_a->file_pos >= arg_b->file_pos + arg_b->count)
    1217        9331 :                 return 1;
    1218             : 
    1219     2222809 :         return 0;
    1220             : }
    1221             : 
    1222             : /**
    1223             :  * Search the extent at the specified file position.
    1224             :  * The search is optimized for sequential accesses.
    1225             :  * \return If not found return 0
    1226             :  */
    1227    11255059 : static struct snapraid_extent* fs_file2extent_get_unlock(struct snapraid_disk* disk, struct snapraid_extent** fs_last, struct snapraid_file* file, block_off_t file_pos)
    1228             : {
    1229             :         struct snapraid_extent* extent;
    1230             : 
    1231             :         /* check if the last accessed extent matches */
    1232    11255059 :         if (*fs_last
    1233    11232768 :                 && file == (*fs_last)->file
    1234     9047495 :                 && file_pos >= (*fs_last)->file_pos
    1235     9047495 :                 && file_pos < (*fs_last)->file_pos + (*fs_last)->count
    1236             :         ) {
    1237     9032242 :                 extent = *fs_last;
    1238             :         } else {
    1239     2222817 :                 struct extent_file_inside arg = { file, file_pos };
    1240     2222817 :                 extent = tommy_tree_search_compare(&disk->fs_file, extent_file_inside_compare_unlock, &arg);
    1241             :         }
    1242             : 
    1243    11255059 :         if (!extent)
    1244           8 :                 return 0;
    1245             : 
    1246             :         /* store the last accessed extent */
    1247    11255051 :         *fs_last = extent;
    1248             : 
    1249    11255051 :         return extent;
    1250             : }
    1251             : 
    1252    51632348 : struct snapraid_file* fs_par2file_find(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos)
    1253             : {
    1254             :         struct snapraid_extent* extent;
    1255             :         struct snapraid_file* file;
    1256             : 
    1257    51632348 :         fs_lock(disk);
    1258             : 
    1259    51632348 :         extent = fs_par2extent_get_unlock(disk, &disk->fs_last, parity_pos);
    1260             : 
    1261    51632348 :         if (!extent) {
    1262     2129197 :                 fs_unlock(disk);
    1263     2129197 :                 return 0;
    1264             :         }
    1265             : 
    1266    49503151 :         if (file_pos)
    1267    49293033 :                 *file_pos = extent->file_pos + (parity_pos - extent->parity_pos);
    1268             : 
    1269    49503151 :         file = extent->file;
    1270             : 
    1271    49503151 :         fs_unlock(disk);
    1272    49503151 :         return file;
    1273             : }
    1274             : 
    1275     5433531 : block_off_t fs_file2par_find(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
    1276             : {
    1277             :         struct snapraid_extent* extent;
    1278             :         block_off_t ret;
    1279             : 
    1280     5433531 :         fs_lock(disk);
    1281             : 
    1282     5433531 :         extent = fs_file2extent_get_unlock(disk, &disk->fs_last, file, file_pos);
    1283     5433531 :         if (!extent) {
    1284           8 :                 fs_unlock(disk);
    1285           8 :                 return POS_NULL;
    1286             :         }
    1287             : 
    1288     5433523 :         ret = extent->parity_pos + (file_pos - extent->file_pos);
    1289             : 
    1290     5433523 :         fs_unlock(disk);
    1291     5433523 :         return ret;
    1292             : }
    1293             : 
    1294     9762616 : void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos)
    1295             : {
    1296             :         struct snapraid_extent* extent;
    1297             :         struct snapraid_extent* parity_extent;
    1298             :         struct snapraid_extent* file_extent;
    1299             : 
    1300     9762616 :         fs_lock(disk);
    1301             : 
    1302     9762616 :         if (file_pos > 0) {
    1303             :                 /* search an existing extent for the previous file_pos */
    1304     5821528 :                 extent = fs_file2extent_get_unlock(disk, &disk->fs_last, file, file_pos - 1);
    1305             : 
    1306     5821528 :                 if (extent != 0 && parity_pos == extent->parity_pos + extent->count) {
    1307             :                         /* ensure that we are extending the extent at the end */
    1308     5793016 :                         if (file_pos != extent->file_pos + extent->count) {
    1309             :                                 /* LCOV_EXCL_START */
    1310             :                                 log_fatal(EINTERNAL, "Internal inconsistency: Allocating file '%s' at position '%u/%u' in the middle of extent '%u:%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, extent->file_pos, extent->count, disk->name);
    1311             :                                 os_abort();
    1312             :                                 /* LCOV_EXCL_STOP */
    1313             :                         }
    1314             : 
    1315             :                         /* extend the existing extent */
    1316     5793016 :                         ++extent->count;
    1317             : 
    1318     5793016 :                         fs_unlock(disk);
    1319     5793016 :                         return;
    1320             :                 }
    1321             :         }
    1322             : 
    1323             :         /* a extent doesn't exist, and we have to create a new one */
    1324     3969600 :         extent = extent_alloc(parity_pos, file, file_pos, 1);
    1325             : 
    1326             :         /* insert the extent in the trees */
    1327     3969600 :         parity_extent = tommy_tree_insert(&disk->fs_parity, &extent->parity_node, extent);
    1328     3969600 :         file_extent = tommy_tree_insert(&disk->fs_file, &extent->file_node, extent);
    1329             : 
    1330     3969600 :         if (parity_extent != extent || file_extent != extent) {
    1331             :                 /* LCOV_EXCL_START */
    1332             :                 log_fatal(EINTERNAL, "Internal inconsistency: Allocating file '%s' at position '%u/%u' for existing extent '%u:%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, extent->file_pos, extent->count, disk->name);
    1333             :                 os_abort();
    1334             :                 /* LCOV_EXCL_STOP */
    1335             :         }
    1336             : 
    1337             :         /* store the last accessed extent */
    1338     3969600 :         disk->fs_last = extent;
    1339             : 
    1340     3969600 :         fs_unlock(disk);
    1341             : }
    1342             : 
    1343      103830 : void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
    1344             : {
    1345             :         struct snapraid_extent* extent;
    1346             :         struct snapraid_extent* second_extent;
    1347             :         struct snapraid_extent* parity_extent;
    1348             :         struct snapraid_extent* file_extent;
    1349             :         block_off_t first_count, second_count;
    1350             : 
    1351      103830 :         fs_lock(disk);
    1352             : 
    1353      103830 :         extent = fs_par2extent_get_unlock(disk, &disk->fs_last, parity_pos);
    1354      103830 :         if (!extent) {
    1355             :                 /* LCOV_EXCL_START */
    1356             :                 log_fatal(EINTERNAL, "Internal inconsistency: Deallocating parity position '%u' for not existing extent in disk '%s'\n", parity_pos, disk->name);
    1357             :                 os_abort();
    1358             :                 /* LCOV_EXCL_STOP */
    1359             :         }
    1360             : 
    1361             :         /* if it's the only block of the extent, delete it */
    1362      103830 :         if (extent->count == 1) {
    1363             :                 /* remove from the trees */
    1364       38192 :                 tommy_tree_remove(&disk->fs_parity, extent);
    1365       38192 :                 tommy_tree_remove(&disk->fs_file, extent);
    1366             : 
    1367             :                 /* deallocate */
    1368       38192 :                 extent_free(extent);
    1369             : 
    1370             :                 /* clear the last accessed extent */
    1371       38192 :                 disk->fs_last = 0;
    1372             : 
    1373       38192 :                 fs_unlock(disk);
    1374       38192 :                 return;
    1375             :         }
    1376             : 
    1377             :         /* if it's at the start of the extent, shrink the extent */
    1378       65638 :         if (parity_pos == extent->parity_pos) {
    1379       65635 :                 ++extent->parity_pos;
    1380       65635 :                 ++extent->file_pos;
    1381       65635 :                 --extent->count;
    1382             : 
    1383       65635 :                 fs_unlock(disk);
    1384       65635 :                 return;
    1385             :         }
    1386             : 
    1387             :         /* if it's at the end of the extent, shrink the extent */
    1388           3 :         if (parity_pos == extent->parity_pos + extent->count - 1) {
    1389           1 :                 --extent->count;
    1390             : 
    1391           1 :                 fs_unlock(disk);
    1392           1 :                 return;
    1393             :         }
    1394             : 
    1395             :         /* otherwise it's in the middle */
    1396           2 :         first_count = parity_pos - extent->parity_pos;
    1397           2 :         second_count = extent->count - first_count - 1;
    1398             : 
    1399             :         /* adjust the first extent */
    1400           2 :         extent->count = first_count;
    1401             : 
    1402             :         /* allocate the second extent */
    1403           2 :         second_extent = extent_alloc(extent->parity_pos + first_count + 1, extent->file, extent->file_pos + first_count + 1, second_count);
    1404             : 
    1405             :         /* insert the extent in the trees */
    1406           2 :         parity_extent = tommy_tree_insert(&disk->fs_parity, &second_extent->parity_node, second_extent);
    1407           2 :         file_extent = tommy_tree_insert(&disk->fs_file, &second_extent->file_node, second_extent);
    1408             : 
    1409           2 :         if (parity_extent != second_extent || file_extent != second_extent) {
    1410             :                 /* LCOV_EXCL_START */
    1411             :                 log_fatal(EINTERNAL, "Internal inconsistency: Deallocating parity position '%u' for splitting extent '%u:%u' in disk '%s'\n", parity_pos, second_extent->file_pos, second_extent->count, disk->name);
    1412             :                 os_abort();
    1413             :                 /* LCOV_EXCL_STOP */
    1414             :         }
    1415             : 
    1416             :         /* store the last accessed extent */
    1417           2 :         disk->fs_last = second_extent;
    1418             : 
    1419           2 :         fs_unlock(disk);
    1420             : }
    1421             : 
    1422    58245299 : struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos)
    1423             : {
    1424    58245299 :         if (file_pos >= file->blockmax) {
    1425             :                 /* LCOV_EXCL_START */
    1426             :                 log_fatal(EINTERNAL, "Internal inconsistency: Dereferencing file '%s' at position '%u/%u'\n", file->sub, file_pos, file->blockmax);
    1427             :                 os_abort();
    1428             :                 /* LCOV_EXCL_STOP */
    1429             :         }
    1430             : 
    1431    58245299 :         return file_block(file, file_pos);
    1432             : }
    1433             : 
    1434    39756942 : struct snapraid_block* fs_par2block_find(struct snapraid_disk* disk, block_off_t parity_pos)
    1435             : {
    1436             :         struct snapraid_file* file;
    1437             :         block_off_t file_pos;
    1438             : 
    1439    39756942 :         file = fs_par2file_find(disk, parity_pos, &file_pos);
    1440    39756942 :         if (file == 0)
    1441     2129197 :                 return BLOCK_NULL;
    1442             : 
    1443    37627745 :         return fs_file2block_get(file, file_pos);
    1444             : }
    1445             : 
    1446        1675 : struct snapraid_map* map_alloc(const char* name, unsigned position, block_off_t total_blocks, block_off_t free_blocks, const char* uuid)
    1447             : {
    1448             :         struct snapraid_map* map;
    1449             : 
    1450        1675 :         map = malloc_nofail(sizeof(struct snapraid_map));
    1451        1675 :         pathcpy(map->name, sizeof(map->name), name);
    1452        1675 :         map->position = position;
    1453        1675 :         map->total_blocks = total_blocks;
    1454        1675 :         map->free_blocks = free_blocks;
    1455        1675 :         pathcpy(map->uuid, sizeof(map->uuid), uuid);
    1456             : 
    1457        1675 :         return map;
    1458             : }
    1459             : 
    1460        1621 : void map_free(struct snapraid_map* map)
    1461             : {
    1462        1621 :         free(map);
    1463        1621 : }
    1464             : 
    1465             : /****************************************************************************/
    1466             : /* bucket */
    1467             : 
    1468         802 : static int bucket_cmp(const void* void_a, const void* void_b)
    1469             : {
    1470         802 :         const struct snapraid_bucket* a = void_a;
    1471         802 :         const struct snapraid_bucket* b = void_b;
    1472         802 :         if (a->time_at < b->time_at)
    1473         401 :                 return -1;
    1474         401 :         if (a->time_at > b->time_at)
    1475         401 :                 return 1;
    1476           0 :         return 0;
    1477             : }
    1478             : 
    1479         794 : void bucket_free(struct snapraid_bucket* bucket)
    1480             : {
    1481         794 :         free(bucket);
    1482         794 : }
    1483             : 
    1484       56806 : void bucket_insert(tommy_hashdyn* bucket_hash, time_t time_at, block_off_t count, int justsynced)
    1485             : {
    1486       56806 :         tommy_hash_t hash = tommy_inthash_u64(time_at);
    1487       56806 :         tommy_node* i = tommy_hashdyn_bucket(bucket_hash, hash);
    1488       57851 :         while (i) {
    1489       57045 :                 struct snapraid_bucket* entry = i->data;
    1490       57045 :                 if (entry->time_at == time_at) {
    1491       56000 :                         if (justsynced)
    1492       27995 :                                 entry->count_justsynced += count;
    1493             :                         else
    1494       28005 :                                 entry->count_scrubbed += count;
    1495       56000 :                         return;
    1496             :                 }
    1497        1045 :                 i = i->next;
    1498             :         }
    1499             : 
    1500         806 :         struct snapraid_bucket* entry = malloc_nofail(sizeof(struct snapraid_bucket));
    1501         806 :         entry->time_at = time_at;
    1502         806 :         if (justsynced) {
    1503         733 :                 entry->count_justsynced = count;
    1504         733 :                 entry->count_scrubbed = 0;
    1505             :         } else {
    1506          73 :                 entry->count_justsynced = 0;
    1507          73 :                 entry->count_scrubbed = count;
    1508             :         }
    1509             : 
    1510         806 :         tommy_hashdyn_insert(bucket_hash, &entry->node, entry, hash);
    1511             : }
    1512             : 
    1513         433 : void bucket_to_list(tommy_hashdyn* bucket_hash, tommy_list* bucket_list, block_off_t* bucketcount)
    1514             : {
    1515             :         /* clear previous list */
    1516         433 :         tommy_list_foreach(bucket_list, (tommy_foreach_func*)bucket_free);
    1517         433 :         tommy_list_init(bucket_list);
    1518             : 
    1519         433 :         log_tag("list:bucket_begin\n");
    1520             : 
    1521         433 :         log_tag("bucket:count:%" PRIu64 "\n", (uint64_t)tommy_hashdyn_count(bucket_hash));
    1522             : 
    1523         433 :         tommy_hashdyn_to_list(bucket_hash, bucket_list);
    1524             : 
    1525         433 :         tommy_list_sort(bucket_list, bucket_cmp);
    1526             : 
    1527         433 :         block_off_t count = 0;
    1528        1239 :         for (tommy_node* i = tommy_list_head(bucket_list); i != 0; i = i->next) {
    1529         806 :                 struct snapraid_bucket* entry = i->data;
    1530         806 :                 count += entry->count_scrubbed + entry->count_justsynced;
    1531         806 :                 log_tag("bucket:entry:%" PRIu64 ":%u:%u\n", (uint64_t)entry->time_at, entry->count_scrubbed, entry->count_justsynced);
    1532             :         }
    1533             : 
    1534         433 :         log_tag("bucket:block_count:%u\n", count);
    1535             : 
    1536         433 :         log_tag("list:bucket_end\n");
    1537             : 
    1538         433 :         *bucketcount = count;
    1539         433 : }
    1540             : 
    1541             : /****************************************************************************/
    1542             : /* format */
    1543             : 
    1544             : int FMT_MODE = FMT_FILE;
    1545             : 
    1546             : /**
    1547             :  * Format a file path for poll reference
    1548             :  */
    1549           6 : const char* fmt_poll(const struct snapraid_disk* disk, const char* str, char* buffer)
    1550             : {
    1551             :         (void)disk;
    1552           6 :         return esc_shell(str, buffer);
    1553             : }
    1554             : 
    1555             : /**
    1556             :  * Format a path name for terminal reference
    1557             :  */
    1558      381818 : const char* fmt_term(const struct snapraid_disk* disk, const char* str, char* buffer)
    1559             : {
    1560             :         const char* out[3];
    1561             : 
    1562      381818 :         switch (FMT_MODE) {
    1563      357753 :         case FMT_FILE :
    1564             :         default :
    1565      357753 :                 return esc_shell(str, buffer);
    1566       12030 :         case FMT_DISK :
    1567       12030 :                 out[0] = disk->name;
    1568       12030 :                 out[1] = ":";
    1569       12030 :                 out[2] = str;
    1570       12030 :                 return esc_shell_multi(out, 3, buffer);
    1571       12035 :         case FMT_PATH :
    1572       12035 :                 out[0] = disk->dir;
    1573       12035 :                 out[1] = str;
    1574       12035 :                 return esc_shell_multi(out, 2, buffer);
    1575             :         }
    1576             : }
    1577             : 
    1578          15 : const char* fmt_size(uint64_t size, char* buffer, size_t buffer_size)
    1579             : {
    1580          15 :         if (size >= 10UL * TERA)
    1581           0 :                 snprintf(buffer, buffer_size, "%" PRIu64 " T", size / TERA);
    1582          15 :         else if (size >= 10UL * GIGA)
    1583           0 :                 snprintf(buffer, buffer_size, "%" PRIu64 " G", size / GIGA);
    1584          15 :         else if (size >= 10UL * MEGA)
    1585           1 :                 snprintf(buffer, buffer_size, "%" PRIu64 " M", size / MEGA);
    1586          14 :         else if (size >= 10UL * KILO)
    1587          13 :                 snprintf(buffer, buffer_size, "%" PRIu64 " k", size / KILO);
    1588             :         else
    1589           1 :                 snprintf(buffer, buffer_size, "%" PRIu64 " ", size);
    1590             : 
    1591          15 :         return buffer;
    1592             : }
    1593             : 

Generated by: LCOV version 1.0