Example #1
0
static gint64 rm_hasher_symlink_read(RmHasher *hasher, RmDigest *digest, char *path) {
    /* Fake an IO operation on the symlink.  */
    RmBuffer *buf = rm_buffer_get(hasher->mem_pool);
    buf->len = 256;
    memset(buf->data, 0, buf->len);

    RmStat stat_buf;
    if(rm_sys_stat(path, &stat_buf) == -1) {
        /* Oops, that did not work out, report as an error */
        rm_log_perror("Cannot stat symbolic link");
        return -1;
    }

    gint data_size = snprintf((char *)buf->data, rm_buffer_size(hasher->mem_pool),
                              "%ld:%ld", (long)stat_buf.st_dev, (long)stat_buf.st_ino);
    buf->len = data_size;
    buf->digest = digest;

    rm_digest_buffered_update(buf);

    /* In case of paranoia: shrink the used data buffer, so comparasion works
     * as expected. Otherwise a full buffer is used with possibly different
     * content */
    if(digest->type == RM_DIGEST_PARANOID) {
        rm_digest_paranoia_shrink(digest, data_size);
    }
    return 0;
}
Example #2
0
static void rm_mounts_freebsd_list_disks(void) {
    char disks[1024];
    size_t disks_len = sizeof(disks);
    memset(disks, 0, sizeof(disks));

    DISK_TABLE = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);

    if(sysctlbyname("kern.disks", disks, &disks_len, NULL, 0) == 0) {
        char **disk_vec = g_strsplit(disks, " ", -1);
        for(int i = 0; disk_vec[i]; ++i) {
            char *disk = g_strdup_printf("/dev/%s", disk_vec[i]);
            RmStat dev_stat;

            if(rm_sys_stat(disk, &dev_stat) != -1) {
                g_hash_table_insert(DISK_TABLE, disk, GUINT_TO_POINTER(dev_stat.st_rdev));
            } else {
                rm_log_perror("stat on /dev");
                g_free(disk);
            }
        }

        g_strfreev(disk_vec);
    } else {
        rm_log_perror("sysctlbyname");
    }
}
Example #3
0
static ino_t rm_path_parent_inode(RmFile *file) {
    char parent_path[PATH_MAX];
    rm_trie_build_path((RmTrie *)&file->session->cfg->file_trie, file->folder->parent, parent_path, PATH_MAX);
    RmStat stat_buf;
    int retval = rm_sys_stat(parent_path, &stat_buf);
    rm_assert_gentle(retval != -1);
    return stat_buf.st_ino;
}
Example #4
0
dev_t rm_mounts_get_disk_id(RmMountTable *self, dev_t partition, const char *path) {
    if(self == NULL) {
        return 0;
    }

#if RM_MOUNTTABLE_IS_USABLE

    RmPartitionInfo *part =
        g_hash_table_lookup(self->part_table, GINT_TO_POINTER(partition));
    if(part) {
        return part->disk;
    } else {
        /* probably a btrfs subvolume which is not a mountpoint; walk up tree until we get
         * to *
         * a recognisable partition */
        char *prev = g_strdup(path);
        while(TRUE) {
            char *temp = g_strdup(prev);
            char *parent_path = g_strdup(dirname(temp));
            g_free(temp);

            RmStat stat_buf;
            if(!rm_sys_stat(parent_path, &stat_buf)) {
                RmPartitionInfo *parent_part = g_hash_table_lookup(
                    self->part_table, GINT_TO_POINTER(stat_buf.st_dev));
                if(parent_part) {
                    /* create new partition table entry */
                    rm_log_debug_line("Adding partition info for " GREEN "%s" RESET
                                      " - looks like subvolume %s on disk " GREEN
                                      "%s" RESET,
                                      path, prev, parent_part->name);
                    part = rm_part_info_new(prev, parent_part->fsname, parent_part->disk);
                    g_hash_table_insert(self->part_table, GINT_TO_POINTER(partition),
                                        part);
                    if(g_hash_table_contains(self->reflinkfs_table,
                                             GUINT_TO_POINTER(stat_buf.st_dev))) {
                        g_hash_table_insert(self->reflinkfs_table,
                                            GUINT_TO_POINTER(partition),
                                            GUINT_TO_POINTER(1));
                    }
                    g_free(prev);
                    g_free(parent_path);
                    return parent_part->disk;
                }
            }
            g_free(prev);
            prev = parent_path;
            rm_assert_gentle(strcmp(prev, "/") != 0);
            rm_assert_gentle(strcmp(prev, ".") != 0);
        }
    }
#else
    (void) partition;
    (void) path;
    return 0;
#endif
}
Example #5
0
bool rm_mounts_is_nonrotational_by_path(RmMountTable *self, const char *path) {
    if(self == NULL) {
        return -1;
    }

    RmStat stat_buf;
    if(rm_sys_stat(path, &stat_buf) == -1) {
        return -1;
    }
    return rm_mounts_is_nonrotational(self, stat_buf.st_dev);
}
Example #6
0
static ino_t rm_path_parent_inode(RmFile *file) {
    char parent_path[PATH_MAX];
    rm_trie_build_path((RmTrie *)&file->session->cfg->file_trie, file->folder->parent, parent_path, PATH_MAX);
    RmStat stat_buf;
    int retval = rm_sys_stat(parent_path, &stat_buf);
    if (retval == -1) {
        rm_log_error_line("Failed to get parent path: stat failed: %s", g_strerror(errno));
        return 0;
    }

    return stat_buf.st_ino;
}
Example #7
0
ino_t rm_util_parent_node(const char *path) {
    char *parent_path = g_path_get_dirname(path);

    RmStat stat_buf;
    if(!rm_sys_stat(parent_path, &stat_buf)) {
        g_free(parent_path);
        return stat_buf.st_ino;
    } else {
        g_free(parent_path);
        return -1;
    }
}
Example #8
0
dev_t rm_mounts_get_disk_id_by_path(RmMountTable *self, const char *path) {
    if(self == NULL) {
        return 0;
    }

    RmStat stat_buf;
    if(rm_sys_stat(path, &stat_buf) == -1) {
        return 0;
    }

    return rm_mounts_get_disk_id(self, stat_buf.st_dev);
}
Example #9
0
ino_t rm_util_parent_node(const char *path) {
    char *dummy  = g_strdup(path);
    char *parent_path = g_strdup(dirname(dummy));
    g_free(dummy);

    RmStat stat_buf;
    if(!rm_sys_stat(parent_path, &stat_buf)) {
        g_free(parent_path);
        return stat_buf.st_ino;
    } else {
        g_free(parent_path);
        return -1;
    }
}
Example #10
0
void rm_json_cache_parse_entry(_U JsonArray *array, _U guint index,
                               JsonNode *element_node, RmTrie *file_trie) {
    if(JSON_NODE_TYPE(element_node) != JSON_NODE_OBJECT) {
        return;
    }

    JsonObject *object = json_node_get_object(element_node);
    JsonNode *mtime_node = json_object_get_member(object, "mtime");
    JsonNode *path_node = json_object_get_member(object, "path");
    JsonNode *cksum_node = json_object_get_member(object, "checksum");
    JsonNode *type_node = json_object_get_member(object, "type");

    if(mtime_node && path_node && cksum_node && type_node) {
        RmStat stat_buf;
        const char *path = json_node_get_string(path_node);
        const char *cksum = json_node_get_string(cksum_node);
        const char *type = json_node_get_string(type_node);

        if(g_strcmp0(type, "duplicate_file") && g_strcmp0(type, "unfinished_cksum")) {
            /* some other file that has a checksum for weird reasons.
             * This is here to prevent errors like reporting files with
             * empty checksums as duplicates.
             * */
            return;
        }

        if(rm_sys_stat(path, &stat_buf) == -1) {
            /* file does not appear to exist */
            return;
        }

        if(json_node_get_int(mtime_node) < rm_sys_stat_mtime_seconds(&stat_buf)) {
            /* file is newer than stored checksum */
            return;
        }

        char *cksum_copy = g_strdup(cksum);
        if(!rm_trie_set_value(file_trie, path, cksum_copy)) {
            g_free(cksum_copy);
        }
        rm_log_debug_line("* Adding cache entry %s (%s)", path, cksum);
    }
}
Example #11
0
static RmDirectory *rm_directory_new(char *dirname) {
    RmDirectory *self = g_new0(RmDirectory, 1);

    self->file_count = 0;
    self->dupe_count = 0;
    self->prefd_files = 0;
    self->was_merged = false;
    self->was_inserted = false;
    self->mergeups = 0;

    self->dirname = dirname;
    self->finished = false;

    self->depth = 0;
    for(char *s = dirname; *s; s++) {
        self->depth += (*s == G_DIR_SEPARATOR);
    }

    RmStat dir_stat;
    if(rm_sys_stat(self->dirname, &dir_stat) == -1) {
        rm_log_perror("stat(2) failed during sort");
    } else {
        self->metadata.dir_mtime = dir_stat.st_mtime;
        self->metadata.dir_inode = dir_stat.st_ino;
        self->metadata.dir_dev = dir_stat.st_dev;
    }

    /* Special cumulative hashsum, that is not dependent on the
     * order in which the file hashes were added.
     * It is not used as full hash, but as sorting speedup.
     */
    self->digest = rm_digest_new(RM_DIGEST_CUMULATIVE, 0, 0, 0, false);

    g_queue_init(&self->known_files);
    g_queue_init(&self->children);

    self->hash_set =
        g_hash_table_new((GHashFunc)rm_digest_hash, (GEqualFunc)rm_digest_equal);

    return self;
}
Example #12
0
static RmTravBuffer *rm_trav_buffer_new(RmSession *session, char *path, bool is_prefd,
                                        unsigned long path_index) {
    RmTravBuffer *self = g_new0(RmTravBuffer, 1);
    self->path = path;
    self->is_prefd = is_prefd;
    self->path_index = path_index;

    RM_BUFFER_DEFINE_PATH(session, self);

    int stat_state;
    if(session->cfg->follow_symlinks) {
        stat_state = rm_sys_stat(self_path, &self->stat_buf);
    } else {
        stat_state = rm_sys_lstat(self_path, &self->stat_buf);
    }

    if(stat_state == -1) {
        rm_log_perror("Unable to stat file");
    }
    return self;
}
Example #13
0
int main(int argc, char *argv[]) {
    RmStat stat_buf;
    bool has_gid, has_uid;
    RmUserGroupNode **list = rm_userlist_new();
    if(argc < 2) {
        puts("Usage: prog <path>");
        return EXIT_FAILURE;
    }
    if(rm_sys_stat(argv[1], &stat_buf) != 0) {
        return EXIT_FAILURE;
    }
    printf("File has UID %"LLU" and GID %"LLU"\n",
           (unsigned long)stat_buf.st_uid,
           (unsigned long)stat_buf.st_gid
          );
    rm_userlist_contains(list, stat_buf.st_uid, stat_buf.st_gid, &has_uid, &has_gid);
    printf("=> Valid UID = %s\n", yes(has_uid));
    printf("=> Valid GID = %s\n", yes(has_gid));
    rm_userlist_destroy(list);
    return EXIT_SUCCESS;
}
Example #14
0
int rm_hasher_main(int argc, const char **argv) {
    RmHasherSession tag;

    /* List of paths we got passed (or NULL)   */
    tag.paths = NULL;

    /* Print hashes in the same order as files in command line args */
    tag.print_in_order = TRUE;

    /* Print a hash with builtin identifier */
    tag.print_multihash = FALSE;

    /* Digest type (user option, default SHA1) */
    tag.digest_type = RM_DIGEST_SHA1;
    gint threads = 8;
    gint64 buffer_mbytes = 256;

    ////////////// Option Parsing ///////////////

    /* clang-format off */

    const GOptionEntry entries[] = {
        {"digest-type"    , 'd'  , 0                      , G_OPTION_ARG_CALLBACK        , (GOptionArgFunc)rm_hasher_parse_type  , _("Digest type [SHA1]")                                                            , "[TYPE]"}   ,
        {"num-threads"    , 't'  , 0                      , G_OPTION_ARG_INT             , &threads                              , _("Number of hashing threads [8]")                                                 , "N"}        ,
        {"multihash"      , 'm'  , 0                      , G_OPTION_ARG_NONE            , &tag.print_multihash                  , _("Print hash as self identifying multihash")                                      , NULL}       ,
        {"buffer-mbytes"  , 'b'  , 0                      , G_OPTION_ARG_INT64           , &buffer_mbytes                        , _("Megabytes read buffer [256 MB]")                                                , "MB"}       ,
        {"ignore-order"   , 'i'  , G_OPTION_FLAG_REVERSE  , G_OPTION_ARG_NONE            , &tag.print_in_order                   , _("Print hashes in order completed, not in order entered (reduces memory usage)")  , NULL}       ,
        {""               , 0    , 0                      , G_OPTION_ARG_FILENAME_ARRAY  , &tag.paths                            , _("Space-separated list of files")                                                 , "[FILEā€¦]"}  ,
        {NULL             , 0    , 0                      , 0                            , NULL                                  , NULL                                                                               , NULL}};

    /* clang-format on */

    GError *error = NULL;
    GOptionContext *context = g_option_context_new(_("Hash a list of files"));
    GOptionGroup *main_group =
        g_option_group_new(argv[0], _("Hash a list of files"), "", &tag, NULL);

    char summary[4096];
    memset(summary, 0, sizeof(summary));

    g_snprintf(summary, sizeof(summary),
               _("Multi-threaded file digest (hash) calculator.\n"
                 "\n  Available digest types:"
                 "\n    %s\n"
                 "\n  Versions with different bit numbers:"
                 "\n    %s\n"
                 "\n  Supported, but not useful:"
                 "\n    %s\n"),
               "spooky, city, xxhash, sha{1,256,512}, md5, murmur",
               "spooky{32,64,128}, city{128,256,512}, murmur{512}",
               "farmhash, cumulative, paranoid, ext, bastard");

    g_option_group_add_entries(main_group, entries);
    g_option_context_set_main_group(context, main_group);
    g_option_context_set_summary(context, summary);

    if(!g_option_context_parse(context, &argc, (char ***)&argv, &error)) {
        /* print g_option error message */
        rm_log_error_line("%s", error->message);
        exit(EXIT_FAILURE);
    }

    if(tag.paths == NULL) {
        /* read paths from stdin */
        char path_buf[PATH_MAX];
        char *tokbuf = NULL;
        GPtrArray *paths = g_ptr_array_new();

        while(fgets(path_buf, PATH_MAX, stdin)) {
            char *abs_path = realpath(strtok_r(path_buf, "\n", &tokbuf), NULL);
            g_ptr_array_add(paths, abs_path);
        }

        tag.paths = (char **)g_ptr_array_free(paths, FALSE);
    }

    if(tag.paths == NULL || tag.paths[0] == NULL) {
        rm_log_error_line(_("No valid paths given."));
        exit(EXIT_FAILURE);
    }

    g_option_context_free(context);

    ////////// Implementation //////

    if(tag.print_in_order) {
        /* allocate buffer to collect results */
        tag.completed_digests_buffer =
            g_slice_alloc0((g_strv_length(tag.paths) + 1) * sizeof(RmDigest *));
        tag.path_index = 0;
    }

    /* initialise structures */
    g_mutex_init(&tag.lock);
    RmHasher *hasher = rm_hasher_new(tag.digest_type,
                                     threads,
                                     FALSE,
                                     4096,
                                     1024 * 1024 * buffer_mbytes,
                                     0,
                                     (RmHasherCallback)rm_hasher_callback,
                                     &tag);

    /* Iterate over paths, pushing to hasher threads */
    for(int i = 0; tag.paths && tag.paths[i]; ++i) {
        /* check it is a regular file */

        RmStat stat_buf;
        if(rm_sys_stat(tag.paths[i], &stat_buf) == -1) {
            rm_log_warning_line(_("Can't open directory or file \"%s\": %s"),
                                tag.paths[i], strerror(errno));
        } else if(S_ISDIR(stat_buf.st_mode)) {
            rm_log_warning_line(_("Directories are not supported: %s"), tag.paths[i]);
        } else if(S_ISREG(stat_buf.st_mode)) {
            RmHasherTask *task = rm_hasher_task_new(hasher, NULL, GINT_TO_POINTER(i));
            rm_hasher_task_hash(task, tag.paths[i], 0, 0, FALSE);
            rm_hasher_task_finish(task);
            continue;
        } else {
            rm_log_warning_line(_("%s: Unknown file type"), tag.paths[i]);
        }

        /* dummy callback for failed paths */
        g_free(tag.paths[i]);
        tag.paths[i] = NULL;
        rm_hasher_callback(hasher, NULL, &tag, GINT_TO_POINTER(i));
    }

    /* wait for all hasher threads to finish... */
    rm_hasher_free(hasher, TRUE);

    /* tidy up */
    if(tag.print_in_order) {
        g_slice_free1((g_strv_length(tag.paths) + 1) * sizeof(RmDigest *),
                      tag.completed_digests_buffer);
    }

    g_strfreev(tag.paths);

    return EXIT_SUCCESS;
}
Example #15
0
static bool rm_tm_count_files(RmTrie *count_tree, char **paths, RmSession *session) {
    if(*paths == NULL) {
        rm_log_error("No paths passed to rm_tm_count_files\n");
        return false;
    }

    int fts_flags = FTS_COMFOLLOW;
    if(session->cfg->follow_symlinks) {
        fts_flags |= FTS_LOGICAL;
    } else {
        fts_flags |= FTS_PHYSICAL;
    }

    /* This tree stores the full file paths.
       It is joined into a full directory tree later.
     */
    RmTrie file_tree;
    rm_trie_init(&file_tree);

    FTS *fts = fts_open(paths, fts_flags, NULL);
    if(fts == NULL) {
        rm_log_perror("fts_open failed");
        return false;
    }

    FTSENT *ent = NULL;
    while((ent = fts_read(fts))) {
        /* Handle large files (where fts fails with FTS_NS) */
        if(ent->fts_info == FTS_NS) {
            RmStat stat_buf;
            if(rm_sys_stat(ent->fts_path, &stat_buf) == -1) {
                rm_log_perror("stat(2) failed");
                continue;
            } else {
                /* Must be a large file (or followed link to it) */
                ent->fts_info = FTS_F;
            }
        }

        switch(ent->fts_info) {
        case FTS_ERR:
        case FTS_DC:
            /* Save this path as an error */
            rm_trie_insert(&file_tree, ent->fts_path, GINT_TO_POINTER(true));
            break;
        case FTS_F:
        case FTS_SL:
        case FTS_NS:
        case FTS_SLNONE:
        case FTS_DEFAULT:
            /* Save this path as countable file */
            if(ent->fts_statp->st_size > 0) {
                rm_trie_insert(&file_tree, ent->fts_path, GINT_TO_POINTER(false));
            }
        case FTS_D:
        case FTS_DNR:
        case FTS_DOT:
        case FTS_DP:
        case FTS_NSOK:
        default:
            /* other fts states, that do not count as errors or files */
            break;
        }
    }

    if(fts_close(fts) != 0) {
        rm_log_perror("fts_close failed");
        return false;
    }

    rm_trie_iter(&file_tree, NULL, true, false, rm_tm_count_art_callback, count_tree);

    /* Now flag everything as a no-go over the given paths,
     * otherwise we would continue merging till / with fatal consequences,
     * since / does not have more files as paths[0]
     */
    for(int i = 0; paths[i]; ++i) {
        /* Just call the callback directly */
        RmNode *node = rm_trie_search_node(&file_tree, paths[i]);
        if(node != NULL) {
            node->data = GINT_TO_POINTER(true);
            rm_tm_count_art_callback(&file_tree, node, 0, count_tree);
        }
    }

#ifdef _RM_TREEMERGE_DEBUG
    rm_trie_print(count_tree);
#endif

    rm_trie_destroy(&file_tree);
    return true;
}
Example #16
0
static gint64 rm_hasher_unbuffered_read(RmHasher *hasher, GThreadPool *hashpipe,
                                        RmDigest *digest, char *path, gint64 start_offset,
                                        gint64 bytes_to_read) {
    gint32 bytes_read = 0;
    gint64 total_bytes_read = 0;
    guint64 file_offset = start_offset;

    if(bytes_to_read == 0) {
        RmStat stat_buf;
        if(rm_sys_stat(path, &stat_buf) != -1) {
            bytes_to_read = MAX(stat_buf.st_size - start_offset, 0);
        }
    }

    /* how many buffers to read? */
    const gint16 N_BUFFERS = MIN(4, DIVIDE_CEIL(bytes_to_read, hasher->buf_size));
    struct iovec readvec[N_BUFFERS + 1];

    int fd = 0;

    fd = rm_sys_open(path, O_RDONLY);
    if(fd == -1) {
        rm_log_info("open(2) failed for %s: %s\n", path, g_strerror(errno));
        goto finish;
    }

    /* preadv() is beneficial for large files since it can cut the
     * number of syscall heavily.  I suggest N_BUFFERS=4 as good
     * compromise between memory and cpu.
     *
     * With 16 buffers: 43% cpu 33,871 total
     * With  8 buffers: 43% cpu 32,098 total
     * With  4 buffers: 42% cpu 32,091 total
     * With  2 buffers: 44% cpu 32,245 total
     * With  1 buffers: 45% cpu 34,491 total
     */

    /* Give the kernel scheduler some hints */
    rm_hasher_request_readahead(fd, start_offset, bytes_to_read);

    /* Initialize the buffers to begin with.
     * After a buffer is full, a new one is retrieved.
     */
    RmBuffer **buffers;
    buffers = g_slice_alloc(sizeof(*buffers) * N_BUFFERS);

    memset(readvec, 0, sizeof(readvec));
    for(int i = 0; i < N_BUFFERS; ++i) {
        /* buffer is one contignous memory block */
        buffers[i] = rm_buffer_get(hasher->mem_pool);
        readvec[i].iov_base = buffers[i]->data;
        readvec[i].iov_len = hasher->buf_size;
    }

    while((bytes_to_read == 0 || total_bytes_read < bytes_to_read) &&
          (bytes_read = rm_sys_preadv(fd, readvec, N_BUFFERS, file_offset)) > 0) {
        bytes_read =
            MIN(bytes_read, bytes_to_read - total_bytes_read); /* ignore over-reads */

        int blocks = DIVIDE_CEIL(bytes_read, hasher->buf_size);
        rm_assert_gentle(blocks <= N_BUFFERS);

        total_bytes_read += bytes_read;
        file_offset += bytes_read;

        for(int i = 0; i < blocks; ++i) {
            /* Get the RmBuffer from the datapointer */
            RmBuffer *buffer = buffers[i];
            buffer->len = MIN(hasher->buf_size, bytes_read - i * hasher->buf_size);
            buffer->digest = digest;
            buffer->user_data = NULL;

            /* Send it to the hasher */
            rm_util_thread_pool_push(hashpipe, buffer);

            /* Allocate a new buffer - hasher will release the old buffer */
            buffers[i] = rm_buffer_get(hasher->mem_pool);
            readvec[i].iov_base = buffers[i]->data;
            readvec[i].iov_len = hasher->buf_size;
        }
    }

    if(bytes_read == -1) {
        rm_log_perror("preadv failed");
    } else if(total_bytes_read != bytes_to_read) {
        rm_log_error_line(_("Something went wrong reading %s; expected %li bytes, "
                            "got %li; ignoring"),
                          path, (long int)bytes_to_read, (long int)total_bytes_read);
    }

    /* Release the rest of the buffers */
    for(int i = 0; i < N_BUFFERS; ++i) {
        rm_buffer_release(buffers[i]);
    }
    g_slice_free1(sizeof(*buffers) * N_BUFFERS, buffers);

finish:
    if(fd > 0) {
        rm_sys_close(fd);
    }

    return total_bytes_read;
}
Example #17
0
static RmMountEntries *rm_mount_list_open(RmMountTable *table) {
    RmMountEntries *self = g_slice_new(RmMountEntries);

    self->mnt_entries = g_unix_mounts_get(NULL);
    self->entries = NULL;
    self->current = NULL;

    for(GList *iter = self->mnt_entries; iter; iter = iter->next) {
        RmMountEntry *wrap_entry = g_slice_new(RmMountEntry);
        GUnixMountEntry *entry = iter->data;

        wrap_entry->fsname = g_strdup(g_unix_mount_get_device_path(entry));
        wrap_entry->dir = g_strdup(g_unix_mount_get_mount_path(entry));
        wrap_entry->type = g_strdup(g_unix_mount_get_fs_type(entry));

        self->entries = g_list_prepend(self->entries, wrap_entry);
    }

    RmMountEntry *wrap_entry = NULL;
    while((wrap_entry = rm_mount_list_next(self))) {
        /* bindfs mounts mirror directory trees.
        * This cannot be detected properly by rmlint since
        * files in it have the same inode as their unmirrored file, but
        * a different dev_t.
        *
        * Also ignore kernel filesystems.
        *
        * So better go and ignore it.
        */
        static struct RmEvilFs {
            /* fsname as show by `mount` */
            const char *name;

            /* Wether to warn about the exclusion on this */
            bool unusual;
        } evilfs_types[] = {{"bindfs", 1},
                            {"nullfs", 1},
                            /* Ignore the usual linux file system spam */
                            {"proc", 0},
                            {"cgroup", 0},
                            {"configfs", 0},
                            {"sys", 0},
                            {"devtmpfs", 0},
                            {"debugfs", 0},
                            {NULL, 0}};

        /* btrfs and ocfs2 filesystems support reflinks for deduplication */
        static const char *reflinkfs_types[] = {"btrfs", "ocfs2", NULL};

        const struct RmEvilFs *evilfs_found = NULL;
        for(int i = 0; evilfs_types[i].name && !evilfs_found; ++i) {
            if(strcmp(evilfs_types[i].name, wrap_entry->type) == 0) {
                evilfs_found = &evilfs_types[i];
            }
        }

        const char *reflinkfs_found = NULL;
        for(int i = 0; reflinkfs_types[i] && !reflinkfs_found; ++i) {
            if(strcmp(reflinkfs_types[i], wrap_entry->type) == 0) {
                reflinkfs_found = reflinkfs_types[i];
                break;
            }
        }

        if(evilfs_found != NULL) {
            RmStat dir_stat;
            rm_sys_stat(wrap_entry->dir, &dir_stat);
            g_hash_table_insert(table->evilfs_table,
                                GUINT_TO_POINTER(dir_stat.st_dev),
                                GUINT_TO_POINTER(1));

            GLogLevelFlags log_level = G_LOG_LEVEL_DEBUG;

            if(evilfs_found->unusual) {
                log_level = G_LOG_LEVEL_WARNING;
                rm_log_warning_prefix();
            } else {
                rm_log_debug_prefix();
            }

            g_log("rmlint", log_level,
                  _("`%s` mount detected at %s (#%u); Ignoring all files in it.\n"),
                  evilfs_found->name, wrap_entry->dir, (unsigned)dir_stat.st_dev);
        }

        rm_log_debug_line("Filesystem %s: %s", wrap_entry->dir,
                          (reflinkfs_found) ? "reflink" : "normal");

        if(reflinkfs_found != NULL) {
            RmStat dir_stat;
            rm_sys_stat(wrap_entry->dir, &dir_stat);
            g_hash_table_insert(table->reflinkfs_table,
                                GUINT_TO_POINTER(dir_stat.st_dev),
                                (gpointer)reflinkfs_found);
        }
    }

    return self;
}
Example #18
0
static bool rm_mounts_create_tables(RmMountTable *self) {
    /* partition dev_t to disk dev_t */
    self->part_table = g_hash_table_new_full(g_direct_hash,
                       g_direct_equal,
                       NULL,
                       (GDestroyNotify)rm_part_info_free);

    /* disk dev_t to boolean indication if disk is rotational */
    self->disk_table = g_hash_table_new_full(g_direct_hash,
                       g_direct_equal,
                       NULL,
                       (GDestroyNotify)rm_disk_info_free);

    self->nfs_table = g_hash_table_new_full(g_str_hash,
                                            g_str_equal,
                                            g_free,
                                            NULL);

    /* Mapping dev_t => true (used as set) */
    self->evilfs_table = g_hash_table_new(NULL, NULL);

    RmMountEntry *entry = NULL;
    RmMountEntries *mnt_entries = rm_mount_list_open(self);

    if(mnt_entries == NULL) {
        return false;
    }

    while((entry = rm_mount_list_next(mnt_entries))) {
        RmStat stat_buf_folder;
        if(rm_sys_stat(entry->dir, &stat_buf_folder) == -1) {
            continue;
        }

        dev_t whole_disk = 0;
        gchar is_rotational = true;
        char diskname[PATH_MAX];
        memset(diskname, 0, sizeof(diskname));

        RmStat stat_buf_dev;
        if(rm_sys_stat(entry->fsname, &stat_buf_dev) == -1) {
            char *nfs_marker = NULL;
            /* folder rm_sys_stat() is ok but devname rm_sys_stat() is not; this happens for example
             * with tmpfs and with nfs mounts.  Try to handle a few such cases.
             * */
            if(rm_mounts_is_ramdisk(entry->fsname)) {
                strncpy(diskname, entry->fsname, sizeof(diskname));
                is_rotational = false;
                whole_disk = stat_buf_folder.st_dev;
            } else if((nfs_marker = strstr(entry->fsname, ":/")) != NULL) {
                size_t until_slash = MIN((int)sizeof(entry->fsname), nfs_marker - entry->fsname);
                strncpy(diskname, entry->fsname, until_slash);
                is_rotational = true;

                /* Assign different dev ids (with major id 0) to different nfs servers */
                if(!g_hash_table_contains(self->nfs_table, diskname)) {
                    g_hash_table_insert(self->nfs_table, g_strdup(diskname), NULL);
                }
                whole_disk = makedev(0, g_hash_table_size(self->nfs_table));
            } else {
                strncpy(diskname, "unknown", sizeof(diskname));
                is_rotational = true;
                whole_disk = 0;
            }
        } else {
            if(rm_mounts_devno_to_wholedisk(
                        entry, stat_buf_dev.st_rdev, diskname, sizeof(diskname), &whole_disk
                    ) == -1) {
                /* folder and devname rm_sys_stat() are ok but blkid failed; this happens when?
                 * Treat as a non-rotational device using devname dev as whole_disk key
                 * */
                rm_log_debug(RED"devno_to_wholedisk failed for %s\n"RESET, entry->fsname);
                whole_disk = stat_buf_dev.st_dev;
                strncpy(diskname, entry->fsname, sizeof(diskname));
                is_rotational = false;
            } else {
                is_rotational = rm_mounts_is_rotational_blockdev(diskname);
            }
        }

        g_hash_table_insert(
            self->part_table,
            GUINT_TO_POINTER(stat_buf_folder.st_dev),
            rm_part_info_new (entry->dir, whole_disk));

        /* small hack, so also the full disk id can be given to the api below */
        if (!g_hash_table_contains(self->part_table, GINT_TO_POINTER(whole_disk))) {
            g_hash_table_insert(
                self->part_table,
                GUINT_TO_POINTER(whole_disk),
                rm_part_info_new (entry->dir, whole_disk));
        }

        if (!g_hash_table_contains(self->disk_table, GINT_TO_POINTER(whole_disk))) {
            g_hash_table_insert(
                self->disk_table,
                GINT_TO_POINTER(whole_disk),
                rm_disk_info_new(diskname, is_rotational));
        }

        rm_log_info("%02u:%02u %50s -> %02u:%02u %-12s (underlying disk: %s; rotational: %3s\n)",
                    major(stat_buf_folder.st_dev), minor(stat_buf_folder.st_dev),
                    entry->dir,
                    major(whole_disk), minor(whole_disk),
                    entry->fsname, diskname, is_rotational ? "yes" : "no"
                   );

    }

#if HAVE_SYSCTL
    if(DISK_TABLE) {
        g_hash_table_unref(DISK_TABLE);
    }
#endif

    rm_mount_list_close(mnt_entries);
    return true;
}
Example #19
0
static RmMountEntries *rm_mount_list_open(RmMountTable *table) {
    RmMountEntries *self = g_slice_new(RmMountEntries);
    self->entries = NULL;
    self->current = NULL;

#if HAVE_GETMNTENT
    struct mntent *entry = NULL;
    self->mnt_ent_file = setmntent("/etc/mtab", "r");

    if(self->mnt_ent_file != NULL) {
        while((entry = getmntent(self->mnt_ent_file))) {
            RmMountEntry *wrap_entry = g_slice_new(RmMountEntry);
            wrap_entry->fsname = g_strdup(entry->mnt_fsname);
            wrap_entry->dir = g_strdup(entry->mnt_dir);
            self->entries = g_list_prepend(self->entries, wrap_entry);
        }

        endmntent(self->mnt_ent_file);
    } else {
        rm_log_perror("getmntent");
    }
#elif HAVE_GETMNTINFO /* probably FreeBSD or other */
    int mnt_list_n = 0;
    struct statfs *mnt_list = NULL;

    if((mnt_list_n = getmntinfo(&mnt_list, MNT_NOWAIT)) != 0) {
        for(int i = 0; i < mnt_list_n; ++i) {
            RmMountEntry *wrap_entry = g_slice_new(RmMountEntry);
            struct statfs *entry = &mnt_list[i];

            wrap_entry->fsname = g_strdup(entry->f_mntfromname);
            wrap_entry->dir = g_strdup(entry->f_mntonname);
            self->entries = g_list_prepend(self->entries, wrap_entry);
        }
    } else {
        rm_log_perror("getmntinfo");
    }
#endif

    RmMountEntry *wrap_entry = NULL;
    while((wrap_entry = rm_mount_list_next(self))) {
        /* bindfs mounts mirror directory trees.
        * This cannot be detected properly by rmlint since
        * files in it have the same inode as their unmirrored file, but
        * a different dev_t.
        *
        * So better go and ignore it.
        */
        static const char *evilfs_types[] = {"bindfs", "nullfs", NULL};

        const char *evilfs_found = NULL;
        for(int i = 0; evilfs_types[i] && !evilfs_found; ++i) {
            if(strcmp(evilfs_types[i], wrap_entry->fsname) == 0) {
                evilfs_found = evilfs_types[i];
            }
        }

        if(evilfs_found != NULL) {
            RmStat dir_stat;
            rm_sys_stat(wrap_entry->dir, &dir_stat);
            g_hash_table_insert(
                table->evilfs_table,
                GUINT_TO_POINTER(dir_stat.st_dev),
                GUINT_TO_POINTER(1)
            );

            rm_log_error(
                YELLOW"WARNING:"RESET" `%s` mount detected at %s (#%u); Ignoring all files in it.\n",
                evilfs_found,
                wrap_entry->dir,
                (unsigned)dir_stat.st_dev
            );
        }
    }

    return self;
}