LCOV - code coverage report
Current view: top level - lib - utilities.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 374 527 71.0 %
Date: 2015-09-30 14:09:30 Functions: 41 45 91.1 %

          Line data    Source code
       1             : /**
       2             :  *  This file is part of rmlint.
       3             :  *
       4             :  *  rmlint 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             :  *  rmlint 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 rmlint.  If not, see <http://www.gnu.org/licenses/>.
      16             :  *
      17             :  * Authors:
      18             :  *
      19             :  *  - Christopher <sahib> Pahl 2010-2015 (https://github.com/sahib)
      20             :  *  - Daniel <SeeSpotRun> T.   2014-2015 (https://github.com/SeeSpotRun)
      21             :  *
      22             :  * Hosted on http://github.com/sahib/rmlint
      23             :  *
      24             :  */
      25             : 
      26             : #include <stdio.h>
      27             : #include <string.h>
      28             : #include <stdlib.h>
      29             : #include <ctype.h>
      30             : #include <unistd.h>
      31             : #include <fcntl.h>
      32             : 
      33             : #include <sys/types.h>
      34             : #include <sys/stat.h>
      35             : #include <sys/ioctl.h>
      36             : 
      37             : #include <pwd.h>
      38             : #include <grp.h>
      39             : 
      40             : #include <fts.h>
      41             : #include <libgen.h>
      42             : 
      43             : #include "config.h"
      44             : 
      45             : /* Not available there,
      46             :  * but might be on other non-linux systems
      47             :  * */
      48             : #if HAVE_GIO_UNIX
      49             : #include <gio/gunixmounts.h>
      50             : #endif
      51             : 
      52             : #if HAVE_FIEMAP
      53             : #include <linux/fs.h>
      54             : #include <linux/fiemap.h>
      55             : #endif
      56             : 
      57             : /* Internal headers */
      58             : #include "config.h"
      59             : #include "utilities.h"
      60             : #include "file.h"
      61             : 
      62             : /* External libraries */
      63             : #include <glib.h>
      64             : 
      65             : #if HAVE_LIBELF
      66             : #include <libelf.h>
      67             : #include <gelf.h>
      68             : #endif
      69             : 
      70             : #if HAVE_BLKID
      71             : #include <blkid.h>
      72             : #endif
      73             : 
      74             : #if HAVE_SYSCTL
      75             : #include <sys/sysctl.h>
      76             : #endif
      77             : 
      78             : #if HAVE_JSON_GLIB
      79             : #include <json-glib/json-glib.h>
      80             : #endif
      81             : 
      82             : ////////////////////////////////////
      83             : //       GENERAL UTILITES         //
      84             : ////////////////////////////////////
      85             : 
      86        1116 : char *rm_util_strsub(const char *string, const char *subs, const char *with) {
      87        1116 :     gchar *result = NULL;
      88        1116 :     if(string != NULL && string[0] != '\0') {
      89        1116 :         gchar **split = g_strsplit(string, subs, 0);
      90        1116 :         if(split != NULL) {
      91        1116 :             result = g_strjoinv(with, split);
      92             :         }
      93        1116 :         g_strfreev(split);
      94             :     }
      95        1116 :     return result;
      96             : }
      97             : 
      98       20913 : char *rm_util_basename(const char *filename) {
      99       20913 :     char *base = strrchr(filename, G_DIR_SEPARATOR);
     100       20913 :     if(base != NULL) {
     101             :         /* Return a pointer to the part behind it
     102             :          * (which may be the empty string)
     103             :          * */
     104       20913 :         return base + 1;
     105             :     }
     106             : 
     107             :     /* It's the full path anyway */
     108           0 :     return (char *)filename;
     109             : }
     110             : 
     111        1092 : char *rm_util_path_extension(const char *basename) {
     112        1092 :     char *point = strrchr(basename, '.');
     113        1092 :     if(point) {
     114         728 :         return point + 1;
     115             :     } else {
     116         364 :         return NULL;
     117             :     }
     118             : }
     119             : 
     120      112640 : bool rm_util_path_is_hidden(const char *path) {
     121      112640 :     if(path == NULL) {
     122           0 :         return false;
     123             :     }
     124             : 
     125      112640 :     if(*path == '.') {
     126           0 :         return true;
     127             :     }
     128             : 
     129     3877412 :     while(*path++) {
     130             :         /* Search for '/.' */
     131     3652162 :         if(*path == G_DIR_SEPARATOR && *(path + 1) == '.') {
     132          30 :             return true;
     133             :         }
     134             :     }
     135             : 
     136      112610 :     return false;
     137             : }
     138             : 
     139      339269 : int rm_util_path_depth(const char *path) {
     140      339269 :     int depth = 0;
     141             : 
     142     2027279 :     while(path) {
     143             :         /* Skip trailing slashes */
     144     1348741 :         if(*path == G_DIR_SEPARATOR && path[1] != 0) {
     145     1348744 :             depth++;
     146             :         }
     147     1348741 :         path = strchr(&path[1], G_DIR_SEPARATOR);
     148             :     }
     149             : 
     150      339269 :     return depth;
     151             : }
     152             : 
     153      106866 : GQueue *rm_hash_table_setdefault(GHashTable *table, gpointer key,
     154             :                                  RmNewFunc default_func) {
     155      106866 :     gpointer value = g_hash_table_lookup(table, key);
     156      106866 :     if(value == NULL) {
     157       41998 :         value = default_func();
     158       41998 :         g_hash_table_insert(table, key, value);
     159             :     }
     160             : 
     161      106866 :     return value;
     162             : }
     163             : 
     164           0 : ino_t rm_util_parent_node(const char *path) {
     165           0 :     char *parent_path = g_path_get_dirname(path);
     166             : 
     167             :     RmStat stat_buf;
     168           0 :     if(!rm_sys_stat(parent_path, &stat_buf)) {
     169           0 :         g_free(parent_path);
     170           0 :         return stat_buf.st_ino;
     171             :     } else {
     172           0 :         g_free(parent_path);
     173           0 :         return -1;
     174             :     }
     175             : }
     176             : 
     177             : /* checks uid and gid; returns 0 if both ok, else RM_LINT_TYPE_ corresponding *
     178             :  * to RmFile->filter types                                            */
     179      225513 : int rm_util_uid_gid_check(RmStat *statp, RmUserList *userlist) {
     180      225513 :     bool has_gid = 1, has_uid = 1;
     181      225513 :     if(!rm_userlist_contains(userlist, statp->st_uid, statp->st_gid, &has_uid,
     182             :                              &has_gid)) {
     183          84 :         if(has_gid == false && has_uid == false) {
     184          28 :             return RM_LINT_TYPE_BADUGID;
     185          56 :         } else if(has_gid == false && has_uid == true) {
     186          28 :             return RM_LINT_TYPE_BADGID;
     187          28 :         } else if(has_gid == true && has_uid == false) {
     188          28 :             return RM_LINT_TYPE_BADUID;
     189             :         }
     190             :     }
     191             : 
     192      225442 :     return RM_LINT_TYPE_UNKNOWN;
     193             : }
     194             : 
     195             : /* Method to test if a file is non stripped binary. Uses libelf*/
     196         112 : bool rm_util_is_nonstripped(_U const char *path, _U RmStat *statp) {
     197         112 :     bool is_ns = false;
     198             : 
     199             : #if HAVE_LIBELF
     200         112 :     g_return_val_if_fail(path, false);
     201             : 
     202         112 :     if(statp && (statp->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) {
     203          56 :         return false;
     204             :     }
     205             : 
     206             :     /* inspired by "jschmier"'s answer at http://stackoverflow.com/a/5159890 */
     207             :     int fd;
     208             : 
     209             :     /* ELF handle */
     210             :     Elf *elf;
     211             : 
     212             :     /* section descriptor pointer */
     213             :     Elf_Scn *scn;
     214             : 
     215             :     /* section header */
     216             :     GElf_Shdr shdr;
     217             : 
     218             :     /* Open ELF file to obtain file descriptor */
     219          56 :     if((fd = rm_sys_open(path, O_RDONLY)) == -1) {
     220           0 :         rm_log_warning_line(_("cannot open file '%s' for nonstripped test: "), path);
     221           0 :         rm_log_perror("");
     222           0 :         return 0;
     223             :     }
     224             : 
     225             :     /* Protect program from using an older library */
     226          56 :     if(elf_version(EV_CURRENT) == EV_NONE) {
     227           0 :         rm_log_error_line(_("ELF Library is out of date!"));
     228           0 :         rm_sys_close(fd);
     229           0 :         return false;
     230             :     }
     231             : 
     232             :     /* Initialize elf pointer for examining contents of file */
     233          56 :     elf = elf_begin(fd, ELF_C_READ, NULL);
     234             : 
     235             :     /* Initialize section descriptor pointer so that elf_nextscn()
     236             :      * returns a pointer to the section descriptor at index 1.
     237             :      * */
     238          56 :     scn = NULL;
     239             : 
     240             :     /* Iterate through ELF sections */
     241        1792 :     while((scn = elf_nextscn(elf, scn)) != NULL) {
     242             :         /* Retrieve section header */
     243        1708 :         gelf_getshdr(scn, &shdr);
     244             : 
     245             :         /* If a section header holding a symbol table (.symtab)
     246             :          * is found, this ELF file has not been stripped. */
     247        1708 :         if(shdr.sh_type == SHT_SYMTAB) {
     248          28 :             is_ns = true;
     249          28 :             break;
     250             :         }
     251             :     }
     252          56 :     elf_end(elf);
     253          56 :     rm_sys_close(fd);
     254             : #endif
     255             : 
     256          56 :     return is_ns;
     257             : }
     258             : 
     259         181 : char *rm_util_get_username(void) {
     260         181 :     struct passwd *user = getpwuid(geteuid());
     261         181 :     if(user) {
     262         181 :         return user->pw_name;
     263             :     } else {
     264           0 :         return NULL;
     265             :     }
     266             : }
     267             : 
     268         181 : char *rm_util_get_groupname(void) {
     269         181 :     struct passwd *user = getpwuid(geteuid());
     270         181 :     struct group *grp = getgrgid(user->pw_gid);
     271         181 :     if(grp) {
     272         181 :         return grp->gr_name;
     273             :     } else {
     274           0 :         return NULL;
     275             :     }
     276             : }
     277             : 
     278         366 : void rm_util_size_to_human_readable(RmOff num, char *in, gsize len) {
     279         366 :     if(num < 512) {
     280         366 :         snprintf(in, len, "%" LLU " B", num);
     281           0 :     } else if(num < 512 * 1024) {
     282           0 :         snprintf(in, len, "%.2f KB", num / 1024.0);
     283           0 :     } else if(num < 512 * 1024 * 1024) {
     284           0 :         snprintf(in, len, "%.2f MB", num / (1024.0 * 1024.0));
     285             :     } else {
     286           0 :         snprintf(in, len, "%.2f GB", num / (1024.0 * 1024.0 * 1024.0));
     287             :     }
     288         366 : }
     289             : 
     290             : /////////////////////////////////////
     291             : //   UID/GID VALIDITY CHECKING     //
     292             : /////////////////////////////////////
     293             : 
     294    24584369 : static int rm_userlist_cmp_ids(gconstpointer a, gconstpointer b, _U gpointer ud) {
     295    24584369 :     return GPOINTER_TO_UINT(a) - GPOINTER_TO_UINT(b);
     296             : }
     297             : 
     298       36878 : RmUserList *rm_userlist_new(void) {
     299       36878 :     struct passwd *node = NULL;
     300       36878 :     struct group *grp = NULL;
     301             : 
     302       36878 :     RmUserList *self = g_malloc0(sizeof(RmUserList));
     303       36878 :     self->users = g_sequence_new(NULL);
     304       36878 :     self->groups = g_sequence_new(NULL);
     305       36878 :     g_mutex_init(&self->mutex);
     306             : 
     307       36878 :     setpwent();
     308     1069462 :     while((node = getpwent()) != NULL) {
     309      995706 :         g_sequence_insert_sorted(self->users, GUINT_TO_POINTER(node->pw_uid),
     310             :                                  rm_userlist_cmp_ids, NULL);
     311      995706 :         g_sequence_insert_sorted(self->groups, GUINT_TO_POINTER(node->pw_gid),
     312             :                                  rm_userlist_cmp_ids, NULL);
     313             :     }
     314       36878 :     endpwent();
     315             : 
     316             :     /* add all groups, not just those that are user primary gid's */
     317     2138924 :     while((grp = getgrent()) != NULL) {
     318     2065168 :         g_sequence_insert_sorted(self->groups, GUINT_TO_POINTER(grp->gr_gid),
     319             :                                  rm_userlist_cmp_ids, NULL);
     320             :     }
     321             : 
     322       36878 :     endgrent();
     323       36878 :     return self;
     324             : }
     325             : 
     326      225512 : bool rm_userlist_contains(RmUserList *self, unsigned long uid, unsigned gid,
     327             :                           bool *valid_uid, bool *valid_gid) {
     328      225512 :     g_assert(self);
     329             : 
     330      225512 :     g_mutex_lock(&self->mutex);
     331      225526 :     bool gid_found =
     332      225526 :         g_sequence_lookup(self->groups, GUINT_TO_POINTER(gid), rm_userlist_cmp_ids, NULL);
     333      225526 :     bool uid_found =
     334      225526 :         g_sequence_lookup(self->users, GUINT_TO_POINTER(uid), rm_userlist_cmp_ids, NULL);
     335      225526 :     g_mutex_unlock(&self->mutex);
     336             : 
     337      225526 :     if(valid_uid != NULL) {
     338      225526 :         *valid_uid = uid_found;
     339             :     }
     340             : 
     341      225526 :     if(valid_gid != NULL) {
     342      225526 :         *valid_gid = gid_found;
     343             :     }
     344             : 
     345      225526 :     return (gid_found && uid_found);
     346             : }
     347             : 
     348       36878 : void rm_userlist_destroy(RmUserList *self) {
     349       36878 :     g_assert(self);
     350             : 
     351       36878 :     g_sequence_free(self->users);
     352       36878 :     g_sequence_free(self->groups);
     353       36878 :     g_mutex_clear(&self->mutex);
     354       36878 :     g_free(self);
     355       36878 : }
     356             : 
     357             : /////////////////////////////////////
     358             : //    JSON CACHE IMPLEMENTATION    //
     359             : /////////////////////////////////////
     360             : 
     361             : #if HAVE_JSON_GLIB
     362             : 
     363         280 : void rm_json_cache_parse_entry(_U JsonArray *array, _U guint index,
     364             :                                JsonNode *element_node, RmTrie *file_trie) {
     365         280 :     if(JSON_NODE_TYPE(element_node) != JSON_NODE_OBJECT) {
     366           0 :         return;
     367             :     }
     368             : 
     369         280 :     JsonObject *object = json_node_get_object(element_node);
     370         280 :     JsonNode *mtime_node = json_object_get_member(object, "mtime");
     371         280 :     JsonNode *path_node = json_object_get_member(object, "path");
     372         280 :     JsonNode *cksum_node = json_object_get_member(object, "checksum");
     373         280 :     JsonNode *type_node = json_object_get_member(object, "type");
     374             : 
     375         280 :     if(mtime_node && path_node && cksum_node && type_node) {
     376             :         RmStat stat_buf;
     377         196 :         const char *path = json_node_get_string(path_node);
     378         196 :         const char *cksum = json_node_get_string(cksum_node);
     379         196 :         const char *type = json_node_get_string(type_node);
     380             : 
     381         196 :         if(g_strcmp0(type, "duplicate_file") && g_strcmp0(type, "unfinished_cksum")) {
     382             :             /* some other file that has a checksum for weird reasons.
     383             :              * This is here to prevent errors like reporting files with
     384             :              * empty checksums as duplicates.
     385             :              * */
     386         112 :             return;
     387             :         }
     388             : 
     389         140 :         if(rm_sys_stat(path, &stat_buf) == -1) {
     390             :             /* file does not appear to exist */
     391           0 :             return;
     392             :         }
     393             : 
     394         140 :         if(json_node_get_int(mtime_node) < rm_sys_stat_mtime_seconds(&stat_buf)) {
     395             :             /* file is newer than stored checksum */
     396           0 :             return;
     397             :         }
     398             : 
     399         140 :         char *cksum_copy = g_strdup(cksum);
     400         140 :         if(!rm_trie_set_value(file_trie, path, cksum_copy)) {
     401           5 :             g_free(cksum_copy);
     402             :         }
     403         140 :         rm_log_debug_line("* Adding cache entry %s (%s)", path, cksum);
     404             :     }
     405             : }
     406             : 
     407             : #endif
     408             : 
     409          84 : int rm_json_cache_read(RmTrie *file_trie, const char *json_path) {
     410             : #if !HAVE_JSON_GLIB
     411             :     (void)file_trie;
     412             :     (void)json_path;
     413             : 
     414             :     rm_log_info_line(_("caching is not supported due to missing json-glib library."));
     415             :     return EXIT_FAILURE;
     416             : #else
     417          84 :     g_assert(file_trie);
     418          84 :     g_assert(json_path);
     419             : 
     420          84 :     int result = EXIT_FAILURE;
     421          84 :     GError *error = NULL;
     422          84 :     size_t keys_in_table = rm_trie_size(file_trie);
     423          84 :     JsonParser *parser = json_parser_new();
     424             : 
     425          84 :     rm_log_info_line(_("Loading json-cache `%s'"), json_path);
     426             : 
     427          84 :     if(!json_parser_load_from_file(parser, json_path, &error)) {
     428          28 :         rm_log_warning_line(_("FAILED: %s\n"), error->message);
     429          28 :         g_error_free(error);
     430          28 :         goto failure;
     431             :     }
     432             : 
     433          56 :     JsonNode *root = json_parser_get_root(parser);
     434          56 :     if(JSON_NODE_TYPE(root) != JSON_NODE_ARRAY) {
     435          28 :         rm_log_warning_line(_("No valid json cache (no array in /)"));
     436          28 :         goto failure;
     437             :     }
     438             : 
     439             :     /* Iterate over all objects in it */
     440          28 :     json_array_foreach_element(json_node_get_array(root),
     441             :                                (JsonArrayForeach)rm_json_cache_parse_entry,
     442             :                                file_trie);
     443             : 
     444             :     /* check if some entries were added */
     445          28 :     result = (keys_in_table >= rm_trie_size(file_trie));
     446             : 
     447             : failure:
     448          84 :     if(parser) {
     449          84 :         g_object_unref(parser);
     450             :     }
     451          84 :     return result;
     452             : #endif
     453             : }
     454             : 
     455             : /////////////////////////////////////
     456             : //    MOUNTTABLE IMPLEMENTATION    //
     457             : /////////////////////////////////////
     458             : 
     459             : typedef struct RmDiskInfo {
     460             :     char *name;
     461             :     bool is_rotational;
     462             : } RmDiskInfo;
     463             : 
     464             : typedef struct RmPartitionInfo {
     465             :     char *name;
     466             :     char *fsname;
     467             :     dev_t disk;
     468             : } RmPartitionInfo;
     469             : 
     470             : #if(HAVE_GIO_UNIX)
     471             : 
     472     1291026 : RmPartitionInfo *rm_part_info_new(char *name, char *fsname, dev_t disk) {
     473     1291026 :     RmPartitionInfo *self = g_new0(RmPartitionInfo, 1);
     474     1291026 :     self->name = g_strdup(name);
     475     1291026 :     self->fsname = g_strdup(fsname);
     476     1291026 :     self->disk = disk;
     477     1291026 :     return self;
     478             : }
     479             : 
     480     1291026 : void rm_part_info_free(RmPartitionInfo *self) {
     481     1291026 :     g_free(self->name);
     482     1291026 :     g_free(self->fsname);
     483     1291026 :     g_free(self);
     484     1291026 : }
     485             : 
     486      737560 : RmDiskInfo *rm_disk_info_new(char *name, char is_rotational) {
     487      737560 :     RmDiskInfo *self = g_new0(RmDiskInfo, 1);
     488      737560 :     self->name = g_strdup(name);
     489      737560 :     self->is_rotational = is_rotational;
     490      737560 :     return self;
     491             : }
     492             : 
     493      737560 : void rm_disk_info_free(RmDiskInfo *self) {
     494      737560 :     g_free(self->name);
     495      737560 :     g_free(self);
     496      737560 : }
     497             : 
     498      110634 : static gchar rm_mounts_is_rotational_blockdev(const char *dev) {
     499      110634 :     gchar is_rotational = -1;
     500             : 
     501             : #if HAVE_SYSBLOCK /* this works only on linux */
     502             :     char sys_path[PATH_MAX];
     503             : 
     504      110634 :     snprintf(sys_path, PATH_MAX, "/sys/block/%s/queue/rotational", dev);
     505             : 
     506      110634 :     FILE *sys_fdes = fopen(sys_path, "r");
     507      110634 :     if(sys_fdes == NULL) {
     508           0 :         return -1;
     509             :     }
     510             : 
     511      110634 :     if(fread(&is_rotational, 1, 1, sys_fdes) == 1) {
     512      110634 :         is_rotational -= '0';
     513             :     }
     514             : 
     515      110634 :     fclose(sys_fdes);
     516             : #elif HAVE_SYSCTL && !RM_IS_APPLE
     517             :     /* try with sysctl() */
     518             :     int device_num = 0;
     519             :     char cmd[32] = {0}, delete_method[32] = {0}, dev_copy[32] = {0};
     520             :     size_t delete_method_len = sizeof(delete_method_len);
     521             :     (void)dev;
     522             : 
     523             :     memset(cmd, 0, sizeof(cmd));
     524             :     memset(delete_method, 0, sizeof(delete_method));
     525             :     strncpy(dev_copy, dev, sizeof(dev_copy));
     526             : 
     527             :     for(int i = 0; dev_copy[i]; ++i) {
     528             :         if(isdigit(dev_copy[i])) {
     529             :             if(i > 0) {
     530             :                 dev_copy[i - 1] = 0;
     531             :             }
     532             : 
     533             :             device_num = g_ascii_strtoll(&dev_copy[i], NULL, 10);
     534             :             break;
     535             :         }
     536             :     }
     537             : 
     538             :     if(snprintf(cmd, sizeof(cmd), "kern.cam.%s.%d.delete_method", dev_copy, device_num) ==
     539             :        -1) {
     540             :         return -1;
     541             :     }
     542             : 
     543             :     if(sysctlbyname(cmd, delete_method, &delete_method_len, NULL, 0) != 0) {
     544             :         rm_log_perror("sysctlbyname");
     545             :     } else {
     546             :         if(memcmp("NONE", delete_method, MIN(delete_method_len, 4)) == 0) {
     547             :             is_rotational = 1;
     548             :         } else {
     549             :             is_rotational = 0;
     550             :         }
     551             :     }
     552             : 
     553             : #else
     554             :     (void)dev;
     555             : #endif
     556             : 
     557      110634 :     return is_rotational;
     558             : }
     559             : 
     560     1069786 : static bool rm_mounts_is_ramdisk(const char *fs_type) {
     561     1069786 :     const char *valid[] = {"tmpfs", "rootfs", "devtmpfs", "cgroup",
     562             :                            "proc",  "sys",    "dev",      NULL};
     563             : 
     564     5644730 :     for(int i = 0; valid[i]; ++i) {
     565     5201898 :         if(strcmp(valid[i], fs_type) == 0) {
     566      626954 :             return true;
     567             :         }
     568             :     }
     569             : 
     570      442832 :     return false;
     571             : }
     572             : 
     573             : typedef struct RmMountEntry {
     574             :     char *fsname; /* name of mounted file system */
     575             :     char *dir;    /* file system path prefix     */
     576             :     char *type;   /* Type of fs: ufs, nfs, etc   */
     577             : } RmMountEntry;
     578             : 
     579             : typedef struct RmMountEntries {
     580             :     GList *mnt_entries;
     581             :     GList *entries;
     582             :     GList *current;
     583             : } RmMountEntries;
     584             : 
     585       36878 : static void rm_mount_list_close(RmMountEntries *self) {
     586       36878 :     g_assert(self);
     587             : 
     588     1290758 :     for(GList *iter = self->entries; iter; iter = iter->next) {
     589     1253880 :         RmMountEntry *entry = iter->data;
     590     1253880 :         g_free(entry->fsname);
     591     1253880 :         g_free(entry->dir);
     592     1253880 :         g_free(entry->type);
     593     1253880 :         g_slice_free(RmMountEntry, entry);
     594             :     }
     595             : 
     596       36878 :     g_list_free_full(self->mnt_entries, (GDestroyNotify)g_unix_mount_free);
     597       36878 :     g_list_free(self->entries);
     598       36878 :     g_slice_free(RmMountEntries, self);
     599       36878 : }
     600             : 
     601     2581516 : static RmMountEntry *rm_mount_list_next(RmMountEntries *self) {
     602     2581516 :     g_assert(self);
     603             : 
     604     2581516 :     if(self->current) {
     605     2507760 :         self->current = self->current->next;
     606             :     } else {
     607       73756 :         self->current = self->entries;
     608             :     }
     609             : 
     610     2581516 :     if(self->current) {
     611     2507760 :         return self->current->data;
     612             :     } else {
     613       73756 :         return NULL;
     614             :     }
     615             : }
     616             : 
     617       36878 : static RmMountEntries *rm_mount_list_open(RmMountTable *table) {
     618       36878 :     RmMountEntries *self = g_slice_new(RmMountEntries);
     619             : 
     620       36878 :     self->mnt_entries = g_unix_mounts_get(NULL);
     621       36878 :     self->entries = NULL;
     622       36878 :     self->current = NULL;
     623             : 
     624     1290758 :     for(GList *iter = self->mnt_entries; iter; iter = iter->next) {
     625     1253880 :         RmMountEntry *wrap_entry = g_slice_new(RmMountEntry);
     626     1253880 :         GUnixMountEntry *entry = iter->data;
     627             : 
     628     1253880 :         wrap_entry->fsname = g_strdup(g_unix_mount_get_device_path(entry));
     629     1253880 :         wrap_entry->dir = g_strdup(g_unix_mount_get_mount_path(entry));
     630     1253880 :         wrap_entry->type = g_strdup(g_unix_mount_get_fs_type(entry));
     631             : 
     632     1253880 :         self->entries = g_list_prepend(self->entries, wrap_entry);
     633             :     }
     634             : 
     635       36878 :     RmMountEntry *wrap_entry = NULL;
     636     1327636 :     while((wrap_entry = rm_mount_list_next(self))) {
     637             :         /* bindfs mounts mirror directory trees.
     638             :         * This cannot be detected properly by rmlint since
     639             :         * files in it have the same inode as their unmirrored file, but
     640             :         * a different dev_t.
     641             :         *
     642             :         * Also ignore kernel filesystems.
     643             :         *
     644             :         * So better go and ignore it.
     645             :         */
     646             :         static struct RmEvilFs {
     647             :             /* fsname as show by `mount` */
     648             :             const char *name;
     649             : 
     650             :             /* Wether to warn about the exclusion on this */
     651             :             bool unusual;
     652             :         } evilfs_types[] = {{"bindfs", 1},
     653             :                             {"nullfs", 1},
     654             :                             /* Ignore the usual linux file system spam */
     655             :                             {"proc", 0},
     656             :                             {"cgroup", 0},
     657             :                             {"configfs", 0},
     658             :                             {"sys", 0},
     659             :                             {"devtmpfs", 0},
     660             :                             {"debugfs", 0},
     661             :                             {NULL, 0}};
     662             : 
     663             :         /* btrfs and ocfs2 filesystems support reflinks for deduplication */
     664             :         static const char *reflinkfs_types[] = {"btrfs", "ocfs2", NULL};
     665             : 
     666     1253880 :         const struct RmEvilFs *evilfs_found = NULL;
     667     9772922 :         for(int i = 0; evilfs_types[i].name && !evilfs_found; ++i) {
     668     8519042 :             if(strcmp(evilfs_types[i].name, wrap_entry->type) == 0) {
     669      442536 :                 evilfs_found = &evilfs_types[i];
     670             :             }
     671             :         }
     672             : 
     673     1253880 :         const char *reflinkfs_found = NULL;
     674     3687884 :         for(int i = 0; reflinkfs_types[i] && !reflinkfs_found; ++i) {
     675     2470882 :             if(strcmp(reflinkfs_types[i], wrap_entry->type) == 0) {
     676       36878 :                 reflinkfs_found = reflinkfs_types[i];
     677       36878 :                 break;
     678             :             }
     679             :         }
     680             : 
     681     1253880 :         if(evilfs_found != NULL) {
     682             :             RmStat dir_stat;
     683      442536 :             rm_sys_stat(wrap_entry->dir, &dir_stat);
     684      442536 :             g_hash_table_insert(table->evilfs_table,
     685      442536 :                                 GUINT_TO_POINTER(dir_stat.st_dev),
     686             :                                 GUINT_TO_POINTER(1));
     687             : 
     688      442536 :             GLogLevelFlags log_level = G_LOG_LEVEL_DEBUG;
     689             : 
     690      442536 :             if(evilfs_found->unusual) {
     691           0 :                 log_level = G_LOG_LEVEL_WARNING;
     692           0 :                 rm_log_warning_prefix();
     693             :             } else {
     694      442536 :                 rm_log_debug_prefix();
     695             :             }
     696             : 
     697      885072 :             g_log("rmlint", log_level,
     698      442536 :                   _("`%s` mount detected at %s (#%u); Ignoring all files in it.\n"),
     699      442536 :                   evilfs_found->name, wrap_entry->dir, (unsigned)dir_stat.st_dev);
     700             :         }
     701             : 
     702     1253880 :         rm_log_debug_line("Filesystem %s: %s", wrap_entry->dir,
     703             :                      (reflinkfs_found) ? "reflink" : "normal");
     704             : 
     705     1253880 :         if(reflinkfs_found != NULL) {
     706             :             RmStat dir_stat;
     707       36878 :             rm_sys_stat(wrap_entry->dir, &dir_stat);
     708       36878 :             g_hash_table_insert(table->reflinkfs_table,
     709       36878 :                                 GUINT_TO_POINTER(dir_stat.st_dev),
     710             :                                 (gpointer)reflinkfs_found);
     711             :         }
     712             :     }
     713             : 
     714       36878 :     return self;
     715             : }
     716             : 
     717             : #if HAVE_SYSCTL && !RM_IS_APPLE
     718             : 
     719             : static GHashTable *DISK_TABLE = NULL;
     720             : 
     721             : static void rm_mounts_freebsd_list_disks(void) {
     722             :     char disks[1024];
     723             :     size_t disks_len = sizeof(disks);
     724             :     memset(disks, 0, sizeof(disks));
     725             : 
     726             :     DISK_TABLE = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
     727             : 
     728             :     if(sysctlbyname("kern.disks", disks, &disks_len, NULL, 0) == 0) {
     729             :         char **disk_vec = g_strsplit(disks, " ", -1);
     730             :         for(int i = 0; disk_vec[i]; ++i) {
     731             :             char *disk = g_strdup_printf("/dev/%s", disk_vec[i]);
     732             :             RmStat dev_stat;
     733             : 
     734             :             if(rm_sys_stat(disk, &dev_stat) != -1) {
     735             :                 g_hash_table_insert(DISK_TABLE, disk, GUINT_TO_POINTER(dev_stat.st_rdev));
     736             :             } else {
     737             :                 rm_log_perror("stat on /dev");
     738             :                 g_free(disk);
     739             :             }
     740             :         }
     741             : 
     742             :         g_strfreev(disk_vec);
     743             :     } else {
     744             :         rm_log_perror("sysctlbyname");
     745             :     }
     746             : }
     747             : #endif
     748             : 
     749      110634 : int rm_mounts_devno_to_wholedisk(_U RmMountEntry *entry, _U dev_t rdev, _U char *disk,
     750             :                                  _U size_t disk_size, _U dev_t *result) {
     751             : #if HAVE_BLKID
     752      110634 :     return blkid_devno_to_wholedisk(rdev, disk, disk_size, result);
     753             : #elif HAVE_SYSCTL && !RM_IS_APPLE
     754             :     if(DISK_TABLE == NULL) {
     755             :         rm_mounts_freebsd_list_disks();
     756             :     }
     757             : 
     758             :     GHashTableIter iter;
     759             :     g_hash_table_iter_init(&iter, DISK_TABLE);
     760             : 
     761             :     gpointer key = NULL;
     762             :     gpointer value = NULL;
     763             : 
     764             :     while(g_hash_table_iter_next(&iter, &key, &value)) {
     765             :         char *str_key = key;
     766             :         if(g_str_has_prefix(str_key, entry->fsname)) {
     767             :             strncpy(disk, strrchr(str_key, '/'), disk_size);
     768             :             *result = (dev_t)GPOINTER_TO_UINT(value);
     769             :             return 0;
     770             :         }
     771             :     }
     772             : #else
     773             :     return -1;
     774             : #endif
     775             : }
     776             : 
     777       36878 : static bool rm_mounts_create_tables(RmMountTable *self, bool force_fiemap) {
     778             :     /* partition dev_t to disk dev_t */
     779       36878 :     self->part_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
     780             :                                              (GDestroyNotify)rm_part_info_free);
     781             : 
     782             :     /* disk dev_t to boolean indication if disk is rotational */
     783       36878 :     self->disk_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
     784             :                                              (GDestroyNotify)rm_disk_info_free);
     785             : 
     786       36878 :     self->nfs_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
     787             : 
     788             :     /* Mapping dev_t => true (used as set) */
     789       36878 :     self->evilfs_table = g_hash_table_new(NULL, NULL);
     790       36878 :     self->reflinkfs_table = g_hash_table_new(NULL, NULL);
     791             : 
     792       36878 :     RmMountEntry *entry = NULL;
     793       36878 :     RmMountEntries *mnt_entries = rm_mount_list_open(self);
     794             : 
     795       36878 :     if(mnt_entries == NULL) {
     796           0 :         return false;
     797             :     }
     798             : 
     799     1327636 :     while((entry = rm_mount_list_next(mnt_entries))) {
     800             :         RmStat stat_buf_folder;
     801     1253880 :         if(rm_sys_stat(entry->dir, &stat_buf_folder) == -1) {
     802      183826 :             continue;
     803             :         }
     804             : 
     805     1180420 :         dev_t whole_disk = 0;
     806     1180420 :         gchar is_rotational = true;
     807             :         char diskname[PATH_MAX];
     808     1180420 :         memset(diskname, 0, sizeof(diskname));
     809             : 
     810             :         RmStat stat_buf_dev;
     811     1180420 :         if(rm_sys_stat(entry->fsname, &stat_buf_dev) == -1) {
     812     1069786 :             char *nfs_marker = NULL;
     813             :             /* folder rm_sys_stat() is ok but devname rm_sys_stat() is not; this happens
     814             :              * for example
     815             :              * with tmpfs and with nfs mounts.  Try to handle a few such cases.
     816             :              * */
     817     1069786 :             if(rm_mounts_is_ramdisk(entry->fsname)) {
     818      626954 :                 strncpy(diskname, entry->fsname, sizeof(diskname));
     819      626954 :                 is_rotational = false;
     820      626954 :                 whole_disk = stat_buf_folder.st_dev;
     821      442832 :             } else if((nfs_marker = strstr(entry->fsname, ":/")) != NULL) {
     822           0 :                 size_t until_slash =
     823           0 :                     MIN((int)sizeof(entry->fsname), nfs_marker - entry->fsname);
     824           0 :                 strncpy(diskname, entry->fsname, until_slash);
     825           0 :                 is_rotational = true;
     826             : 
     827             :                 /* Assign different dev ids (with major id 0) to different nfs servers */
     828           0 :                 if(!g_hash_table_contains(self->nfs_table, diskname)) {
     829           0 :                     g_hash_table_insert(self->nfs_table, g_strdup(diskname), NULL);
     830             :                 }
     831           0 :                 whole_disk = makedev(0, g_hash_table_size(self->nfs_table));
     832             :             } else {
     833      442832 :                 strncpy(diskname, "unknown", sizeof(diskname));
     834      442832 :                 is_rotational = true;
     835      442832 :                 whole_disk = 0;
     836             :             }
     837             :         } else {
     838      110634 :             if(rm_mounts_devno_to_wholedisk(entry, stat_buf_dev.st_rdev, diskname,
     839             :                                             sizeof(diskname), &whole_disk) == -1) {
     840             :                 /* folder and devname rm_sys_stat() are ok but blkid failed; this happens
     841             :                  * when?
     842             :                  * Treat as a non-rotational device using devname dev as whole_disk key
     843             :                  * */
     844           0 :                 rm_log_debug_line(RED "devno_to_wholedisk failed for %s" RESET,
     845             :                              entry->fsname);
     846           0 :                 whole_disk = stat_buf_dev.st_dev;
     847           0 :                 strncpy(diskname, entry->fsname, sizeof(diskname));
     848           0 :                 is_rotational = false;
     849             :             } else {
     850      110634 :                 is_rotational = rm_mounts_is_rotational_blockdev(diskname);
     851             :             }
     852             :         }
     853             : 
     854     1180420 :         is_rotational |= force_fiemap;
     855             : 
     856     1180420 :         RmPartitionInfo *existing = g_hash_table_lookup(
     857     1180420 :             self->part_table, GUINT_TO_POINTER(stat_buf_folder.st_dev));
     858     1180420 :         if(!existing || (existing->disk == 0 && whole_disk != 0)) {
     859     1143514 :             if(existing) {
     860           0 :                 rm_log_debug_line("Replacing part_table entry %s for path %s with %s",
     861             :                              existing->fsname, entry->dir, entry->fsname);
     862             :             }
     863     2287028 :             g_hash_table_insert(self->part_table,
     864     1143514 :                                 GUINT_TO_POINTER(stat_buf_folder.st_dev),
     865     1143514 :                                 rm_part_info_new(entry->dir, entry->fsname, whole_disk));
     866             :         } else {
     867       36906 :             rm_log_debug_line("Skipping duplicate mount entry for dir %s dev %02u:%02u",
     868             :                          entry->dir, major(stat_buf_folder.st_dev),
     869             :                          minor(stat_buf_folder.st_dev));
     870       36906 :             continue;
     871             :         }
     872             : 
     873             :         /* small hack, so also the full disk id can be given to the api below */
     874     1143514 :         if(!g_hash_table_contains(self->part_table, GINT_TO_POINTER(whole_disk))) {
     875      295024 :             g_hash_table_insert(self->part_table,
     876             :                                 GUINT_TO_POINTER(whole_disk),
     877      147512 :                                 rm_part_info_new(entry->dir, entry->fsname, whole_disk));
     878             :         }
     879             : 
     880     1143514 :         if(!g_hash_table_contains(self->disk_table, GINT_TO_POINTER(whole_disk))) {
     881      737560 :             g_hash_table_insert(self->disk_table,
     882             :                                 GINT_TO_POINTER(whole_disk),
     883      737560 :                                 rm_disk_info_new(diskname, is_rotational));
     884             :         }
     885             : 
     886     1143514 :         rm_log_debug_line(
     887             :             "%02u:%02u %50s -> %02u:%02u %-12s (underlying disk: %s; rotational: %3s)",
     888             :             major(stat_buf_folder.st_dev), minor(stat_buf_folder.st_dev), entry->dir,
     889             :             major(whole_disk), minor(whole_disk), entry->fsname, diskname,
     890             :             is_rotational ? "yes" : "no");
     891             :     }
     892             : 
     893             : #if HAVE_SYSCTL && !RM_IS_APPLE
     894             :     if(DISK_TABLE) {
     895             :         g_hash_table_unref(DISK_TABLE);
     896             :     }
     897             : #endif
     898             : 
     899       36878 :     rm_mount_list_close(mnt_entries);
     900       36878 :     return true;
     901             : }
     902             : 
     903             : /////////////////////////////////
     904             : //         PUBLIC API          //
     905             : /////////////////////////////////
     906             : 
     907       36878 : RmMountTable *rm_mounts_table_new(bool force_fiemap) {
     908       36878 :     RmMountTable *self = g_slice_new(RmMountTable);
     909       36878 :     if(rm_mounts_create_tables(self, force_fiemap) == false) {
     910           0 :         g_slice_free(RmMountTable, self);
     911           0 :         return NULL;
     912             :     } else {
     913       36878 :         return self;
     914             :     }
     915             : }
     916             : 
     917       36878 : void rm_mounts_table_destroy(RmMountTable *self) {
     918       36878 :     g_hash_table_unref(self->part_table);
     919       36878 :     g_hash_table_unref(self->disk_table);
     920       36878 :     g_hash_table_unref(self->nfs_table);
     921       36878 :     g_hash_table_unref(self->evilfs_table);
     922       36878 :     g_hash_table_unref(self->reflinkfs_table);
     923       36878 :     g_slice_free(RmMountTable, self);
     924       36878 : }
     925             : 
     926             : #else /* probably FreeBSD */
     927             : 
     928             : RmMountTable *rm_mounts_table_new(_U bool force_fiemap) {
     929             :     return NULL;
     930             : }
     931             : 
     932             : void rm_mounts_table_destroy(_U RmMountTable *self) {
     933             :     /* NO-OP */
     934             : }
     935             : 
     936             : #endif
     937             : 
     938      462547 : bool rm_mounts_is_nonrotational(RmMountTable *self, dev_t device) {
     939      462547 :     if(self == NULL) {
     940           0 :         return true;
     941             :     }
     942             : 
     943      462547 :     RmPartitionInfo *part =
     944      462547 :         g_hash_table_lookup(self->part_table, GINT_TO_POINTER(device));
     945      462547 :     if(part) {
     946      462547 :         RmDiskInfo *disk =
     947      462547 :             g_hash_table_lookup(self->disk_table, GINT_TO_POINTER(part->disk));
     948      462547 :         if(disk) {
     949      462547 :             return !disk->is_rotational;
     950             :         } else {
     951           0 :             rm_log_error_line("Disk not found in rm_mounts_is_nonrotational");
     952           0 :             return true;
     953             :         }
     954             :     } else {
     955           0 :         rm_log_error_line("Partition not found in rm_mounts_is_nonrotational");
     956           0 :         return true;
     957             :     }
     958             : }
     959             : 
     960             : // static void rm_mounts_subvol_add(RmMountTable *self, dev_t subvol, dev_t parent) {
     961             : // if(g_hash_table_contains(self->subvol_table, GUINT_TO_POINTER(parent))) {
     962             : /* parent volume is a subvolume itself */
     963             : 
     964             : //}
     965             : //}
     966             : 
     967      740742 : dev_t rm_mounts_get_disk_id(RmMountTable *self, dev_t partition, const char *path) {
     968      740742 :     if(self == NULL) {
     969           0 :         return 0;
     970             :     }
     971             : 
     972      740742 :     RmPartitionInfo *part =
     973      740742 :         g_hash_table_lookup(self->part_table, GINT_TO_POINTER(partition));
     974      740742 :     if(part) {
     975      740742 :         return part->disk;
     976             :     } else {
     977             :         /* probably a btrfs subvolume which is not a mountpoint; walk up tree until we get
     978             :          * to *
     979             :          * a recognisable partition */
     980           0 :         char *prev = g_strdup(path);
     981             :         while(TRUE) {
     982           0 :             char *temp = g_strdup(prev);
     983           0 :             char *parent_path = g_strdup(dirname(temp));
     984           0 :             g_free(temp);
     985             : 
     986             :             RmStat stat_buf;
     987           0 :             if(!rm_sys_stat(parent_path, &stat_buf)) {
     988           0 :                 RmPartitionInfo *parent_part = g_hash_table_lookup(
     989           0 :                     self->part_table, GINT_TO_POINTER(stat_buf.st_dev));
     990           0 :                 if(parent_part) {
     991             :                     /* create new partition table entry */
     992           0 :                     rm_log_debug_line("Adding partition info for " GREEN "%s" RESET
     993             :                                  " - looks like subvolume %s on disk " GREEN "%s" RESET,
     994             :                                  path, prev, parent_part->name);
     995           0 :                     part = rm_part_info_new(prev, parent_part->fsname, parent_part->disk);
     996           0 :                     g_hash_table_insert(self->part_table, GINT_TO_POINTER(partition),
     997             :                                         part);
     998           0 :                     if(g_hash_table_contains(self->reflinkfs_table,
     999           0 :                                              GUINT_TO_POINTER(stat_buf.st_dev))) {
    1000           0 :                         g_hash_table_insert(self->reflinkfs_table,
    1001             :                                             GUINT_TO_POINTER(partition),
    1002             :                                             GUINT_TO_POINTER(1));
    1003             :                     }
    1004           0 :                     g_free(prev);
    1005           0 :                     g_free(parent_path);
    1006           0 :                     return parent_part->disk;
    1007             :                 }
    1008             :             }
    1009           0 :             g_free(prev);
    1010           0 :             prev = parent_path;
    1011           0 :             g_assert(strcmp(prev, "/") != 0);
    1012           0 :             g_assert(strcmp(prev, ".") != 0);
    1013           0 :         }
    1014             :     }
    1015             : }
    1016             : 
    1017       97662 : dev_t rm_mounts_get_disk_id_by_path(RmMountTable *self, const char *path) {
    1018       97662 :     if(self == NULL) {
    1019           0 :         return 0;
    1020             :     }
    1021             : 
    1022             :     RmStat stat_buf;
    1023       97662 :     if(rm_sys_stat(path, &stat_buf) == -1) {
    1024           0 :         return 0;
    1025             :     }
    1026             : 
    1027       97662 :     return rm_mounts_get_disk_id(self, stat_buf.st_dev, path);
    1028             : }
    1029             : 
    1030           0 : char *rm_mounts_get_disk_name(RmMountTable *self, dev_t device) {
    1031           0 :     if(self == NULL) {
    1032           0 :         return NULL;
    1033             :     }
    1034             : 
    1035           0 :     RmPartitionInfo *part =
    1036           0 :         g_hash_table_lookup(self->part_table, GINT_TO_POINTER(device));
    1037           0 :     if(part) {
    1038           0 :         RmDiskInfo *disk =
    1039           0 :             g_hash_table_lookup(self->disk_table, GINT_TO_POINTER(part->disk));
    1040           0 :         return disk->name;
    1041             :     } else {
    1042           0 :         return NULL;
    1043             :     }
    1044             : }
    1045             : 
    1046      224600 : bool rm_mounts_is_evil(RmMountTable *self, dev_t to_check) {
    1047      224600 :     g_assert(self);
    1048             : 
    1049      224600 :     return g_hash_table_contains(self->evilfs_table, GUINT_TO_POINTER(to_check));
    1050             : }
    1051             : 
    1052           0 : bool rm_mounts_can_reflink(RmMountTable *self, dev_t source, dev_t dest) {
    1053           0 :     g_assert(self);
    1054           0 :     if(g_hash_table_contains(self->reflinkfs_table, GUINT_TO_POINTER(source))) {
    1055           0 :         if(source == dest) {
    1056           0 :             return true;
    1057             :         } else {
    1058           0 :             RmPartitionInfo *source_part =
    1059           0 :                 g_hash_table_lookup(self->part_table, GINT_TO_POINTER(source));
    1060           0 :             RmPartitionInfo *dest_part =
    1061           0 :                 g_hash_table_lookup(self->part_table, GINT_TO_POINTER(dest));
    1062           0 :             g_assert(source_part);
    1063           0 :             g_assert(dest_part);
    1064           0 :             return (strcmp(source_part->fsname, dest_part->fsname) == 0);
    1065             :         }
    1066             :     } else {
    1067           0 :         return false;
    1068             :     }
    1069             : }
    1070             : 
    1071             : /////////////////////////////////
    1072             : //    FIEMAP IMPLEMENATION     //
    1073             : /////////////////////////////////
    1074             : 
    1075             : #if HAVE_FIEMAP
    1076             : 
    1077             : /* Return fiemap structure containing n_extents for file descriptor fd.
    1078             :  * Return NULL if errors encountered.
    1079             :  * Needs to be freed with g_free if not NULL.
    1080             :  * */
    1081        7867 : static struct fiemap *rm_offset_get_fiemap(int fd, const int n_extents,
    1082             :                                            const int file_offset) {
    1083             :     /* struct fiemap does not allocate any extents by default,
    1084             :      * so we allocate the nominated number
    1085             :      * */
    1086        7867 :     struct fiemap *fm =
    1087        7867 :         g_malloc0(sizeof(struct fiemap) + n_extents * sizeof(struct fiemap_extent));
    1088             : 
    1089        7867 :     fm->fm_flags = 0;
    1090        7867 :     fm->fm_extent_count = n_extents;
    1091        7867 :     fm->fm_length = FIEMAP_MAX_OFFSET;
    1092        7867 :     fm->fm_start = file_offset;
    1093             : 
    1094        7867 :     if(ioctl(fd, FS_IOC_FIEMAP, (unsigned long)fm) == -1) {
    1095        7867 :         g_free(fm);
    1096        7867 :         fm = NULL;
    1097             :     }
    1098        7867 :     return fm;
    1099             : }
    1100             : 
    1101             : /* Return physical (disk) offset of the beginning of the file extent containing the
    1102             :  * specified logical file_offset.
    1103             :  * If a pointer to file_offset_next is provided then read fiemap extents until
    1104             :  * the next non-contiguous extent (fragment) is encountered and writes the corresponding
    1105             :  * file offset to &file_offset_next.
    1106             :  * */
    1107        7867 : RmOff rm_offset_get_from_fd(int fd, RmOff file_offset, RmOff *file_offset_next) {
    1108        7867 :     RmOff result = 0;
    1109        7867 :     bool done = FALSE;
    1110             : 
    1111             :     /* used for detecting contiguous extents */
    1112        7867 :     unsigned long expected = 0;
    1113             : 
    1114       23601 :     while(!done) {
    1115             :         /* read in one extent */
    1116        7867 :         struct fiemap *fm = rm_offset_get_fiemap(fd, 1, file_offset);
    1117        7867 :         if(!fm) {
    1118        7867 :             done = TRUE;
    1119             :         } else {
    1120           0 :             if(!file_offset_next) {
    1121             :                 /* no need to find end of fragment so one loop is enough*/
    1122           0 :                 done = TRUE;
    1123             :             }
    1124           0 :             if(fm->fm_mapped_extents > 0) {
    1125             :                 /* retrieve data from fiemap */
    1126           0 :                 struct fiemap_extent *fm_ext = fm->fm_extents;
    1127           0 :                 file_offset += fm_ext[0].fe_length;
    1128           0 :                 if(result == 0) {
    1129             :                     /* this is the first extent */
    1130           0 :                     result = fm_ext[0].fe_physical;
    1131           0 :                     if(result == 0) {
    1132             :                         /* looks suspicious - let's get out of here */
    1133           0 :                         done = TRUE;
    1134             :                     }
    1135           0 :                 } else if(fm_ext[0].fe_physical > expected ||
    1136           0 :                           fm_ext[0].fe_physical < result) {
    1137             :                     /* current extent is not contiguous with previous, so we can stop*/
    1138           0 :                     done = TRUE;
    1139           0 :                     if(file_offset_next) {
    1140             :                         /* caller wants to know logical offset of next fragment */
    1141           0 :                         *file_offset_next = fm_ext[0].fe_logical;
    1142             :                     }
    1143             :                 }
    1144           0 :                 if(fm_ext[0].fe_flags & FIEMAP_EXTENT_LAST) {
    1145           0 :                     if(!done) {
    1146           0 :                         done = TRUE;
    1147           0 :                         if(file_offset_next) {
    1148             :                             /* caller wants to know logical offset of next fragment -
    1149             :                              * signal
    1150             :                              * that it is EOF */
    1151           0 :                             *file_offset_next =
    1152           0 :                                 fm_ext[0].fe_logical + fm_ext[0].fe_length;
    1153             :                         }
    1154             :                     }
    1155             :                 }
    1156           0 :                 if(!done) {
    1157           0 :                     expected = fm_ext[0].fe_physical + fm_ext[0].fe_length;
    1158             :                 }
    1159             :             } else {
    1160             :                 /* got no extents from rm_offset_get_fiemap */
    1161           0 :                 done = true;
    1162           0 :                 if(file_offset_next) {
    1163             :                     /* caller wants to know logical offset of next fragment but
    1164             :                      * we have an error... */
    1165           0 :                     *file_offset_next = 0;
    1166             :                 }
    1167             :             }
    1168           0 :             g_free(fm);
    1169             :         }
    1170             :     }
    1171        7867 :     return result;
    1172             : }
    1173             : 
    1174        7867 : RmOff rm_offset_get_from_path(const char *path, RmOff file_offset,
    1175             :                               RmOff *file_offset_next) {
    1176        7867 :     int fd = rm_sys_open(path, O_RDONLY);
    1177        7867 :     if(fd == -1) {
    1178           0 :         rm_log_info("Error opening %s in rm_offset_get_from_path\n", path);
    1179           0 :         return 0;
    1180             :     }
    1181        7867 :     RmOff result = rm_offset_get_from_fd(fd, file_offset, file_offset_next);
    1182        7867 :     rm_sys_close(fd);
    1183        7867 :     return result;
    1184             : }
    1185             : 
    1186           0 : bool rm_offsets_match(char *path1, char *path2) {
    1187           0 :     bool result = FALSE;
    1188           0 :     int fd1 = rm_sys_open(path1, O_RDONLY);
    1189           0 :     if(fd1 != -1) {
    1190           0 :         int fd2 = rm_sys_open(path2, O_RDONLY);
    1191           0 :         if(fd2 != -1) {
    1192           0 :             RmOff file1_offset_next = 0;
    1193           0 :             RmOff file2_offset_next = 0;
    1194           0 :             RmOff file_offset_current = 0;
    1195           0 :             while(!result &&
    1196           0 :                   (rm_offset_get_from_fd(fd1, file_offset_current, &file1_offset_next) ==
    1197           0 :                    rm_offset_get_from_fd(fd2, file_offset_current, &file2_offset_next)) &&
    1198           0 :                   file1_offset_next != 0 && file1_offset_next == file2_offset_next) {
    1199           0 :                 if(file1_offset_next == file_offset_current) {
    1200             :                     /* phew, we got to the end */
    1201           0 :                     result = TRUE;
    1202           0 :                     break;
    1203             :                 }
    1204           0 :                 file_offset_current = file1_offset_next;
    1205             :             }
    1206           0 :             rm_sys_close(fd2);
    1207             :         } else {
    1208           0 :             rm_log_info("Error opening %s in rm_offsets_match\n", path2);
    1209             :         }
    1210           0 :         rm_sys_close(fd1);
    1211             :     } else {
    1212           0 :         rm_log_info("Error opening %s in rm_offsets_match\n", path1);
    1213             :     }
    1214           0 :     return result;
    1215             : }
    1216             : 
    1217             : #else /* Probably FreeBSD */
    1218             : 
    1219             : RmOff rm_offset_get_from_fd(_U int fd, _U RmOff file_offset, _U RmOff *file_offset_next) {
    1220             :     return 0;
    1221             : }
    1222             : 
    1223             : RmOff rm_offset_get_from_path(_U const char *path, _U RmOff file_offset,
    1224             :                               _U RmOff *file_offset_next) {
    1225             :     return 0;
    1226             : }
    1227             : 
    1228             : bool rm_offsets_match(char *path1, char *path2) {
    1229             :     return (path1 == path2);
    1230             : }
    1231             : 
    1232             : #endif
    1233             : 
    1234             : /////////////////////////////////
    1235             : //  GTHREADPOOL WRAPPERS       //
    1236             : /////////////////////////////////
    1237             : 
    1238             : /* wrapper for g_thread_pool_push with error reporting */
    1239      675579 : bool rm_util_thread_pool_push(GThreadPool *pool, gpointer data) {
    1240      675579 :     GError *error = NULL;
    1241      675579 :     g_thread_pool_push(pool, data, &error);
    1242      675646 :     if(error != NULL) {
    1243           0 :         rm_log_error_line("Unable to push thread to pool %p: %s", pool, error->message);
    1244           0 :         g_error_free(error);
    1245           4 :         return false;
    1246             :     } else {
    1247      675646 :         return true;
    1248             :     }
    1249             : }
    1250             : 
    1251             : /* wrapper for g_thread_pool_new with error reporting */
    1252     1236243 : GThreadPool *rm_util_thread_pool_new(GFunc func, gpointer data, int threads) {
    1253     1236243 :     GError *error = NULL;
    1254     1236243 :     GThreadPool *pool = g_thread_pool_new(func, data, threads, FALSE, &error);
    1255             : 
    1256     1236243 :     if(error != NULL) {
    1257           0 :         rm_log_error_line("Unable to create thread pool.");
    1258           0 :         g_error_free(error);
    1259             :     }
    1260     1236243 :     return pool;
    1261             : }
    1262             : 
    1263             : //////////////////////////////
    1264             : //    TIMESTAMP HELPERS     //
    1265             : //////////////////////////////
    1266             : 
    1267           3 : time_t rm_iso8601_parse(const char *string) {
    1268             :     GTimeVal time_result;
    1269           3 :     if(!g_time_val_from_iso8601(string, &time_result)) {
    1270           0 :         rm_log_perror("Converting time failed");
    1271           0 :         return 0;
    1272             :     }
    1273             : 
    1274           3 :     return time_result.tv_sec;
    1275             : }
    1276             : 
    1277           7 : bool rm_iso8601_format(time_t stamp, char *buf, gsize buf_size) {
    1278           7 :     const struct tm *now_ctime = localtime(&stamp);
    1279           7 :     return (strftime(buf, buf_size, "%FT%T%z", now_ctime) != 0);
    1280             : }

Generated by: LCOV version 1.11