Line data Source code
1 : // SPDX-License-Identifier: GPL-3.0-or-later
2 : // Copyright (C) 2011 Andrea Mazzoleni
3 :
4 : #include "portable.h"
5 :
6 : #include "elem.h"
7 : #include "support.h"
8 : #include "state.h"
9 : #include "util.h"
10 :
11 : /****************************************************************************/
12 : /* snapraid */
13 :
14 : int BLOCK_HASH_SIZE = HASH_MAX;
15 :
16 1717 : struct snapraid_content* content_alloc(const char* path, uint64_t dev)
17 : {
18 : struct snapraid_content* content;
19 :
20 1717 : content = malloc_nofail(sizeof(struct snapraid_content));
21 1717 : pathimport(content->content, sizeof(content->content), path);
22 1717 : content->device = dev;
23 :
24 1717 : return content;
25 : }
26 :
27 1589 : void content_free(struct snapraid_content* content)
28 : {
29 1589 : free(content);
30 1589 : }
31 :
32 699 : struct snapraid_filter* filter_alloc_file(int direction, const char* root, const char* pattern)
33 : {
34 : struct snapraid_filter* filter;
35 : char* i;
36 : char* first;
37 : char* last;
38 : int token_is_valid;
39 : int token_is_filled;
40 :
41 699 : filter = malloc_nofail(sizeof(struct snapraid_filter));
42 699 : pathimport(filter->pattern, sizeof(filter->pattern), pattern);
43 699 : pathimport(filter->root, sizeof(filter->root), root);
44 699 : filter->direction = direction;
45 :
46 : /* find first and last slash */
47 699 : first = 0;
48 699 : last = 0;
49 :
50 : /* reject invalid tokens, like "<empty>", ".", ".." and more dots */
51 699 : token_is_valid = 0;
52 699 : token_is_filled = 0;
53 7912 : for (i = filter->pattern; *i; ++i) {
54 7214 : if (*i == '/') {
55 : /* reject invalid tokens, but accept an empty one as first */
56 117 : if (!token_is_valid && (first != 0 || token_is_filled)) {
57 1 : free(filter);
58 1 : return 0;
59 : }
60 116 : token_is_valid = 0;
61 116 : token_is_filled = 0;
62 :
63 : /* update slash position */
64 116 : if (!first)
65 77 : first = i;
66 116 : last = i;
67 7097 : } else if (*i != '.') {
68 6488 : token_is_valid = 1;
69 6488 : token_is_filled = 1;
70 : } else {
71 609 : token_is_filled = 1;
72 : }
73 : }
74 :
75 : /* reject invalid tokens, but accept an empty one as last, but not if it's the only one */
76 698 : if (!token_is_valid && (first == 0 || token_is_filled)) {
77 1 : free(filter);
78 1 : return 0;
79 : }
80 :
81 : /* it's a file filter */
82 697 : filter->is_disk = 0;
83 :
84 697 : if (first == 0) {
85 : /* no slash */
86 620 : filter->is_abs = 0;
87 620 : filter->is_dir = 0;
88 : } else {
89 77 : if (first == filter->pattern) {
90 : /* slash at the start */
91 39 : filter->is_abs = 1;
92 : } else {
93 : /* no slash at the start */
94 38 : filter->is_abs = 0;
95 : }
96 77 : if (last[1] == 0) {
97 : /* slash at the end */
98 76 : filter->is_dir = 1;
99 76 : last[0] = 0; /* clear the slash */
100 : } else {
101 : /* no slash at the end */
102 1 : filter->is_dir = 0;
103 : }
104 : }
105 :
106 697 : return filter;
107 : }
108 :
109 16 : struct snapraid_filter* filter_alloc_disk(int direction, const char* pattern)
110 : {
111 : struct snapraid_filter* filter;
112 :
113 16 : filter = malloc_nofail(sizeof(struct snapraid_filter));
114 16 : pathimport(filter->pattern, sizeof(filter->pattern), pattern);
115 16 : filter->direction = direction;
116 :
117 : /* it's a disk filter */
118 16 : filter->is_disk = 1;
119 16 : filter->is_abs = 0;
120 16 : filter->is_dir = 0;
121 :
122 : /* no slash allowed in disk names */
123 16 : if (strchr(filter->pattern, '/') != 0) {
124 : /* LCOV_EXCL_START */
125 : free(filter);
126 : return 0;
127 : /* LCOV_EXCL_STOP */
128 : }
129 :
130 16 : return filter;
131 : }
132 :
133 669 : void filter_free(void* filter)
134 : {
135 669 : free(filter);
136 669 : }
137 :
138 124911 : const char* filter_type(struct snapraid_filter* filter, char* out, size_t out_size)
139 : {
140 : const char* direction;
141 :
142 124911 : if (filter->direction < 0)
143 124626 : direction = "exclude";
144 : else
145 285 : direction = "include";
146 :
147 124911 : if (filter->is_disk)
148 0 : pathprint(out, out_size, "%s %s:", direction, filter->pattern);
149 124911 : else if (filter->is_dir)
150 104 : pathprint(out, out_size, "%s %s/", direction, filter->pattern);
151 : else
152 124807 : pathprint(out, out_size, "%s %s", direction, filter->pattern);
153 :
154 124911 : return out;
155 : }
156 :
157 : /**
158 : * Apply a filter to the specified path
159 : *
160 : * @param filter The filter to apply.
161 : * @param reason Assign to it the filter that caused rejection.
162 : * @param path The path to filter.
163 : * @param is_dir if the path refers to a directory, otherwise to a file.
164 : * @return < 0 if the path is excluded, > 0 if the path is included, 0 if the filter doesn't apply.
165 : */
166 6848578 : static int filter_apply(struct snapraid_filter* filter, struct snapraid_filter** reason, const char* path, int is_dir)
167 : {
168 6848578 : if (filter->root[0] != 0) { /* if it's a local filter */
169 127913 : if (!path_is_root_of(filter->root, path)) /* applies it only if it matches the root */
170 68719 : return 0;
171 : /* advance the path to compare to make it local */
172 59194 : path += strlen(filter->root);
173 : }
174 :
175 : /* if the filter is for files, it doesn't applies to directories */
176 6779859 : if (!filter->is_dir && is_dir)
177 6510 : return 0;
178 :
179 : /*
180 : * If the filter is for directories, it should be applied to all files inside
181 : * that directory.
182 : *
183 : * This is done allowing a partial matching as far it ends at a directory separator
184 : */
185 6773349 : int match_sub = 0;
186 6773349 : if (filter->is_dir && !is_dir)
187 167830 : match_sub = 1;
188 :
189 6773349 : int ret = 0;
190 :
191 6773349 : if (filter->is_abs) {
192 : /* skip initial slash, as always missing in the path */
193 116278 : if (wnmatch_sub(filter->pattern + 1, path, match_sub) == 0)
194 0 : ret = filter->direction;
195 : } else {
196 : /* the path is relative, first try to match from the root */
197 6657071 : if (wnmatch_sub(filter->pattern, path, match_sub) == 0) {
198 3284 : ret = filter->direction;
199 : } else {
200 : /* then try to match after all the / presents */
201 6653787 : const char* slash = strchr(path, '/');
202 13116284 : while (slash) {
203 6586486 : if (wnmatch_sub(filter->pattern, slash + 1, match_sub) == 0) {
204 123989 : ret = filter->direction;
205 123989 : break;
206 : }
207 :
208 6462497 : slash = strchr(slash + 1, '/');
209 : }
210 : }
211 : }
212 :
213 6773349 : if (reason != 0 && ret < 0)
214 124283 : *reason = filter;
215 :
216 6773349 : return ret;
217 : }
218 :
219 : /**
220 : * Apply a list of filter to a specific disk/sub path
221 : *
222 : * @param filterlist The list of filter to apply
223 : * @param reason Assign to it the filter that caused rejection.
224 : * @param disk The disk that contain the path
225 : * @param sub The path in the disk
226 : * @param is_dir If the path refers to a directory, otherwise it's a file
227 : * @param is_def_include If no filter apply, if by default force the inclusion. Used to include directory by default.
228 : * @return < 0 if the path is excluded, == 0 if the path is included.
229 : */
230 :
231 4861404 : static int filter_element(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub, int is_dir, int is_def_include)
232 : {
233 : tommy_node* i;
234 :
235 : /**
236 : * The rule is that the latest filter defines the behaviour of paths
237 : * that doesn't match any filter.
238 : *
239 : * If the latest filter is an 'exclusion' filter, the default is to include.
240 : * If the latest filter is an 'inclusion' filter, the default is to exclude.
241 : * If there are no filter, the default is to include.
242 : */
243 4861404 : int default_direction = 1;
244 :
245 : /* for each filter */
246 11710396 : for (i = tommy_list_head(filterlist); i != 0; i = i->next) {
247 : int ret;
248 6989883 : struct snapraid_filter* filter = i->data;
249 :
250 6989883 : if (filter->is_disk) {
251 141305 : if (wnmatch(filter->pattern, disk) == 0) {
252 13618 : ret = filter->direction;
253 : } else {
254 127687 : ret = 0;
255 : }
256 141305 : if (reason != 0 && ret < 0)
257 0 : *reason = filter;
258 : } else {
259 6848578 : ret = filter_apply(filter, reason, sub, is_dir);
260 : }
261 :
262 6989883 : if (ret > 0) {
263 : /* include the file */
264 16608 : return 0;
265 6973275 : } else if (ret < 0) {
266 : /* exclude the file */
267 124283 : return -1;
268 : } else {
269 : /* default is opposite of the last filter */
270 6848992 : default_direction = -filter->direction;
271 6848992 : if (reason != 0 && default_direction < 0)
272 3271839 : *reason = filter;
273 : /* continue with the next one */
274 : }
275 : }
276 :
277 : /*
278 : * Directories are always included by default, otherwise we cannot apply rules
279 : * to the contained files
280 : */
281 4720513 : if (is_def_include)
282 4852 : return 0;
283 :
284 : /* files are excluded/included depending of the last rule processed */
285 4715661 : if (default_direction < 0)
286 83987 : return -1;
287 :
288 4631674 : return 0;
289 : }
290 :
291 4856443 : int filter_path(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
292 : {
293 4856443 : return filter_element(filterlist, reason, disk, sub, 0, 0);
294 : }
295 :
296 4912 : int filter_subdir(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
297 : {
298 4912 : return filter_element(filterlist, reason, disk, sub, 1, 1);
299 : }
300 :
301 49 : int filter_emptydir(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
302 : {
303 49 : return filter_element(filterlist, reason, disk, sub, 1, 0);
304 : }
305 :
306 85513 : int filter_existence(int filter_missing, const char* dir, const char* sub)
307 : {
308 : char path[PATH_MAX];
309 : struct stat st;
310 :
311 85513 : if (!filter_missing)
312 39907 : return 0;
313 :
314 : /* we directly check if in the disk the file is present or not */
315 45606 : pathprint(path, sizeof(path), "%s%s", dir, sub);
316 :
317 45606 : if (lstat(path, &st) != 0) {
318 : /* if the file doesn't exist, we don't filter it out */
319 5018 : if (errno == ENOENT)
320 5018 : return 0;
321 : /* LCOV_EXCL_START */
322 : log_fatal(errno, "Error in stat file '%s'. %s.\n", path, strerror(errno));
323 : exit(EXIT_FAILURE);
324 : /* LCOV_EXCL_STOP */
325 : }
326 :
327 : /* the file is present, so we filter it out */
328 40588 : return 1;
329 : }
330 :
331 43605 : int filter_correctness(int filter_error, tommy_arrayblkof* infoarr, struct snapraid_disk* disk, struct snapraid_file* file)
332 : {
333 : unsigned i;
334 :
335 43605 : if (!filter_error)
336 21057 : return 0;
337 :
338 : /* check each block of the file */
339 60722 : for (i = 0; i < file->blockmax; ++i) {
340 46465 : block_off_t parity_pos = fs_file2par_get(disk, file, i);
341 46465 : snapraid_info info = info_get(infoarr, parity_pos);
342 :
343 : /* if the file has a bad block, don't exclude it */
344 46465 : if (info_get_bad(info))
345 8291 : return 0;
346 : }
347 :
348 : /* the file is correct, so we filter it out */
349 14257 : return 1;
350 : }
351 :
352 3385095 : int filter_content(tommy_list* contentlist, const char* path)
353 : {
354 : tommy_node* i;
355 :
356 21691578 : for (i = tommy_list_head(contentlist); i != 0; i = i->next) {
357 18306483 : struct snapraid_content* content = i->data;
358 : char tmp[PATH_MAX];
359 :
360 18306483 : if (pathcmp(content->content, path) == 0)
361 0 : return -1;
362 :
363 : /* exclude also the ".tmp" copy used to save it */
364 18306483 : pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
365 18306483 : if (pathcmp(tmp, path) == 0)
366 0 : return -1;
367 :
368 : /* exclude also the ".lock" file */
369 18306483 : pathprint(tmp, sizeof(tmp), "%s.lock", content->content);
370 18306483 : if (pathcmp(tmp, path) == 0)
371 0 : return -1;
372 : }
373 :
374 3385095 : return 0;
375 : }
376 :
377 3984957 : struct snapraid_file* file_alloc(unsigned block_size, const char* sub, data_off_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical)
378 : {
379 : struct snapraid_file* file;
380 : block_off_t i;
381 :
382 3984957 : file = malloc_nofail(sizeof(struct snapraid_file));
383 3984957 : file->sub = strdup_nofail(sub);
384 3984957 : file->size = size;
385 3984957 : file->blockmax = (size + block_size - 1) / block_size;
386 3984957 : file->mtime_sec = mtime_sec;
387 3984957 : file->mtime_nsec = mtime_nsec;
388 3984957 : file->inode = inode;
389 3984957 : file->physical = physical;
390 3984957 : file->flag = 0;
391 3984957 : file->shared_flag = 0;
392 3984957 : file->blockvec = malloc_nofail(file->blockmax * block_sizeof());
393 :
394 13752906 : for (i = 0; i < file->blockmax; ++i) {
395 9767949 : struct snapraid_block* block = file_block(file, i);
396 9767949 : block_state_set(block, BLOCK_STATE_CHG);
397 9767949 : hash_invalid_set(block->hash);
398 : }
399 :
400 3984957 : return file;
401 : }
402 :
403 24482 : struct snapraid_file* file_dup(struct snapraid_file* copy)
404 : {
405 : struct snapraid_file* file;
406 : block_off_t i;
407 :
408 24482 : file = malloc_nofail(sizeof(struct snapraid_file));
409 24482 : file->sub = strdup_nofail(copy->sub);
410 24482 : file->size = copy->size;
411 24482 : file->blockmax = copy->blockmax;
412 24482 : file->mtime_sec = copy->mtime_sec;
413 24482 : file->mtime_nsec = copy->mtime_nsec;
414 24482 : file->inode = copy->inode;
415 24482 : file->physical = copy->physical;
416 24482 : file->flag = copy->flag;
417 24482 : file->blockvec = malloc_nofail(file->blockmax * block_sizeof());
418 :
419 83246 : for (i = 0; i < file->blockmax; ++i) {
420 58764 : struct snapraid_block* block = file_block(file, i);
421 58764 : struct snapraid_block* copy_block = file_block(copy, i);
422 58764 : block->state = copy_block->state;
423 58764 : memcpy(block->hash, copy_block->hash, BLOCK_HASH_SIZE);
424 : }
425 :
426 24482 : return file;
427 : }
428 :
429 3754911 : void file_free(struct snapraid_file* file)
430 : {
431 3754911 : free(file->sub);
432 3754911 : file->sub = 0;
433 3754911 : free(file->blockvec);
434 3754911 : file->blockvec = 0;
435 3754911 : free(file);
436 3754911 : }
437 :
438 155 : void file_rename(struct snapraid_file* file, const char* sub)
439 : {
440 155 : free(file->sub);
441 155 : file->sub = strdup_nofail(sub);
442 155 : }
443 :
444 22989 : void file_copy(struct snapraid_file* src_file, struct snapraid_file* dst_file)
445 : {
446 : block_off_t i;
447 :
448 22989 : if (src_file->size != dst_file->size) {
449 : /* LCOV_EXCL_START */
450 : log_fatal(EINTERNAL, "Internal inconsistency: Copy file with different size\n");
451 : os_abort();
452 : /* LCOV_EXCL_STOP */
453 : }
454 :
455 22989 : if (src_file->mtime_sec != dst_file->mtime_sec) {
456 : /* LCOV_EXCL_START */
457 : log_fatal(EINTERNAL, "Internal inconsistency: Copy file with different mtime_sec\n");
458 : os_abort();
459 : /* LCOV_EXCL_STOP */
460 : }
461 :
462 22989 : if (src_file->mtime_nsec != dst_file->mtime_nsec) {
463 : /* LCOV_EXCL_START */
464 : log_fatal(EINTERNAL, "Internal inconsistency: Copy file with different mtime_nsec\n");
465 : os_abort();
466 : /* LCOV_EXCL_STOP */
467 : }
468 :
469 79542 : for (i = 0; i < dst_file->blockmax; ++i) {
470 : /* set a block with hash computed but without parity */
471 56553 : block_state_set(file_block(dst_file, i), BLOCK_STATE_REP);
472 :
473 : /* copy the hash */
474 56553 : memcpy(file_block(dst_file, i)->hash, file_block(src_file, i)->hash, BLOCK_HASH_SIZE);
475 : }
476 :
477 22989 : file_flag_set(dst_file, FILE_IS_COPY);
478 22989 : }
479 :
480 46526 : const char* file_name(const struct snapraid_file* file)
481 : {
482 46526 : const char* r = strrchr(file->sub, '/');
483 :
484 46526 : if (!r)
485 78 : r = file->sub;
486 : else
487 46448 : ++r;
488 46526 : return r;
489 : }
490 :
491 7150476 : unsigned file_block_size(struct snapraid_file* file, block_off_t file_pos, unsigned block_size)
492 : {
493 : /* if it's the last block */
494 7150476 : if (file_pos + 1 == file->blockmax) {
495 : unsigned block_remainder;
496 2936926 : if (file->size == 0)
497 0 : return 0;
498 2936926 : block_remainder = file->size % block_size;
499 2936926 : if (block_remainder == 0)
500 3217 : block_remainder = block_size;
501 2936926 : return block_remainder;
502 : }
503 :
504 4213550 : return block_size;
505 : }
506 :
507 5434596 : int file_block_is_last(struct snapraid_file* file, block_off_t file_pos)
508 : {
509 5434596 : if (file_pos == 0 && file->blockmax == 0)
510 0 : return 1;
511 :
512 5434596 : if (file_pos >= file->blockmax) {
513 : /* LCOV_EXCL_START */
514 : log_fatal(EINTERNAL, "Internal inconsistency: File block position over the max\n");
515 : os_abort();
516 : /* LCOV_EXCL_STOP */
517 : }
518 :
519 5434596 : return file_pos == file->blockmax - 1;
520 : }
521 :
522 1108112 : int file_inode_compare_to_arg(const void* void_arg, const void* void_data)
523 : {
524 1108112 : const uint64_t* arg = void_arg;
525 1108112 : const struct snapraid_file* file = void_data;
526 :
527 1108112 : if (*arg < file->inode)
528 0 : return -1;
529 1108112 : if (*arg > file->inode)
530 0 : return 1;
531 1108112 : return 0;
532 : }
533 :
534 0 : int file_inode_compare(const void* void_a, const void* void_b)
535 : {
536 0 : const struct snapraid_file* file_a = void_a;
537 0 : const struct snapraid_file* file_b = void_b;
538 :
539 0 : if (file_a->inode < file_b->inode)
540 0 : return -1;
541 0 : if (file_a->inode > file_b->inode)
542 0 : return 1;
543 0 : return 0;
544 : }
545 :
546 193514 : int file_path_compare(const void* void_a, const void* void_b)
547 : {
548 193514 : const struct snapraid_file* file_a = void_a;
549 193514 : const struct snapraid_file* file_b = void_b;
550 :
551 193514 : return strcmp(file_a->sub, file_b->sub);
552 : }
553 :
554 94548 : int file_physical_compare(const void* void_a, const void* void_b)
555 : {
556 94548 : const struct snapraid_file* file_a = void_a;
557 94548 : const struct snapraid_file* file_b = void_b;
558 :
559 94548 : if (file_a->physical < file_b->physical)
560 50224 : return -1;
561 44324 : if (file_a->physical > file_b->physical)
562 44322 : return 1;
563 2 : return 0;
564 : }
565 :
566 109661 : int file_path_compare_to_arg(const void* void_arg, const void* void_data)
567 : {
568 109661 : const char* arg = void_arg;
569 109661 : const struct snapraid_file* file = void_data;
570 :
571 109661 : return strcmp(arg, file->sub);
572 : }
573 :
574 23263 : int file_name_compare(const void* void_a, const void* void_b)
575 : {
576 23263 : const struct snapraid_file* file_a = void_a;
577 23263 : const struct snapraid_file* file_b = void_b;
578 23263 : const char* name_a = file_name(file_a);
579 23263 : const char* name_b = file_name(file_b);
580 :
581 23263 : return strcmp(name_a, name_b);
582 : }
583 :
584 23020 : int file_stamp_compare(const void* void_a, const void* void_b)
585 : {
586 23020 : const struct snapraid_file* file_a = void_a;
587 23020 : const struct snapraid_file* file_b = void_b;
588 :
589 23020 : if (file_a->size < file_b->size)
590 0 : return -1;
591 23020 : if (file_a->size > file_b->size)
592 0 : return 1;
593 :
594 23020 : if (file_a->mtime_sec < file_b->mtime_sec)
595 0 : return -1;
596 23020 : if (file_a->mtime_sec > file_b->mtime_sec)
597 0 : return 1;
598 :
599 23020 : if (file_a->mtime_nsec < file_b->mtime_nsec)
600 0 : return -1;
601 23020 : if (file_a->mtime_nsec > file_b->mtime_nsec)
602 0 : return 1;
603 :
604 23020 : return 0;
605 : }
606 :
607 23263 : int file_namestamp_compare(const void* void_a, const void* void_b)
608 : {
609 : int ret;
610 :
611 23263 : ret = file_name_compare(void_a, void_b);
612 23263 : if (ret != 0)
613 243 : return ret;
614 :
615 23020 : return file_stamp_compare(void_a, void_b);
616 : }
617 :
618 0 : int file_pathstamp_compare(const void* void_a, const void* void_b)
619 : {
620 : int ret;
621 :
622 0 : ret = file_path_compare(void_a, void_b);
623 0 : if (ret != 0)
624 0 : return ret;
625 :
626 0 : return file_stamp_compare(void_a, void_b);
627 : }
628 :
629 4032445 : struct snapraid_extent* extent_alloc(block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos, block_off_t count)
630 : {
631 : struct snapraid_extent* extent;
632 :
633 4032445 : if (count == 0) {
634 : /* LCOV_EXCL_START */
635 : log_fatal(EINTERNAL, "Internal inconsistency: Allocating empty extent for file '%s' at position '%u/%u'\n", file->sub, file_pos, file->blockmax);
636 : os_abort();
637 : /* LCOV_EXCL_STOP */
638 : }
639 4032445 : if (file_pos + count > file->blockmax) {
640 : /* LCOV_EXCL_START */
641 : log_fatal(EINTERNAL, "Internal inconsistency: Allocating overflowing extent for file '%s' at position '%u:%u/%u'\n", file->sub, file_pos, count, file->blockmax);
642 : os_abort();
643 : /* LCOV_EXCL_STOP */
644 : }
645 :
646 4032445 : extent = malloc_nofail(sizeof(struct snapraid_extent));
647 4032445 : extent->parity_pos = parity_pos;
648 4032445 : extent->file = file;
649 4032445 : extent->file_pos = file_pos;
650 4032445 : extent->count = count;
651 :
652 4032445 : return extent;
653 : }
654 :
655 3784251 : void extent_free(struct snapraid_extent* extent)
656 : {
657 3784251 : free(extent);
658 3784251 : }
659 :
660 47856421 : int extent_parity_compare(const void* void_a, const void* void_b)
661 : {
662 47856421 : const struct snapraid_extent* arg_a = void_a;
663 47856421 : const struct snapraid_extent* arg_b = void_b;
664 :
665 47856421 : if (arg_a->parity_pos < arg_b->parity_pos)
666 1009380 : return -1;
667 46847041 : if (arg_a->parity_pos > arg_b->parity_pos)
668 46804858 : return 1;
669 :
670 42183 : return 0;
671 : }
672 :
673 48195985 : int extent_file_compare(const void* void_a, const void* void_b)
674 : {
675 48195985 : const struct snapraid_extent* arg_a = void_a;
676 48195985 : const struct snapraid_extent* arg_b = void_b;
677 :
678 48195985 : if (arg_a->file < arg_b->file)
679 1097997 : return -1;
680 47097988 : if (arg_a->file > arg_b->file)
681 47022710 : return 1;
682 :
683 75278 : if (arg_a->file_pos < arg_b->file_pos)
684 196 : return -1;
685 75082 : if (arg_a->file_pos > arg_b->file_pos)
686 32899 : return 1;
687 :
688 42183 : return 0;
689 : }
690 :
691 129174 : struct snapraid_link* link_alloc(const char* sub, const char* linkto, unsigned link_flag)
692 : {
693 : struct snapraid_link* slink;
694 :
695 129174 : slink = malloc_nofail(sizeof(struct snapraid_link));
696 129174 : slink->sub = strdup_nofail(sub);
697 129174 : slink->linkto = strdup_nofail(linkto);
698 129174 : slink->flag = link_flag;
699 :
700 129174 : return slink;
701 : }
702 :
703 121199 : void link_free(struct snapraid_link* slink)
704 : {
705 121199 : free(slink->sub);
706 121199 : free(slink->linkto);
707 121199 : free(slink);
708 121199 : }
709 :
710 38146 : int link_name_compare_to_arg(const void* void_arg, const void* void_data)
711 : {
712 38146 : const char* arg = void_arg;
713 38146 : const struct snapraid_link* slink = void_data;
714 :
715 38146 : return strcmp(arg, slink->sub);
716 : }
717 :
718 3337 : int link_alpha_compare(const void* void_a, const void* void_b)
719 : {
720 3337 : const struct snapraid_link* slink_a = void_a;
721 3337 : const struct snapraid_link* slink_b = void_b;
722 :
723 3337 : return strcmp(slink_a->sub, slink_b->sub);
724 : }
725 :
726 872 : struct snapraid_dir* dir_alloc(const char* sub)
727 : {
728 : struct snapraid_dir* dir;
729 :
730 872 : dir = malloc_nofail(sizeof(struct snapraid_dir));
731 872 : dir->sub = strdup_nofail(sub);
732 872 : dir->flag = 0;
733 :
734 872 : return dir;
735 : }
736 :
737 827 : void dir_free(struct snapraid_dir* dir)
738 : {
739 827 : free(dir->sub);
740 827 : free(dir);
741 827 : }
742 :
743 285 : int dir_name_compare(const void* void_arg, const void* void_data)
744 : {
745 285 : const char* arg = void_arg;
746 285 : const struct snapraid_dir* dir = void_data;
747 :
748 285 : return strcmp(arg, dir->sub);
749 : }
750 :
751 63681 : struct snapraid_dealloc* dealloc_alloc(unsigned block_size, const char* sub, data_off_t size, int64_t mtime_sec, int32_t mtime_nsec)
752 : {
753 : struct snapraid_dealloc* dealloc;
754 :
755 63681 : dealloc = malloc_nofail(sizeof(struct snapraid_dealloc));
756 63681 : dealloc->sub = strdup_nofail(sub);
757 63681 : dealloc->size = size;
758 63681 : dealloc->mtime_sec = mtime_sec;
759 63681 : dealloc->mtime_nsec = mtime_nsec;
760 63681 : dealloc->blockmax = (size + block_size - 1) / block_size;
761 63681 : dealloc->blockhash = malloc_nofail(dealloc->blockmax * BLOCK_HASH_SIZE);
762 :
763 63681 : return dealloc;
764 : }
765 :
766 34375 : void dealloc_import(struct snapraid_dealloc* dealloc, struct snapraid_file* file)
767 : {
768 34375 : assert(dealloc->blockmax == file->blockmax);
769 :
770 : /* for all the blocks of the file */
771 117437 : for (block_off_t i = 0; i < dealloc->blockmax; ++i) {
772 83062 : struct snapraid_block* block = fs_file2block_get(file, i);
773 83062 : unsigned char* hash = dealloc->blockhash + i * BLOCK_HASH_SIZE;
774 :
775 83062 : unsigned state = block_state_get(block);
776 83062 : switch (state) {
777 83018 : case BLOCK_STATE_BLK :
778 : case BLOCK_STATE_REP :
779 83018 : memcpy(hash, block->hash, BLOCK_HASH_SIZE);
780 83018 : break;
781 44 : case BLOCK_STATE_CHG :
782 44 : hash_invalid_set(hash);
783 44 : break;
784 0 : default :
785 : /* LCOV_EXCL_START */
786 : log_fatal(EINTERNAL, "Internal inconsistency: State for dealloc block %u state %u\n", i, state);
787 : exit(EXIT_FAILURE);
788 : /* LCOV_EXCL_STOP */
789 : }
790 : }
791 34375 : }
792 :
793 62183 : void dealloc_free(struct snapraid_dealloc* dealloc)
794 : {
795 62183 : free(dealloc->sub);
796 62183 : free(dealloc->blockhash);
797 62183 : free(dealloc);
798 62183 : }
799 :
800 1850 : struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev, const char* uuid, int skip_access)
801 : {
802 : struct snapraid_disk* disk;
803 : unsigned i;
804 :
805 1850 : disk = malloc_nofail(sizeof(struct snapraid_disk));
806 1850 : pathcpy(disk->name, sizeof(disk->name), name);
807 1850 : pathimport(disk->mount_point, sizeof(disk->mount_point), dir);
808 1850 : pathcpy(disk->uuid, sizeof(disk->uuid), uuid);
809 :
810 : /* ensure that the dir terminate with "/" if it isn't empty */
811 1850 : pathslash(disk->mount_point, sizeof(disk->mount_point));
812 :
813 : /* no snapshot by default */
814 1850 : disk->snapshot_root[0] = 0;
815 1850 : pathcpy(disk->dir, sizeof(disk->dir), disk->mount_point);
816 :
817 : #if HAVE_THREAD
818 1850 : disk->single_thread = 0;
819 1850 : thread_mutex_init(&disk->fs_mutex);
820 : #endif
821 :
822 1850 : disk->smartctl[0] = 0;
823 9250 : for (i = 0; i < SMART_IGNORE_MAX; ++i)
824 7400 : disk->smartignore[i] = 0;
825 1850 : disk->fstype[0] = 0;
826 1850 : disk->fslabel[0] = 0;
827 1850 : disk->mount_device = dev;
828 1850 : disk->dir_device = dev;
829 1850 : disk->tick = 0;
830 1850 : disk->cached_blocks = 0;
831 1850 : disk->progress_file = 0;
832 1850 : disk->total_blocks = 0;
833 1850 : disk->free_blocks = 0;
834 1850 : disk->first_free_block = 0;
835 1850 : disk->has_volatile_inodes = 0;
836 1850 : disk->has_volatile_hardlinks = 0;
837 1850 : disk->has_unreliable_physical = 0;
838 1850 : disk->has_different_uuid = 0;
839 1850 : disk->has_unsupported_uuid = *uuid == 0; /* empty UUID means unsupported */
840 1850 : disk->had_empty_uuid = 0;
841 1850 : disk->mapping_idx = -1;
842 1850 : disk->skip_access = skip_access;
843 1850 : tommy_list_init(&disk->filelist);
844 1850 : tommy_list_init(&disk->deletedlist);
845 1850 : tommy_hashdyn_init(&disk->inodeset);
846 1850 : tommy_hashdyn_init(&disk->pathset);
847 1850 : tommy_hashdyn_init(&disk->stampset);
848 1850 : tommy_list_init(&disk->linklist);
849 1850 : tommy_hashdyn_init(&disk->linkset);
850 1850 : tommy_list_init(&disk->dirlist);
851 1850 : tommy_hashdyn_init(&disk->dirset);
852 1850 : tommy_list_init(&disk->dealloclist);
853 1850 : tommy_tree_init(&disk->fs_parity, extent_parity_compare);
854 1850 : tommy_tree_init(&disk->fs_file, extent_file_compare);
855 1850 : disk->fs_last = 0;
856 :
857 1850 : return disk;
858 : }
859 :
860 1718 : void disk_free(struct snapraid_disk* disk)
861 : {
862 1718 : tommy_list_foreach(&disk->filelist, (tommy_foreach_func*)file_free);
863 1718 : tommy_list_foreach(&disk->deletedlist, (tommy_foreach_func*)file_free);
864 1718 : tommy_tree_foreach(&disk->fs_file, (tommy_foreach_func*)extent_free);
865 1718 : tommy_hashdyn_done(&disk->inodeset);
866 1718 : tommy_hashdyn_done(&disk->pathset);
867 1718 : tommy_hashdyn_done(&disk->stampset);
868 1718 : tommy_list_foreach(&disk->linklist, (tommy_foreach_func*)link_free);
869 1718 : tommy_hashdyn_done(&disk->linkset);
870 1718 : tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free);
871 1718 : tommy_hashdyn_done(&disk->dirset);
872 1718 : tommy_list_foreach(&disk->dealloclist, (tommy_foreach_func*)dealloc_free);
873 :
874 : #if HAVE_THREAD
875 1718 : thread_mutex_destroy(&disk->fs_mutex);
876 : #endif
877 :
878 1718 : free(disk);
879 1718 : }
880 :
881 14 : struct snapraid_extra* extra_alloc(const char* name, const char* dir, uint64_t dev, const char* uuid)
882 : {
883 : struct snapraid_extra* extra;
884 : unsigned i;
885 :
886 14 : extra = malloc_nofail(sizeof(struct snapraid_extra));
887 14 : pathcpy(extra->name, sizeof(extra->name), name);
888 14 : pathimport(extra->dir, sizeof(extra->dir), dir);
889 14 : pathcpy(extra->uuid, sizeof(extra->uuid), uuid);
890 :
891 : /* ensure that the dir terminate with "/" if it isn't empty */
892 14 : pathslash(extra->dir, sizeof(extra->dir));
893 :
894 14 : extra->smartctl[0] = 0;
895 70 : for (i = 0; i < SMART_IGNORE_MAX; ++i)
896 56 : extra->smartignore[i] = 0;
897 14 : extra->fstype[0] = 0;
898 14 : extra->fslabel[0] = 0;
899 14 : extra->device = dev;
900 :
901 14 : return extra;
902 : }
903 :
904 14 : void extra_free(struct snapraid_extra* extra)
905 : {
906 14 : free(extra);
907 14 : }
908 :
909 :
910 67632373 : static inline void fs_lock(struct snapraid_disk* disk)
911 : {
912 : #if HAVE_THREAD
913 67632373 : if (!disk->single_thread)
914 46459602 : thread_mutex_lock(&disk->fs_mutex);
915 : #else
916 : (void)disk;
917 : #endif
918 67632373 : }
919 :
920 67632373 : static inline void fs_unlock(struct snapraid_disk* disk)
921 : {
922 : #if HAVE_THREAD
923 67632373 : if (!disk->single_thread)
924 46459602 : thread_mutex_unlock(&disk->fs_mutex);
925 : #else
926 : (void)disk;
927 : #endif
928 67632373 : }
929 :
930 : struct extent_disk_empty {
931 : block_off_t blockmax;
932 : };
933 :
934 : /**
935 : * Compare the extent if inside the specified blockmax.
936 : */
937 12 : static int extent_disk_empty_compare_unlock(const void* void_a, const void* void_b)
938 : {
939 12 : const struct extent_disk_empty* arg_a = void_a;
940 12 : const struct snapraid_extent* arg_b = void_b;
941 :
942 : /* if the block is inside the specified blockmax, it's found */
943 12 : if (arg_a->blockmax > arg_b->parity_pos)
944 7 : return 0;
945 :
946 : /* otherwise search for a smaller one */
947 5 : return -1;
948 : }
949 :
950 1023 : int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
951 : {
952 1023 : struct extent_disk_empty arg = { blockmax };
953 :
954 : /*
955 : * If there is an element, it's not empty
956 : * even if links and dirs have no block allocation
957 : */
958 1023 : if (!tommy_list_empty(&disk->filelist))
959 924 : return 0;
960 99 : if (!tommy_list_empty(&disk->linklist))
961 0 : return 0;
962 99 : if (!tommy_list_empty(&disk->dirlist))
963 0 : return 0;
964 :
965 99 : fs_lock(disk);
966 :
967 : /* search for any extent inside blockmax */
968 99 : if (tommy_tree_search_compare(&disk->fs_parity, extent_disk_empty_compare_unlock, &arg) != 0) {
969 7 : fs_unlock(disk);
970 7 : return 0;
971 : }
972 :
973 : /* finally, it's empty */
974 92 : fs_unlock(disk);
975 92 : return 1;
976 : }
977 :
978 : struct extent_disk_size {
979 : block_off_t size;
980 : };
981 :
982 : /**
983 : * Compare the extent by highest parity position.
984 : *
985 : * The maximum parity position is stored as size.
986 : */
987 57349 : static int extent_disk_size_compare_unlock(const void* void_a, const void* void_b)
988 : {
989 57349 : struct extent_disk_size* arg_a = (void*)void_a;
990 57349 : const struct snapraid_extent* arg_b = void_b;
991 :
992 : /* get the maximum size */
993 57349 : if (arg_a->size < arg_b->parity_pos + arg_b->count)
994 57349 : arg_a->size = arg_b->parity_pos + arg_b->count;
995 :
996 : /* search always for a bigger one */
997 57349 : return 1;
998 : }
999 :
1000 4915 : block_off_t fs_size(struct snapraid_disk* disk)
1001 : {
1002 4915 : struct extent_disk_size arg = { 0 };
1003 :
1004 4915 : fs_lock(disk);
1005 :
1006 4915 : tommy_tree_search_compare(&disk->fs_parity, extent_disk_size_compare_unlock, &arg);
1007 :
1008 4915 : fs_unlock(disk);
1009 :
1010 4915 : return arg.size;
1011 : }
1012 :
1013 : struct extent_check {
1014 : const struct snapraid_extent* prev;
1015 : int result;
1016 : };
1017 :
1018 7205868 : static void extent_parity_check_foreach_unlock(void* void_arg, void* void_obj)
1019 : {
1020 7205868 : struct extent_check* arg = void_arg;
1021 7205868 : const struct snapraid_extent* obj = void_obj;
1022 7205868 : const struct snapraid_extent* prev = arg->prev;
1023 :
1024 : /* set the next previous block */
1025 7205868 : arg->prev = obj;
1026 :
1027 : /* stop reporting if too many errors */
1028 7205868 : if (arg->result > 100) {
1029 : /* LCOV_EXCL_START */
1030 : return;
1031 : /* LCOV_EXCL_STOP */
1032 : }
1033 :
1034 7205868 : if (obj->count == 0) {
1035 : /* LCOV_EXCL_START */
1036 : log_fatal(EINTERNAL, "Internal inconsistency: Parity count zero for file '%s' at '%u'\n",
1037 : obj->file->sub, obj->parity_pos);
1038 : ++arg->result;
1039 : return;
1040 : /* LCOV_EXCL_STOP */
1041 : }
1042 :
1043 : /* check only if there is a previous block */
1044 7205868 : if (!prev)
1045 3104 : return;
1046 :
1047 : /* check the order */
1048 7202764 : if (prev->parity_pos >= obj->parity_pos) {
1049 : /* LCOV_EXCL_START */
1050 : log_fatal(EINTERNAL, "Internal inconsistency: Parity order for files '%s' at '%u:%u' and '%s' at '%u:%u'\n",
1051 : prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count);
1052 : ++arg->result;
1053 : return;
1054 : /* LCOV_EXCL_STOP */
1055 : }
1056 :
1057 : /* check that the extents don't overlap */
1058 7202764 : if (prev->parity_pos + prev->count > obj->parity_pos) {
1059 : /* LCOV_EXCL_START */
1060 : log_fatal(EINTERNAL, "Internal inconsistency: Parity overlap for files '%s' at '%u:%u' and '%s' at '%u:%u'\n",
1061 : prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count);
1062 : ++arg->result;
1063 : return;
1064 : /* LCOV_EXCL_STOP */
1065 : }
1066 : }
1067 :
1068 7205868 : static void extent_file_check_foreach_unlock(void* void_arg, void* void_obj)
1069 : {
1070 7205868 : struct extent_check* arg = void_arg;
1071 7205868 : const struct snapraid_extent* obj = void_obj;
1072 7205868 : const struct snapraid_extent* prev = arg->prev;
1073 :
1074 : /* set the next previous block */
1075 7205868 : arg->prev = obj;
1076 :
1077 : /* stop reporting if too many errors */
1078 7205868 : if (arg->result > 100) {
1079 : /* LCOV_EXCL_START */
1080 : return;
1081 : /* LCOV_EXCL_STOP */
1082 : }
1083 :
1084 7205868 : if (obj->count == 0) {
1085 : /* LCOV_EXCL_START */
1086 : log_fatal(EINTERNAL, "Internal inconsistency: File count zero for file '%s' at '%u'\n",
1087 : obj->file->sub, obj->file_pos);
1088 : ++arg->result;
1089 : return;
1090 : /* LCOV_EXCL_STOP */
1091 : }
1092 :
1093 : /* note that for deleted files, some extents may be missing */
1094 :
1095 : /* if the files are different */
1096 7205868 : if (!prev || prev->file != obj->file) {
1097 7156577 : if (prev != 0) {
1098 7153473 : if (file_flag_has(prev->file, FILE_IS_DELETED)) {
1099 : /* check that the extent doesn't overflow the file */
1100 59453 : if (prev->file_pos + prev->count > prev->file->blockmax) {
1101 : /* LCOV_EXCL_START */
1102 : log_fatal(EINTERNAL, "Internal inconsistency: Delete end for file '%s' at '%u:%u' overflowing size '%u'\n",
1103 : prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax);
1104 : ++arg->result;
1105 : return;
1106 : /* LCOV_EXCL_STOP */
1107 : }
1108 : } else {
1109 : /* check that the extent ends the file */
1110 7094020 : if (prev->file_pos + prev->count != prev->file->blockmax) {
1111 : /* LCOV_EXCL_START */
1112 : log_fatal(EINTERNAL, "Internal inconsistency: File end for file '%s' at '%u:%u' instead of size '%u'\n",
1113 : prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax);
1114 : ++arg->result;
1115 : return;
1116 : /* LCOV_EXCL_STOP */
1117 : }
1118 : }
1119 : }
1120 :
1121 7156577 : if (file_flag_has(obj->file, FILE_IS_DELETED)) {
1122 : /* check that the extent doesn't overflow the file */
1123 59540 : if (obj->file_pos + obj->count > obj->file->blockmax) {
1124 : /* LCOV_EXCL_START */
1125 : log_fatal(EINTERNAL, "Internal inconsistency: Delete start for file '%s' at '%u:%u' overflowing size '%u'\n",
1126 : obj->file->sub, obj->file_pos, obj->count, obj->file->blockmax);
1127 : ++arg->result;
1128 : return;
1129 : /* LCOV_EXCL_STOP */
1130 : }
1131 : } else {
1132 : /* check that the extent starts the file */
1133 7097037 : if (obj->file_pos != 0) {
1134 : /* LCOV_EXCL_START */
1135 : log_fatal(EINTERNAL, "Internal inconsistency: File start for file '%s' at '%u:%u'\n",
1136 : obj->file->sub, obj->file_pos, obj->count);
1137 : ++arg->result;
1138 : return;
1139 : /* LCOV_EXCL_STOP */
1140 : }
1141 : }
1142 : } else {
1143 : /* check the order */
1144 49291 : if (prev->file_pos >= obj->file_pos) {
1145 : /* LCOV_EXCL_START */
1146 : log_fatal(EINTERNAL, "Internal inconsistency: File order for file '%s' at '%u:%u' and at '%u:%u'\n",
1147 : prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
1148 : ++arg->result;
1149 : return;
1150 : /* LCOV_EXCL_STOP */
1151 : }
1152 :
1153 49291 : if (file_flag_has(obj->file, FILE_IS_DELETED)) {
1154 : /* check that the extents don't overlap */
1155 27 : if (prev->file_pos + prev->count > obj->file_pos) {
1156 : /* LCOV_EXCL_START */
1157 : log_fatal(EINTERNAL, "Internal inconsistency: Delete sequence for file '%s' at '%u:%u' and at '%u:%u'\n",
1158 : prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
1159 : ++arg->result;
1160 : return;
1161 : /* LCOV_EXCL_STOP */
1162 : }
1163 : } else {
1164 : /* check that the extents are sequential */
1165 49264 : if (prev->file_pos + prev->count != obj->file_pos) {
1166 : /* LCOV_EXCL_START */
1167 : log_fatal(EINTERNAL, "Internal inconsistency: File sequence for file '%s' at '%u:%u' and at '%u:%u'\n",
1168 : prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
1169 : ++arg->result;
1170 : return;
1171 : /* LCOV_EXCL_STOP */
1172 : }
1173 : }
1174 : }
1175 : }
1176 :
1177 3369 : int fs_check(struct snapraid_disk* disk)
1178 : {
1179 : struct extent_check arg;
1180 :
1181 : /* error count starts from 0 */
1182 3369 : arg.result = 0;
1183 :
1184 3369 : fs_lock(disk);
1185 :
1186 : /* check parity sequence */
1187 3369 : arg.prev = 0;
1188 3369 : tommy_tree_foreach_arg(&disk->fs_parity, extent_parity_check_foreach_unlock, &arg);
1189 :
1190 : /* check file sequence */
1191 3369 : arg.prev = 0;
1192 3369 : tommy_tree_foreach_arg(&disk->fs_file, extent_file_check_foreach_unlock, &arg);
1193 :
1194 3369 : fs_unlock(disk);
1195 :
1196 3369 : if (arg.result != 0)
1197 0 : return -1;
1198 :
1199 3369 : return 0;
1200 : }
1201 :
1202 : struct extent_parity_inside {
1203 : block_off_t parity_pos;
1204 : };
1205 :
1206 : /**
1207 : * Compare the extent if containing the specified parity position.
1208 : */
1209 168817336 : static int extent_parity_inside_compare_unlock(const void* void_a, const void* void_b)
1210 : {
1211 168817336 : const struct extent_parity_inside* arg_a = void_a;
1212 168817336 : const struct snapraid_extent* arg_b = void_b;
1213 :
1214 168817336 : if (arg_a->parity_pos < arg_b->parity_pos)
1215 61055388 : return -1;
1216 107761948 : if (arg_a->parity_pos >= arg_b->parity_pos + arg_b->count)
1217 94496629 : return 1;
1218 :
1219 13265319 : return 0;
1220 : }
1221 :
1222 : /**
1223 : * Search the extent at the specified parity position.
1224 : * The search is optimized for sequential accesses.
1225 : * \return If not found return 0
1226 : */
1227 52267377 : static struct snapraid_extent* fs_par2extent_get_unlock(struct snapraid_disk* disk, struct snapraid_extent** fs_last, block_off_t parity_pos)
1228 : {
1229 : struct snapraid_extent* extent;
1230 :
1231 : /* check if the last accessed extent matches */
1232 52267377 : if (*fs_last
1233 52231872 : && parity_pos >= (*fs_last)->parity_pos
1234 51520624 : && parity_pos < (*fs_last)->parity_pos + (*fs_last)->count
1235 : ) {
1236 36839343 : extent = *fs_last;
1237 : } else {
1238 15428034 : struct extent_parity_inside arg = { parity_pos };
1239 15428034 : extent = tommy_tree_search_compare(&disk->fs_parity, extent_parity_inside_compare_unlock, &arg);
1240 : }
1241 :
1242 52267377 : if (!extent)
1243 2162715 : return 0;
1244 :
1245 : /* store the last accessed extent */
1246 50104662 : *fs_last = extent;
1247 :
1248 50104662 : return extent;
1249 : }
1250 :
1251 : struct extent_file_inside {
1252 : struct snapraid_file* file;
1253 : block_off_t file_pos;
1254 : };
1255 :
1256 : /**
1257 : * Compare the extent if containing the specified file position.
1258 : */
1259 24151891 : static int extent_file_inside_compare_unlock(const void* void_a, const void* void_b)
1260 : {
1261 24151891 : const struct extent_file_inside* arg_a = void_a;
1262 24151891 : const struct snapraid_extent* arg_b = void_b;
1263 :
1264 24151891 : if (arg_a->file < arg_b->file)
1265 10149791 : return -1;
1266 14002100 : if (arg_a->file > arg_b->file)
1267 11695109 : return 1;
1268 :
1269 2306991 : if (arg_a->file_pos < arg_b->file_pos)
1270 8410 : return -1;
1271 2298581 : if (arg_a->file_pos >= arg_b->file_pos + arg_b->count)
1272 9076 : return 1;
1273 :
1274 2289505 : return 0;
1275 : }
1276 :
1277 : /**
1278 : * Search the extent at the specified file position.
1279 : * The search is optimized for sequential accesses.
1280 : * \return If not found return 0
1281 : */
1282 11352639 : static struct snapraid_extent* fs_file2extent_get_unlock(struct snapraid_disk* disk, struct snapraid_extent** fs_last, struct snapraid_file* file, block_off_t file_pos)
1283 : {
1284 : struct snapraid_extent* extent;
1285 :
1286 : /* check if the last accessed extent matches */
1287 11352639 : if (*fs_last
1288 11330392 : && file == (*fs_last)->file
1289 9078430 : && file_pos >= (*fs_last)->file_pos
1290 9078430 : && file_pos < (*fs_last)->file_pos + (*fs_last)->count
1291 : ) {
1292 9063126 : extent = *fs_last;
1293 : } else {
1294 2289513 : struct extent_file_inside arg = { file, file_pos };
1295 2289513 : extent = tommy_tree_search_compare(&disk->fs_file, extent_file_inside_compare_unlock, &arg);
1296 : }
1297 :
1298 11352639 : if (!extent)
1299 8 : return 0;
1300 :
1301 : /* store the last accessed extent */
1302 11352631 : *fs_last = extent;
1303 :
1304 11352631 : return extent;
1305 : }
1306 :
1307 52159052 : struct snapraid_file* fs_par2file_find(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos)
1308 : {
1309 : struct snapraid_extent* extent;
1310 : struct snapraid_file* file;
1311 :
1312 52159052 : fs_lock(disk);
1313 :
1314 52159052 : extent = fs_par2extent_get_unlock(disk, &disk->fs_last, parity_pos);
1315 :
1316 52159052 : if (!extent) {
1317 2162715 : fs_unlock(disk);
1318 2162715 : return 0;
1319 : }
1320 :
1321 49996337 : if (file_pos)
1322 49785512 : *file_pos = extent->file_pos + (parity_pos - extent->parity_pos);
1323 :
1324 49996337 : file = extent->file;
1325 :
1326 49996337 : fs_unlock(disk);
1327 49996337 : return file;
1328 : }
1329 :
1330 5529900 : block_off_t fs_file2par_find(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
1331 : {
1332 : struct snapraid_extent* extent;
1333 : block_off_t ret;
1334 :
1335 5529900 : fs_lock(disk);
1336 :
1337 5529900 : extent = fs_file2extent_get_unlock(disk, &disk->fs_last, file, file_pos);
1338 5529900 : if (!extent) {
1339 8 : fs_unlock(disk);
1340 8 : return POS_NULL;
1341 : }
1342 :
1343 5529892 : ret = extent->parity_pos + (file_pos - extent->file_pos);
1344 :
1345 5529892 : fs_unlock(disk);
1346 5529892 : return ret;
1347 : }
1348 :
1349 9826713 : void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos)
1350 : {
1351 : struct snapraid_extent* extent;
1352 : struct snapraid_extent* parity_extent;
1353 : struct snapraid_extent* file_extent;
1354 :
1355 9826713 : fs_lock(disk);
1356 :
1357 9826713 : if (file_pos > 0) {
1358 : /* search an existing extent for the previous file_pos */
1359 5822739 : extent = fs_file2extent_get_unlock(disk, &disk->fs_last, file, file_pos - 1);
1360 :
1361 5822739 : if (extent != 0 && parity_pos == extent->parity_pos + extent->count) {
1362 : /* ensure that we are extending the extent at the end */
1363 5794270 : if (file_pos != extent->file_pos + extent->count) {
1364 : /* LCOV_EXCL_START */
1365 : log_fatal(EINTERNAL, "Internal inconsistency: Allocating file '%s' at position '%u/%u' in the middle of extent '%u:%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, extent->file_pos, extent->count, disk->name);
1366 : os_abort();
1367 : /* LCOV_EXCL_STOP */
1368 : }
1369 :
1370 : /* extend the existing extent */
1371 5794270 : ++extent->count;
1372 :
1373 5794270 : fs_unlock(disk);
1374 5794270 : return;
1375 : }
1376 : }
1377 :
1378 : /* a extent doesn't exist, and we have to create a new one */
1379 4032443 : extent = extent_alloc(parity_pos, file, file_pos, 1);
1380 :
1381 : /* insert the extent in the trees */
1382 4032443 : parity_extent = tommy_tree_insert(&disk->fs_parity, &extent->parity_node, extent);
1383 4032443 : file_extent = tommy_tree_insert(&disk->fs_file, &extent->file_node, extent);
1384 :
1385 4032443 : if (parity_extent != extent || file_extent != extent) {
1386 : /* LCOV_EXCL_START */
1387 : log_fatal(EINTERNAL, "Internal inconsistency: Allocating file '%s' at position '%u/%u' for existing extent '%u:%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, extent->file_pos, extent->count, disk->name);
1388 : os_abort();
1389 : /* LCOV_EXCL_STOP */
1390 : }
1391 :
1392 : /* store the last accessed extent */
1393 4032443 : disk->fs_last = extent;
1394 :
1395 4032443 : fs_unlock(disk);
1396 : }
1397 :
1398 108325 : void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
1399 : {
1400 : struct snapraid_extent* extent;
1401 : struct snapraid_extent* second_extent;
1402 : struct snapraid_extent* parity_extent;
1403 : struct snapraid_extent* file_extent;
1404 : block_off_t first_count, second_count;
1405 :
1406 108325 : fs_lock(disk);
1407 :
1408 108325 : extent = fs_par2extent_get_unlock(disk, &disk->fs_last, parity_pos);
1409 108325 : if (!extent) {
1410 : /* LCOV_EXCL_START */
1411 : log_fatal(EINTERNAL, "Internal inconsistency: Deallocating parity position '%u' for not existing extent in disk '%s'\n", parity_pos, disk->name);
1412 : os_abort();
1413 : /* LCOV_EXCL_STOP */
1414 : }
1415 :
1416 : /* if it's the only block of the extent, delete it */
1417 108325 : if (extent->count == 1) {
1418 : /* remove from the trees */
1419 42183 : tommy_tree_remove(&disk->fs_parity, extent);
1420 42183 : tommy_tree_remove(&disk->fs_file, extent);
1421 :
1422 : /* deallocate */
1423 42183 : extent_free(extent);
1424 :
1425 : /* clear the last accessed extent */
1426 42183 : disk->fs_last = 0;
1427 :
1428 42183 : fs_unlock(disk);
1429 42183 : return;
1430 : }
1431 :
1432 : /* if it's at the start of the extent, shrink the extent */
1433 66142 : if (parity_pos == extent->parity_pos) {
1434 66139 : ++extent->parity_pos;
1435 66139 : ++extent->file_pos;
1436 66139 : --extent->count;
1437 :
1438 66139 : fs_unlock(disk);
1439 66139 : return;
1440 : }
1441 :
1442 : /* if it's at the end of the extent, shrink the extent */
1443 3 : if (parity_pos == extent->parity_pos + extent->count - 1) {
1444 1 : --extent->count;
1445 :
1446 1 : fs_unlock(disk);
1447 1 : return;
1448 : }
1449 :
1450 : /* otherwise it's in the middle */
1451 2 : first_count = parity_pos - extent->parity_pos;
1452 2 : second_count = extent->count - first_count - 1;
1453 :
1454 : /* adjust the first extent */
1455 2 : extent->count = first_count;
1456 :
1457 : /* allocate the second extent */
1458 2 : second_extent = extent_alloc(extent->parity_pos + first_count + 1, extent->file, extent->file_pos + first_count + 1, second_count);
1459 :
1460 : /* insert the extent in the trees */
1461 2 : parity_extent = tommy_tree_insert(&disk->fs_parity, &second_extent->parity_node, second_extent);
1462 2 : file_extent = tommy_tree_insert(&disk->fs_file, &second_extent->file_node, second_extent);
1463 :
1464 2 : if (parity_extent != second_extent || file_extent != second_extent) {
1465 : /* LCOV_EXCL_START */
1466 : log_fatal(EINTERNAL, "Internal inconsistency: Deallocating parity position '%u' for splitting extent '%u:%u' in disk '%s'\n", parity_pos, second_extent->file_pos, second_extent->count, disk->name);
1467 : os_abort();
1468 : /* LCOV_EXCL_STOP */
1469 : }
1470 :
1471 : /* store the last accessed extent */
1472 2 : disk->fs_last = second_extent;
1473 :
1474 2 : fs_unlock(disk);
1475 : }
1476 :
1477 58991917 : struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos)
1478 : {
1479 58991917 : if (file_pos >= file->blockmax) {
1480 : /* LCOV_EXCL_START */
1481 : log_fatal(EINTERNAL, "Internal inconsistency: Dereferencing file '%s' at position '%u/%u'\n", file->sub, file_pos, file->blockmax);
1482 : os_abort();
1483 : /* LCOV_EXCL_STOP */
1484 : }
1485 :
1486 58991917 : return file_block(file, file_pos);
1487 : }
1488 :
1489 40196795 : struct snapraid_block* fs_par2block_find(struct snapraid_disk* disk, block_off_t parity_pos)
1490 : {
1491 : struct snapraid_file* file;
1492 : block_off_t file_pos;
1493 :
1494 40196795 : file = fs_par2file_find(disk, parity_pos, &file_pos);
1495 40196795 : if (file == 0)
1496 2162715 : return BLOCK_NULL;
1497 :
1498 38034080 : return fs_file2block_get(file, file_pos);
1499 : }
1500 :
1501 1744 : struct snapraid_map* map_alloc(const char* name, unsigned position, block_off_t total_blocks, block_off_t free_blocks, const char* uuid)
1502 : {
1503 : struct snapraid_map* map;
1504 :
1505 1744 : map = malloc_nofail(sizeof(struct snapraid_map));
1506 1744 : pathcpy(map->name, sizeof(map->name), name);
1507 1744 : map->position = position;
1508 1744 : map->total_blocks = total_blocks;
1509 1744 : map->free_blocks = free_blocks;
1510 1744 : pathcpy(map->uuid, sizeof(map->uuid), uuid);
1511 :
1512 1744 : return map;
1513 : }
1514 :
1515 1618 : void map_free(struct snapraid_map* map)
1516 : {
1517 1618 : free(map);
1518 1618 : }
1519 :
1520 : /****************************************************************************/
1521 : /* bucket */
1522 :
1523 898 : static int bucket_cmp(const void* void_a, const void* void_b)
1524 : {
1525 898 : const struct snapraid_bucket* a = void_a;
1526 898 : const struct snapraid_bucket* b = void_b;
1527 898 : if (a->time_at < b->time_at)
1528 477 : return -1;
1529 421 : if (a->time_at > b->time_at)
1530 421 : return 1;
1531 0 : return 0;
1532 : }
1533 :
1534 862 : void bucket_free(struct snapraid_bucket* bucket)
1535 : {
1536 862 : free(bucket);
1537 862 : }
1538 :
1539 63916 : void bucket_insert(tommy_hashdyn* bucket_hash, time_t time_at, block_off_t count, int justsynced)
1540 : {
1541 63916 : tommy_hash_t hash = tommy_inthash_u64(time_at);
1542 63916 : tommy_node* i = tommy_hashdyn_bucket(bucket_hash, hash);
1543 63980 : while (i) {
1544 63083 : struct snapraid_bucket* entry = i->data;
1545 63083 : if (entry->time_at == time_at) {
1546 63019 : if (justsynced)
1547 28765 : entry->count_justsynced += count;
1548 : else
1549 34254 : entry->count_scrubbed += count;
1550 63019 : return;
1551 : }
1552 64 : i = i->next;
1553 : }
1554 :
1555 897 : struct snapraid_bucket* entry = malloc_nofail(sizeof(struct snapraid_bucket));
1556 897 : entry->time_at = time_at;
1557 897 : if (justsynced) {
1558 812 : entry->count_justsynced = count;
1559 812 : entry->count_scrubbed = 0;
1560 : } else {
1561 85 : entry->count_justsynced = 0;
1562 85 : entry->count_scrubbed = count;
1563 : }
1564 :
1565 897 : tommy_hashdyn_insert(bucket_hash, &entry->node, entry, hash);
1566 : }
1567 :
1568 473 : void bucket_to_list(tommy_hashdyn* bucket_hash, tommy_list* bucket_list, block_off_t* bucketcount)
1569 : {
1570 : /* clear previous list */
1571 473 : tommy_list_foreach(bucket_list, (tommy_foreach_func*)bucket_free);
1572 473 : tommy_list_init(bucket_list);
1573 :
1574 473 : log_tag("list:bucket_begin\n");
1575 :
1576 473 : log_tag("bucket:count:%" PRIu64 "\n", (uint64_t)tommy_hashdyn_count(bucket_hash));
1577 :
1578 473 : tommy_hashdyn_to_list(bucket_hash, bucket_list);
1579 :
1580 473 : tommy_list_sort(bucket_list, bucket_cmp);
1581 :
1582 473 : block_off_t count = 0;
1583 1370 : for (tommy_node* i = tommy_list_head(bucket_list); i != 0; i = i->next) {
1584 897 : struct snapraid_bucket* entry = i->data;
1585 897 : count += entry->count_scrubbed + entry->count_justsynced;
1586 897 : log_tag("bucket:entry:%" PRIu64 ":%u:%u\n", (uint64_t)entry->time_at, entry->count_scrubbed, entry->count_justsynced);
1587 : }
1588 :
1589 473 : log_tag("bucket:block_count:%u\n", count);
1590 :
1591 473 : log_tag("list:bucket_end\n");
1592 :
1593 473 : *bucketcount = count;
1594 473 : }
1595 :
1596 : /****************************************************************************/
1597 : /* format */
1598 :
1599 : int FMT_MODE = FMT_FILE;
1600 :
1601 : /**
1602 : * Format a file path for poll reference
1603 : */
1604 6 : const char* fmt_poll(const struct snapraid_disk* disk, const char* str, char* buffer)
1605 : {
1606 : (void)disk;
1607 6 : return esc_shell(str, buffer);
1608 : }
1609 :
1610 : /**
1611 : * Format a path name for terminal reference
1612 : */
1613 389533 : const char* fmt_term(const struct snapraid_disk* disk, const char* str, char* buffer)
1614 : {
1615 : const char* out[3];
1616 :
1617 389533 : switch (FMT_MODE) {
1618 365468 : case FMT_FILE :
1619 : default :
1620 365468 : return esc_shell(str, buffer);
1621 12030 : case FMT_DISK :
1622 12030 : out[0] = disk->name;
1623 12030 : out[1] = ":";
1624 12030 : out[2] = str;
1625 12030 : return esc_shell_multi(out, 3, buffer);
1626 12035 : case FMT_PATH :
1627 12035 : out[0] = disk->mount_point;
1628 12035 : out[1] = str;
1629 12035 : return esc_shell_multi(out, 2, buffer);
1630 : }
1631 : }
1632 :
1633 15 : const char* fmt_size(uint64_t size, char* buffer, size_t buffer_size)
1634 : {
1635 15 : if (size >= 10UL * TERA)
1636 0 : snprintf(buffer, buffer_size, "%" PRIu64 " T", size / TERA);
1637 15 : else if (size >= 10UL * GIGA)
1638 0 : snprintf(buffer, buffer_size, "%" PRIu64 " G", size / GIGA);
1639 15 : else if (size >= 10UL * MEGA)
1640 1 : snprintf(buffer, buffer_size, "%" PRIu64 " M", size / MEGA);
1641 14 : else if (size >= 10UL * KILO)
1642 13 : snprintf(buffer, buffer_size, "%" PRIu64 " k", size / KILO);
1643 : else
1644 1 : snprintf(buffer, buffer_size, "%" PRIu64 " ", size);
1645 :
1646 15 : return buffer;
1647 : }
1648 :
|