1 /*
2  * Copyright (C) 2011 Andrea Mazzoleni
3  *
4  * This program 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  * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "portable.h"
19 
20 #include "elem.h"
21 #include "support.h"
22 #include "util.h"
23 
24 /****************************************************************************/
25 /* snapraid */
26 
27 int BLOCK_HASH_SIZE = HASH_MAX;
28 
content_alloc(const char * path,uint64_t dev)29 struct snapraid_content* content_alloc(const char* path, uint64_t dev)
30 {
31 	struct snapraid_content* content;
32 
33 	content = malloc_nofail(sizeof(struct snapraid_content));
34 	pathimport(content->content, sizeof(content->content), path);
35 	content->device = dev;
36 
37 	return content;
38 }
39 
content_free(struct snapraid_content * content)40 void content_free(struct snapraid_content* content)
41 {
42 	free(content);
43 }
44 
filter_alloc_file(int direction,const char * pattern)45 struct snapraid_filter* filter_alloc_file(int direction, const char* pattern)
46 {
47 	struct snapraid_filter* filter;
48 	char* i;
49 	char* first;
50 	char* last;
51 	int token_is_valid;
52 	int token_is_filled;
53 
54 	filter = malloc_nofail(sizeof(struct snapraid_filter));
55 	pathimport(filter->pattern, sizeof(filter->pattern), pattern);
56 	filter->direction = direction;
57 
58 	/* find first and last slash */
59 	first = 0;
60 	last = 0;
61 	/* reject invalid tokens, like "<empty>", ".", ".." and more dots */
62 	token_is_valid = 0;
63 	token_is_filled = 0;
64 	for (i = filter->pattern; *i; ++i) {
65 		if (*i == '/') {
66 			/* reject invalid tokens, but accept an empty one as first */
67 			if (!token_is_valid && (first != 0 || token_is_filled)) {
68 				free(filter);
69 				return 0;
70 			}
71 			token_is_valid = 0;
72 			token_is_filled = 0;
73 
74 			/* update slash position */
75 			if (!first)
76 				first = i;
77 			last = i;
78 		} else if (*i != '.') {
79 			token_is_valid = 1;
80 			token_is_filled = 1;
81 		} else {
82 			token_is_filled = 1;
83 		}
84 	}
85 
86 	/* reject invalid tokens, but accept an empty one as last, but not if it's the only one */
87 	if (!token_is_valid && (first == 0 || token_is_filled)) {
88 		free(filter);
89 		return 0;
90 	}
91 
92 	/* it's a file filter */
93 	filter->is_disk = 0;
94 
95 	if (first == 0) {
96 		/* no slash */
97 		filter->is_path = 0;
98 		filter->is_dir = 0;
99 	} else if (first == last && last[1] == 0) {
100 		/* one slash at the end */
101 		filter->is_path = 0;
102 		filter->is_dir = 1;
103 		last[0] = 0;
104 	} else {
105 		/* at least a slash not at the end */
106 		filter->is_path = 1;
107 		if (last[1] == 0) {
108 			filter->is_dir = 1;
109 			last[0] = 0;
110 		} else {
111 			filter->is_dir = 0;
112 		}
113 
114 		/* a slash must be the first char, as we don't support PATH/FILE and PATH/DIR/ */
115 		if (filter->pattern[0] != '/') {
116 			free(filter);
117 			return 0;
118 		}
119 	}
120 
121 	return filter;
122 }
123 
filter_alloc_disk(int direction,const char * pattern)124 struct snapraid_filter* filter_alloc_disk(int direction, const char* pattern)
125 {
126 	struct snapraid_filter* filter;
127 
128 	filter = malloc_nofail(sizeof(struct snapraid_filter));
129 	pathimport(filter->pattern, sizeof(filter->pattern), pattern);
130 	filter->direction = direction;
131 
132 	/* it's a disk filter */
133 	filter->is_disk = 1;
134 	filter->is_path = 0;
135 	filter->is_dir = 0;
136 
137 	/* no slash allowed in disk names */
138 	if (strchr(filter->pattern, '/') != 0) {
139 		/* LCOV_EXCL_START */
140 		free(filter);
141 		return 0;
142 		/* LCOV_EXCL_STOP */
143 	}
144 
145 	return filter;
146 }
147 
filter_free(struct snapraid_filter * filter)148 void filter_free(struct snapraid_filter* filter)
149 {
150 	free(filter);
151 }
152 
filter_type(struct snapraid_filter * filter,char * out,size_t out_size)153 const char* filter_type(struct snapraid_filter* filter, char* out, size_t out_size)
154 {
155 	const char* direction;
156 
157 	if (filter->direction < 0)
158 		direction = "exclude";
159 	else
160 		direction = "include";
161 
162 	if (filter->is_disk)
163 		pathprint(out, out_size, "%s %s:", direction, filter->pattern);
164 	else if (filter->is_dir)
165 		pathprint(out, out_size, "%s %s/", direction, filter->pattern);
166 	else
167 		pathprint(out, out_size, "%s %s", direction, filter->pattern);
168 
169 	return out;
170 }
171 
filter_apply(struct snapraid_filter * filter,struct snapraid_filter ** reason,const char * path,const char * name,int is_dir)172 static int filter_apply(struct snapraid_filter* filter, struct snapraid_filter** reason, const char* path, const char* name, int is_dir)
173 {
174 	int ret = 0;
175 
176 	/* match dirs with dirs and files with files */
177 	if (filter->is_dir && !is_dir)
178 		return 0;
179 	if (!filter->is_dir && is_dir)
180 		return 0;
181 
182 	if (filter->is_path) {
183 		/* skip initial slash, as always missing from the path */
184 		if (fnmatch(filter->pattern + 1, path, FNM_PATHNAME | FNM_CASEINSENSITIVE_FOR_WIN) == 0)
185 			ret = filter->direction;
186 	} else {
187 		if (fnmatch(filter->pattern, name, FNM_CASEINSENSITIVE_FOR_WIN) == 0)
188 			ret = filter->direction;
189 	}
190 
191 	if (reason != 0 && ret < 0)
192 		*reason = filter;
193 
194 	return ret;
195 }
196 
filter_recurse(struct snapraid_filter * filter,struct snapraid_filter ** reason,const char * const_path,int is_dir)197 static int filter_recurse(struct snapraid_filter* filter, struct snapraid_filter** reason, const char* const_path, int is_dir)
198 {
199 	char path[PATH_MAX];
200 	char* name;
201 	unsigned i;
202 
203 	pathcpy(path, sizeof(path), const_path);
204 
205 	/* filter for all the directories */
206 	name = path;
207 	for (i = 0; path[i] != 0; ++i) {
208 		if (path[i] == '/') {
209 			/* set a terminator */
210 			path[i] = 0;
211 
212 			/* filter the directory */
213 			if (filter_apply(filter, reason, path, name, 1) != 0)
214 				return filter->direction;
215 
216 			/* restore the slash */
217 			path[i] = '/';
218 
219 			/* next name */
220 			name = path + i + 1;
221 		}
222 	}
223 
224 	/* filter the final file */
225 	if (filter_apply(filter, reason, path, name, is_dir) != 0)
226 		return filter->direction;
227 
228 	return 0;
229 }
230 
filter_element(tommy_list * filterlist,struct snapraid_filter ** reason,const char * disk,const char * sub,int is_dir,int is_def_include)231 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 	int direction = 1; /* by default include all */
236 
237 	/* for each filter */
238 	for (i = tommy_list_head(filterlist); i != 0; i = i->next) {
239 		int ret;
240 		struct snapraid_filter* filter = i->data;
241 
242 		if (filter->is_disk) {
243 			if (fnmatch(filter->pattern, disk, FNM_CASEINSENSITIVE_FOR_WIN) == 0)
244 				ret = filter->direction;
245 			else
246 				ret = 0;
247 			if (reason != 0 && ret < 0)
248 				*reason = filter;
249 		} else {
250 			ret = filter_recurse(filter, reason, sub, is_dir);
251 		}
252 
253 		if (ret > 0) {
254 			/* include the file */
255 			return 0;
256 		} else if (ret < 0) {
257 			/* exclude the file */
258 			return -1;
259 		} else {
260 			/* default is opposite of the last filter */
261 			direction = -filter->direction;
262 			if (reason != 0 && direction < 0)
263 				*reason = filter;
264 			/* continue with the next one */
265 		}
266 	}
267 
268 	/* directories are always included by default, otherwise we cannot apply rules */
269 	/* to the contained files */
270 	if (is_def_include)
271 		return 0;
272 
273 	/* files are excluded/included depending of the last rule processed */
274 	if (direction < 0)
275 		return -1;
276 
277 	return 0;
278 }
279 
filter_path(tommy_list * filterlist,struct snapraid_filter ** reason,const char * disk,const char * sub)280 int filter_path(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
281 {
282 	return filter_element(filterlist, reason, disk, sub, 0, 0);
283 }
284 
filter_subdir(tommy_list * filterlist,struct snapraid_filter ** reason,const char * disk,const char * sub)285 int filter_subdir(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
286 {
287 	return filter_element(filterlist, reason, disk, sub, 1, 1);
288 }
289 
filter_emptydir(tommy_list * filterlist,struct snapraid_filter ** reason,const char * disk,const char * sub)290 int filter_emptydir(tommy_list* filterlist, struct snapraid_filter** reason, const char* disk, const char* sub)
291 {
292 	return filter_element(filterlist, reason, disk, sub, 1, 0);
293 }
294 
filter_existence(int filter_missing,const char * dir,const char * sub)295 int filter_existence(int filter_missing, const char* dir, const char* sub)
296 {
297 	char path[PATH_MAX];
298 	struct stat st;
299 
300 	if (!filter_missing)
301 		return 0;
302 
303 	/* we directly check if in the disk the file is present or not */
304 	pathprint(path, sizeof(path), "%s%s", dir, sub);
305 
306 	if (lstat(path, &st) != 0) {
307 		/* if the file doesn't exist, we don't filter it out */
308 		if (errno == ENOENT)
309 			return 0;
310 		/* LCOV_EXCL_START */
311 		log_fatal("Error in stat file '%s'. %s.\n", path, strerror(errno));
312 		exit(EXIT_FAILURE);
313 		/* LCOV_EXCL_STOP */
314 	}
315 
316 	/* the file is present, so we filter it out */
317 	return 1;
318 }
319 
filter_correctness(int filter_error,tommy_arrayblkof * infoarr,struct snapraid_disk * disk,struct snapraid_file * file)320 int filter_correctness(int filter_error, tommy_arrayblkof* infoarr, struct snapraid_disk* disk, struct snapraid_file* file)
321 {
322 	unsigned i;
323 
324 	if (!filter_error)
325 		return 0;
326 
327 	/* check each block of the file */
328 	for (i = 0; i < file->blockmax; ++i) {
329 		block_off_t parity_pos = fs_file2par_get(disk, file, i);
330 		snapraid_info info = info_get(infoarr, parity_pos);
331 
332 		/* if the file has a bad block, don't exclude it */
333 		if (info_get_bad(info))
334 			return 0;
335 	}
336 
337 	/* the file is correct, so we filter it out */
338 	return 1;
339 }
340 
filter_content(tommy_list * contentlist,const char * path)341 int filter_content(tommy_list* contentlist, const char* path)
342 {
343 	tommy_node* i;
344 
345 	for (i = tommy_list_head(contentlist); i != 0; i = i->next) {
346 		struct snapraid_content* content = i->data;
347 		char tmp[PATH_MAX];
348 
349 		if (pathcmp(content->content, path) == 0)
350 			return -1;
351 
352 		/* exclude also the ".tmp" copy used to save it */
353 		pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
354 		if (pathcmp(tmp, path) == 0)
355 			return -1;
356 
357 		/* exclude also the ".lock" file */
358 		pathprint(tmp, sizeof(tmp), "%s.lock", content->content);
359 		if (pathcmp(tmp, path) == 0)
360 			return -1;
361 	}
362 
363 	return 0;
364 }
365 
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)366 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)
367 {
368 	struct snapraid_file* file;
369 	block_off_t i;
370 
371 	file = malloc_nofail(sizeof(struct snapraid_file));
372 	file->sub = strdup_nofail(sub);
373 	file->size = size;
374 	file->blockmax = (size + block_size - 1) / block_size;
375 	file->mtime_sec = mtime_sec;
376 	file->mtime_nsec = mtime_nsec;
377 	file->inode = inode;
378 	file->physical = physical;
379 	file->flag = 0;
380 	file->blockvec = malloc_nofail(file->blockmax * block_sizeof());
381 
382 	for (i = 0; i < file->blockmax; ++i) {
383 		struct snapraid_block* block = file_block(file, i);
384 		block_state_set(block, BLOCK_STATE_CHG);
385 		hash_invalid_set(block->hash);
386 	}
387 
388 	return file;
389 }
390 
file_dup(struct snapraid_file * copy)391 struct snapraid_file* file_dup(struct snapraid_file* copy)
392 {
393 	struct snapraid_file* file;
394 	block_off_t i;
395 
396 	file = malloc_nofail(sizeof(struct snapraid_file));
397 	file->sub = strdup_nofail(copy->sub);
398 	file->size = copy->size;
399 	file->blockmax = copy->blockmax;
400 	file->mtime_sec = copy->mtime_sec;
401 	file->mtime_nsec = copy->mtime_nsec;
402 	file->inode = copy->inode;
403 	file->physical = copy->physical;
404 	file->flag = copy->flag;
405 	file->blockvec = malloc_nofail(file->blockmax * block_sizeof());
406 
407 	for (i = 0; i < file->blockmax; ++i) {
408 		struct snapraid_block* block = file_block(file, i);
409 		struct snapraid_block* copy_block = file_block(copy, i);
410 		block->state = copy_block->state;
411 		memcpy(block->hash, copy_block->hash, BLOCK_HASH_SIZE);
412 	}
413 
414 	return file;
415 }
416 
file_free(struct snapraid_file * file)417 void file_free(struct snapraid_file* file)
418 {
419 	free(file->sub);
420 	file->sub = 0;
421 	free(file->blockvec);
422 	file->blockvec = 0;
423 	free(file);
424 }
425 
file_rename(struct snapraid_file * file,const char * sub)426 void file_rename(struct snapraid_file* file, const char* sub)
427 {
428 	free(file->sub);
429 	file->sub = strdup_nofail(sub);
430 }
431 
file_copy(struct snapraid_file * src_file,struct snapraid_file * dst_file)432 void file_copy(struct snapraid_file* src_file, struct snapraid_file* dst_file)
433 {
434 	block_off_t i;
435 
436 	if (src_file->size != dst_file->size) {
437 		/* LCOV_EXCL_START */
438 		log_fatal("Internal inconsistency in copy file with different size\n");
439 		os_abort();
440 		/* LCOV_EXCL_STOP */
441 	}
442 
443 	if (src_file->mtime_sec != dst_file->mtime_sec) {
444 		/* LCOV_EXCL_START */
445 		log_fatal("Internal inconsistency in copy file with different mtime_sec\n");
446 		os_abort();
447 		/* LCOV_EXCL_STOP */
448 	}
449 
450 	if (src_file->mtime_nsec != dst_file->mtime_nsec) {
451 		/* LCOV_EXCL_START */
452 		log_fatal("Internal inconsistency in copy file with different mtime_nsec\n");
453 		os_abort();
454 		/* LCOV_EXCL_STOP */
455 	}
456 
457 	for (i = 0; i < dst_file->blockmax; ++i) {
458 		/* set a block with hash computed but without parity */
459 		block_state_set(file_block(dst_file, i), BLOCK_STATE_REP);
460 
461 		/* copy the hash */
462 		memcpy(file_block(dst_file, i)->hash, file_block(src_file, i)->hash, BLOCK_HASH_SIZE);
463 	}
464 
465 	file_flag_set(dst_file, FILE_IS_COPY);
466 }
467 
file_name(const struct snapraid_file * file)468 const char* file_name(const struct snapraid_file* file)
469 {
470 	const char* r = strrchr(file->sub, '/');
471 
472 	if (!r)
473 		r = file->sub;
474 	else
475 		++r;
476 	return r;
477 }
478 
file_block_size(struct snapraid_file * file,block_off_t file_pos,unsigned block_size)479 unsigned file_block_size(struct snapraid_file* file, block_off_t file_pos, unsigned block_size)
480 {
481 	/* if it's the last block */
482 	if (file_pos + 1 == file->blockmax) {
483 		unsigned block_remainder;
484 		if (file->size == 0)
485 			return 0;
486 		block_remainder = file->size % block_size;
487 		if (block_remainder == 0)
488 			block_remainder = block_size;
489 		return block_remainder;
490 	}
491 
492 	return block_size;
493 }
494 
file_block_is_last(struct snapraid_file * file,block_off_t file_pos)495 int file_block_is_last(struct snapraid_file* file, block_off_t file_pos)
496 {
497 	if (file_pos == 0 && file->blockmax == 0)
498 		return 1;
499 
500 	if (file_pos >= file->blockmax) {
501 		/* LCOV_EXCL_START */
502 		log_fatal("Internal inconsistency in file block position\n");
503 		os_abort();
504 		/* LCOV_EXCL_STOP */
505 	}
506 
507 	return file_pos == file->blockmax - 1;
508 }
509 
file_inode_compare_to_arg(const void * void_arg,const void * void_data)510 int file_inode_compare_to_arg(const void* void_arg, const void* void_data)
511 {
512 	const uint64_t* arg = void_arg;
513 	const struct snapraid_file* file = void_data;
514 
515 	if (*arg < file->inode)
516 		return -1;
517 	if (*arg > file->inode)
518 		return 1;
519 	return 0;
520 }
521 
file_inode_compare(const void * void_a,const void * void_b)522 int file_inode_compare(const void* void_a, const void* void_b)
523 {
524 	const struct snapraid_file* file_a = void_a;
525 	const struct snapraid_file* file_b = void_b;
526 
527 	if (file_a->inode < file_b->inode)
528 		return -1;
529 	if (file_a->inode > file_b->inode)
530 		return 1;
531 	return 0;
532 }
533 
file_path_compare(const void * void_a,const void * void_b)534 int file_path_compare(const void* void_a, const void* void_b)
535 {
536 	const struct snapraid_file* file_a = void_a;
537 	const struct snapraid_file* file_b = void_b;
538 
539 	return strcmp(file_a->sub, file_b->sub);
540 }
541 
file_physical_compare(const void * void_a,const void * void_b)542 int file_physical_compare(const void* void_a, const void* void_b)
543 {
544 	const struct snapraid_file* file_a = void_a;
545 	const struct snapraid_file* file_b = void_b;
546 
547 	if (file_a->physical < file_b->physical)
548 		return -1;
549 	if (file_a->physical > file_b->physical)
550 		return 1;
551 	return 0;
552 }
553 
file_path_compare_to_arg(const void * void_arg,const void * void_data)554 int file_path_compare_to_arg(const void* void_arg, const void* void_data)
555 {
556 	const char* arg = void_arg;
557 	const struct snapraid_file* file = void_data;
558 
559 	return strcmp(arg, file->sub);
560 }
561 
file_name_compare(const void * void_a,const void * void_b)562 int file_name_compare(const void* void_a, const void* void_b)
563 {
564 	const struct snapraid_file* file_a = void_a;
565 	const struct snapraid_file* file_b = void_b;
566 	const char* name_a = file_name(file_a);
567 	const char* name_b = file_name(file_b);
568 
569 	return strcmp(name_a, name_b);
570 }
571 
file_stamp_compare(const void * void_a,const void * void_b)572 int file_stamp_compare(const void* void_a, const void* void_b)
573 {
574 	const struct snapraid_file* file_a = void_a;
575 	const struct snapraid_file* file_b = void_b;
576 
577 	if (file_a->size < file_b->size)
578 		return -1;
579 	if (file_a->size > file_b->size)
580 		return 1;
581 
582 	if (file_a->mtime_sec < file_b->mtime_sec)
583 		return -1;
584 	if (file_a->mtime_sec > file_b->mtime_sec)
585 		return 1;
586 
587 	if (file_a->mtime_nsec < file_b->mtime_nsec)
588 		return -1;
589 	if (file_a->mtime_nsec > file_b->mtime_nsec)
590 		return 1;
591 
592 	return 0;
593 }
594 
file_namestamp_compare(const void * void_a,const void * void_b)595 int file_namestamp_compare(const void* void_a, const void* void_b)
596 {
597 	int ret;
598 
599 	ret = file_name_compare(void_a, void_b);
600 	if (ret != 0)
601 		return ret;
602 
603 	return file_stamp_compare(void_a, void_b);
604 }
605 
file_pathstamp_compare(const void * void_a,const void * void_b)606 int file_pathstamp_compare(const void* void_a, const void* void_b)
607 {
608 	int ret;
609 
610 	ret = file_path_compare(void_a, void_b);
611 	if (ret != 0)
612 		return ret;
613 
614 	return file_stamp_compare(void_a, void_b);
615 }
616 
extent_alloc(block_off_t parity_pos,struct snapraid_file * file,block_off_t file_pos,block_off_t count)617 struct snapraid_extent* extent_alloc(block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos, block_off_t count)
618 {
619 	struct snapraid_extent* extent;
620 
621 	if (count == 0) {
622 		/* LCOV_EXCL_START */
623 		log_fatal("Internal inconsistency when allocating empty extent for file '%s' at position '%u/%u'\n", file->sub, file_pos, file->blockmax);
624 		os_abort();
625 		/* LCOV_EXCL_STOP */
626 	}
627 	if (file_pos + count > file->blockmax) {
628 		/* LCOV_EXCL_START */
629 		log_fatal("Internal inconsistency when allocating overflowing extent for file '%s' at position '%u:%u/%u'\n", file->sub, file_pos, count, file->blockmax);
630 		os_abort();
631 		/* LCOV_EXCL_STOP */
632 	}
633 
634 	extent = malloc_nofail(sizeof(struct snapraid_extent));
635 	extent->parity_pos = parity_pos;
636 	extent->file = file;
637 	extent->file_pos = file_pos;
638 	extent->count = count;
639 
640 	return extent;
641 }
642 
extent_free(struct snapraid_extent * extent)643 void extent_free(struct snapraid_extent* extent)
644 {
645 	free(extent);
646 }
647 
extent_parity_compare(const void * void_a,const void * void_b)648 int extent_parity_compare(const void* void_a, const void* void_b)
649 {
650 	const struct snapraid_extent* arg_a = void_a;
651 	const struct snapraid_extent* arg_b = void_b;
652 
653 	if (arg_a->parity_pos < arg_b->parity_pos)
654 		return -1;
655 	if (arg_a->parity_pos > arg_b->parity_pos)
656 		return 1;
657 
658 	return 0;
659 }
660 
extent_file_compare(const void * void_a,const void * void_b)661 int extent_file_compare(const void* void_a, const void* void_b)
662 {
663 	const struct snapraid_extent* arg_a = void_a;
664 	const struct snapraid_extent* arg_b = void_b;
665 
666 	if (arg_a->file < arg_b->file)
667 		return -1;
668 	if (arg_a->file > arg_b->file)
669 		return 1;
670 
671 	if (arg_a->file_pos < arg_b->file_pos)
672 		return -1;
673 	if (arg_a->file_pos > arg_b->file_pos)
674 		return 1;
675 
676 	return 0;
677 }
678 
link_alloc(const char * sub,const char * linkto,unsigned link_flag)679 struct snapraid_link* link_alloc(const char* sub, const char* linkto, unsigned link_flag)
680 {
681 	struct snapraid_link* slink;
682 
683 	slink = malloc_nofail(sizeof(struct snapraid_link));
684 	slink->sub = strdup_nofail(sub);
685 	slink->linkto = strdup_nofail(linkto);
686 	slink->flag = link_flag;
687 
688 	return slink;
689 }
690 
link_free(struct snapraid_link * slink)691 void link_free(struct snapraid_link* slink)
692 {
693 	free(slink->sub);
694 	free(slink->linkto);
695 	free(slink);
696 }
697 
link_name_compare_to_arg(const void * void_arg,const void * void_data)698 int link_name_compare_to_arg(const void* void_arg, const void* void_data)
699 {
700 	const char* arg = void_arg;
701 	const struct snapraid_link* slink = void_data;
702 
703 	return strcmp(arg, slink->sub);
704 }
705 
link_alpha_compare(const void * void_a,const void * void_b)706 int link_alpha_compare(const void* void_a, const void* void_b)
707 {
708 	const struct snapraid_link* slink_a = void_a;
709 	const struct snapraid_link* slink_b = void_b;
710 
711 	return strcmp(slink_a->sub, slink_b->sub);
712 }
713 
dir_alloc(const char * sub)714 struct snapraid_dir* dir_alloc(const char* sub)
715 {
716 	struct snapraid_dir* dir;
717 
718 	dir = malloc_nofail(sizeof(struct snapraid_dir));
719 	dir->sub = strdup_nofail(sub);
720 	dir->flag = 0;
721 
722 	return dir;
723 }
724 
dir_free(struct snapraid_dir * dir)725 void dir_free(struct snapraid_dir* dir)
726 {
727 	free(dir->sub);
728 	free(dir);
729 }
730 
dir_name_compare(const void * void_arg,const void * void_data)731 int dir_name_compare(const void* void_arg, const void* void_data)
732 {
733 	const char* arg = void_arg;
734 	const struct snapraid_dir* dir = void_data;
735 
736 	return strcmp(arg, dir->sub);
737 }
738 
disk_alloc(const char * name,const char * dir,uint64_t dev,const char * uuid,int skip_access)739 struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev, const char* uuid, int skip_access)
740 {
741 	struct snapraid_disk* disk;
742 
743 	disk = malloc_nofail(sizeof(struct snapraid_disk));
744 	pathcpy(disk->name, sizeof(disk->name), name);
745 	pathimport(disk->dir, sizeof(disk->dir), dir);
746 	pathcpy(disk->uuid, sizeof(disk->uuid), uuid);
747 
748 	/* ensure that the dir terminate with "/" if it isn't empty */
749 	pathslash(disk->dir, sizeof(disk->dir));
750 
751 #if HAVE_THREAD
752 	thread_mutex_init(&disk->fs_mutex);
753 	disk->fs_mutex_enabled = 0; /* lock will be enabled at threads start */
754 #endif
755 
756 	disk->smartctl[0] = 0;
757 	disk->device = dev;
758 	disk->tick = 0;
759 	disk->cached_blocks = 0;
760 	disk->progress_file = 0;
761 	disk->total_blocks = 0;
762 	disk->free_blocks = 0;
763 	disk->first_free_block = 0;
764 	disk->has_volatile_inodes = 0;
765 	disk->has_volatile_hardlinks = 0;
766 	disk->has_unreliable_physical = 0;
767 	disk->has_different_uuid = 0;
768 	disk->has_unsupported_uuid = *uuid == 0; /* empty UUID means unsupported */
769 	disk->had_empty_uuid = 0;
770 	disk->mapping_idx = -1;
771 	disk->skip_access = skip_access;
772 	tommy_list_init(&disk->filelist);
773 	tommy_list_init(&disk->deletedlist);
774 	tommy_hashdyn_init(&disk->inodeset);
775 	tommy_hashdyn_init(&disk->pathset);
776 	tommy_hashdyn_init(&disk->stampset);
777 	tommy_list_init(&disk->linklist);
778 	tommy_hashdyn_init(&disk->linkset);
779 	tommy_list_init(&disk->dirlist);
780 	tommy_hashdyn_init(&disk->dirset);
781 	tommy_tree_init(&disk->fs_parity, extent_parity_compare);
782 	tommy_tree_init(&disk->fs_file, extent_file_compare);
783 	disk->fs_last = 0;
784 
785 	return disk;
786 }
787 
disk_free(struct snapraid_disk * disk)788 void disk_free(struct snapraid_disk* disk)
789 {
790 	tommy_list_foreach(&disk->filelist, (tommy_foreach_func*)file_free);
791 	tommy_list_foreach(&disk->deletedlist, (tommy_foreach_func*)file_free);
792 	tommy_tree_foreach(&disk->fs_file, (tommy_foreach_func*)extent_free);
793 	tommy_hashdyn_done(&disk->inodeset);
794 	tommy_hashdyn_done(&disk->pathset);
795 	tommy_hashdyn_done(&disk->stampset);
796 	tommy_list_foreach(&disk->linklist, (tommy_foreach_func*)link_free);
797 	tommy_hashdyn_done(&disk->linkset);
798 	tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free);
799 	tommy_hashdyn_done(&disk->dirset);
800 
801 #if HAVE_THREAD
802 	thread_mutex_destroy(&disk->fs_mutex);
803 #endif
804 
805 	free(disk);
806 }
807 
disk_start_thread(struct snapraid_disk * disk)808 void disk_start_thread(struct snapraid_disk* disk)
809 {
810 #if HAVE_THREAD
811 	disk->fs_mutex_enabled = 1;
812 #else
813 	(void)disk;
814 #endif
815 }
816 
fs_lock(struct snapraid_disk * disk)817 static inline void fs_lock(struct snapraid_disk* disk)
818 {
819 #if HAVE_THREAD
820 	if (disk->fs_mutex_enabled)
821 		thread_mutex_lock(&disk->fs_mutex);
822 #else
823 	(void)disk;
824 #endif
825 }
826 
fs_unlock(struct snapraid_disk * disk)827 static inline void fs_unlock(struct snapraid_disk* disk)
828 {
829 #if HAVE_THREAD
830 	if (disk->fs_mutex_enabled)
831 		thread_mutex_unlock(&disk->fs_mutex);
832 #else
833 	(void)disk;
834 #endif
835 }
836 
837 struct extent_disk_empty {
838 	block_off_t blockmax;
839 };
840 
841 /**
842  * Compare the extent if inside the specified blockmax.
843  */
extent_disk_empty_compare_unlock(const void * void_a,const void * void_b)844 static int extent_disk_empty_compare_unlock(const void* void_a, const void* void_b)
845 {
846 	const struct extent_disk_empty* arg_a = void_a;
847 	const struct snapraid_extent* arg_b = void_b;
848 
849 	/* if the block is inside the specified blockmax, it's found */
850 	if (arg_a->blockmax > arg_b->parity_pos)
851 		return 0;
852 
853 	/* otherwise search for a smaller one */
854 	return -1;
855 }
856 
fs_is_empty(struct snapraid_disk * disk,block_off_t blockmax)857 int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
858 {
859 	struct extent_disk_empty arg = { blockmax };
860 
861 	/* if there is an element, it's not empty */
862 	/* even if links and dirs have no block allocation */
863 	if (!tommy_list_empty(&disk->filelist))
864 		return 0;
865 	if (!tommy_list_empty(&disk->linklist))
866 		return 0;
867 	if (!tommy_list_empty(&disk->dirlist))
868 		return 0;
869 
870 	fs_lock(disk);
871 
872 	/* search for any extent inside blockmax */
873 	if (tommy_tree_search_compare(&disk->fs_parity, extent_disk_empty_compare_unlock, &arg) != 0) {
874 		fs_unlock(disk);
875 		return 0;
876 	}
877 
878 	/* finally, it's empty */
879 	fs_unlock(disk);
880 	return 1;
881 }
882 
883 struct extent_disk_size {
884 	block_off_t size;
885 };
886 
887 /**
888  * Compare the extent by highest parity position.
889  *
890  * The maximum parity position is stored as size.
891  */
extent_disk_size_compare_unlock(const void * void_a,const void * void_b)892 static int extent_disk_size_compare_unlock(const void* void_a, const void* void_b)
893 {
894 	struct extent_disk_size* arg_a = (void*)void_a;
895 	const struct snapraid_extent* arg_b = void_b;
896 
897 	/* get the maximum size */
898 	if (arg_a->size < arg_b->parity_pos + arg_b->count)
899 		arg_a->size = arg_b->parity_pos + arg_b->count;
900 
901 	/* search always for a bigger one */
902 	return 1;
903 }
904 
fs_size(struct snapraid_disk * disk)905 block_off_t fs_size(struct snapraid_disk* disk)
906 {
907 	struct extent_disk_size arg = { 0 };
908 
909 	fs_lock(disk);
910 
911 	tommy_tree_search_compare(&disk->fs_parity, extent_disk_size_compare_unlock, &arg);
912 
913 	fs_unlock(disk);
914 
915 	return arg.size;
916 }
917 
918 struct extent_check {
919 	const struct snapraid_extent* prev;
920 	int result;
921 };
922 
extent_parity_check_foreach_unlock(void * void_arg,void * void_obj)923 static void extent_parity_check_foreach_unlock(void* void_arg, void* void_obj)
924 {
925 	struct extent_check* arg = void_arg;
926 	const struct snapraid_extent* obj = void_obj;
927 	const struct snapraid_extent* prev = arg->prev;
928 
929 	/* set the next previous block */
930 	arg->prev = obj;
931 
932 	/* stop reporting if too many errors */
933 	if (arg->result > 100) {
934 		/* LCOV_EXCL_START */
935 		return;
936 		/* LCOV_EXCL_STOP */
937 	}
938 
939 	if (obj->count == 0) {
940 		/* LCOV_EXCL_START */
941 		log_fatal("Internal inconsistency in parity count zero for file '%s' at '%u'\n",
942 			obj->file->sub, obj->parity_pos);
943 		++arg->result;
944 		return;
945 		/* LCOV_EXCL_STOP */
946 	}
947 
948 	/* check only if there is a previous block */
949 	if (!prev)
950 		return;
951 
952 	/* check the order */
953 	if (prev->parity_pos >= obj->parity_pos) {
954 		/* LCOV_EXCL_START */
955 		log_fatal("Internal inconsistency in parity order for files '%s' at '%u:%u' and '%s' at '%u:%u'\n",
956 			prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count);
957 		++arg->result;
958 		return;
959 		/* LCOV_EXCL_STOP */
960 	}
961 
962 	/* check that the extents don't overlap */
963 	if (prev->parity_pos + prev->count > obj->parity_pos) {
964 		/* LCOV_EXCL_START */
965 		log_fatal("Internal inconsistency for parity overlap for files '%s' at '%u:%u' and '%s' at '%u:%u'\n",
966 			prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count);
967 		++arg->result;
968 		return;
969 		/* LCOV_EXCL_STOP */
970 	}
971 }
972 
extent_file_check_foreach_unlock(void * void_arg,void * void_obj)973 static void extent_file_check_foreach_unlock(void* void_arg, void* void_obj)
974 {
975 	struct extent_check* arg = void_arg;
976 	const struct snapraid_extent* obj = void_obj;
977 	const struct snapraid_extent* prev = arg->prev;
978 
979 	/* set the next previous block */
980 	arg->prev = obj;
981 
982 	/* stop reporting if too many errors */
983 	if (arg->result > 100) {
984 		/* LCOV_EXCL_START */
985 		return;
986 		/* LCOV_EXCL_STOP */
987 	}
988 
989 	if (obj->count == 0) {
990 		/* LCOV_EXCL_START */
991 		log_fatal("Internal inconsistency in file count zero for file '%s' at '%u'\n",
992 			obj->file->sub, obj->file_pos);
993 		++arg->result;
994 		return;
995 		/* LCOV_EXCL_STOP */
996 	}
997 
998 	/* note that for deleted files, some extents may be missing */
999 
1000 	/* if the files are different */
1001 	if (!prev || prev->file != obj->file) {
1002 		if (prev != 0) {
1003 			if (file_flag_has(prev->file, FILE_IS_DELETED)) {
1004 				/* check that the extent doesn't overflow the file */
1005 				if (prev->file_pos + prev->count > prev->file->blockmax) {
1006 					/* LCOV_EXCL_START */
1007 					log_fatal("Internal inconsistency in delete end for file '%s' at '%u:%u' overflowing size '%u'\n",
1008 						prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax);
1009 					++arg->result;
1010 					return;
1011 					/* LCOV_EXCL_STOP */
1012 				}
1013 			} else {
1014 				/* check that the extent ends the file */
1015 				if (prev->file_pos + prev->count != prev->file->blockmax) {
1016 					/* LCOV_EXCL_START */
1017 					log_fatal("Internal inconsistency in file end for file '%s' at '%u:%u' instead of size '%u'\n",
1018 						prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax);
1019 					++arg->result;
1020 					return;
1021 					/* LCOV_EXCL_STOP */
1022 				}
1023 			}
1024 		}
1025 
1026 		if (file_flag_has(obj->file, FILE_IS_DELETED)) {
1027 			/* check that the extent doesn't overflow the file */
1028 			if (obj->file_pos + obj->count > obj->file->blockmax) {
1029 				/* LCOV_EXCL_START */
1030 				log_fatal("Internal inconsistency in delete start for file '%s' at '%u:%u' overflowing size '%u'\n",
1031 					obj->file->sub, obj->file_pos, obj->count, obj->file->blockmax);
1032 				++arg->result;
1033 				return;
1034 				/* LCOV_EXCL_STOP */
1035 			}
1036 		} else {
1037 			/* check that the extent starts the file */
1038 			if (obj->file_pos != 0) {
1039 				/* LCOV_EXCL_START */
1040 				log_fatal("Internal inconsistency in file start for file '%s' at '%u:%u'\n",
1041 					obj->file->sub, obj->file_pos, obj->count);
1042 				++arg->result;
1043 				return;
1044 				/* LCOV_EXCL_STOP */
1045 			}
1046 		}
1047 	} else {
1048 		/* check the order */
1049 		if (prev->file_pos >= obj->file_pos) {
1050 			/* LCOV_EXCL_START */
1051 			log_fatal("Internal inconsistency in file order for file '%s' at '%u:%u' and at '%u:%u'\n",
1052 				prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
1053 			++arg->result;
1054 			return;
1055 			/* LCOV_EXCL_STOP */
1056 		}
1057 
1058 		if (file_flag_has(obj->file, FILE_IS_DELETED)) {
1059 			/* check that the extents don't overlap */
1060 			if (prev->file_pos + prev->count > obj->file_pos) {
1061 				/* LCOV_EXCL_START */
1062 				log_fatal("Internal inconsistency in delete sequence for file '%s' at '%u:%u' and at '%u:%u'\n",
1063 					prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
1064 				++arg->result;
1065 				return;
1066 				/* LCOV_EXCL_STOP */
1067 			}
1068 		} else {
1069 			/* check that the extents are sequential */
1070 			if (prev->file_pos + prev->count != obj->file_pos) {
1071 				/* LCOV_EXCL_START */
1072 				log_fatal("Internal inconsistency in file sequence for file '%s' at '%u:%u' and at '%u:%u'\n",
1073 					prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count);
1074 				++arg->result;
1075 				return;
1076 				/* LCOV_EXCL_STOP */
1077 			}
1078 		}
1079 	}
1080 }
1081 
fs_check(struct snapraid_disk * disk)1082 int fs_check(struct snapraid_disk* disk)
1083 {
1084 	struct extent_check arg;
1085 
1086 	/* error count starts from 0 */
1087 	arg.result = 0;
1088 
1089 	fs_lock(disk);
1090 
1091 	/* check parity sequence */
1092 	arg.prev = 0;
1093 	tommy_tree_foreach_arg(&disk->fs_parity, extent_parity_check_foreach_unlock, &arg);
1094 
1095 	/* check file sequence */
1096 	arg.prev = 0;
1097 	tommy_tree_foreach_arg(&disk->fs_file, extent_file_check_foreach_unlock, &arg);
1098 
1099 	fs_unlock(disk);
1100 
1101 	if (arg.result != 0)
1102 		return -1;
1103 
1104 	return 0;
1105 }
1106 
1107 struct extent_parity_inside {
1108 	block_off_t parity_pos;
1109 };
1110 
1111 /**
1112  * Compare the extent if containing the specified parity position.
1113  */
extent_parity_inside_compare_unlock(const void * void_a,const void * void_b)1114 static int extent_parity_inside_compare_unlock(const void* void_a, const void* void_b)
1115 {
1116 	const struct extent_parity_inside* arg_a = void_a;
1117 	const struct snapraid_extent* arg_b = void_b;
1118 
1119 	if (arg_a->parity_pos < arg_b->parity_pos)
1120 		return -1;
1121 	if (arg_a->parity_pos >= arg_b->parity_pos + arg_b->count)
1122 		return 1;
1123 
1124 	return 0;
1125 }
1126 
1127 /**
1128  * Search the extent at the specified parity position.
1129  * The search is optimized for sequential accesses.
1130  * \return If not found return 0
1131  */
fs_par2extent_get_unlock(struct snapraid_disk * disk,struct snapraid_extent ** fs_last,block_off_t parity_pos)1132 static struct snapraid_extent* fs_par2extent_get_unlock(struct snapraid_disk* disk, struct snapraid_extent** fs_last, block_off_t parity_pos)
1133 {
1134 	struct snapraid_extent* extent;
1135 
1136 	/* check if the last accessed extent matches */
1137 	if (*fs_last
1138 		&& parity_pos >= (*fs_last)->parity_pos
1139 		&& parity_pos < (*fs_last)->parity_pos + (*fs_last)->count
1140 	) {
1141 		extent = *fs_last;
1142 	} else {
1143 		struct extent_parity_inside arg = { parity_pos };
1144 		extent = tommy_tree_search_compare(&disk->fs_parity, extent_parity_inside_compare_unlock, &arg);
1145 	}
1146 
1147 	if (!extent)
1148 		return 0;
1149 
1150 	/* store the last accessed extent */
1151 	*fs_last = extent;
1152 
1153 	return extent;
1154 }
1155 
1156 struct extent_file_inside {
1157 	struct snapraid_file* file;
1158 	block_off_t file_pos;
1159 };
1160 
1161 /**
1162  * Compare the extent if containing the specified file position.
1163  */
extent_file_inside_compare_unlock(const void * void_a,const void * void_b)1164 static int extent_file_inside_compare_unlock(const void* void_a, const void* void_b)
1165 {
1166 	const struct extent_file_inside* arg_a = void_a;
1167 	const struct snapraid_extent* arg_b = void_b;
1168 
1169 	if (arg_a->file < arg_b->file)
1170 		return -1;
1171 	if (arg_a->file > arg_b->file)
1172 		return 1;
1173 
1174 	if (arg_a->file_pos < arg_b->file_pos)
1175 		return -1;
1176 	if (arg_a->file_pos >= arg_b->file_pos + arg_b->count)
1177 		return 1;
1178 
1179 	return 0;
1180 }
1181 
1182 /**
1183  * Search the extent at the specified file position.
1184  * The search is optimized for sequential accesses.
1185  * \return If not found return 0
1186  */
fs_file2extent_get_unlock(struct snapraid_disk * disk,struct snapraid_extent ** fs_last,struct snapraid_file * file,block_off_t file_pos)1187 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)
1188 {
1189 	struct snapraid_extent* extent;
1190 
1191 	/* check if the last accessed extent matches */
1192 	if (*fs_last
1193 		&& file == (*fs_last)->file
1194 		&& file_pos >= (*fs_last)->file_pos
1195 		&& file_pos < (*fs_last)->file_pos + (*fs_last)->count
1196 	) {
1197 		extent = *fs_last;
1198 	} else {
1199 		struct extent_file_inside arg = { file, file_pos };
1200 		extent = tommy_tree_search_compare(&disk->fs_file, extent_file_inside_compare_unlock, &arg);
1201 	}
1202 
1203 	if (!extent)
1204 		return 0;
1205 
1206 	/* store the last accessed extent */
1207 	*fs_last = extent;
1208 
1209 	return extent;
1210 }
1211 
fs_par2file_find(struct snapraid_disk * disk,block_off_t parity_pos,block_off_t * file_pos)1212 struct snapraid_file* fs_par2file_find(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos)
1213 {
1214 	struct snapraid_extent* extent;
1215 	struct snapraid_file* file;
1216 
1217 	fs_lock(disk);
1218 
1219 	extent = fs_par2extent_get_unlock(disk, &disk->fs_last, parity_pos);
1220 
1221 	if (!extent) {
1222 		fs_unlock(disk);
1223 		return 0;
1224 	}
1225 
1226 	if (file_pos)
1227 		*file_pos = extent->file_pos + (parity_pos - extent->parity_pos);
1228 
1229 	file = extent->file;
1230 
1231 	fs_unlock(disk);
1232 	return file;
1233 }
1234 
fs_file2par_find(struct snapraid_disk * disk,struct snapraid_file * file,block_off_t file_pos)1235 block_off_t fs_file2par_find(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
1236 {
1237 	struct snapraid_extent* extent;
1238 	block_off_t ret;
1239 
1240 	fs_lock(disk);
1241 
1242 	extent = fs_file2extent_get_unlock(disk, &disk->fs_last, file, file_pos);
1243 	if (!extent) {
1244 		fs_unlock(disk);
1245 		return POS_NULL;
1246 	}
1247 
1248 	ret = extent->parity_pos + (file_pos - extent->file_pos);
1249 
1250 	fs_unlock(disk);
1251 	return ret;
1252 }
1253 
fs_allocate(struct snapraid_disk * disk,block_off_t parity_pos,struct snapraid_file * file,block_off_t file_pos)1254 void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos)
1255 {
1256 	struct snapraid_extent* extent;
1257 	struct snapraid_extent* parity_extent;
1258 	struct snapraid_extent* file_extent;
1259 
1260 	fs_lock(disk);
1261 
1262 	if (file_pos > 0) {
1263 		/* search an existing extent for the previous file_pos */
1264 		extent = fs_file2extent_get_unlock(disk, &disk->fs_last, file, file_pos - 1);
1265 
1266 		if (extent != 0 && parity_pos == extent->parity_pos + extent->count) {
1267 			/* ensure that we are extending the extent at the end */
1268 			if (file_pos != extent->file_pos + extent->count) {
1269 				/* LCOV_EXCL_START */
1270 				log_fatal("Internal inconsistency when 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);
1271 				os_abort();
1272 				/* LCOV_EXCL_STOP */
1273 			}
1274 
1275 			/* extend the existing extent */
1276 			++extent->count;
1277 
1278 			fs_unlock(disk);
1279 			return;
1280 		}
1281 	}
1282 
1283 	/* a extent doesn't exist, and we have to create a new one */
1284 	extent = extent_alloc(parity_pos, file, file_pos, 1);
1285 
1286 	/* insert the extent in the trees */
1287 	parity_extent = tommy_tree_insert(&disk->fs_parity, &extent->parity_node, extent);
1288 	file_extent = tommy_tree_insert(&disk->fs_file, &extent->file_node, extent);
1289 
1290 	if (parity_extent != extent || file_extent != extent) {
1291 		/* LCOV_EXCL_START */
1292 		log_fatal("Internal inconsistency when 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);
1293 		os_abort();
1294 		/* LCOV_EXCL_STOP */
1295 	}
1296 
1297 	/* store the last accessed extent */
1298 	disk->fs_last = extent;
1299 
1300 	fs_unlock(disk);
1301 }
1302 
fs_deallocate(struct snapraid_disk * disk,block_off_t parity_pos)1303 void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
1304 {
1305 	struct snapraid_extent* extent;
1306 	struct snapraid_extent* second_extent;
1307 	struct snapraid_extent* parity_extent;
1308 	struct snapraid_extent* file_extent;
1309 	block_off_t first_count, second_count;
1310 
1311 	fs_lock(disk);
1312 
1313 	extent = fs_par2extent_get_unlock(disk, &disk->fs_last, parity_pos);
1314 	if (!extent) {
1315 		/* LCOV_EXCL_START */
1316 		log_fatal("Internal inconsistency when deallocating parity position '%u' for not existing extent in disk '%s'\n", parity_pos, disk->name);
1317 		os_abort();
1318 		/* LCOV_EXCL_STOP */
1319 	}
1320 
1321 	/* if it's the only block of the extent, delete it */
1322 	if (extent->count == 1) {
1323 		/* remove from the trees */
1324 		tommy_tree_remove(&disk->fs_parity, extent);
1325 		tommy_tree_remove(&disk->fs_file, extent);
1326 
1327 		/* deallocate */
1328 		extent_free(extent);
1329 
1330 		/* clear the last accessed extent */
1331 		disk->fs_last = 0;
1332 
1333 		fs_unlock(disk);
1334 		return;
1335 	}
1336 
1337 	/* if it's at the start of the extent, shrink the extent */
1338 	if (parity_pos == extent->parity_pos) {
1339 		++extent->parity_pos;
1340 		++extent->file_pos;
1341 		--extent->count;
1342 
1343 		fs_unlock(disk);
1344 		return;
1345 	}
1346 
1347 	/* if it's at the end of the extent, shrink the extent */
1348 	if (parity_pos == extent->parity_pos + extent->count - 1) {
1349 		--extent->count;
1350 
1351 		fs_unlock(disk);
1352 		return;
1353 	}
1354 
1355 	/* otherwise it's in the middle */
1356 	first_count = parity_pos - extent->parity_pos;
1357 	second_count = extent->count - first_count - 1;
1358 
1359 	/* adjust the first extent */
1360 	extent->count = first_count;
1361 
1362 	/* allocate the second extent */
1363 	second_extent = extent_alloc(extent->parity_pos + first_count + 1, extent->file, extent->file_pos + first_count + 1, second_count);
1364 
1365 	/* insert the extent in the trees */
1366 	parity_extent = tommy_tree_insert(&disk->fs_parity, &second_extent->parity_node, second_extent);
1367 	file_extent = tommy_tree_insert(&disk->fs_file, &second_extent->file_node, second_extent);
1368 
1369 	if (parity_extent != second_extent || file_extent != second_extent) {
1370 		/* LCOV_EXCL_START */
1371 		log_fatal("Internal inconsistency when deallocating parity position '%u' for splitting extent '%u:%u' in disk '%s'\n", parity_pos, second_extent->file_pos, second_extent->count, disk->name);
1372 		os_abort();
1373 		/* LCOV_EXCL_STOP */
1374 	}
1375 
1376 	/* store the last accessed extent */
1377 	disk->fs_last = second_extent;
1378 
1379 	fs_unlock(disk);
1380 }
1381 
fs_file2block_get(struct snapraid_file * file,block_off_t file_pos)1382 struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos)
1383 {
1384 	if (file_pos >= file->blockmax) {
1385 		/* LCOV_EXCL_START */
1386 		log_fatal("Internal inconsistency when dereferencing file '%s' at position '%u/%u'\n", file->sub, file_pos, file->blockmax);
1387 		os_abort();
1388 		/* LCOV_EXCL_STOP */
1389 	}
1390 
1391 	return file_block(file, file_pos);
1392 }
1393 
fs_par2block_find(struct snapraid_disk * disk,block_off_t parity_pos)1394 struct snapraid_block* fs_par2block_find(struct snapraid_disk* disk, block_off_t parity_pos)
1395 {
1396 	struct snapraid_file* file;
1397 	block_off_t file_pos;
1398 
1399 	file = fs_par2file_find(disk, parity_pos, &file_pos);
1400 	if (file == 0)
1401 		return BLOCK_NULL;
1402 
1403 	return fs_file2block_get(file, file_pos);
1404 }
1405 
map_alloc(const char * name,unsigned position,block_off_t total_blocks,block_off_t free_blocks,const char * uuid)1406 struct snapraid_map* map_alloc(const char* name, unsigned position, block_off_t total_blocks, block_off_t free_blocks, const char* uuid)
1407 {
1408 	struct snapraid_map* map;
1409 
1410 	map = malloc_nofail(sizeof(struct snapraid_map));
1411 	pathcpy(map->name, sizeof(map->name), name);
1412 	map->position = position;
1413 	map->total_blocks = total_blocks;
1414 	map->free_blocks = free_blocks;
1415 	pathcpy(map->uuid, sizeof(map->uuid), uuid);
1416 
1417 	return map;
1418 }
1419 
map_free(struct snapraid_map * map)1420 void map_free(struct snapraid_map* map)
1421 {
1422 	free(map);
1423 }
1424 
time_compare(const void * void_a,const void * void_b)1425 int time_compare(const void* void_a, const void* void_b)
1426 {
1427 	const time_t* time_a = void_a;
1428 	const time_t* time_b = void_b;
1429 
1430 	if (*time_a < *time_b)
1431 		return -1;
1432 	if (*time_a > *time_b)
1433 		return 1;
1434 	return 0;
1435 }
1436 
1437 /****************************************************************************/
1438 /* format */
1439 
1440 int FMT_MODE = FMT_FILE;
1441 
1442 /**
1443  * Format a file path for poll reference
1444  */
fmt_poll(const struct snapraid_disk * disk,const char * str,char * buffer)1445 const char* fmt_poll(const struct snapraid_disk* disk, const char* str, char* buffer)
1446 {
1447 	(void)disk;
1448 	return esc_shell(str, buffer);
1449 }
1450 
1451 /**
1452  * Format a path name for terminal reference
1453  */
fmt_term(const struct snapraid_disk * disk,const char * str,char * buffer)1454 const char* fmt_term(const struct snapraid_disk* disk, const char* str, char* buffer)
1455 {
1456 	const char* out[3];
1457 
1458 	switch (FMT_MODE) {
1459 	case FMT_FILE :
1460 	default :
1461 		return esc_shell(str, buffer);
1462 	case FMT_DISK :
1463 		out[0] = disk->name;
1464 		out[1] = ":";
1465 		out[2] = str;
1466 		return esc_shell_multi(out, 3, buffer);
1467 	case FMT_PATH :
1468 		out[0] = disk->dir;
1469 		out[1] = str;
1470 		return esc_shell_multi(out, 2, buffer);
1471 	}
1472 }
1473 
1474