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 : }
|