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: 2017-11-06 22:14:04 Functions: 9 9 100.0 %

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

Generated by: LCOV version 1.13