LCOV - code coverage report
Current view: top level - cmdline - pool.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 129 133 97.0 %
Date: 2026-04-29 15:04:44 Functions: 9 9 100.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 "elem.h"
       8             : #include "state.h"
       9             : 
      10             : struct snapraid_pool {
      11             :         char file[PATH_MAX];
      12             :         char linkto[PATH_MAX];
      13             :         int64_t mtime_sec;
      14             :         int mtime_nsec;
      15             : 
      16             :         /* nodes for data structures */
      17             :         tommy_hashdyn_node node;
      18             : };
      19             : 
      20        5751 : struct snapraid_pool* pool_alloc(const char* dir, const char* name, const char* linkto, const struct stat* st)
      21             : {
      22             :         struct snapraid_pool* pool;
      23             : 
      24        5751 :         pool = malloc_nofail(sizeof(struct snapraid_pool));
      25        5751 :         pathprint(pool->file, sizeof(pool->file), "%s%s", dir, name);
      26        5751 :         pathcpy(pool->linkto, sizeof(pool->linkto), linkto);
      27        5751 :         pool->mtime_sec = st->st_mtime;
      28        5751 :         pool->mtime_nsec = STAT_NSEC(st);
      29             : 
      30        5751 :         return pool;
      31             : }
      32             : 
      33       23340 : static inline tommy_uint32_t pool_hash(const char* file)
      34             : {
      35       23340 :         return tommy_hash_u32(0, file, strlen(file));
      36             : }
      37             : 
      38        5751 : void pool_free(struct snapraid_pool* pool)
      39             : {
      40        5751 :         free(pool);
      41        5751 : }
      42             : 
      43        5690 : int pool_compare(const void* void_arg, const void* void_data)
      44             : {
      45        5690 :         const char* arg = void_arg;
      46        5690 :         const struct snapraid_pool* pool = void_data;
      47             : 
      48        5690 :         return strcmp(arg, pool->file);
      49             : }
      50             : 
      51             : /**
      52             :  * Remove empty dir.
      53             :  * Return == 0 if the directory is empty, and it can be removed
      54             :  */
      55           7 : static int clean_dir(const char* dir)
      56             : {
      57             :         DIR* d;
      58           7 :         int full = 0;
      59             : 
      60           7 :         d = opendir(dir);
      61           7 :         if (!d) {
      62             :                 /* LCOV_EXCL_START */
      63             :                 log_fatal(errno, "Error opening pool directory '%s'. %s.\n", dir, strerror(errno));
      64             :                 exit(EXIT_FAILURE);
      65             :                 /* LCOV_EXCL_STOP */
      66             :         }
      67             : 
      68       17086 :         while (1) {
      69             :                 char path_next[PATH_MAX];
      70             :                 struct stat st;
      71             :                 const char* name;
      72             :                 struct dirent* dd;
      73             : 
      74             :                 /* clear errno to detect erroneous conditions */
      75       17093 :                 errno = 0;
      76       17093 :                 dd = readdir(d);
      77       17093 :                 if (dd == 0 && errno != 0) {
      78             :                         /* LCOV_EXCL_START */
      79             :                         log_fatal(errno, "Error reading pool directory '%s'. %s.\n", dir, strerror(errno));
      80             :                         exit(EXIT_FAILURE);
      81             :                         /* LCOV_EXCL_STOP */
      82             :                 }
      83       17093 :                 if (dd == 0 && errno == 0) {
      84           7 :                         break; /* finished */
      85             :                 }
      86             : 
      87             :                 /* skip "." and ".." files */
      88       17086 :                 name = dd->d_name;
      89       17086 :                 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
      90          14 :                         continue;
      91             : 
      92       17072 :                 pathprint(path_next, sizeof(path_next), "%s%s", dir, name);
      93             : 
      94             : #if HAVE_STRUCT_DIRENT_D_STAT
      95             :                 /* convert dirent to lstat result */
      96             :                 dirent_lstat(dd, &st);
      97             : 
      98             :                 /*
      99             :                  * If the st_mode field is missing, takes care to fill it using normal lstat()
     100             :                  * at now this can happen only in Windows
     101             :                  */
     102             :                 if (st.st_mode == 0) {
     103             :                         if (lstat(path_next, &st) != 0) {
     104             :                                 /* LCOV_EXCL_START */
     105             :                                 log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
     106             :                                 exit(EXIT_FAILURE);
     107             :                                 /* LCOV_EXCL_STOP */
     108             :                         }
     109             :                 }
     110             : #else
     111             :                 /* get lstat info about the file */
     112       17072 :                 if (lstat(path_next, &st) != 0) {
     113             :                         /* LCOV_EXCL_START */
     114             :                         log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
     115             :                         exit(EXIT_FAILURE);
     116             :                         /* LCOV_EXCL_STOP */
     117             :                 }
     118             : #endif
     119             : 
     120       17072 :                 if (S_ISDIR(st.st_mode)) {
     121             :                         /* recurse */
     122           4 :                         pathslash(path_next, sizeof(path_next));
     123           4 :                         if (clean_dir(path_next) == 0) {
     124             :                                 int ret;
     125             : 
     126             :                                 /* directory is empty, try to remove it */
     127           0 :                                 ret = rmdir(path_next);
     128           0 :                                 if (ret < 0) {
     129             : #ifdef _WIN32
     130             :                                         if (errno == EACCES) {
     131             :                                                 /*
     132             :                                                  * In Windows just ignore EACCES errors removing directories
     133             :                                                  * because it could happen that the directory is in use
     134             :                                                  * and it cannot be removed
     135             :                                                  */
     136             :                                                 log_fatal(errno, "Directory '%s' not removed because it's in use.\n", path_next);
     137             :                                                 full = 1;
     138             :                                         } else
     139             : #endif
     140             :                                         {
     141             :                                                 /* LCOV_EXCL_START */
     142             :                                                 log_fatal(errno, "Error removing pool directory '%s'. %s.\n", path_next, strerror(errno));
     143             :                                                 exit(EXIT_FAILURE);
     144             :                                                 /* LCOV_EXCL_STOP */
     145             :                                         }
     146             :                                 }
     147             :                         } else {
     148             :                                 /* something is present */
     149           4 :                                 full = 1;
     150             :                         }
     151             :                 } else {
     152             :                         /* something is present */
     153       17068 :                         full = 1;
     154             :                 }
     155             :         }
     156             : 
     157           7 :         if (closedir(d) != 0) {
     158             :                 /* LCOV_EXCL_START */
     159             :                 log_fatal(errno, "Error closing pool directory '%s'. %s.\n", dir, strerror(errno));
     160             :                 exit(EXIT_FAILURE);
     161             :                 /* LCOV_EXCL_STOP */
     162             :         }
     163             : 
     164           7 :         return full;
     165             : }
     166             : 
     167             : /**
     168             :  * Read all the links in a directory tree.
     169             :  */
     170           5 : static void read_dir(tommy_hashdyn* poolset, const char* base_dir, const char* sub_dir)
     171             : {
     172             :         char dir[PATH_MAX];
     173             :         DIR* d;
     174             : 
     175           5 :         pathprint(dir, sizeof(dir), "%s%s", base_dir, sub_dir);
     176           5 :         d = opendir(dir);
     177           5 :         if (!d) {
     178             :                 /* LCOV_EXCL_START */
     179             :                 log_fatal(errno, "Error opening pool directory '%s'. %s.\n", dir, strerror(errno));
     180             :                 exit(EXIT_FAILURE);
     181             :                 /* LCOV_EXCL_STOP */
     182             :         }
     183             : 
     184        5763 :         while (1) {
     185             :                 char path_next[PATH_MAX];
     186             :                 struct stat st;
     187             :                 const char* name;
     188             :                 struct dirent* dd;
     189             : 
     190             :                 /* clear errno to detect erroneous conditions */
     191        5768 :                 errno = 0;
     192        5768 :                 dd = readdir(d);
     193        5768 :                 if (dd == 0 && errno != 0) {
     194             :                         /* LCOV_EXCL_START */
     195             :                         log_fatal(errno, "Error reading pool directory '%s'. %s.\n", dir, strerror(errno));
     196             :                         exit(EXIT_FAILURE);
     197             :                         /* LCOV_EXCL_STOP */
     198             :                 }
     199        5768 :                 if (dd == 0 && errno == 0) {
     200           5 :                         break; /* finished */
     201             :                 }
     202             : 
     203             :                 /* skip "." and ".." files */
     204        5763 :                 name = dd->d_name;
     205        5763 :                 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
     206          10 :                         continue;
     207             : 
     208        5753 :                 pathprint(path_next, sizeof(path_next), "%s%s", dir, name);
     209             : 
     210             : #if HAVE_STRUCT_DIRENT_D_STAT
     211             :                 /* convert dirent to lstat result */
     212             :                 dirent_lstat(dd, &st);
     213             : 
     214             :                 /*
     215             :                  * If the st_mode field is missing, takes care to fill it using normal lstat()
     216             :                  * at now this can happen only in Windows
     217             :                  */
     218             :                 if (st.st_mode == 0) {
     219             :                         if (lstat(path_next, &st) != 0) {
     220             :                                 /* LCOV_EXCL_START */
     221             :                                 log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
     222             :                                 exit(EXIT_FAILURE);
     223             :                                 /* LCOV_EXCL_STOP */
     224             :                         }
     225             :                 }
     226             : #else
     227             :                 /* get lstat info about the file */
     228        5753 :                 if (lstat(path_next, &st) != 0) {
     229             :                         /* LCOV_EXCL_START */
     230             :                         log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
     231             :                         exit(EXIT_FAILURE);
     232             :                         /* LCOV_EXCL_STOP */
     233             :                 }
     234             : #endif
     235             : 
     236        5753 :                 if (S_ISLNK(st.st_mode)) {
     237             :                         struct snapraid_pool* pool;
     238             :                         char linkto[PATH_MAX];
     239             :                         int ret;
     240             : 
     241        5751 :                         ret = readlink(path_next, linkto, sizeof(linkto));
     242        5751 :                         if (ret < 0 || ret >= PATH_MAX) {
     243             :                                 /* LCOV_EXCL_START */
     244             :                                 log_fatal(errno, "Error in readlink symlink '%s'. %s.\n", path_next, strerror(errno));
     245             :                                 exit(EXIT_FAILURE);
     246             :                                 /* LCOV_EXCL_STOP */
     247             :                         }
     248        5751 :                         linkto[ret] = 0;
     249             : 
     250             :                         /* store the link info */
     251        5751 :                         pool = pool_alloc(sub_dir, name, linkto, &st);
     252             : 
     253        5751 :                         tommy_hashdyn_insert(poolset, &pool->node, pool, pool_hash(pool->file));
     254             : 
     255           2 :                 } else if (S_ISDIR(st.st_mode)) {
     256           2 :                         pathprint(path_next, sizeof(path_next), "%s%s/", sub_dir, name);
     257             : 
     258           2 :                         read_dir(poolset, base_dir, path_next);
     259             :                 } else {
     260           0 :                         msg_verbose("Ignoring pool file '%s'\n", path_next);
     261             :                 }
     262             :         }
     263             : 
     264           5 :         if (closedir(d) != 0) {
     265             :                 /* LCOV_EXCL_START */
     266             :                 log_fatal(errno, "Error closing pool directory '%s'. %s.\n", dir, strerror(errno));
     267             :                 exit(EXIT_FAILURE);
     268             :                 /* LCOV_EXCL_STOP */
     269             :         }
     270           5 : }
     271             : 
     272             : /**
     273             :  * Remove the link
     274             :  */
     275          61 : static void remove_link(void* void_arg, void* void_pool)
     276             : {
     277             :         char path[PATH_MAX];
     278          61 :         const char* arg = void_arg;
     279          61 :         struct snapraid_pool* pool = void_pool;
     280             :         int ret;
     281             : 
     282          61 :         pathprint(path, sizeof(path), "%s%s", arg, pool->file);
     283             : 
     284             :         /* delete the link */
     285          61 :         ret = remove(path);
     286          61 :         if (ret < 0) {
     287             :                 /* LCOV_EXCL_START */
     288             :                 log_fatal(errno, "Error removing symlink '%s'. %s.\n", path, strerror(errno));
     289             :                 exit(EXIT_FAILURE);
     290             :                 /* LCOV_EXCL_STOP */
     291             :         }
     292          61 : }
     293             : 
     294             : /**
     295             :  * Create a link to the specified disk link.
     296             :  */
     297       17589 : static void make_link(tommy_hashdyn* poolset, const char* pool_dir, const char* share_dir, struct snapraid_disk* disk, const char* sub, int64_t mtime_sec, int mtime_nsec)
     298             : {
     299             :         char path[PATH_MAX];
     300             :         char linkto[PATH_MAX];
     301             :         char linkto_exported[PATH_MAX];
     302             :         struct snapraid_pool* found;
     303             :         int ret;
     304             : 
     305             :         /* make the source path */
     306       17589 :         pathprint(path, sizeof(path), "%s%s", pool_dir, sub);
     307             : 
     308             :         /* make the linkto path */
     309       17589 :         if (share_dir[0] != 0) {
     310             :                 /* with a shared directory, use it */
     311       17589 :                 pathprint(linkto, sizeof(linkto), "%s%s/%s", share_dir, disk->name, sub);
     312             :         } else {
     313             :                 /* without a share directory, use the local disk paths */
     314           0 :                 pathprint(linkto, sizeof(linkto), "%s%s", disk->dir, sub);
     315             :         }
     316             : 
     317             :         /* search for the sub path */
     318       17589 :         found = tommy_hashdyn_search(poolset, pool_compare, sub, pool_hash(sub));
     319       17589 :         if (found) {
     320             :                 /* remove from the set */
     321        5690 :                 tommy_hashdyn_remove_existing(poolset, &found->node);
     322             : 
     323             :                 /* check if the info match */
     324        5690 :                 if (found->mtime_sec == mtime_sec
     325        5406 :                         && found->mtime_nsec == mtime_nsec
     326        5385 :                         && strcmp(found->linkto, linkto) == 0
     327             :                 ) {
     328             :                         /* nothing to do */
     329        5355 :                         pool_free(found);
     330        5355 :                         return;
     331             :                 }
     332             : 
     333             :                 /* delete the link */
     334         335 :                 ret = remove(path);
     335         335 :                 if (ret < 0) {
     336             :                         /* LCOV_EXCL_START */
     337             :                         log_fatal(errno, "Error removing symlink '%s'. %s.\n", path, strerror(errno));
     338             :                         exit(EXIT_FAILURE);
     339             :                         /* LCOV_EXCL_STOP */
     340             :                 }
     341             : 
     342         335 :                 pool_free(found);
     343             :         }
     344             : 
     345             :         /* create the ancestor directories */
     346       12234 :         ret = mkancestor(path);
     347       12234 :         if (ret != 0) {
     348             :                 /* LCOV_EXCL_START */
     349             :                 exit(EXIT_FAILURE);
     350             :                 /* LCOV_EXCL_STOP */
     351             :         }
     352             : 
     353             :         /* convert back slashes */
     354       12234 :         pathexport(linkto_exported, sizeof(linkto_exported), linkto);
     355             : 
     356             :         /* create the symlink */
     357       12234 :         ret = symlink(linkto_exported, path);
     358       12234 :         if (ret != 0) {
     359         521 :                 if (errno == EEXIST) {
     360         521 :                         log_fatal(errno, "WARNING! Duplicate pooling for '%s'\n", path);
     361             : #ifdef _WIN32
     362             :                 } else if (errno == EPERM) {
     363             :                         /* LCOV_EXCL_START */
     364             :                         log_fatal(errno, "You must run as Administrator to be able to create symlinks.\n");
     365             :                         exit(EXIT_FAILURE);
     366             :                         /* LCOV_EXCL_STOP */
     367             : #endif
     368             :                 } else {
     369             :                         /* LCOV_EXCL_START */
     370             :                         log_fatal(errno, "Error writing symlink '%s'. %s.\n", path, strerror(errno));
     371             :                         exit(EXIT_FAILURE);
     372             :                         /* LCOV_EXCL_STOP */
     373             :                 }
     374             :         }
     375             : 
     376       12234 :         if (mtime_sec) {
     377       11645 :                 ret = lmtime(path, mtime_sec, mtime_nsec);
     378       11645 :                 if (ret != 0) {
     379             :                         /* LCOV_EXCL_START */
     380             :                         log_fatal(errno, "Error setting time to symlink '%s'. %s.\n", path, strerror(errno));
     381             :                         exit(EXIT_FAILURE);
     382             :                         /* LCOV_EXCL_STOP */
     383             :                 }
     384             :         }
     385             : }
     386             : 
     387           3 : void state_pool(struct snapraid_state* state)
     388             : {
     389             :         tommy_hashdyn poolset;
     390             :         tommy_node* i;
     391             :         char pool_dir[PATH_MAX];
     392             :         char share_dir[PATH_MAX];
     393             :         unsigned count;
     394             : 
     395           3 :         tommy_hashdyn_init(&poolset);
     396             : 
     397           3 :         if (state->pool[0] == 0) {
     398             :                 /* LCOV_EXCL_START */
     399             :                 log_fatal(EUSER, "To use the 'pool' command you must set the pool directory in the configuration file\n");
     400             :                 exit(EXIT_FAILURE);
     401             :                 /* LCOV_EXCL_STOP */
     402             :         }
     403             : 
     404           3 :         msg_progress("Reading...\n");
     405             : 
     406             :         /* pool directory with final slash */
     407           3 :         pathprint(pool_dir, sizeof(pool_dir), "%s", state->pool);
     408           3 :         pathslash(pool_dir, sizeof(pool_dir));
     409             : 
     410             :         /* share directory with final slash */
     411           3 :         pathprint(share_dir, sizeof(share_dir), "%s", state->share);
     412           3 :         pathslash(share_dir, sizeof(share_dir));
     413             : 
     414             :         /* first read the previous pool tree */
     415           3 :         read_dir(&poolset, pool_dir, "");
     416             : 
     417           3 :         msg_progress("Writing...\n");
     418             : 
     419             :         /* for each disk */
     420           3 :         count = 0;
     421          21 :         for (i = state->disklist; i != 0; i = i->next) {
     422             :                 tommy_node* j;
     423          18 :                 struct snapraid_disk* disk = i->data;
     424             : 
     425             :                 /* for each file */
     426       17018 :                 for (j = disk->filelist; j != 0; j = j->next) {
     427       17000 :                         struct snapraid_file* file = j->data;
     428       17000 :                         make_link(&poolset, pool_dir, share_dir, disk, file->sub, file->mtime_sec, file->mtime_nsec);
     429       17000 :                         ++count;
     430             :                 }
     431             : 
     432             :                 /* for each link */
     433         607 :                 for (j = disk->linklist; j != 0; j = j->next) {
     434         589 :                         struct snapraid_link* slink = j->data;
     435         589 :                         make_link(&poolset, pool_dir, share_dir, disk, slink->sub, 0, 0);
     436         589 :                         ++count;
     437             :                 }
     438             : 
     439             :                 /* we ignore empty dirs in disk->dir */
     440             :         }
     441             : 
     442           3 :         msg_progress("Cleaning...\n");
     443             : 
     444             :         /* delete all the remaining links */
     445           3 :         tommy_hashdyn_foreach_arg(&poolset, (tommy_foreach_arg_func*)remove_link, pool_dir);
     446             : 
     447             :         /* delete empty dirs */
     448           3 :         clean_dir(pool_dir);
     449             : 
     450           3 :         tommy_hashdyn_foreach(&poolset, (tommy_foreach_func*)pool_free);
     451           3 :         tommy_hashdyn_done(&poolset);
     452             : 
     453           3 :         if (count)
     454           2 :                 msg_status("%u links\n", count);
     455             :         else
     456           1 :                 msg_status("No link\n");
     457             : 
     458           3 :         log_tag("summary:link_count::%u\n", count);
     459           3 :         log_tag("summary:exit:ok\n");
     460           3 :         log_flush();
     461           3 : }
     462             : 

Generated by: LCOV version 1.0