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; }
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"); } }
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; }
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 }
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); }
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; }
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; } }
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); }
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; } }
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); } }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }