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 "import.h"
8 : #include "search.h"
9 : #include "state.h"
10 : #include "support.h"
11 : #include "parity.h"
12 : #include "stream.h"
13 : #include "handle.h"
14 : #include "io.h"
15 : #include "thermal.h"
16 : #include "raid/raid.h"
17 : #include "raid/cpu.h"
18 :
19 : /**
20 : * Configure the multithread support.
21 : *
22 : * Multi thread for write could be either faster or slower, depending
23 : * on the specific conditions. With multithreads it's likely faster
24 : * writing to disk, but you'll need to access multiple times the same data,
25 : * being potentially slower.
26 : *
27 : * Multi thread for verify is instead always generally faster,
28 : * so we enable it if possible.
29 : */
30 : #if HAVE_THREAD
31 : /* #define HAVE_MT_WRITE 1 */
32 : #define HAVE_MT_VERIFY 1
33 : #endif
34 :
35 7 : const char* lev_name(unsigned l)
36 : {
37 7 : switch (l) {
38 2 : case 0 : return "Parity";
39 1 : case 1 : return "2-Parity";
40 1 : case 2 : return "3-Parity";
41 1 : case 3 : return "4-Parity";
42 1 : case 4 : return "5-Parity";
43 1 : case 5 : return "6-Parity";
44 : }
45 :
46 0 : return "invalid";
47 : }
48 :
49 290009 : const char* lev_config_name(unsigned l)
50 : {
51 290009 : switch (l) {
52 96573 : case 0 : return "parity";
53 76786 : case 1 : return "2-parity";
54 39717 : case 2 : return "3-parity";
55 25672 : case 3 : return "4-parity";
56 25647 : case 4 : return "5-parity";
57 25614 : case 5 : return "6-parity";
58 : }
59 :
60 0 : return "invalid";
61 : }
62 :
63 7691 : static int lev_config_scan(const char* s, unsigned* level, unsigned* mode)
64 : {
65 7691 : if (strcmp(s, "parity") == 0 || strcmp(s, "1-parity") == 0) {
66 705 : *level = 0;
67 705 : return 0;
68 : }
69 :
70 6986 : if (strcmp(s, "q-parity") == 0 || strcmp(s, "2-parity") == 0) {
71 248 : *level = 1;
72 248 : return 0;
73 : }
74 :
75 6738 : if (strcmp(s, "r-parity") == 0 || strcmp(s, "3-parity") == 0) {
76 213 : *level = 2;
77 213 : return 0;
78 : }
79 :
80 6525 : if (strcmp(s, "4-parity") == 0) {
81 210 : *level = 3;
82 210 : return 0;
83 : }
84 :
85 6315 : if (strcmp(s, "5-parity") == 0) {
86 207 : *level = 4;
87 207 : return 0;
88 : }
89 :
90 6108 : if (strcmp(s, "6-parity") == 0) {
91 203 : *level = 5;
92 203 : return 0;
93 : }
94 :
95 5905 : if (strcmp(s, "z-parity") == 0) {
96 3 : *level = 2;
97 3 : if (mode)
98 3 : *mode = RAID_MODE_VANDERMONDE;
99 3 : return 0;
100 : }
101 :
102 5902 : return -1;
103 : }
104 :
105 315 : const char* lev_raid_name(unsigned mode, unsigned n)
106 : {
107 315 : switch (n) {
108 68 : case 1 : return "par1";
109 32 : case 2 : return "par2";
110 6 : case 3 : if (mode == RAID_MODE_CAUCHY)
111 3 : return "par3";
112 : else
113 3 : return "parz";
114 3 : case 4 : return "par4";
115 4 : case 5 : return "par5";
116 202 : case 6 : return "par6";
117 : }
118 :
119 0 : return 0;
120 : }
121 :
122 317 : void state_init(struct snapraid_state* state)
123 : {
124 : unsigned l, s, i;
125 :
126 317 : memset(&state->opt, 0, sizeof(state->opt));
127 317 : state->mapped_device = 0;
128 317 : state->snapshot = 0;
129 317 : state->filter_hidden = 0;
130 317 : state->autosave = 0;
131 317 : state->need_write = 0;
132 317 : state->written = 0;
133 317 : state->checked_read = 0;
134 317 : state->block_size = 256 * KIBI; /* default 256 KiB */
135 317 : state->raid_mode = RAID_MODE_CAUCHY;
136 317 : state->file_mode = ADVISE_DEFAULT;
137 2219 : for (l = 0; l < LEV_MAX; ++l) {
138 1902 : state->parity[l].split_mac = 0;
139 17118 : for (s = 0; s < SPLIT_MAX; ++s) {
140 15216 : state->parity[l].split_map[s].path[0] = 0;
141 15216 : state->parity[l].split_map[s].uuid[0] = 0;
142 15216 : state->parity[l].split_map[s].fstype[0] = 0;
143 15216 : state->parity[l].split_map[s].fslabel[0] = 0;
144 15216 : state->parity[l].split_map[s].size = PARITY_SIZE_INVALID;
145 15216 : state->parity[l].split_map[s].device = 0;
146 : }
147 1902 : state->parity[l].smartctl[0] = 0;
148 9510 : for (i = 0; i < SMART_IGNORE_MAX; ++i)
149 7608 : state->parity[l].smartignore[i] = 0;
150 1902 : state->parity[l].total_blocks = 0;
151 1902 : state->parity[l].free_blocks = 0;
152 1902 : state->parity[l].skip_access = 0;
153 1902 : state->parity[l].tick = 0;
154 1902 : state->parity[l].cached_blocks = 0;
155 1902 : state->parity[l].is_excluded_by_filter = 0;
156 : }
157 317 : state->tick_io = 0;
158 317 : state->tick_misc = 0;
159 317 : state->tick_sched = 0;
160 317 : state->tick_raid = 0;
161 317 : state->tick_hash = 0;
162 317 : state->tick_last = os_tick();
163 317 : state->share[0] = 0;
164 317 : state->pool[0] = 0;
165 317 : state->pool_device = 0;
166 317 : state->lockfile[0] = 0;
167 317 : state->level = 1; /* default is the lowest protection */
168 317 : state->no_conf = 0;
169 1585 : for (i = 0; i < SMART_IGNORE_MAX; ++i)
170 1268 : state->smartignore[i] = 0;
171 317 : state->rehash_blocks = 0;
172 317 : state->bad_blocks = 0;
173 317 : state->unsynced_blocks = 0;
174 317 : state->unscrubbed_blocks = 0;
175 317 : state->thermal_stop_gathering = 0;
176 317 : state->thermal_ambient_temperature = 0;
177 317 : state->thermal_highest_temperature = 0;
178 317 : state->thermal_first = 0;
179 317 : state->thermal_latest = 0;
180 317 : state->thermal_temperature_limit = 0;
181 317 : state->thermal_cooldown_time = 0;
182 :
183 317 : tommy_list_init(&state->disklist);
184 317 : tommy_list_init(&state->extralist);
185 317 : tommy_list_init(&state->maplist);
186 317 : tommy_list_init(&state->contentlist);
187 317 : tommy_list_init(&state->filterlist);
188 317 : tommy_list_init(&state->importlist);
189 317 : tommy_list_init(&state->thermallist);
190 317 : tommy_hashdyn_init(&state->importset);
191 317 : tommy_hashdyn_init(&state->previmportset);
192 317 : tommy_hashdyn_init(&state->searchset);
193 317 : tommy_arrayblkof_init(&state->infoarr, sizeof(snapraid_info));
194 317 : tommy_list_init(&state->bucketlist);
195 317 : }
196 :
197 292 : void state_done(struct snapraid_state* state)
198 : {
199 292 : tommy_list_foreach(&state->disklist, (tommy_foreach_func*)disk_free);
200 292 : tommy_list_foreach(&state->extralist, (tommy_foreach_func*)extra_free);
201 292 : tommy_list_foreach(&state->maplist, (tommy_foreach_func*)map_free);
202 292 : tommy_list_foreach(&state->contentlist, (tommy_foreach_func*)content_free);
203 292 : tommy_list_foreach(&state->filterlist, (tommy_foreach_func*)filter_free);
204 292 : tommy_list_foreach(&state->importlist, (tommy_foreach_func*)import_file_free);
205 292 : tommy_list_foreach(&state->thermallist, (tommy_foreach_func*)thermal_free);
206 292 : tommy_hashdyn_foreach(&state->searchset, (tommy_foreach_func*)search_file_free);
207 292 : tommy_hashdyn_done(&state->importset);
208 292 : tommy_hashdyn_done(&state->previmportset);
209 292 : tommy_hashdyn_done(&state->searchset);
210 292 : tommy_arrayblkof_done(&state->infoarr);
211 292 : tommy_list_foreach(&state->bucketlist, (tommy_foreach_func*)bucket_free);
212 292 : }
213 :
214 : /**
215 : * Check the configuration.
216 : */
217 315 : static void state_config_check(struct snapraid_state* state, const char* path, tommy_list* filterlist_disk)
218 : {
219 : tommy_node* i;
220 : unsigned l, s;
221 :
222 : /* check for parity level */
223 315 : if (state->raid_mode == RAID_MODE_VANDERMONDE) {
224 3 : if (state->level > 3) {
225 : /* LCOV_EXCL_START */
226 : log_fatal(EUSER, "Using z-parity limits you to a maximum of 3 parities.\n");
227 : exit(EXIT_FAILURE);
228 : /* LCOV_EXCL_STIO */
229 : }
230 : }
231 :
232 : for (l = 0; l < state->level; ++l) {
233 : if (state->parity[l].split_mac == 0) {
234 : /* LCOV_EXCL_START */
235 : log_fatal(EUSER, "Missing '%s' specification in '%s'\n", lev_config_name(l), path);
236 : exit(EXIT_FAILURE);
237 : /* LCOV_EXCL_STOP */
238 : }
239 : }
240 :
241 315 : if (tommy_list_empty(&state->contentlist)) {
242 : /* LCOV_EXCL_START */
243 : log_fatal(EUSER, "Missing 'content' specification in '%s'\n", path);
244 : exit(EXIT_FAILURE);
245 : /* LCOV_EXCL_STOP */
246 : }
247 :
248 : /* check for equal paths */
249 2031 : for (i = state->contentlist; i != 0; i = i->next) {
250 1716 : struct snapraid_content* content = i->data;
251 :
252 10794 : for (l = 0; l < state->level; ++l) {
253 45084 : for (s = 0; s < state->parity[l].split_mac; ++s) {
254 36006 : if (pathcmp(state->parity[l].split_map[s].path, content->content) == 0) {
255 : /* LCOV_EXCL_START */
256 : log_fatal(EUSER, "Same path used for '%s' and 'content' as '%s'\n", lev_config_name(l), content->content);
257 : exit(EXIT_FAILURE);
258 : /* LCOV_EXCL_STOP */
259 : }
260 : }
261 : }
262 : }
263 :
264 : /* check device of data disks */
265 315 : if (!state->opt.skip_device && !state->opt.skip_disk_access) {
266 77 : for (i = state->disklist; i != 0; i = i->next) {
267 : tommy_node* j;
268 70 : struct snapraid_disk* disk = i->data;
269 :
270 : /* skip data disks that are not accessible */
271 70 : if (disk->skip_access)
272 0 : continue;
273 :
274 : #ifdef _WIN32
275 : if (disk->dir_device == 0) {
276 : /* LCOV_EXCL_START */
277 : log_fatal(ESOFT, "Disk '%s' has a zero serial number.\n", disk->dir);
278 : log_fatal(ESOFT, "This is not necessarily wrong, but for using SnapRAID\n");
279 : log_fatal(ESOFT, "it's better to change the serial number of the disk.\n");
280 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
281 : exit(EXIT_FAILURE);
282 : /* LCOV_EXCL_STOP */
283 : }
284 : #endif
285 :
286 385 : for (j = i->next; j != 0; j = j->next) {
287 315 : struct snapraid_disk* other = j->data;
288 315 : if (disk->mount_device == other->mount_device) {
289 0 : if (state->opt.force_device) {
290 : /*
291 : * Note that we just ignore the issue
292 : * and we DON'T mark the disk to be skipped
293 : * because we want to use these disks
294 : */
295 0 : if (!state->opt.no_warnings)
296 0 : log_fatal(EUSER, "DANGER! Ignoring that disks '%s' and '%s' are on the same device\n", disk->name, other->name);
297 : } else {
298 : /* LCOV_EXCL_START */
299 : log_fatal(ESOFT, "Disks '%s' and '%s' are on the same device.\n", disk->mount_point, other->mount_point);
300 : #ifdef _WIN32
301 : log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device);
302 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
303 : log_fatal(ESOFT, "to change one of the disk serial.\n");
304 : #endif
305 : /* in "fix" we allow to continue anyway */
306 : if (strcmp(state->command, "fix") == 0) {
307 : log_fatal(ESOFT, "You can '%s' anyway, using 'snapraid --force-device %s'.\n", state->command, state->command);
308 : }
309 : exit(EXIT_FAILURE);
310 : /* LCOV_EXCL_STOP */
311 : }
312 : }
313 : }
314 :
315 : /* skip data disks that are not accessible */
316 70 : if (disk->skip_access)
317 0 : continue;
318 :
319 70 : if (!state->opt.skip_parity_access) {
320 210 : for (l = 0; l < state->level; ++l) {
321 280 : for (s = 0; s < state->parity[l].split_mac; ++s) {
322 140 : if (disk->mount_device == state->parity[l].split_map[s].device) {
323 0 : if (state->opt.force_device) {
324 : /*
325 : * Note that we just ignore the issue
326 : * and we DON'T mark the disk to be skipped
327 : * because we want to use these disks
328 : */
329 0 : if (!state->opt.no_warnings)
330 0 : log_fatal(EUSER, "DANGER! Ignoring that disks '%s' and %s '%s' are on the same device\n", disk->mount_point, lev_name(l), state->parity[l].split_map[s].path);
331 : } else {
332 : /* LCOV_EXCL_START */
333 : log_fatal(ESOFT, "Disk '%s' and %s '%s' are on the same device.\n", disk->mount_point, lev_name(l), state->parity[l].split_map[s].path);
334 : #ifdef _WIN32
335 : log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device);
336 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
337 : log_fatal(ESOFT, "to change one of the disk serial.\n");
338 : #endif
339 : exit(EXIT_FAILURE);
340 : /* LCOV_EXCL_STOP */
341 : }
342 : }
343 : }
344 : }
345 : }
346 :
347 70 : if (state->pool[0] != 0 && disk->mount_device == state->pool_device) {
348 : /* LCOV_EXCL_START */
349 : log_fatal(ESOFT, "Disk '%s' and pool '%s' are on the same device.\n", disk->mount_point, state->pool);
350 : #ifdef _WIN32
351 : log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device);
352 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
353 : log_fatal(ESOFT, "to change one of the disk serial.\n");
354 : #endif
355 : exit(EXIT_FAILURE);
356 : /* LCOV_EXCL_STOP */
357 : }
358 : }
359 : }
360 :
361 : /* check device of parity disks */
362 315 : if (!state->opt.skip_device && !state->opt.skip_parity_access) {
363 21 : for (l = 0; l < state->level; ++l) {
364 28 : for (s = 0; s < state->parity[l].split_mac; ++s) {
365 : unsigned j, t;
366 :
367 : /* skip parity disks that are not accessible */
368 14 : if (state->parity[l].skip_access)
369 0 : continue;
370 :
371 : #ifdef _WIN32
372 : if (state->parity[l].split_map[s].device == 0) {
373 : /* LCOV_EXCL_START */
374 : log_fatal(ESOFT, "Disk '%s' has a zero serial number.\n", state->parity[l].split_map[s].path);
375 : log_fatal(ESOFT, "This is not necessarily wrong, but for using SnapRAID\n");
376 : log_fatal(ESOFT, "it's better to change the serial number of the disk.\n");
377 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
378 : exit(EXIT_FAILURE);
379 : /* LCOV_EXCL_STOP */
380 : }
381 : #endif
382 :
383 21 : for (j = l + 1; j < state->level; ++j) {
384 14 : for (t = 0; t < state->parity[j].split_mac; ++t) {
385 7 : if (state->parity[l].split_map[s].device == state->parity[j].split_map[t].device) {
386 0 : if (state->opt.force_device) {
387 : /*
388 : * Note that we just ignore the issue
389 : * and we DON'T mark the disk to be skipped
390 : * because we want to use these disks
391 : */
392 0 : if (!state->opt.no_warnings)
393 0 : log_fatal(EUSER, "DANGER! Skipping parities '%s' and '%s' on the same device\n", lev_config_name(l), lev_config_name(j));
394 : } else {
395 : /* LCOV_EXCL_START */
396 : log_fatal(ESOFT, "Parity '%s' and '%s' are on the same device.\n", state->parity[l].split_map[s].path, state->parity[j].split_map[t].path);
397 : #ifdef _WIN32
398 : log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", state->parity[l].split_map[s].device);
399 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
400 : log_fatal(ESOFT, "to change one of the disk serial.\n");
401 : #endif
402 : /* in "fix" we allow to continue anyway */
403 : if (strcmp(state->command, "fix") == 0) {
404 : log_fatal(ESOFT, "You can '%s' anyway, using 'snapraid --force-device %s'.\n", state->command, state->command);
405 : }
406 : exit(EXIT_FAILURE);
407 : /* LCOV_EXCL_STOP */
408 : }
409 : }
410 : }
411 : }
412 :
413 14 : if (state->pool[0] != 0 && state->pool_device == state->parity[l].split_map[s].device) {
414 : /* LCOV_EXCL_START */
415 : log_fatal(ESOFT, "Pool '%s' and parity '%s' are on the same device.\n", state->pool, state->parity[l].split_map[s].path);
416 : #ifdef _WIN32
417 : log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", state->pool_device);
418 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
419 : log_fatal(ESOFT, "to change one of the disk serial.\n");
420 : #endif
421 : exit(EXIT_FAILURE);
422 : /* LCOV_EXCL_STOP */
423 : }
424 : }
425 : }
426 : }
427 :
428 : /* check device of pool disk */
429 : #ifdef _WIN32
430 : if (!state->opt.skip_device) {
431 : if (state->pool[0] != 0 && state->pool_device == 0) {
432 : /* LCOV_EXCL_START */
433 : log_fatal(ESOFT, "Disk '%s' has a zero serial number.\n", state->pool);
434 : log_fatal(ESOFT, "This is not necessarily wrong, but for using SnapRAID\n");
435 : log_fatal(ESOFT, "it's better to change the serial number of the disk.\n");
436 : log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
437 : exit(EXIT_FAILURE);
438 : /* LCOV_EXCL_STOP */
439 : }
440 : }
441 : #endif
442 :
443 : /* count the content files */
444 315 : if (!state->opt.skip_device && !state->opt.skip_content_access) {
445 : unsigned content_count;
446 :
447 7 : content_count = 0;
448 35 : for (i = state->contentlist; i != 0; i = i->next) {
449 : tommy_node* j;
450 28 : struct snapraid_content* content = i->data;
451 :
452 : /* check if there are others in the same disk */
453 70 : for (j = i->next; j != 0; j = j->next) {
454 42 : struct snapraid_content* other = j->data;
455 42 : if (content->device == other->device) {
456 0 : log_fatal(ESOFT, "WARNING! Content files on the same disk: '%s' and '%s'.\n", content->content, other->content);
457 0 : break;
458 : }
459 : }
460 28 : if (j != 0) {
461 : /* skip it */
462 0 : continue;
463 : }
464 :
465 28 : ++content_count;
466 : }
467 :
468 7 : if (content_count < state->level + 1) {
469 : /* LCOV_EXCL_START */
470 : log_fatal(EUSER, "You must have at least %d 'content' files in different disks.\n", state->level + 1);
471 : exit(EXIT_FAILURE);
472 : /* LCOV_EXCL_STOP */
473 : }
474 : }
475 :
476 : /* check for speed */
477 : #ifdef CONFIG_X86
478 315 : if (!raid_cpu_has_ssse3())
479 : #endif
480 0 : if (state->raid_mode == RAID_MODE_CAUCHY && !state->opt.no_warnings) {
481 0 : if (state->level == 3) {
482 0 : log_fatal(ESOFT, "WARNING! Your CPU doesn't have a fast implementation for triple parity.\n");
483 0 : log_fatal(ESOFT, "WARNING! It's recommended to switch to 'z-parity' instead than '3-parity'.\n");
484 0 : } else if (state->level > 3) {
485 0 : log_fatal(ESOFT, "WARNING! Your CPU doesn't have a fast implementation beyond triple parity.\n");
486 0 : log_fatal(ESOFT, "WARNING! It's recommended to reduce the parity levels to triple parity.\n");
487 : }
488 : }
489 :
490 : /* ensure that specified filter disks are valid ones */
491 331 : for (i = tommy_list_head(filterlist_disk); i != 0; i = i->next) {
492 : tommy_node* j;
493 16 : struct snapraid_filter* filter = i->data;
494 66 : for (j = state->disklist; j != 0; j = j->next) {
495 59 : struct snapraid_disk* disk = j->data;
496 59 : if (wnmatch(filter->pattern, disk->name) == 0)
497 9 : break;
498 : }
499 16 : if (j == 0) {
500 7 : for (j = state->extralist; j != 0; j = j->next) {
501 0 : struct snapraid_extra* extra = j->data;
502 0 : if (wnmatch(filter->pattern, extra->name) == 0)
503 0 : break;
504 : }
505 : }
506 16 : if (j == 0) {
507 : /* check matching with parity disks */
508 24 : for (l = 0; l < state->level; ++l)
509 24 : if (wnmatch(filter->pattern, lev_config_name(l)) == 0)
510 7 : break;
511 7 : if (l == state->level) {
512 : /* LCOV_EXCL_START */
513 : log_fatal(EUSER, "Option -d, --filter-disk %s doesn't match any data or parity or extra disk.\n", filter->pattern);
514 : exit(EXIT_FAILURE);
515 : /* LCOV_EXCL_STOP */
516 : }
517 : }
518 : }
519 315 : }
520 :
521 : /**
522 : * Validate the smartctl command.
523 : *
524 : * It must contains only one %s string, and not other % chars.
525 : */
526 390 : static int validate_smartctl(const char* custom)
527 : {
528 390 : const char* s = custom;
529 390 : int arg = 0;
530 :
531 2340 : while (*s) {
532 1950 : if (s[0] == '%' && s[1] == 's') {
533 195 : if (arg) {
534 : /* LCOV_EXCL_START */
535 : return -1;
536 : /* LCOV_EXCL_STOP */
537 : }
538 195 : arg = 1;
539 1755 : } else if (s[0] == '%') {
540 : /* LCOV_EXCL_START */
541 : return -1;
542 : /* LCOV_EXCL_STOP */
543 : }
544 :
545 1950 : ++s;
546 : }
547 :
548 390 : return 0;
549 : }
550 :
551 316 : void state_config(struct snapraid_state* state, const char* path, const char* command, struct snapraid_option* opt, tommy_list* filterlist_disk)
552 : {
553 : STREAM* f;
554 : unsigned line;
555 : tommy_node* i;
556 : unsigned l, s;
557 : char esc_buffer[ESC_MAX];
558 : char esc_buffer1[ESC_MAX];
559 :
560 : /* copy the options */
561 316 : state->opt = *opt;
562 :
563 : /* if unset, sort by physical order */
564 316 : if (!state->opt.force_order)
565 8 : state->opt.force_order = SORT_PHYSICAL;
566 :
567 : /* adjust file mode */
568 316 : if (state->opt.file_mode != ADVISE_DEFAULT) {
569 308 : state->file_mode = state->opt.file_mode;
570 : } else {
571 : /* default mode, if nothing is specified */
572 8 : state->file_mode = ADVISE_DISCARD;
573 : }
574 :
575 : /* store current command */
576 316 : state->command = command;
577 :
578 316 : log_tag("conf:file:%s\n", esc_tag(path, esc_buffer));
579 :
580 316 : f = sopen_read(path, 0);
581 316 : if (!f) {
582 : /* LCOV_EXCL_START */
583 : if (errno == ENOENT) {
584 : log_fatal(errno, "No configuration file found at '%s'\n", path);
585 : } else if (errno == EACCES) {
586 : log_fatal(errno, "You do not have rights to access the configuration file '%s'\n", path);
587 : } else {
588 : log_fatal(errno, "Error opening the configuration file '%s'. %s.\n", path, strerror(errno));
589 : }
590 : exit(EXIT_FAILURE);
591 : /* LCOV_EXCL_STOP */
592 : }
593 :
594 316 : line = 1;
595 7152 : while (1) {
596 : char tag[PATH_MAX];
597 : char buffer[PATH_MAX];
598 : int ret;
599 : int c;
600 : unsigned level;
601 :
602 : /* skip initial spaces */
603 7468 : sgetspace(f);
604 :
605 : /* read the command */
606 7468 : ret = sgettok(f, tag, sizeof(tag));
607 7468 : if (ret < 0) {
608 : /* LCOV_EXCL_START */
609 : log_fatal(EUSER, "Error reading the configuration file '%s' at line %u\n", path, line);
610 : exit(EXIT_FAILURE);
611 : /* LCOV_EXCL_STOP */
612 : }
613 :
614 : /* skip spaces after the command */
615 7468 : sgetspace(f);
616 :
617 7468 : if (strcmp(tag, "blocksize") == 0
618 : /* block_size is the old format of the option */
619 7159 : || strcmp(tag, "block_size") == 0) {
620 :
621 316 : ret = sgetu32(f, &state->block_size);
622 316 : if (ret < 0) {
623 : /* LCOV_EXCL_START */
624 : log_fatal(EUSER, "Invalid 'blocksize' specification in '%s' at line %u\n", path, line);
625 : exit(EXIT_FAILURE);
626 : /* LCOV_EXCL_STOP */
627 : }
628 316 : if (state->block_size < 1) {
629 : /* LCOV_EXCL_START */
630 : log_fatal(EUSER, "Too small 'blocksize' specification in '%s' at line %u\n", path, line);
631 : exit(EXIT_FAILURE);
632 : /* LCOV_EXCL_STOP */
633 : }
634 316 : if (state->block_size > 16 * KIBI) {
635 : /* LCOV_EXCL_START */
636 : log_fatal(EUSER, "Too big 'blocksize' specification in '%s' at line %u\n", path, line);
637 : exit(EXIT_FAILURE);
638 : /* LCOV_EXCL_STOP */
639 : }
640 : /* check if it's a power of 2 */
641 316 : if ((state->block_size & (state->block_size - 1)) != 0) {
642 : /* LCOV_EXCL_START */
643 : log_fatal(EUSER, "Not power of 2 'blocksize' specification in '%s' at line %u\n", path, line);
644 : exit(EXIT_FAILURE);
645 : /* LCOV_EXCL_STOP */
646 : }
647 316 : state->block_size *= KIBI;
648 7152 : } else if (strcmp(tag, "hashsize") == 0
649 7107 : || strcmp(tag, "hash_size") == 0 /* v11.0 used incorrectly this one, kept now for backward compatibility */
650 45 : ) {
651 : uint32_t hash_size;
652 :
653 45 : ret = sgetu32(f, &hash_size);
654 45 : if (ret < 0) {
655 : /* LCOV_EXCL_START */
656 : log_fatal(EUSER, "Invalid 'hashsize' specification in '%s' at line %u\n", path, line);
657 : exit(EXIT_FAILURE);
658 : /* LCOV_EXCL_STOP */
659 : }
660 45 : if (hash_size < 2) {
661 : /* LCOV_EXCL_START */
662 : log_fatal(EUSER, "Too small 'hashsize' specification in '%s' at line %u\n", path, line);
663 : exit(EXIT_FAILURE);
664 : /* LCOV_EXCL_STOP */
665 : }
666 45 : if (hash_size > HASH_MAX) {
667 : /* LCOV_EXCL_START */
668 : log_fatal(EUSER, "Too big 'hashsize' specification in '%s' at line %u\n", path, line);
669 : exit(EXIT_FAILURE);
670 : /* LCOV_EXCL_STOP */
671 : }
672 : /* check if it's a power of 2 */
673 45 : if ((hash_size & (hash_size - 1)) != 0) {
674 : /* LCOV_EXCL_START */
675 : log_fatal(EUSER, "Not power of 2 'hashsize' specification in '%s' at line %u\n", path, line);
676 : exit(EXIT_FAILURE);
677 : /* LCOV_EXCL_STOP */
678 : }
679 :
680 45 : BLOCK_HASH_SIZE = hash_size;
681 7107 : } else if (lev_config_scan(tag, &level, &state->raid_mode) == 0) {
682 : char device[PATH_MAX];
683 : char* split_map[SPLIT_MAX + 1];
684 : unsigned split_mac;
685 : uint64_t dev;
686 : int skip_access;
687 :
688 1400 : if (state->parity[level].split_mac != 0) {
689 : /* LCOV_EXCL_START */
690 : log_fatal(EUSER, "Multiple '%s' specification in '%s' at line %u\n", tag, path, line);
691 : exit(EXIT_FAILURE);
692 : /* LCOV_EXCL_STOP */
693 : }
694 :
695 1400 : ret = sgetlasttok(f, buffer, sizeof(buffer));
696 1400 : if (ret < 0) {
697 : /* LCOV_EXCL_START */
698 : log_fatal(EUSER, "Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
699 : exit(EXIT_FAILURE);
700 : /* LCOV_EXCL_STOP */
701 : }
702 :
703 1400 : if (!*buffer) {
704 : /* LCOV_EXCL_START */
705 : log_fatal(EUSER, "Empty '%s' specification in '%s' at line %u\n", tag, path, line);
706 : exit(EXIT_FAILURE);
707 : /* LCOV_EXCL_STOP */
708 : }
709 :
710 1400 : split_mac = strsplit(split_map, SPLIT_MAX + 1, buffer, ",");
711 :
712 1400 : if (split_mac > SPLIT_MAX) {
713 : /* LCOV_EXCL_START */
714 : log_fatal(EUSER, "Too many files in '%s' specification in '%s' at line %u\n", tag, path, line);
715 : exit(EXIT_FAILURE);
716 : /* LCOV_EXCL_STOP */
717 : }
718 :
719 1400 : skip_access = 0;
720 1400 : state->parity[level].split_mac = split_mac;
721 6885 : for (s = 0; s < split_mac; ++s) {
722 : char uuid[UUID_MAX];
723 5486 : pathimport(state->parity[level].split_map[s].path, sizeof(state->parity[level].split_map[s].path), split_map[s]);
724 :
725 5486 : if (!state->opt.skip_parity_access) {
726 : struct stat st;
727 :
728 : /* get the device of the directory containing the parity file */
729 4772 : pathimport(device, sizeof(device), split_map[s]);
730 :
731 : /* use only the dir, as the parity file may not exist yet */
732 4772 : pathcut(device);
733 :
734 4772 : if (stat(device, &st) == 0) {
735 4767 : dev = st.st_dev;
736 :
737 : /* read the uuid, if unsupported use an empty one */
738 4767 : if (devuuid(dev, device, uuid, sizeof(uuid)) != 0) {
739 0 : *uuid = 0;
740 : }
741 : } else {
742 : /* if the disk can be skipped */
743 5 : if (state->opt.force_device) {
744 : /* use a fake device, and mark the disk to be skipped */
745 4 : dev = 0;
746 4 : *uuid = 0;
747 4 : skip_access = 1;
748 4 : log_fatal(errno, "DANGER! Skipping inaccessible parity disk '%s'...\n", tag);
749 : } else {
750 : /* LCOV_EXCL_START */
751 : log_fatal(errno, "Error accessing 'parity' dir '%s' specification in '%s' at line %u\n", device, path, line);
752 :
753 : /* in "fix" we allow to continue anyway */
754 : if (strcmp(state->command, "fix") == 0) {
755 : log_fatal(errno, "You can '%s' anyway, using 'snapraid --force-device %s'.\n", state->command, state->command);
756 : }
757 : exit(EXIT_FAILURE);
758 : /* LCOV_EXCL_STOP */
759 : }
760 : }
761 : } else {
762 : /* use a fake device */
763 714 : dev = 0;
764 714 : *uuid = 0;
765 : }
766 :
767 5485 : state->parity[level].split_map[s].device = dev;
768 5485 : pathcpy(state->parity[level].split_map[s].uuid, sizeof(state->parity[level].split_map[s].uuid), uuid);
769 : }
770 :
771 : /* store the global parity skip_access */
772 1399 : state->parity[level].skip_access = skip_access;
773 :
774 : /* adjust the level */
775 1399 : if (state->level < level + 1)
776 1083 : state->level = level + 1;
777 5707 : } else if (strcmp(tag, "share") == 0) {
778 45 : if (*state->share) {
779 : /* LCOV_EXCL_START */
780 : log_fatal(EUSER, "Multiple 'share' specification in '%s' at line %u\n", path, line);
781 : exit(EXIT_FAILURE);
782 : /* LCOV_EXCL_STOP */
783 : }
784 :
785 45 : ret = sgetlasttok(f, buffer, sizeof(buffer));
786 45 : if (ret < 0) {
787 : /* LCOV_EXCL_START */
788 : log_fatal(EUSER, "Invalid 'share' specification in '%s' at line %u\n", path, line);
789 : exit(EXIT_FAILURE);
790 : /* LCOV_EXCL_STOP */
791 : }
792 :
793 45 : if (!*buffer) {
794 : /* LCOV_EXCL_START */
795 : log_fatal(EUSER, "Empty 'share' specification in '%s' at line %u\n", path, line);
796 : exit(EXIT_FAILURE);
797 : /* LCOV_EXCL_STOP */
798 : }
799 :
800 45 : pathimport(state->share, sizeof(state->share), buffer);
801 5662 : } else if (strcmp(tag, "pool") == 0) {
802 : struct stat st;
803 :
804 52 : if (*state->pool) {
805 : /* LCOV_EXCL_START */
806 : log_fatal(EUSER, "Multiple 'pool' specification in '%s' at line %u\n", path, line);
807 : exit(EXIT_FAILURE);
808 : /* LCOV_EXCL_STOP */
809 : }
810 :
811 52 : ret = sgetlasttok(f, buffer, sizeof(buffer));
812 52 : if (ret < 0) {
813 : /* LCOV_EXCL_START */
814 : log_fatal(EUSER, "Invalid 'pool' specification in '%s' at line %u\n", path, line);
815 : exit(EXIT_FAILURE);
816 : /* LCOV_EXCL_STOP */
817 : }
818 :
819 52 : if (!*buffer) {
820 : /* LCOV_EXCL_START */
821 : log_fatal(EUSER, "Empty 'pool' specification in '%s' at line %u\n", path, line);
822 : exit(EXIT_FAILURE);
823 : /* LCOV_EXCL_STOP */
824 : }
825 :
826 52 : pathimport(state->pool, sizeof(state->pool), buffer);
827 :
828 : /* get the device of the directory containing the pool tree */
829 52 : if (stat(buffer, &st) != 0) {
830 : /* LCOV_EXCL_START */
831 : log_fatal(errno, "Error accessing 'pool' dir '%s' specification in '%s' at line %u\n", buffer, path, line);
832 : exit(EXIT_FAILURE);
833 : /* LCOV_EXCL_STOP */
834 : }
835 :
836 52 : state->pool_device = st.st_dev;
837 5610 : } else if (strcmp(tag, "content") == 0) {
838 : struct snapraid_content* content;
839 : char device[PATH_MAX];
840 : char* slash;
841 : struct stat st;
842 : uint64_t dev;
843 :
844 1716 : ret = sgetlasttok(f, buffer, sizeof(buffer));
845 1716 : if (ret < 0) {
846 : /* LCOV_EXCL_START */
847 : log_fatal(EUSER, "Invalid 'content' specification in '%s' at line %u\n", path, line);
848 : exit(EXIT_FAILURE);
849 : /* LCOV_EXCL_STOP */
850 : }
851 :
852 1716 : if (pathcmp(buffer, "/dev/null") == 0 || pathcmp(buffer, "NUL") == 0) {
853 : /* LCOV_EXCL_START */
854 : log_fatal(EUSER, "You cannot use the null device as 'content' specification in '%s' at line %u\n", path, line);
855 : exit(EXIT_FAILURE);
856 : /* LCOV_EXCL_STOP */
857 : }
858 :
859 1716 : if (!*buffer) {
860 : /* LCOV_EXCL_START */
861 : log_fatal(EUSER, "Empty 'content' specification in '%s' at line %u\n", path, line);
862 : exit(EXIT_FAILURE);
863 : /* LCOV_EXCL_STOP */
864 : }
865 :
866 : /* check if the content file is already specified */
867 6269 : for (i = state->contentlist; i != 0; i = i->next) {
868 4553 : content = i->data;
869 4553 : if (pathcmp(content->content, buffer) == 0)
870 0 : break;
871 : }
872 1716 : if (i) {
873 : /* LCOV_EXCL_START */
874 : log_fatal(EUSER, "Duplicate 'content' specification in '%s' at line %u\n", path, line);
875 : exit(EXIT_FAILURE);
876 : /* LCOV_EXCL_STOP */
877 : }
878 :
879 : /* get the device of the directory containing the content file */
880 1716 : pathimport(device, sizeof(device), buffer);
881 1716 : slash = strrchr(device, '/');
882 1716 : if (slash)
883 1716 : *(slash + 1) = 0;
884 : else
885 0 : pathcpy(device, sizeof(device), ".");
886 1716 : if (stat(device, &st) == 0) {
887 1715 : dev = st.st_dev;
888 : } else {
889 1 : if (state->opt.skip_content_access) {
890 : /* use a fake device */
891 1 : dev = 0;
892 1 : log_fatal(errno, "WARNING! Skipping inaccessible content file '%s'...\n", buffer);
893 : } else {
894 : /* LCOV_EXCL_START */
895 : log_fatal(errno, "Error accessing 'content' dir '%s' specification in '%s' at line %u\n", device, path, line);
896 : exit(EXIT_FAILURE);
897 : /* LCOV_EXCL_STOP */
898 : }
899 : }
900 :
901 : /* set the lock file at the first accessible content file */
902 1716 : if (state->lockfile[0] == 0 && dev != 0) {
903 315 : pathcpy(state->lockfile, sizeof(state->lockfile), buffer);
904 315 : pathcat(state->lockfile, sizeof(state->lockfile), ".lock");
905 : }
906 :
907 1716 : content = content_alloc(buffer, dev);
908 :
909 1716 : tommy_list_insert_tail(&state->contentlist, &content->node, content);
910 5738 : } else if (strcmp(tag, "data") == 0 || strcmp(tag, "disk") == 0) {
911 : /* "disk" is the deprecated name up to SnapRAID 9.x */
912 : char dir[PATH_MAX];
913 : char device[PATH_MAX];
914 : char uuid[UUID_MAX];
915 : struct snapraid_disk* disk;
916 : uint64_t dev;
917 : int skip_access;
918 :
919 1844 : ret = sgettok(f, buffer, sizeof(buffer));
920 1844 : if (ret < 0) {
921 : /* LCOV_EXCL_START */
922 : log_fatal(EUSER, "Invalid 'data' name specification in '%s' at line %u\n", path, line);
923 : exit(EXIT_FAILURE);
924 : /* LCOV_EXCL_STOP */
925 : }
926 :
927 1844 : if (!*buffer) {
928 : /* LCOV_EXCL_START */
929 : log_fatal(EUSER, "Empty 'data' name specification in '%s' at line %u\n", path, line);
930 : exit(EXIT_FAILURE);
931 : /* LCOV_EXCL_STOP */
932 : }
933 :
934 1844 : sgetspace(f);
935 :
936 1844 : ret = sgetlasttok(f, dir, sizeof(dir));
937 1844 : if (ret < 0) {
938 : /* LCOV_EXCL_START */
939 : log_fatal(EUSER, "Invalid 'data' dir specification in '%s' at line %u\n", path, line);
940 : exit(EXIT_FAILURE);
941 : /* LCOV_EXCL_STOP */
942 : }
943 :
944 1844 : if (!*dir) {
945 : /* LCOV_EXCL_START */
946 : log_fatal(EUSER, "Empty 'data' dir specification in '%s' at line %u\n", path, line);
947 : exit(EXIT_FAILURE);
948 : /* LCOV_EXCL_STOP */
949 : }
950 :
951 : /* get the device of the dir */
952 1844 : pathimport(device, sizeof(device), dir);
953 :
954 : /* check if the disk name already exists */
955 6478 : for (i = state->disklist; i != 0; i = i->next) {
956 4634 : disk = i->data;
957 4634 : if (strcmp(disk->name, buffer) == 0)
958 0 : break;
959 : }
960 1844 : if (i) {
961 : /* LCOV_EXCL_START */
962 : log_fatal(EUSER, "Duplicate 'data' name '%s' at line %u\n", buffer, line);
963 : exit(EXIT_FAILURE);
964 : /* LCOV_EXCL_STOP */
965 : }
966 :
967 : /* if the disk has to be present */
968 1844 : skip_access = 0;
969 1844 : if (!state->opt.skip_disk_access) {
970 : struct stat st;
971 :
972 1682 : if (stat(device, &st) == 0) {
973 1681 : dev = st.st_dev;
974 :
975 : /* read the uuid, if unsupported use an empty one */
976 1681 : if (devuuid(dev, device, uuid, sizeof(uuid)) != 0) {
977 0 : *uuid = 0;
978 : }
979 :
980 : /* fake a different UUID when testing */
981 1681 : if (state->opt.fake_uuid) {
982 2 : snprintf(uuid, sizeof(uuid), "fake-uuid-%d", state->opt.fake_uuid);
983 2 : --state->opt.fake_uuid;
984 : }
985 : } else {
986 : /* if the disk can be skipped */
987 1 : if (state->opt.force_device) {
988 : /* use a fake device, and mark the disk to be skipped */
989 1 : dev = 0;
990 1 : *uuid = 0;
991 1 : skip_access = 1;
992 1 : log_fatal(errno, "DANGER! Skipping inaccessible data disk '%s'...\n", buffer);
993 : } else {
994 : /* LCOV_EXCL_START */
995 : log_fatal(errno, "Error accessing 'disk' '%s' specification in '%s' at line %u\n", dir, device, line);
996 :
997 : /* in "fix" we allow to continue anyway */
998 : if (strcmp(state->command, "fix") == 0) {
999 : log_fatal(errno, "You can '%s' anyway, using 'snapraid --force-device %s'.\n", state->command, state->command);
1000 : }
1001 : exit(EXIT_FAILURE);
1002 : /* LCOV_EXCL_STOP */
1003 : }
1004 : }
1005 : } else {
1006 : /* use a fake device */
1007 162 : dev = 0;
1008 162 : *uuid = 0;
1009 : }
1010 :
1011 1844 : disk = disk_alloc(buffer, dir, dev, uuid, skip_access);
1012 :
1013 1844 : tommy_list_insert_tail(&state->disklist, &disk->node, disk);
1014 2050 : } else if (strcmp(tag, "extra") == 0) {
1015 : char dir[PATH_MAX];
1016 : char device[PATH_MAX];
1017 : char uuid[UUID_MAX];
1018 : struct snapraid_extra* extra;
1019 : uint64_t dev;
1020 :
1021 14 : ret = sgettok(f, buffer, sizeof(buffer));
1022 14 : if (ret < 0) {
1023 : /* LCOV_EXCL_START */
1024 : log_fatal(EUSER, "Invalid 'extra' name specification in '%s' at line %u\n", path, line);
1025 : exit(EXIT_FAILURE);
1026 : /* LCOV_EXCL_STOP */
1027 : }
1028 :
1029 14 : if (!*buffer) {
1030 : /* LCOV_EXCL_START */
1031 : log_fatal(EUSER, "Empty 'extra' name specification in '%s' at line %u\n", path, line);
1032 : exit(EXIT_FAILURE);
1033 : /* LCOV_EXCL_STOP */
1034 : }
1035 :
1036 14 : sgetspace(f);
1037 :
1038 14 : ret = sgetlasttok(f, dir, sizeof(dir));
1039 14 : if (ret < 0) {
1040 : /* LCOV_EXCL_START */
1041 : log_fatal(EUSER, "Invalid 'extra' dir specification in '%s' at line %u\n", path, line);
1042 : exit(EXIT_FAILURE);
1043 : /* LCOV_EXCL_STOP */
1044 : }
1045 :
1046 14 : if (!*dir) {
1047 : /* LCOV_EXCL_START */
1048 : log_fatal(EUSER, "Empty 'extra' dir specification in '%s' at line %u\n", path, line);
1049 : exit(EXIT_FAILURE);
1050 : /* LCOV_EXCL_STOP */
1051 : }
1052 :
1053 : /* get the device of the dir */
1054 14 : pathimport(device, sizeof(device), dir);
1055 :
1056 : /* check if the disk name already exists */
1057 21 : for (i = state->extralist; i != 0; i = i->next) {
1058 7 : extra = i->data;
1059 7 : if (strcmp(extra->name, buffer) == 0)
1060 0 : break;
1061 : }
1062 14 : if (i) {
1063 : /* LCOV_EXCL_START */
1064 : log_fatal(EUSER, "Duplicate 'extra' name '%s' at line %u\n", buffer, line);
1065 : exit(EXIT_FAILURE);
1066 : /* LCOV_EXCL_STOP */
1067 : }
1068 :
1069 : /* if the disk has to be present */
1070 14 : if (!state->opt.skip_disk_access) {
1071 : struct stat st;
1072 :
1073 14 : if (stat(device, &st) == 0) {
1074 14 : dev = st.st_dev;
1075 :
1076 : /* read the uuid, if unsupported use an empty one */
1077 14 : if (devuuid(dev, device, uuid, sizeof(uuid)) != 0) {
1078 0 : *uuid = 0;
1079 : }
1080 :
1081 : /* fake a different UUID when testing */
1082 14 : if (state->opt.fake_uuid) {
1083 0 : snprintf(uuid, sizeof(uuid), "fake-uuid-%d", state->opt.fake_uuid);
1084 0 : --state->opt.fake_uuid;
1085 : }
1086 : } else {
1087 : /* use a fake device, and mark the disk to be skipped */
1088 0 : dev = 0;
1089 0 : *uuid = 0;
1090 : }
1091 : } else {
1092 : /* use a fake device */
1093 0 : dev = 0;
1094 0 : *uuid = 0;
1095 : }
1096 :
1097 14 : extra = extra_alloc(buffer, dir, dev, uuid);
1098 :
1099 14 : tommy_list_insert_tail(&state->extralist, &extra->node, extra);
1100 2036 : } else if (strcmp(tag, "smartctl") == 0) {
1101 : char custom[PATH_MAX];
1102 :
1103 390 : ret = sgettok(f, buffer, sizeof(buffer));
1104 390 : if (ret < 0) {
1105 : /* LCOV_EXCL_START */
1106 : log_fatal(EUSER, "Invalid 'smartctl' name in '%s' at line %u\n", path, line);
1107 : exit(EXIT_FAILURE);
1108 : /* LCOV_EXCL_STOP */
1109 : }
1110 :
1111 390 : if (!*buffer) {
1112 : /* LCOV_EXCL_START */
1113 : log_fatal(EUSER, "Empty 'smartctl' name in '%s' at line %u\n", path, line);
1114 : exit(EXIT_FAILURE);
1115 : /* LCOV_EXCL_STOP */
1116 : }
1117 :
1118 390 : sgetspace(f);
1119 :
1120 390 : ret = sgetlasttok(f, custom, sizeof(custom));
1121 390 : if (ret < 0) {
1122 : /* LCOV_EXCL_START */
1123 : log_fatal(EUSER, "Invalid 'smartctl' option in '%s' at line %u\n", path, line);
1124 : exit(EXIT_FAILURE);
1125 : /* LCOV_EXCL_STOP */
1126 : }
1127 :
1128 390 : if (!*custom) {
1129 : /* LCOV_EXCL_START */
1130 : log_fatal(EUSER, "Empty 'smartctl' option in '%s' at line %u\n", path, line);
1131 : exit(EXIT_FAILURE);
1132 : /* LCOV_EXCL_STOP */
1133 : }
1134 :
1135 390 : if (validate_smartctl(custom) != 0) {
1136 : /* LCOV_EXCL_START */
1137 : log_fatal(EUSER, "Invalid 'smartctl' option in '%s' at line %u\n", path, line);
1138 : exit(EXIT_FAILURE);
1139 : /* LCOV_EXCL_STOP */
1140 : }
1141 :
1142 : /* search for parity */
1143 390 : if (lev_config_scan(buffer, &level, 0) == 0) {
1144 195 : if (state->parity[level].smartctl[0] != 0) {
1145 : /* LCOV_EXCL_START */
1146 : log_fatal(EUSER, "Duplicate parity 'smartctl' '%s' at line %u\n", buffer, line);
1147 : exit(EXIT_FAILURE);
1148 : /* LCOV_EXCL_STOP */
1149 : }
1150 :
1151 195 : pathcpy(state->parity[level].smartctl, sizeof(state->parity[level].smartctl), custom);
1152 : } else {
1153 : /* search the disk */
1154 195 : struct snapraid_disk* found_disk = 0;
1155 195 : for (i = state->disklist; i != 0; i = i->next) {
1156 195 : struct snapraid_disk* disk = i->data;
1157 195 : if (strcmp(disk->name, buffer) == 0) {
1158 195 : found_disk = disk;
1159 195 : break;
1160 : }
1161 : }
1162 195 : struct snapraid_extra* found_extra = 0;
1163 195 : for (i = state->extralist; i != 0; i = i->next) {
1164 0 : struct snapraid_extra* extra = i->data;
1165 0 : if (strcmp(extra->name, buffer) == 0) {
1166 0 : found_extra = extra;
1167 0 : break;
1168 : }
1169 : }
1170 195 : if (found_disk) {
1171 195 : if (found_disk->smartctl[0] != 0) {
1172 : /* LCOV_EXCL_START */
1173 : log_fatal(EUSER, "Duplicate 'smartctl' name '%s' at line %u\n", buffer, line);
1174 : exit(EXIT_FAILURE);
1175 : /* LCOV_EXCL_STOP */
1176 : }
1177 :
1178 195 : pathcpy(found_disk->smartctl, sizeof(found_disk->smartctl), custom);
1179 0 : } else if (found_extra) {
1180 0 : if (found_extra->smartctl[0] != 0) {
1181 : /* LCOV_EXCL_START */
1182 : log_fatal(EUSER, "Duplicate 'smartctl' name '%s' at line %u\n", buffer, line);
1183 : exit(EXIT_FAILURE);
1184 : /* LCOV_EXCL_STOP */
1185 : }
1186 :
1187 0 : pathcpy(found_extra->smartctl, sizeof(found_extra->smartctl), custom);
1188 : } else {
1189 : /* LCOV_EXCL_START */
1190 : log_fatal(EUSER, "Unknown 'smartctl' name '%s' at line %u\n", buffer, line);
1191 : exit(EXIT_FAILURE);
1192 : /* LCOV_EXCL_STOP */
1193 : }
1194 : }
1195 1646 : } else if (strcmp(tag, "smartignore") == 0) {
1196 : int* smart;
1197 :
1198 388 : ret = sgettok(f, buffer, sizeof(buffer));
1199 388 : if (ret < 0) {
1200 : /* LCOV_EXCL_START */
1201 : log_fatal(EUSER, "Invalid 'smartignore' name in '%s' at line %u\n", path, line);
1202 : exit(EXIT_FAILURE);
1203 : /* LCOV_EXCL_STOP */
1204 : }
1205 :
1206 388 : if (!*buffer) {
1207 : /* LCOV_EXCL_START */
1208 : log_fatal(EUSER, "Empty 'smartignore' name in '%s' at line %u\n", path, line);
1209 : exit(EXIT_FAILURE);
1210 : /* LCOV_EXCL_STOP */
1211 : }
1212 :
1213 388 : sgetspace(f);
1214 :
1215 388 : if (strcmp(buffer, "*") == 0) {
1216 194 : smart = state->smartignore;
1217 194 : } else if (lev_config_scan(buffer, &level, 0) == 0) { /* search for parity */
1218 194 : smart = state->parity[level].smartignore;
1219 : } else {
1220 : /* search the disk */
1221 0 : struct snapraid_disk* found_disk = 0;
1222 0 : for (i = state->disklist; i != 0; i = i->next) {
1223 0 : struct snapraid_disk* disk = i->data;
1224 0 : if (strcmp(disk->name, buffer) == 0) {
1225 0 : found_disk = disk;
1226 0 : break;
1227 : }
1228 : }
1229 0 : struct snapraid_extra* found_extra = 0;
1230 0 : for (i = state->extralist; i != 0; i = i->next) {
1231 0 : struct snapraid_extra* extra = i->data;
1232 0 : if (strcmp(extra->name, buffer) == 0) {
1233 0 : found_extra = extra;
1234 0 : break;
1235 : }
1236 : }
1237 0 : if (found_disk) {
1238 0 : smart = found_disk->smartignore;
1239 0 : } else if (found_extra) {
1240 0 : smart = found_extra->smartignore;
1241 : } else {
1242 : /* LCOV_EXCL_START */
1243 : log_fatal(EUSER, "Unknown 'smartignore' name '%s' at line %u\n", buffer, line);
1244 : exit(EXIT_FAILURE);
1245 : /* LCOV_EXCL_STOP */
1246 : }
1247 : }
1248 :
1249 388 : int si = 0;
1250 :
1251 388 : while (1) {
1252 : char* e;
1253 :
1254 776 : ret = sgettok(f, buffer, sizeof(buffer));
1255 776 : if (ret < 0) {
1256 : /* LCOV_EXCL_START */
1257 : log_fatal(EUSER, "Invalid 'smartignore' specification in '%s' at line %u\n", path, line);
1258 : exit(EXIT_FAILURE);
1259 : /* LCOV_EXCL_STOP */
1260 : }
1261 :
1262 776 : if (!*buffer)
1263 388 : break;
1264 :
1265 388 : if (si == SMART_IGNORE_MAX) {
1266 : /* LCOV_EXCL_START */
1267 : log_fatal(EUSER, "Too many 'smartignore' specification in '%s' at line %u\n", path, line);
1268 : exit(EXIT_FAILURE);
1269 : /* LCOV_EXCL_STOP */
1270 : }
1271 :
1272 388 : smart[si] = strtoul(buffer, &e, 0);
1273 :
1274 388 : if (!e || *e) {
1275 : /* LCOV_EXCL_START */
1276 : log_fatal(EUSER, "Invalid 'smartignore' specification in '%s' at line %u\n", path, line);
1277 : exit(EXIT_FAILURE);
1278 : /* LCOV_EXCL_STOP */
1279 : }
1280 :
1281 388 : ++si;
1282 :
1283 388 : sgetspace(f);
1284 : }
1285 :
1286 388 : if (si == 0) {
1287 : /* LCOV_EXCL_START */
1288 : log_fatal(EUSER, "Invalid 'smartignore' specification in '%s' at line %u\n", path, line);
1289 : exit(EXIT_FAILURE);
1290 : /* LCOV_EXCL_STOP */
1291 : }
1292 1258 : } else if (strcmp(tag, "temp_limit") == 0) {
1293 : unsigned temp;
1294 :
1295 8 : ret = sgetu32(f, &temp);
1296 8 : if (ret < 0) {
1297 : /* LCOV_EXCL_START */
1298 : log_fatal(EUSER, "Invalid 'temp_limit' specification in '%s' at line %u\n", path, line);
1299 : exit(EXIT_FAILURE);
1300 : /* LCOV_EXCL_STOP */
1301 : }
1302 8 : if (temp < 40 || temp > 70) {
1303 : /* LCOV_EXCL_START */
1304 : log_fatal(EUSER, "Invalid 'temp_limit' temperature specification in '%s' at line %u. It must be between 40 and 70\n", path, line);
1305 : exit(EXIT_FAILURE);
1306 : /* LCOV_EXCL_STOP */
1307 : }
1308 :
1309 8 : state->thermal_temperature_limit = temp;
1310 1250 : } else if (strcmp(tag, "temp_sleep") == 0) {
1311 : unsigned time;
1312 :
1313 8 : ret = sgetu32(f, &time);
1314 8 : if (ret < 0) {
1315 : /* LCOV_EXCL_START */
1316 : log_fatal(EUSER, "Invalid 'temp_sleep' specification in '%s' at line %u\n", path, line);
1317 : exit(EXIT_FAILURE);
1318 : /* LCOV_EXCL_STOP */
1319 : }
1320 8 : if (time < 3 || time > 120) {
1321 : /* LCOV_EXCL_START */
1322 : log_fatal(EUSER, "Invalid 'temp_sleep' temperature specification in '%s' at line %u. It must be between 3 and 120\n", path, line);
1323 : exit(EXIT_FAILURE);
1324 : /* LCOV_EXCL_STOP */
1325 : }
1326 :
1327 8 : state->thermal_cooldown_time = time * 60;
1328 1242 : } else if (strcmp(tag, "nohidden") == 0) {
1329 0 : state->filter_hidden = 1;
1330 1242 : } else if (strcmp(tag, "snapshot") == 0) {
1331 23 : state->snapshot = 1;
1332 1219 : } else if (strcmp(tag, "exclude") == 0) {
1333 : struct snapraid_filter* filter;
1334 :
1335 343 : ret = sgetlasttok(f, buffer, sizeof(buffer));
1336 343 : if (ret < 0) {
1337 : /* LCOV_EXCL_START */
1338 : log_fatal(EUSER, "Invalid 'exclude' specification in '%s' at line %u\n", path, line);
1339 : exit(EXIT_FAILURE);
1340 : /* LCOV_EXCL_STOP */
1341 : }
1342 :
1343 343 : if (!*buffer) {
1344 : /* LCOV_EXCL_START */
1345 : log_fatal(EUSER, "Empty 'exclude' specification in '%s' at line %u\n", path, line);
1346 : exit(EXIT_FAILURE);
1347 : /* LCOV_EXCL_STOP */
1348 : }
1349 :
1350 343 : filter = filter_alloc_file(-1, "", buffer);
1351 343 : if (!filter) {
1352 : /* LCOV_EXCL_START */
1353 : log_fatal(EUSER, "Invalid 'exclude' specification '%s' in '%s' at line %u\n", buffer, path, line);
1354 : exit(EXIT_FAILURE);
1355 : /* LCOV_EXCL_STOP */
1356 : }
1357 343 : tommy_list_insert_tail(&state->filterlist, &filter->node, filter);
1358 876 : } else if (strcmp(tag, "include") == 0) {
1359 : struct snapraid_filter* filter;
1360 :
1361 285 : ret = sgetlasttok(f, buffer, sizeof(buffer));
1362 285 : if (ret < 0) {
1363 : /* LCOV_EXCL_START */
1364 : log_fatal(EUSER, "Invalid 'include' specification in '%s' at line %u\n", path, line);
1365 : exit(EXIT_FAILURE);
1366 : /* LCOV_EXCL_STOP */
1367 : }
1368 :
1369 285 : if (!*buffer) {
1370 : /* LCOV_EXCL_START */
1371 : log_fatal(EUSER, "Empty 'include' specification in '%s' at line %u\n", path, line);
1372 : exit(EXIT_FAILURE);
1373 : /* LCOV_EXCL_STOP */
1374 : }
1375 :
1376 285 : filter = filter_alloc_file(1, "", buffer);
1377 285 : if (!filter) {
1378 : /* LCOV_EXCL_START */
1379 : log_fatal(EUSER, "Invalid 'include' specification '%s' in '%s' at line %u\n", buffer, path, line);
1380 : exit(EXIT_FAILURE);
1381 : /* LCOV_EXCL_STOP */
1382 : }
1383 285 : tommy_list_insert_tail(&state->filterlist, &filter->node, filter);
1384 591 : } else if (strcmp(tag, "autosave") == 0) {
1385 : char* e;
1386 :
1387 52 : ret = sgetlasttok(f, buffer, sizeof(buffer));
1388 52 : if (ret < 0) {
1389 : /* LCOV_EXCL_START */
1390 : log_fatal(EUSER, "Invalid 'autosave' specification in '%s' at line %u\n", path, line);
1391 : exit(EXIT_FAILURE);
1392 : /* LCOV_EXCL_STOP */
1393 : }
1394 :
1395 52 : if (!*buffer) {
1396 : /* LCOV_EXCL_START */
1397 : log_fatal(EUSER, "Empty 'autosave' specification in '%s' at line %u\n", path, line);
1398 : exit(EXIT_FAILURE);
1399 : /* LCOV_EXCL_STOP */
1400 : }
1401 :
1402 52 : state->autosave = strtoul(buffer, &e, 0);
1403 :
1404 52 : if (!e || *e) {
1405 : /* LCOV_EXCL_START */
1406 : log_fatal(EUSER, "Invalid 'autosave' specification in '%s' at line %u\n", path, line);
1407 : exit(EXIT_FAILURE);
1408 : /* LCOV_EXCL_STOP */
1409 : }
1410 :
1411 : /* convert to GB */
1412 52 : state->autosave *= GIGA;
1413 539 : } else if (tag[0] == 0) {
1414 : /* allow empty lines */
1415 115 : } else if (tag[0] == '#') {
1416 115 : ret = sgetline(f, buffer, sizeof(buffer));
1417 115 : if (ret < 0) {
1418 : /* LCOV_EXCL_START */
1419 : log_fatal(EUSER, "Invalid comment in '%s' at line %u\n", path, line);
1420 : exit(EXIT_FAILURE);
1421 : /* LCOV_EXCL_STOP */
1422 : }
1423 : } else {
1424 : /* LCOV_EXCL_START */
1425 : log_fatal(EUSER, "Invalid command '%s' in '%s' at line %u\n", tag, path, line);
1426 : exit(EXIT_FAILURE);
1427 : /* LCOV_EXCL_STOP */
1428 : }
1429 :
1430 : /* skip final spaces */
1431 7467 : sgetspace(f);
1432 :
1433 : /* next line */
1434 7467 : c = sgeteol(f);
1435 7467 : if (c == EOF) {
1436 315 : break;
1437 : }
1438 7152 : if (c != '\n') {
1439 : /* LCOV_EXCL_START */
1440 : log_fatal(EUSER, "Extra data in '%s' at line %u\n", path, line);
1441 : exit(EXIT_FAILURE);
1442 : /* LCOV_EXCL_STOP */
1443 : }
1444 7152 : ++line;
1445 : }
1446 :
1447 315 : if (serror(f)) {
1448 : /* LCOV_EXCL_START */
1449 : log_fatal(errno, "Error reading the configuration file '%s' at line %u\n", path, line);
1450 : exit(EXIT_FAILURE);
1451 : /* LCOV_EXCL_STOP */
1452 : }
1453 :
1454 315 : sclose(f);
1455 :
1456 315 : state_config_check(state, path, filterlist_disk);
1457 :
1458 : /* select the default hash */
1459 315 : if (state->opt.force_murmur3) {
1460 1 : state->besthash = HASH_MURMUR3;
1461 314 : } else if (state->opt.force_spooky2) {
1462 1 : state->besthash = HASH_SPOOKY2;
1463 : } else {
1464 313 : state->besthash = membesthash();
1465 : }
1466 :
1467 : /* by default use the best hash */
1468 315 : state->hash = state->besthash;
1469 :
1470 : /* by default use a random hash seed */
1471 315 : if (randomize(state->hashseed, HASH_MAX) != 0) {
1472 : /* LCOV_EXCL_START */
1473 : log_fatal(errno, "Failed to retrieve random values.\n");
1474 : exit(EXIT_FAILURE);
1475 : /* LCOV_EXCL_STOP */
1476 : }
1477 :
1478 : /* no previous hash by default */
1479 315 : state->prevhash = HASH_UNDEFINED;
1480 :
1481 : /* intentionally not set the prevhashseed, if used valgrind will warn about it */
1482 :
1483 315 : log_tag("blocksize:%u\n", state->block_size);
1484 2159 : for (i = state->disklist; i != 0; i = i->next) {
1485 1844 : struct snapraid_disk* disk = i->data;
1486 1844 : log_tag("data:%s:%s:%s\n", esc_tag(disk->name, esc_buffer), esc_tag(disk->mount_point, esc_buffer1), disk->uuid);
1487 : }
1488 329 : for (i = state->extralist; i != 0; i = i->next) {
1489 14 : struct snapraid_extra* extra = i->data;
1490 14 : log_tag("extra:%s:%s:%s\n", esc_tag(extra->name, esc_buffer), esc_tag(extra->dir, esc_buffer1), extra->uuid);
1491 : }
1492 :
1493 315 : log_tag("mode:%s\n", lev_raid_name(state->raid_mode, state->level));
1494 1709 : for (l = 0; l < state->level; ++l) {
1495 1394 : log_tag("%s:%s:%s\n", lev_config_name(l), esc_tag(state->parity[l].split_map[0].path, esc_buffer), state->parity[l].split_map[0].uuid);
1496 5465 : for (s = 1; s < state->parity[l].split_mac; ++s) {
1497 4071 : log_tag("%s/%u:%s:%s\n", lev_config_name(l), s, esc_tag(state->parity[l].split_map[s].path, esc_buffer), state->parity[l].split_map[s].uuid);
1498 : }
1499 : }
1500 315 : if (state->pool[0] != 0)
1501 52 : log_tag("pool:%s\n", esc_tag(state->pool, esc_buffer));
1502 315 : if (state->share[0] != 0)
1503 45 : log_tag("share:%s\n", esc_tag(state->share, esc_buffer));
1504 315 : if (state->autosave != 0)
1505 52 : log_tag("autosave:%" PRIu64 "\n", state->autosave);
1506 943 : for (i = tommy_list_head(&state->filterlist); i != 0; i = i->next) {
1507 : char out[PATH_MAX];
1508 628 : struct snapraid_filter* filter = i->data;
1509 628 : log_tag("filter:%s\n", esc_tag(filter_type(filter, out, sizeof(out)), esc_buffer));
1510 : }
1511 315 : if (state->filter_hidden)
1512 0 : log_tag("filter:nohidden:\n");
1513 315 : log_flush();
1514 315 : }
1515 :
1516 : /**
1517 : * Find a disk by name.
1518 : */
1519 8120 : static struct snapraid_disk* find_disk_by_name(struct snapraid_state* state, const char* name)
1520 : {
1521 : tommy_node* i;
1522 :
1523 27801 : for (i = state->disklist; i != 0; i = i->next) {
1524 27793 : struct snapraid_disk* disk = i->data;
1525 27793 : if (strcmp(disk->name, name) == 0)
1526 8112 : return disk;
1527 : }
1528 :
1529 8 : if (state->no_conf) {
1530 : /* without a configuration file, add disks automatically */
1531 : struct snapraid_disk* disk;
1532 :
1533 6 : disk = disk_alloc(name, "DUMMY/", -1, "", 0);
1534 :
1535 6 : tommy_list_insert_tail(&state->disklist, &disk->node, disk);
1536 :
1537 6 : return disk;
1538 : }
1539 :
1540 2 : return 0;
1541 : }
1542 :
1543 : /**
1544 : * Find a disk by UUID.
1545 : */
1546 2 : static struct snapraid_disk* find_disk_by_uuid(struct snapraid_state* state, const char* uuid)
1547 : {
1548 : tommy_node* i;
1549 2 : struct snapraid_disk* found = 0;
1550 :
1551 : /*
1552 : * Special test case to find the first matching UUID
1553 : * when testing UUID are all equal or not supported
1554 : * and we should handle this case specifically
1555 : */
1556 2 : if (state->opt.match_first_uuid)
1557 2 : return state->disklist->data;
1558 :
1559 : /* LCOV_EXCL_START */
1560 : /* never find an empty uuid */
1561 : if (!*uuid)
1562 : return 0;
1563 :
1564 : for (i = state->disklist; i != 0; i = i->next) {
1565 : struct snapraid_disk* disk = i->data;
1566 : if (strcmp(disk->uuid, uuid) == 0) {
1567 : /* never match duplicate UUID */
1568 : if (found)
1569 : return 0;
1570 : found = disk;
1571 : }
1572 : }
1573 :
1574 : return found;
1575 : /* LCOV_EXCL_STOP */
1576 : }
1577 :
1578 : /**
1579 : * Update the disk mapping if required.
1580 : */
1581 302 : static void state_map(struct snapraid_state* state)
1582 : {
1583 : unsigned hole;
1584 : tommy_node* i;
1585 : unsigned uuid_mismatch;
1586 : unsigned diskcount;
1587 : unsigned l, s;
1588 :
1589 : /*
1590 : * Remove all the mapping without a disk
1591 : * this happens when a disk is removed from the configuration file
1592 : * From SnapRAID 4.0 mappings are automatically removed if a disk is not used
1593 : * when saving the content file, but we keep this code to import older content files.
1594 : */
1595 1879 : for (i = state->maplist; i != 0; ) {
1596 1577 : struct snapraid_map* map = i->data;
1597 : struct snapraid_disk* disk;
1598 :
1599 1577 : disk = find_disk_by_name(state, map->name);
1600 :
1601 : /* go to the next mapping before removing */
1602 1577 : i = i->next;
1603 :
1604 1577 : if (disk == 0) {
1605 : /* disk not found, remove the mapping */
1606 0 : tommy_list_remove_existing(&state->maplist, &map->node);
1607 0 : map_free(map);
1608 : }
1609 : }
1610 :
1611 : /*
1612 : * Maps each unmapped disk present in the configuration file in the first available hole
1613 : * this happens when you add disks for the first time in the configuration file
1614 : */
1615 302 : hole = 0; /* first position to try */
1616 2040 : for (i = state->disklist; i != 0; i = i->next) {
1617 1738 : struct snapraid_disk* disk = i->data;
1618 : struct snapraid_map* map;
1619 : tommy_node* j;
1620 :
1621 : /* check if the disk is already mapped */
1622 5967 : for (j = state->maplist; j != 0; j = j->next) {
1623 5806 : map = j->data;
1624 5806 : if (strcmp(disk->name, map->name) == 0) {
1625 : /* mapping found */
1626 1577 : break;
1627 : }
1628 : }
1629 1738 : if (j != 0) {
1630 : /* mapping is present, then copy the free blocks into to disk */
1631 1577 : disk->total_blocks = map->total_blocks;
1632 1577 : disk->free_blocks = map->free_blocks;
1633 1577 : continue;
1634 : }
1635 :
1636 : /* mapping not found, search for an hole */
1637 : while (1) {
1638 1109 : for (j = state->maplist; j != 0; j = j->next) {
1639 948 : map = j->data;
1640 948 : if (map->position == hole) {
1641 : /* position already used */
1642 160 : break;
1643 : }
1644 : }
1645 321 : if (j == 0) {
1646 : /* hole found */
1647 161 : break;
1648 : }
1649 :
1650 : /* try with the next one */
1651 160 : ++hole;
1652 : }
1653 :
1654 : /* insert the new mapping */
1655 161 : map = map_alloc(disk->name, hole, 0, 0, "");
1656 :
1657 161 : tommy_list_insert_tail(&state->maplist, &map->node, map);
1658 : }
1659 :
1660 : /* without configuration don't check for number of data disks or uuid changes */
1661 302 : if (state->no_conf)
1662 1 : return;
1663 :
1664 : /* counter for the number of UUID mismatches */
1665 301 : uuid_mismatch = 0;
1666 :
1667 : /* check if mapping match the disk uuid */
1668 301 : if (!state->opt.skip_disk_access) {
1669 1843 : for (i = state->maplist; i != 0; i = i->next) {
1670 1570 : struct snapraid_map* map = i->data;
1671 : struct snapraid_disk* disk;
1672 :
1673 1570 : disk = find_disk_by_name(state, map->name);
1674 1570 : if (disk == 0) {
1675 : /* LCOV_EXCL_START */
1676 : log_fatal(EINTERNAL, "Internal inconsistency: Mapping not found for '%s'\n", map->name);
1677 : os_abort();
1678 : /* LCOV_EXCL_STOP */
1679 : }
1680 :
1681 1570 : if (disk->has_unsupported_uuid) {
1682 : /* if uuid is not available, skip this one */
1683 1 : continue;
1684 : }
1685 :
1686 : /* if the uuid is changed */
1687 1569 : if (strcmp(disk->uuid, map->uuid) != 0) {
1688 : /* mark the disk as with an UUID change */
1689 116 : disk->has_different_uuid = 1;
1690 :
1691 : /* if the previous uuid is available */
1692 116 : if (map->uuid[0] != 0) {
1693 : /* count the number of uuid change */
1694 4 : ++uuid_mismatch;
1695 4 : log_fatal(ESOFT, "UUID change for disk '%s' from '%s' to '%s'\n", disk->name, map->uuid, disk->uuid);
1696 : } else {
1697 : /*
1698 : * No message here, because having a disk without
1699 : * UUID is the normal state of an empty disk
1700 : */
1701 112 : disk->had_empty_uuid = 1;
1702 : }
1703 :
1704 : /* update the uuid in the mapping, */
1705 116 : pathcpy(map->uuid, sizeof(map->uuid), disk->uuid);
1706 :
1707 : /* write the new state with the new uuid */
1708 116 : state->need_write = 1;
1709 : }
1710 : }
1711 : }
1712 :
1713 : /* check the parity uuid */
1714 301 : if (!state->opt.skip_parity_access) {
1715 1419 : for (l = 0; l < state->level; ++l) {
1716 5727 : for (s = 0; s < state->parity[l].split_mac; ++s) {
1717 : char uuid[UUID_MAX];
1718 : int ret;
1719 :
1720 4569 : ret = devuuid(state->parity[l].split_map[s].device, state->parity[l].split_map[s].path, uuid, sizeof(uuid));
1721 :
1722 4569 : if (ret != 0) {
1723 : /* uuid not available, just ignore */
1724 4 : continue;
1725 : }
1726 :
1727 : /* if the uuid is changed */
1728 4565 : if (strcmp(uuid, state->parity[l].split_map[s].uuid) != 0) {
1729 : /* if the previous uuid is available */
1730 0 : if (state->parity[l].split_map[s].uuid[0] != 0) {
1731 : /* count the number of uuid change */
1732 0 : ++uuid_mismatch;
1733 0 : log_fatal(ESOFT, "UUID change for parity '%s/%u' from '%s' to '%s'\n", lev_config_name(l), s, state->parity[l].split_map[s].uuid, uuid);
1734 : }
1735 :
1736 : /* update the uuid */
1737 0 : pathcpy(state->parity[l].split_map[s].uuid, sizeof(state->parity[l].split_map[s].uuid), uuid);
1738 :
1739 : /* write the new state with the new uuid */
1740 0 : state->need_write = 1;
1741 : }
1742 : }
1743 : }
1744 : }
1745 :
1746 301 : if (!state->opt.force_uuid && uuid_mismatch > state->level) {
1747 : /* LCOV_EXCL_START */
1748 : log_fatal(ESOFT, "Too many disks have changed UUIDs since the last 'sync'.\n");
1749 : log_fatal(ESOFT, "If this happened because you actually replaced them,\n");
1750 : log_fatal(ESOFT, "you can still '%s', using 'snapraid --force-uuid %s'.\n", state->command, state->command);
1751 : log_fatal(ESOFT, "Alternatively, you may have misconfigured the disk mount points,\n");
1752 : log_fatal(ESOFT, "and you need to restore the mount points to the state of the latest sync.\n");
1753 : exit(EXIT_FAILURE);
1754 : /* LCOV_EXCL_STOP */
1755 : }
1756 :
1757 : /* count the number of data disks, including holes left after removing some */
1758 301 : diskcount = 0;
1759 2033 : for (i = state->maplist; i != 0; i = i->next) {
1760 1732 : struct snapraid_map* map = i->data;
1761 :
1762 1732 : if (map->position + 1 > diskcount)
1763 1544 : diskcount = map->position + 1;
1764 : }
1765 :
1766 : /* ensure to don't go over the limit of the RAID engine */
1767 301 : if (diskcount > RAID_DATA_MAX) {
1768 : /* LCOV_EXCL_START */
1769 : log_fatal(ESOFT, "Too many data disks. Maximum allowed is %u.\n", RAID_DATA_MAX);
1770 : exit(EXIT_FAILURE);
1771 : /* LCOV_EXCL_STOP */
1772 : }
1773 :
1774 : /* now count the real number of data disks, excluding holes left after removing some */
1775 301 : diskcount = tommy_list_count(&state->maplist);
1776 :
1777 : /* recommend number of parities */
1778 301 : if (!state->opt.no_warnings) {
1779 : /* LCOV_EXCL_START */
1780 : if (diskcount >= 36 && state->level < 6) {
1781 : log_error(EUSER, "WARNING! For %u disks, it's recommended to use six parity levels.\n", diskcount);
1782 : } else if (diskcount >= 29 && state->level < 5) {
1783 : log_error(EUSER, "WARNING! For %u disks, it's recommended to use five parity levels.\n", diskcount);
1784 : } else if (diskcount >= 22 && state->level < 4) {
1785 : log_error(EUSER, "WARNING! For %u disks, it's recommended to use four parity levels.\n", diskcount);
1786 : } else if (diskcount >= 15 && state->level < 3) {
1787 : log_error(EUSER, "WARNING! For %u disks, it's recommended to use three parity levels.\n", diskcount);
1788 : } else if (diskcount >= 5 && state->level < 2) {
1789 : log_error(EUSER, "WARNING! For %u disks, it's recommended to use two parity levels.\n", diskcount);
1790 : }
1791 : /* LCOV_EXCL_STOP */
1792 : }
1793 : }
1794 :
1795 234 : void state_refresh(struct snapraid_state* state)
1796 : {
1797 : char esc_buffer[ESC_MAX];
1798 : tommy_node* i;
1799 : unsigned l, s;
1800 234 : uint64_t bs = (uint64_t)state->block_size;
1801 :
1802 : /* for all disks */
1803 1572 : for (i = state->maplist; i != 0; i = i->next) {
1804 1338 : struct snapraid_map* map = i->data;
1805 : struct snapraid_disk* disk;
1806 : uint64_t total_space;
1807 : uint64_t free_space;
1808 : int ret;
1809 :
1810 1338 : disk = find_disk_by_name(state, map->name);
1811 1338 : if (disk == 0) {
1812 : /* LCOV_EXCL_START */
1813 : log_fatal(EINTERNAL, "Internal inconsistency: Mapping not found for '%s'\n", map->name);
1814 : os_abort();
1815 : /* LCOV_EXCL_STOP */
1816 : }
1817 :
1818 1338 : ret = fsinfo(disk->mount_point, 0, 0, &total_space, &free_space, disk->fstype, sizeof(disk->fstype), disk->fslabel, sizeof(disk->fslabel));
1819 1338 : if (ret != 0) {
1820 : /* LCOV_EXCL_START */
1821 : log_fatal(errno, "Error accessing disk '%s' to retrieve file-system info. %s.\n", disk->mount_point, strerror(errno));
1822 : exit(EXIT_FAILURE);
1823 : /* LCOV_EXCL_STOP */
1824 : }
1825 :
1826 : /* set the new free blocks */
1827 1338 : map->total_blocks = total_space / bs;
1828 1338 : map->free_blocks = free_space / bs;
1829 :
1830 : /* also update the disk info */
1831 1338 : disk->total_blocks = map->total_blocks;
1832 1338 : disk->free_blocks = map->free_blocks;
1833 :
1834 1338 : log_tag("fsinfo_data_split:%s:%" PRIu64 ":%" PRIu64 ":%s:%s\n", esc_tag(disk->name, esc_buffer), disk->total_blocks * bs, disk->free_blocks * bs, disk->fstype, disk->fslabel);
1835 1338 : log_tag("fsinfo_data:%s:%" PRIu64 ":%" PRIu64 "\n", esc_tag(disk->name, esc_buffer), disk->total_blocks * bs, disk->free_blocks * bs);
1836 : }
1837 :
1838 : /* for all parities */
1839 1428 : for (l = 0; l < state->level; ++l) {
1840 : /* set the new free blocks */
1841 1194 : state->parity[l].total_blocks = 0;
1842 1194 : state->parity[l].free_blocks = 0;
1843 :
1844 5907 : for (s = 0; s < state->parity[l].split_mac; ++s) {
1845 4713 : struct snapraid_split* split = &state->parity[l].split_map[s];
1846 : uint64_t total_space;
1847 : uint64_t free_space;
1848 : int ret;
1849 :
1850 4713 : ret = fsinfo(split->path, 0, 0, &total_space, &free_space, split->fstype, sizeof(split->fstype), split->fslabel, sizeof(split->fslabel));
1851 4713 : if (ret != 0) {
1852 : /* LCOV_EXCL_START */
1853 : log_fatal(errno, "Error accessing file '%s' to retrieve file-system info. %s.\n", split->path, strerror(errno));
1854 : exit(EXIT_FAILURE);
1855 : /* LCOV_EXCL_STOP */
1856 : }
1857 :
1858 : #ifdef WIN32
1859 : /*
1860 : * Take into account the space we have to leave free
1861 : * to avoid the waring about low space
1862 : */
1863 : if (free_space >= WINDOWS_SPACEHOLDER_SIZE)
1864 : free_space -= WINDOWS_SPACEHOLDER_SIZE;
1865 : else
1866 : free_space = 0;
1867 : #endif
1868 :
1869 : /* add the new free blocks */
1870 4713 : uint64_t split_total_blocks = total_space / bs;
1871 4713 : uint64_t split_free_blocks = free_space / bs;
1872 :
1873 4713 : state->parity[l].total_blocks += split_total_blocks;
1874 4713 : state->parity[l].free_blocks += split_free_blocks;
1875 :
1876 4713 : if (s == 0)
1877 1194 : log_tag("fsinfo_parity_split:%s:%" PRIu64 ":%" PRIu64 ":%s:%s\n", lev_config_name(l), split_total_blocks * bs, split_free_blocks * bs, split->fstype, split->fslabel);
1878 : else
1879 3519 : log_tag("fsinfo_parity_split:%s/%u:%" PRIu64 ":%" PRIu64 ":%s:%s\n", lev_config_name(l), s, split_total_blocks * bs, split_free_blocks * bs, split->fstype, split->fslabel);
1880 : }
1881 :
1882 1194 : log_tag("fsinfo_parity:%s:%" PRIu64 ":%" PRIu64 "\n", lev_config_name(l), state->parity[l].total_blocks * bs, state->parity[l].free_blocks * bs);
1883 : }
1884 :
1885 : /* for all extra disks */
1886 234 : for (i = state->extralist; i != 0; i = i->next) {
1887 0 : struct snapraid_extra* extra = i->data;
1888 : uint64_t total_space;
1889 : uint64_t free_space;
1890 : int ret;
1891 :
1892 0 : ret = fsinfo(extra->dir, 0, 0, &total_space, &free_space, extra->fstype, sizeof(extra->fstype), extra->fslabel, sizeof(extra->fslabel));
1893 0 : if (ret != 0) {
1894 : /* LCOV_EXCL_START */
1895 : log_fatal(errno, "Error accessing disk '%s' to retrieve file-system info. %s.\n", extra->dir, strerror(errno));
1896 : exit(EXIT_FAILURE);
1897 : /* LCOV_EXCL_STOP */
1898 : }
1899 :
1900 0 : log_tag("fsinfo_extra:%s:%" PRIu64 ":%" PRIu64 "\n", esc_tag(extra->name, esc_buffer), total_space, free_space);
1901 : }
1902 :
1903 :
1904 : /*
1905 : * Note what we don't set need_write = 1, because we don't want
1906 : * to update the content file only for the free space info.
1907 : */
1908 234 : }
1909 :
1910 : /**
1911 : * Check the content.
1912 : */
1913 294 : static void state_content_check(struct snapraid_state* state, const char* path)
1914 : {
1915 : tommy_node* i;
1916 :
1917 : /* check that any map has different name and position */
1918 1987 : for (i = state->maplist; i != 0; i = i->next) {
1919 1693 : struct snapraid_map* map = i->data;
1920 : tommy_node* j;
1921 5814 : for (j = i->next; j != 0; j = j->next) {
1922 4121 : struct snapraid_map* other = j->data;
1923 4121 : if (strcmp(map->name, other->name) == 0) {
1924 : /* LCOV_EXCL_START */
1925 : log_fatal(ECONTENT, "Conflicting 'map' disk specification in '%s'\n", path);
1926 : exit(EXIT_FAILURE);
1927 : /* LCOV_EXCL_STOP */
1928 : }
1929 4121 : if (map->position == other->position) {
1930 : /* LCOV_EXCL_START */
1931 : log_fatal(ECONTENT, "Conflicting 'map' index specification in '%s'\n", path);
1932 : exit(EXIT_FAILURE);
1933 : /* LCOV_EXCL_STOP */
1934 : }
1935 : }
1936 : }
1937 294 : }
1938 :
1939 : /**
1940 : * Check if the position is REQUIRED, or we can completely clear it from the state.
1941 : *
1942 : * Note that position with only DELETED blocks are discharged.
1943 : */
1944 847980 : static int fs_position_is_required(struct snapraid_state* state, block_off_t pos)
1945 : {
1946 : tommy_node* i;
1947 :
1948 : /* check for each disk */
1949 931194 : for (i = state->disklist; i != 0; i = i->next) {
1950 929316 : struct snapraid_disk* disk = i->data;
1951 929316 : struct snapraid_block* block = fs_par2block_find(disk, pos);
1952 :
1953 : /* if we have at least one file, the position is needed */
1954 929316 : if (block_has_file(block))
1955 846102 : return 1;
1956 : }
1957 :
1958 1878 : return 0;
1959 : }
1960 :
1961 : /**
1962 : * Check if the info block is REQUIREQ.
1963 : *
1964 : * This is used to ensure that we keep the last check used for scrubbing.
1965 : * and that we add it when importing old context files.
1966 : *
1967 : * Note that you can have position without info blocks, for example
1968 : * if all the blocks are not synced.
1969 : *
1970 : * Note also that not requiring an info block, doesn't mean that if present it
1971 : * can be discarded.
1972 : */
1973 1681497 : static int fs_info_is_required(struct snapraid_state* state, block_off_t pos)
1974 : {
1975 : tommy_node* i;
1976 :
1977 : /* check for each disk */
1978 2218263 : for (i = state->disklist; i != 0; i = i->next) {
1979 2164381 : struct snapraid_disk* disk = i->data;
1980 2164381 : struct snapraid_block* block = fs_par2block_find(disk, pos);
1981 :
1982 : /* if we have at least one synced file, the info is required */
1983 2164381 : if (block_state_get(block) == BLOCK_STATE_BLK)
1984 1627615 : return 1;
1985 : }
1986 :
1987 53882 : return 0;
1988 : }
1989 :
1990 1878 : static void fs_position_clear_deleted(struct snapraid_state* state, block_off_t pos)
1991 : {
1992 : tommy_node* i;
1993 :
1994 : /* check for each disk if block is really used */
1995 13146 : for (i = state->disklist; i != 0; i = i->next) {
1996 11268 : struct snapraid_disk* disk = i->data;
1997 11268 : struct snapraid_block* block = fs_par2block_find(disk, pos);
1998 :
1999 : /* if the block is deleted */
2000 11268 : if (block_state_get(block) == BLOCK_STATE_DELETED) {
2001 : /* set it to empty */
2002 939 : fs_deallocate(disk, pos);
2003 : }
2004 : }
2005 1878 : }
2006 :
2007 : /**
2008 : * Check if a block position in a disk is deleted.
2009 : */
2010 5032492 : static int fs_is_block_deleted(struct snapraid_disk* disk, block_off_t pos)
2011 : {
2012 5032492 : struct snapraid_block* block = fs_par2block_find(disk, pos);
2013 :
2014 5032492 : return block_state_get(block) == BLOCK_STATE_DELETED;
2015 : }
2016 :
2017 : /**
2018 : * Check if a block parity needs sync
2019 : *
2020 : * This is the same logic used in "status" to detect an incomplete "sync",
2021 : * that ignores invalid block, if they are not used by a file in any disk.
2022 : *
2023 : * This means that DELETED blocks won't necessarily imply an invalid parity.
2024 : */
2025 2529477 : static int fs_is_block_unsynced(struct snapraid_state* state, block_off_t pos)
2026 : {
2027 2529477 : int one_invalid = 0;
2028 2529477 : int one_valid = 0;
2029 16275696 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
2030 13984350 : struct snapraid_disk* disk = i->data;
2031 13984350 : struct snapraid_block* block = fs_par2block_find(disk, pos);
2032 :
2033 13984350 : if (block_has_file(block))
2034 13578578 : one_valid = 1;
2035 13984350 : if (block_has_invalid_parity(block))
2036 239319 : one_invalid = 1;
2037 :
2038 13984350 : if (one_invalid && one_valid)
2039 238131 : return 1;
2040 : }
2041 :
2042 2291346 : return 0;
2043 : }
2044 :
2045 : /**
2046 : * Flush the file checking the final CRC.
2047 : * We exploit the fact that the CRC is always stored in the last 4 bytes.
2048 : */
2049 1 : static void decoding_error(const char* path, STREAM* f)
2050 : {
2051 : unsigned char buf[4];
2052 : uint32_t crc_stored;
2053 : uint32_t crc_computed;
2054 :
2055 1 : if (seof(f)) {
2056 : /* LCOV_EXCL_START */
2057 : log_fatal(ECONTENT, "Unexpected end of content file '%s' at offset %" PRIi64 "\n", path, stell(f));
2058 : log_fatal(ECONTENT, "This content file is truncated. Please use an alternate copy.\n");
2059 : exit(EXIT_FAILURE);
2060 : /* LCOV_EXCL_STOP */
2061 : }
2062 :
2063 1 : if (serror(f)) {
2064 : /* LCOV_EXCL_START */
2065 : log_fatal(errno, "Error reading the content file '%s' at offset %" PRIi64 "\n", path, stell(f));
2066 : exit(EXIT_FAILURE);
2067 : /* LCOV_EXCL_STOP */
2068 : }
2069 :
2070 1 : log_fatal(ECONTENT, "Error decoding '%s' at offset %" PRIi64 "\n", path, stell(f));
2071 :
2072 1 : if (sdeplete(f, buf) != 0) {
2073 : /* LCOV_EXCL_START */
2074 : log_fatal(errno, "Failed to flush content file '%s' at offset %" PRIi64 "\n", path, stell(f));
2075 : exit(EXIT_FAILURE);
2076 : /* LCOV_EXCL_STOP */
2077 : }
2078 :
2079 : /* get the stored crc from the last four bytes */
2080 1 : crc_stored = buf[0] | (uint32_t)buf[1] << 8 | (uint32_t)buf[2] << 16 | (uint32_t)buf[3] << 24;
2081 :
2082 : /* get the computed crc */
2083 1 : crc_computed = scrc(f);
2084 :
2085 : /* adjust the stored crc to include itself */
2086 1 : crc_stored = crc32c(crc_stored, buf, 4);
2087 :
2088 1 : if (crc_computed != crc_stored) {
2089 1 : log_fatal(ECONTENT, "CRC mismatch in '%s'\n", path);
2090 1 : log_fatal(ECONTENT, "This content file is damaged! Please use an alternate copy.\n");
2091 1 : exit(EXIT_FAILURE);
2092 : } else {
2093 0 : log_fatal(ECONTENT, "The CRC of the file is correct!\n");
2094 : }
2095 0 : }
2096 :
2097 295 : static void state_read_content(struct snapraid_state* state, const char* path, STREAM* f)
2098 : {
2099 : char esc_buffer[ESC_MAX];
2100 : char esc_buffer1[ESC_MAX];
2101 : block_off_t blockmax;
2102 : unsigned count_file;
2103 : unsigned count_hardlink;
2104 : unsigned count_symlink;
2105 : unsigned count_dir;
2106 : unsigned count_bad;
2107 : unsigned count_rehash;
2108 : unsigned count_unsynced;
2109 : unsigned count_unscrubbed;
2110 : int crc_checked;
2111 : char buffer[PATH_MAX];
2112 : int ret;
2113 : tommy_array disk_mapping;
2114 : uint32_t mapping_max;
2115 : tommy_hashdyn bucket_hash;
2116 :
2117 295 : blockmax = 0;
2118 295 : count_file = 0;
2119 295 : count_hardlink = 0;
2120 295 : count_symlink = 0;
2121 295 : count_dir = 0;
2122 295 : count_bad = 0;
2123 295 : count_rehash = 0;
2124 295 : count_unsynced = 0;
2125 295 : count_unscrubbed = 0;
2126 295 : crc_checked = 0;
2127 295 : mapping_max = 0;
2128 295 : tommy_array_init(&disk_mapping);
2129 295 : tommy_hashdyn_init(&bucket_hash);
2130 :
2131 : /* mark all disks as single threads */
2132 1988 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
2133 1693 : struct snapraid_disk* disk = i->data;
2134 1693 : disk->single_thread = 1;
2135 : }
2136 :
2137 295 : ret = sread(f, buffer, 12);
2138 295 : if (ret < 0) {
2139 : /* LCOV_EXCL_START */
2140 : decoding_error(path, f);
2141 : log_fatal(ECONTENT, "Invalid header!\n");
2142 : os_abort();
2143 : /* LCOV_EXCL_STOP */
2144 : }
2145 :
2146 : /*
2147 : * File format versions:
2148 : * - SNAPCNT1/SnapRAID 4.0 First version.
2149 : * - SNAPCNT2/SnapRAID 7.0 Adds entries 'M' and 'P', to add free_blocks support.
2150 : * The previous 'm' entry is now deprecated, but supported for importing.
2151 : * Similarly for text file, we add 'mapping' and 'parity' deprecating 'map'.
2152 : * - SNAPCNT3/SnapRAID 11.0 Adds entry 'y' for hash size.
2153 : * - SNAPCNT3/SnapRAID 11.0 Adds entry 'Q' for multi parity file.
2154 : * The previous 'P' entry is now deprecated, but supported for importing.
2155 : * - SNAPCNT4/SnapRAID 15.0 Adds entry 'd' for dealloc file.
2156 : */
2157 295 : if (memcmp(buffer, "SNAPCNT1\n\3\0\0", 12) != 0
2158 295 : && memcmp(buffer, "SNAPCNT2\n\3\0\0", 12) != 0
2159 295 : && memcmp(buffer, "SNAPCNT3\n\3\0\0", 12) != 0
2160 24 : && memcmp(buffer, "SNAPCNT4\n\3\0\0", 12) != 0
2161 : ) {
2162 : /* LCOV_EXCL_START */
2163 : if (memcmp(buffer, "SNAPCNT", 7) != 0) {
2164 : decoding_error(path, f);
2165 : log_fatal(ECONTENT, "Invalid header!\n");
2166 : os_abort();
2167 : } else {
2168 : log_fatal(ECONTENT, "The content file '%s' was generated with a newer version of SnapRAID!\n", path);
2169 : exit(EXIT_FAILURE);
2170 : }
2171 : /* LCOV_EXCL_STOP */
2172 : }
2173 :
2174 4037419 : while (1) {
2175 : int c;
2176 :
2177 : /* read the command */
2178 4037714 : c = sgetc(f);
2179 4037714 : if (c == EOF) {
2180 294 : break;
2181 : }
2182 :
2183 4037420 : if (c == 'f') {
2184 : /* file */
2185 : char sub[PATH_MAX];
2186 : uint64_t v_size;
2187 : uint64_t v_mtime_sec;
2188 : uint32_t v_mtime_nsec;
2189 : uint64_t v_inode;
2190 : uint32_t v_idx;
2191 : struct snapraid_file* file;
2192 : struct snapraid_disk* disk;
2193 : uint32_t mapping;
2194 :
2195 3903454 : ret = sgetb32(f, &mapping);
2196 3903454 : if (ret < 0 || mapping >= mapping_max) {
2197 : /* LCOV_EXCL_START */
2198 : decoding_error(path, f);
2199 : log_fatal(EINTERNAL, "Internal inconsistency: File mapping index out of range\n");
2200 : os_abort();
2201 : /* LCOV_EXCL_STOP */
2202 : }
2203 3903454 : disk = tommy_array_get(&disk_mapping, mapping);
2204 :
2205 3903454 : ret = sgetb64(f, &v_size);
2206 3903454 : if (ret < 0) {
2207 : /* LCOV_EXCL_START */
2208 : decoding_error(path, f);
2209 : os_abort();
2210 : /* LCOV_EXCL_STOP */
2211 : }
2212 :
2213 3903454 : if (state->block_size == 0) {
2214 : /* LCOV_EXCL_START */
2215 : decoding_error(path, f);
2216 : log_fatal(EINTERNAL, "Internal inconsistency: Zero blocksize\n");
2217 : exit(EXIT_FAILURE);
2218 : /* LCOV_EXCL_STOP */
2219 : }
2220 :
2221 : /* check for impossible file size to avoid to crash for a too big allocation */
2222 3903454 : if (v_size / state->block_size > blockmax) {
2223 : /* LCOV_EXCL_START */
2224 : decoding_error(path, f);
2225 : log_fatal(EINTERNAL, "Internal inconsistency: File size too big!\n");
2226 : os_abort();
2227 : /* LCOV_EXCL_STOP */
2228 : }
2229 :
2230 3903454 : ret = sgetb64(f, &v_mtime_sec);
2231 3903454 : if (ret < 0) {
2232 : /* LCOV_EXCL_START */
2233 : decoding_error(path, f);
2234 : os_abort();
2235 : /* LCOV_EXCL_STOP */
2236 : }
2237 :
2238 3903454 : ret = sgetb32(f, &v_mtime_nsec);
2239 3903454 : if (ret < 0) {
2240 : /* LCOV_EXCL_START */
2241 : decoding_error(path, f);
2242 : os_abort();
2243 : /* LCOV_EXCL_STOP */
2244 : }
2245 :
2246 : /* STAT_NSEC_INVALID is encoded as 0 */
2247 3903454 : if (v_mtime_nsec == 0)
2248 0 : v_mtime_nsec = STAT_NSEC_INVALID;
2249 : else
2250 3903454 : --v_mtime_nsec;
2251 :
2252 3903454 : ret = sgetb64(f, &v_inode);
2253 3903454 : if (ret < 0) {
2254 : /* LCOV_EXCL_START */
2255 : decoding_error(path, f);
2256 : os_abort();
2257 : /* LCOV_EXCL_STOP */
2258 : }
2259 :
2260 3903454 : ret = sgetbs(f, sub, sizeof(sub));
2261 3903454 : if (ret < 0) {
2262 : /* LCOV_EXCL_START */
2263 : decoding_error(path, f);
2264 : os_abort();
2265 : /* LCOV_EXCL_STOP */
2266 : }
2267 3903454 : if (!*sub) {
2268 : /* LCOV_EXCL_START */
2269 : decoding_error(path, f);
2270 : log_fatal(EINTERNAL, "Internal inconsistency: Null file!\n");
2271 : os_abort();
2272 : /* LCOV_EXCL_STOP */
2273 : }
2274 :
2275 : /* allocate the file */
2276 3903454 : file = file_alloc(state->block_size, sub, v_size, v_mtime_sec, v_mtime_nsec, v_inode, 0);
2277 :
2278 : /* insert the file in the file containers */
2279 3903454 : tommy_hashdyn_insert(&disk->inodeset, &file->nodeset, file, file_inode_hash(file->inode));
2280 3903454 : tommy_hashdyn_insert(&disk->pathset, &file->pathset, file, file_path_hash(file->sub));
2281 3903454 : tommy_hashdyn_insert(&disk->stampset, &file->stampset, file, file_stamp_hash(file->size, file->mtime_sec, file->mtime_nsec));
2282 3903454 : tommy_list_insert_tail(&disk->filelist, &file->nodelist, file);
2283 :
2284 : /* read all the blocks */
2285 3903454 : v_idx = 0;
2286 7828972 : while (v_idx < file->blockmax) {
2287 : block_off_t v_pos;
2288 : uint32_t v_count;
2289 :
2290 : /* get the "subcommand */
2291 3925518 : c = sgetc(f);
2292 :
2293 3925518 : ret = sgetb32(f, &v_pos);
2294 3925518 : if (ret < 0) {
2295 : /* LCOV_EXCL_START */
2296 : decoding_error(path, f);
2297 : os_abort();
2298 : /* LCOV_EXCL_STOP */
2299 : }
2300 :
2301 3925518 : ret = sgetb32(f, &v_count);
2302 3925518 : if (ret < 0) {
2303 : /* LCOV_EXCL_START */
2304 : decoding_error(path, f);
2305 : os_abort();
2306 : /* LCOV_EXCL_STOP */
2307 : }
2308 :
2309 3925518 : if (v_idx + v_count > file->blockmax) {
2310 : /* LCOV_EXCL_START */
2311 : decoding_error(path, f);
2312 : log_fatal(EINTERNAL, "Internal inconsistency: Block number out of range\n");
2313 : os_abort();
2314 : /* LCOV_EXCL_STOP */
2315 : }
2316 :
2317 3925518 : if (v_pos + v_count > blockmax) {
2318 : /* LCOV_EXCL_START */
2319 : decoding_error(path, f);
2320 : log_fatal(EINTERNAL, "Internal inconsistency: Block size %u/%u!\n", blockmax, v_pos + v_count);
2321 : os_abort();
2322 : /* LCOV_EXCL_START */
2323 : }
2324 :
2325 : /* fill the blocks in the run */
2326 : while (v_count) {
2327 : struct snapraid_block* block = fs_file2block_get(file, v_idx);
2328 :
2329 : switch (c) {
2330 : case 'b' :
2331 : block_state_set(block, BLOCK_STATE_BLK);
2332 : break;
2333 : case 'n' :
2334 : /* deprecated NEW blocks are converted to CHG ones */
2335 : block_state_set(block, BLOCK_STATE_CHG);
2336 : break;
2337 : case 'g' :
2338 : block_state_set(block, BLOCK_STATE_CHG);
2339 : break;
2340 : case 'p' :
2341 : block_state_set(block, BLOCK_STATE_REP);
2342 : break;
2343 : default :
2344 : /* LCOV_EXCL_START */
2345 : decoding_error(path, f);
2346 : log_fatal(ECONTENT, "Invalid block type!\n");
2347 : os_abort();
2348 : /* LCOV_EXCL_STOP */
2349 : }
2350 :
2351 : /* read the hash only for 'blk/chg/rep', and not for 'new' */
2352 9542162 : if (c != 'n') {
2353 9542162 : ret = sread(f, block->hash, BLOCK_HASH_SIZE);
2354 9542162 : if (ret < 0) {
2355 : /* LCOV_EXCL_START */
2356 : decoding_error(path, f);
2357 : os_abort();
2358 : /* LCOV_EXCL_STOP */
2359 : }
2360 : } else {
2361 : /* set the ZERO hash for deprecated NEW blocks */
2362 0 : hash_zero_set(block->hash);
2363 : }
2364 :
2365 : /*
2366 : * If we are disabling the copy optimization
2367 : * we want also to clear any already previously stored information
2368 : * in other sync commands
2369 : */
2370 9542162 : if (state->opt.force_nocopy && block_state_get(block) == BLOCK_STATE_REP) {
2371 : /* set the hash value to INVALID */
2372 1 : hash_invalid_set(block->hash);
2373 :
2374 : /* convert from REP to CHG block */
2375 1 : block_state_set(block, BLOCK_STATE_CHG);
2376 : }
2377 :
2378 : /* set the parity association */
2379 9542162 : fs_allocate(disk, v_pos, file, v_idx);
2380 :
2381 : /* go to the next block */
2382 9542162 : ++v_idx;
2383 9542162 : ++v_pos;
2384 9542162 : --v_count;
2385 : }
2386 : }
2387 :
2388 : /* stat */
2389 3903454 : ++count_file;
2390 133966 : } else if (c == 'i') {
2391 : /* "inf" command */
2392 : snapraid_info info;
2393 : uint32_t v_pos;
2394 : uint64_t v_oldest;
2395 :
2396 294 : ret = sgetb64(f, &v_oldest);
2397 294 : if (ret < 0) {
2398 : /* LCOV_EXCL_START */
2399 : decoding_error(path, f);
2400 : os_abort();
2401 : /* LCOV_EXCL_STOP */
2402 : }
2403 :
2404 294 : v_pos = 0;
2405 46885 : while (v_pos < blockmax) {
2406 : int bad;
2407 : int rehash;
2408 : int justsynced;
2409 : uint64_t t64;
2410 : uint32_t flag;
2411 : uint32_t v_count;
2412 :
2413 46591 : ret = sgetb32(f, &v_count);
2414 46591 : if (ret < 0) {
2415 : /* LCOV_EXCL_START */
2416 : decoding_error(path, f);
2417 : os_abort();
2418 : /* LCOV_EXCL_STOP */
2419 : }
2420 :
2421 46591 : if (v_pos + v_count > blockmax) {
2422 : /* LCOV_EXCL_START */
2423 : decoding_error(path, f);
2424 : log_fatal(EINTERNAL, "Internal inconsistency: Info size %u/%u!\n", blockmax, v_pos + v_count);
2425 : os_abort();
2426 : /* LCOV_EXCL_STOP */
2427 : }
2428 :
2429 46591 : ret = sgetb32(f, &flag);
2430 46591 : if (ret < 0) {
2431 : /* LCOV_EXCL_START */
2432 : decoding_error(path, f);
2433 : os_abort();
2434 : /* LCOV_EXCL_STOP */
2435 : }
2436 :
2437 : /* if there is an info */
2438 46591 : if ((flag & 1) != 0) {
2439 : /* read the time */
2440 46574 : ret = sgetb64(f, &t64);
2441 46574 : if (ret < 0) {
2442 : /* LCOV_EXCL_START */
2443 : decoding_error(path, f);
2444 : os_abort();
2445 : /* LCOV_EXCL_STOP */
2446 : }
2447 :
2448 : /* analyze the flags */
2449 46574 : bad = (flag & 2) != 0;
2450 46574 : rehash = (flag & 4) != 0;
2451 46574 : justsynced = (flag & 8) != 0;
2452 :
2453 46574 : if (bad)
2454 5268 : count_bad += v_count;
2455 46574 : if (rehash)
2456 14268 : count_rehash += v_count;
2457 46574 : if (justsynced)
2458 21404 : count_unscrubbed += v_count;
2459 :
2460 46574 : if (rehash && state->prevhash == HASH_UNDEFINED) {
2461 : /* LCOV_EXCL_START */
2462 : decoding_error(path, f);
2463 : log_fatal(EINTERNAL, "Internal inconsistency: Missing previous checksum!\n");
2464 : os_abort();
2465 : /* LCOV_EXCL_STOP */
2466 : }
2467 :
2468 46574 : info = info_make(t64 + v_oldest, bad, rehash, justsynced);
2469 :
2470 46574 : bucket_insert(&bucket_hash, t64 + v_oldest, v_count, justsynced);
2471 : } else {
2472 17 : info = 0;
2473 : }
2474 :
2475 1728088 : while (v_count) {
2476 : /* insert the info in the array */
2477 1681497 : info_set(&state->infoarr, v_pos, info);
2478 :
2479 : /* ensure that an info is present only for used positions */
2480 1681497 : if (fs_info_is_required(state, v_pos)) {
2481 1627615 : if (!info) {
2482 : /* LCOV_EXCL_START */
2483 : decoding_error(path, f);
2484 : log_fatal(EINTERNAL, "Internal inconsistency: Missing info!\n");
2485 : os_abort();
2486 : /* LCOV_EXCL_STOP */
2487 : }
2488 : } else {
2489 : /*
2490 : * Extra info are accepted for backward compatibility
2491 : * they are discarded at the first write
2492 : */
2493 : }
2494 :
2495 1681497 : if (fs_is_block_unsynced(state, v_pos))
2496 120017 : ++count_unsynced;
2497 :
2498 : /* go to next block */
2499 1681497 : ++v_pos;
2500 1681497 : --v_count;
2501 : }
2502 : }
2503 133672 : } else if (c == 'h') {
2504 : /* hole */
2505 : uint32_t v_pos;
2506 : struct snapraid_disk* disk;
2507 : uint32_t mapping;
2508 :
2509 1577 : ret = sgetb32(f, &mapping);
2510 1577 : if (ret < 0 || mapping >= mapping_max) {
2511 : /* LCOV_EXCL_START */
2512 : decoding_error(path, f);
2513 : log_fatal(EINTERNAL, "Internal inconsistency: Hole mapping index out of range\n");
2514 : os_abort();
2515 : /* LCOV_EXCL_STOP */
2516 : }
2517 1577 : disk = tommy_array_get(&disk_mapping, mapping);
2518 :
2519 1577 : v_pos = 0;
2520 3252 : while (v_pos < blockmax) {
2521 : uint32_t v_idx;
2522 : uint32_t v_count;
2523 : struct snapraid_file* deleted;
2524 :
2525 1675 : ret = sgetb32(f, &v_count);
2526 1675 : if (ret < 0) {
2527 : /* LCOV_EXCL_START */
2528 : decoding_error(path, f);
2529 : os_abort();
2530 : /* LCOV_EXCL_STOP */
2531 : }
2532 :
2533 1675 : if (v_pos + v_count > blockmax) {
2534 : /* LCOV_EXCL_START */
2535 : decoding_error(path, f);
2536 : log_fatal(EINTERNAL, "Internal inconsistency: Hole size %u/%u!\n", blockmax, v_pos + v_count);
2537 : os_abort();
2538 : /* LCOV_EXCL_STOP */
2539 : }
2540 :
2541 : /* get the sub-command */
2542 1675 : c = sgetc(f);
2543 :
2544 1675 : switch (c) {
2545 51 : case 'o' :
2546 : /* if it's a run of deleted blocks */
2547 :
2548 : /* allocate a fake deleted file */
2549 51 : deleted = file_alloc(state->block_size, "<deleted>", v_count * (data_off_t)state->block_size, 0, 0, 0, 0);
2550 :
2551 : /* mark the file as deleted */
2552 51 : file_flag_set(deleted, FILE_IS_DELETED);
2553 :
2554 : /* insert it in the list of deleted files */
2555 51 : tommy_list_insert_tail(&disk->deletedlist, &deleted->nodelist, deleted);
2556 :
2557 : /* process all blocks */
2558 51 : v_idx = 0;
2559 29990 : while (v_count) {
2560 29939 : struct snapraid_block* block = fs_file2block_get(deleted, v_idx);
2561 :
2562 : /* set the block as deleted */
2563 29939 : block_state_set(block, BLOCK_STATE_DELETED);
2564 :
2565 : /* read the hash */
2566 29939 : ret = sread(f, block->hash, BLOCK_HASH_SIZE);
2567 29939 : if (ret < 0) {
2568 : /* LCOV_EXCL_START */
2569 : decoding_error(path, f);
2570 : os_abort();
2571 : /* LCOV_EXCL_STOP */
2572 : }
2573 :
2574 : /* insert the block in the block array */
2575 29939 : fs_allocate(disk, v_pos, deleted, v_idx);
2576 :
2577 : /* go to next block */
2578 29939 : ++v_pos;
2579 29939 : ++v_idx;
2580 29939 : --v_count;
2581 : }
2582 51 : break;
2583 1624 : case 'O' :
2584 : /* go to the next run */
2585 1624 : v_pos += v_count;
2586 1624 : break;
2587 0 : default :
2588 : /* LCOV_EXCL_START */
2589 : decoding_error(path, f);
2590 : log_fatal(ECONTENT, "Invalid hole type!\n");
2591 : os_abort();
2592 : /* LCOV_EXCL_STOP */
2593 : }
2594 : }
2595 132095 : } else if (c == 'd') {
2596 : /* dealloc */
2597 : uint32_t v_count;
2598 : uint32_t v_pos;
2599 : struct snapraid_disk* disk;
2600 : uint32_t mapping;
2601 :
2602 28 : ret = sgetb32(f, &mapping);
2603 28 : if (ret < 0 || mapping >= mapping_max) {
2604 : /* LCOV_EXCL_START */
2605 : decoding_error(path, f);
2606 : log_fatal(EINTERNAL, "Internal inconsistency: Dealloc mapping index out of range\n");
2607 : os_abort();
2608 : /* LCOV_EXCL_STOP */
2609 : }
2610 28 : disk = tommy_array_get(&disk_mapping, mapping);
2611 :
2612 28 : ret = sgetb32(f, &v_count);
2613 28 : if (ret < 0) {
2614 : /* LCOV_EXCL_START */
2615 : decoding_error(path, f);
2616 : os_abort();
2617 : /* LCOV_EXCL_STOP */
2618 : }
2619 :
2620 28 : log_tag("content_info:dealloc:%s:%u\n", esc_tag(disk->name, esc_buffer), v_count);
2621 :
2622 29334 : for (v_pos = 0; v_pos < v_count; ++v_pos) {
2623 : char sub[PATH_MAX];
2624 : uint64_t v_size;
2625 : uint64_t v_mtime_sec;
2626 : uint32_t v_mtime_nsec;
2627 :
2628 29306 : ret = sgetbs(f, sub, sizeof(sub));
2629 29306 : if (ret < 0) {
2630 : /* LCOV_EXCL_START */
2631 : decoding_error(path, f);
2632 : os_abort();
2633 : /* LCOV_EXCL_STOP */
2634 : }
2635 29306 : if (!*sub) {
2636 : /* LCOV_EXCL_START */
2637 : decoding_error(path, f);
2638 : log_fatal(EINTERNAL, "Internal inconsistency: Null dealloc!\n");
2639 : os_abort();
2640 : /* LCOV_EXCL_STOP */
2641 : }
2642 :
2643 29306 : ret = sgetb64(f, &v_size);
2644 29306 : if (ret < 0) {
2645 : /* LCOV_EXCL_START */
2646 : decoding_error(path, f);
2647 : os_abort();
2648 : /* LCOV_EXCL_STOP */
2649 : }
2650 :
2651 29306 : ret = sgetb64(f, &v_mtime_sec);
2652 29306 : if (ret < 0) {
2653 : /* LCOV_EXCL_START */
2654 : decoding_error(path, f);
2655 : os_abort();
2656 : /* LCOV_EXCL_STOP */
2657 : }
2658 :
2659 29306 : ret = sgetb32(f, &v_mtime_nsec);
2660 29306 : if (ret < 0) {
2661 : /* LCOV_EXCL_START */
2662 : decoding_error(path, f);
2663 : os_abort();
2664 : /* LCOV_EXCL_STOP */
2665 : }
2666 :
2667 : /* STAT_NSEC_INVALID is encoded as 0 */
2668 29306 : if (v_mtime_nsec == 0)
2669 0 : v_mtime_nsec = STAT_NSEC_INVALID;
2670 : else
2671 29306 : --v_mtime_nsec;
2672 :
2673 : /* allocate the file */
2674 29306 : struct snapraid_dealloc* dealloc = dealloc_alloc(state->block_size, sub, v_size, v_mtime_sec, v_mtime_nsec);
2675 :
2676 29306 : log_tag("content_info:dealloc_entry:%s:%s:%" PRIu64 ":%" PRIu64 ":%u\n", esc_tag(disk->name, esc_buffer), esc_tag(dealloc->sub, esc_buffer1), dealloc->size, dealloc->mtime_sec, dealloc->mtime_nsec);
2677 :
2678 : /* read all hashes */
2679 90342 : for (block_off_t k = 0; k < dealloc->blockmax; ++k) {
2680 61036 : unsigned char* hash = dealloc->blockhash + k * BLOCK_HASH_SIZE;
2681 :
2682 61036 : ret = sread(f, hash, BLOCK_HASH_SIZE);
2683 61036 : if (ret < 0) {
2684 : /* LCOV_EXCL_START */
2685 : decoding_error(path, f);
2686 : os_abort();
2687 : /* LCOV_EXCL_STOP */
2688 : }
2689 : }
2690 :
2691 : /* insert the dealloc in the dealloc containers */
2692 29306 : tommy_list_insert_tail(&disk->dealloclist, &dealloc->nodelist, dealloc);
2693 : }
2694 132067 : } else if (c == 's') {
2695 : /* symlink */
2696 : char sub[PATH_MAX];
2697 : char linkto[PATH_MAX];
2698 : struct snapraid_link* slink;
2699 : struct snapraid_disk* disk;
2700 : uint32_t mapping;
2701 :
2702 125957 : ret = sgetb32(f, &mapping);
2703 125957 : if (ret < 0 || mapping >= mapping_max) {
2704 : /* LCOV_EXCL_START */
2705 : decoding_error(path, f);
2706 : log_fatal(EINTERNAL, "Internal inconsistency: Symlink mapping index out of range\n");
2707 : os_abort();
2708 : /* LCOV_EXCL_STOP */
2709 : }
2710 125957 : disk = tommy_array_get(&disk_mapping, mapping);
2711 :
2712 125957 : ret = sgetbs(f, sub, sizeof(sub));
2713 125957 : if (ret < 0) {
2714 : /* LCOV_EXCL_START */
2715 : decoding_error(path, f);
2716 : os_abort();
2717 : /* LCOV_EXCL_STOP */
2718 : }
2719 :
2720 125957 : if (!*sub) {
2721 : /* LCOV_EXCL_START */
2722 : decoding_error(path, f);
2723 : log_fatal(EINTERNAL, "Internal inconsistency: Null symlink!\n");
2724 : os_abort();
2725 : /* LCOV_EXCL_STOP */
2726 : }
2727 :
2728 125957 : ret = sgetbs(f, linkto, sizeof(linkto));
2729 125957 : if (ret < 0) {
2730 : /* LCOV_EXCL_START */
2731 : decoding_error(path, f);
2732 : os_abort();
2733 : /* LCOV_EXCL_STOP */
2734 : }
2735 :
2736 : /* allocate the link as symbolic link */
2737 125957 : slink = link_alloc(sub, linkto, FILE_IS_SYMLINK);
2738 :
2739 : /* insert the link in the link containers */
2740 125957 : tommy_hashdyn_insert(&disk->linkset, &slink->nodeset, slink, link_name_hash(slink->sub));
2741 125957 : tommy_list_insert_tail(&disk->linklist, &slink->nodelist, slink);
2742 :
2743 : /* stat */
2744 125957 : ++count_symlink;
2745 6110 : } else if (c == 'a') {
2746 : /* hardlink */
2747 : char sub[PATH_MAX];
2748 : char linkto[PATH_MAX];
2749 : struct snapraid_link* slink;
2750 : struct snapraid_disk* disk;
2751 : uint32_t mapping;
2752 :
2753 651 : ret = sgetb32(f, &mapping);
2754 651 : if (ret < 0 || mapping >= mapping_max) {
2755 : /* LCOV_EXCL_START */
2756 : decoding_error(path, f);
2757 : log_fatal(EINTERNAL, "Internal inconsistency: Hardlink mapping index out of range!\n");
2758 : os_abort();
2759 : /* LCOV_EXCL_STOP */
2760 : }
2761 651 : disk = tommy_array_get(&disk_mapping, mapping);
2762 :
2763 651 : ret = sgetbs(f, sub, sizeof(sub));
2764 651 : if (ret < 0) {
2765 : /* LCOV_EXCL_START */
2766 : decoding_error(path, f);
2767 : os_abort();
2768 : /* LCOV_EXCL_STOP */
2769 : }
2770 :
2771 651 : if (!*sub) {
2772 : /* LCOV_EXCL_START */
2773 : decoding_error(path, f);
2774 : log_fatal(EINTERNAL, "Internal inconsistency: Null hardlink!\n");
2775 : os_abort();
2776 : /* LCOV_EXCL_STOP */
2777 : }
2778 :
2779 651 : ret = sgetbs(f, linkto, sizeof(linkto));
2780 651 : if (ret < 0) {
2781 : /* LCOV_EXCL_START */
2782 : decoding_error(path, f);
2783 : os_abort();
2784 : /* LCOV_EXCL_STOP */
2785 : }
2786 :
2787 651 : if (!*linkto) {
2788 : /* LCOV_EXCL_START */
2789 : decoding_error(path, f);
2790 : log_fatal(EINTERNAL, "Internal inconsistency: Empty hardlink '%s'!\n", sub);
2791 : os_abort();
2792 : /* LCOV_EXCL_STOP */
2793 : }
2794 :
2795 : /* allocate the link as hard link */
2796 651 : slink = link_alloc(sub, linkto, FILE_IS_HARDLINK);
2797 :
2798 : /* insert the link in the link containers */
2799 651 : tommy_hashdyn_insert(&disk->linkset, &slink->nodeset, slink, link_name_hash(slink->sub));
2800 651 : tommy_list_insert_tail(&disk->linklist, &slink->nodelist, slink);
2801 :
2802 : /* stat */
2803 651 : ++count_hardlink;
2804 5459 : } else if (c == 'r') {
2805 : /* dir */
2806 : char sub[PATH_MAX];
2807 : struct snapraid_dir* dir;
2808 : struct snapraid_disk* disk;
2809 : uint32_t mapping;
2810 :
2811 862 : ret = sgetb32(f, &mapping);
2812 862 : if (ret < 0 || mapping >= mapping_max) {
2813 : /* LCOV_EXCL_START */
2814 : decoding_error(path, f);
2815 : log_fatal(EINTERNAL, "Internal inconsistency: Dir mapping index out of range!\n");
2816 : os_abort();
2817 : /* LCOV_EXCL_STOP */
2818 : }
2819 862 : disk = tommy_array_get(&disk_mapping, mapping);
2820 :
2821 862 : ret = sgetbs(f, sub, sizeof(sub));
2822 862 : if (ret < 0) {
2823 : /* LCOV_EXCL_START */
2824 : decoding_error(path, f);
2825 : os_abort();
2826 : /* LCOV_EXCL_STOP */
2827 : }
2828 :
2829 862 : if (!*sub) {
2830 : /* LCOV_EXCL_START */
2831 : decoding_error(path, f);
2832 : log_fatal(EINTERNAL, "Internal inconsistency: Null dir!\n");
2833 : os_abort();
2834 : /* LCOV_EXCL_STOP */
2835 : }
2836 :
2837 : /* allocate the dir */
2838 862 : dir = dir_alloc(sub);
2839 :
2840 : /* insert the dir in the dir containers */
2841 862 : tommy_hashdyn_insert(&disk->dirset, &dir->nodeset, dir, dir_name_hash(dir->sub));
2842 862 : tommy_list_insert_tail(&disk->dirlist, &dir->nodelist, dir);
2843 :
2844 : /* stat */
2845 862 : ++count_dir;
2846 4597 : } else if (c == 'c') {
2847 : /* get the subcommand */
2848 295 : c = sgetc(f);
2849 :
2850 295 : switch (c) {
2851 158 : case 'u' :
2852 158 : state->hash = HASH_MURMUR3;
2853 158 : break;
2854 137 : case 'k' :
2855 137 : state->hash = HASH_SPOOKY2;
2856 137 : break;
2857 0 : case 'm' :
2858 0 : state->hash = HASH_METRO;
2859 0 : break;
2860 0 : default :
2861 : /* LCOV_EXCL_START */
2862 : decoding_error(path, f);
2863 : log_fatal(ECONTENT, "Invalid checksum!\n");
2864 : os_abort();
2865 : /* LCOV_EXCL_STOP */
2866 : }
2867 :
2868 : /* read the seed */
2869 295 : ret = sread(f, state->hashseed, HASH_MAX);
2870 295 : if (ret < 0) {
2871 : /* LCOV_EXCL_START */
2872 : decoding_error(path, f);
2873 : os_abort();
2874 : /* LCOV_EXCL_STOP */
2875 : }
2876 4302 : } else if (c == 'C') {
2877 : /* get the sub-command */
2878 4 : c = sgetc(f);
2879 :
2880 4 : switch (c) {
2881 4 : case 'u' :
2882 4 : state->prevhash = HASH_MURMUR3;
2883 4 : break;
2884 0 : case 'k' :
2885 0 : state->prevhash = HASH_SPOOKY2;
2886 0 : break;
2887 0 : case 'm' :
2888 0 : state->prevhash = HASH_METRO;
2889 0 : break;
2890 0 : default :
2891 : /* LCOV_EXCL_START */
2892 : decoding_error(path, f);
2893 : log_fatal(ECONTENT, "Invalid checksum!\n");
2894 : os_abort();
2895 : /* LCOV_EXCL_STOP */
2896 : }
2897 :
2898 : /* read the seed */
2899 4 : ret = sread(f, state->prevhashseed, HASH_MAX);
2900 4 : if (ret < 0) {
2901 : /* LCOV_EXCL_START */
2902 : decoding_error(path, f);
2903 : os_abort();
2904 : /* LCOV_EXCL_STOP */
2905 : }
2906 4298 : } else if (c == 'z') {
2907 : uint32_t block_size;
2908 :
2909 295 : ret = sgetb32(f, &block_size);
2910 295 : if (ret < 0) {
2911 : /* LCOV_EXCL_START */
2912 : decoding_error(path, f);
2913 : os_abort();
2914 : /* LCOV_EXCL_STOP */
2915 : }
2916 :
2917 295 : if (block_size == 0) {
2918 : /* LCOV_EXCL_START */
2919 : decoding_error(path, f);
2920 : log_fatal(ECONTENT, "Zero 'blocksize' specification in the content file!\n");
2921 : exit(EXIT_FAILURE);
2922 : /* LCOV_EXCL_STOP */
2923 : }
2924 :
2925 : /* without configuration, auto assign the block size */
2926 295 : if (state->no_conf) {
2927 1 : state->block_size = block_size;
2928 : }
2929 :
2930 295 : if (block_size != state->block_size) {
2931 : /* LCOV_EXCL_START */
2932 : decoding_error(path, f);
2933 : log_fatal(EUSER, "Mismatching 'blocksize' specification in the content file!\n");
2934 : log_fatal(EUSER, "Please restore the 'blocksize' value in the configuration file to '%u'\n", block_size / KIBI);
2935 : exit(EXIT_FAILURE);
2936 : /* LCOV_EXCL_STOP */
2937 : }
2938 4003 : } else if (c == 'y') {
2939 : uint32_t hash_size;
2940 :
2941 295 : ret = sgetb32(f, &hash_size);
2942 295 : if (ret < 0) {
2943 : /* LCOV_EXCL_START */
2944 : decoding_error(path, f);
2945 : os_abort();
2946 : /* LCOV_EXCL_STOP */
2947 : }
2948 :
2949 295 : if (hash_size < 2 || hash_size > HASH_MAX) {
2950 : /* LCOV_EXCL_START */
2951 : decoding_error(path, f);
2952 : log_fatal(ECONTENT, "Invalid 'hashsize' specification in the content file!\n");
2953 : exit(EXIT_FAILURE);
2954 : /* LCOV_EXCL_STOP */
2955 : }
2956 :
2957 : /* without configuration, auto assign the block size */
2958 295 : if (state->no_conf) {
2959 1 : BLOCK_HASH_SIZE = hash_size;
2960 : }
2961 :
2962 295 : if ((int)hash_size != BLOCK_HASH_SIZE) {
2963 : /* LCOV_EXCL_START */
2964 : decoding_error(path, f);
2965 : log_fatal(EUSER, "Mismatching 'hashsize' specification in the content file!\n");
2966 : log_fatal(EUSER, "Please restore the 'hashsize' value in the configuration file to '%u'\n", hash_size);
2967 : exit(EXIT_FAILURE);
2968 : /* LCOV_EXCL_STOP */
2969 : }
2970 3708 : } else if (c == 'x') {
2971 295 : ret = sgetb32(f, &blockmax);
2972 295 : if (ret < 0) {
2973 : /* LCOV_EXCL_START */
2974 : decoding_error(path, f);
2975 : os_abort();
2976 : /* LCOV_EXCL_STOP */
2977 : }
2978 3413 : } else if (c == 'm' || c == 'M') {
2979 : struct snapraid_map* map;
2980 : char uuid[UUID_MAX];
2981 : uint32_t v_pos;
2982 : uint32_t v_total_blocks;
2983 : uint32_t v_free_blocks;
2984 : struct snapraid_disk* disk;
2985 :
2986 1583 : ret = sgetbs(f, buffer, sizeof(buffer));
2987 1583 : if (ret < 0) {
2988 : /* LCOV_EXCL_START */
2989 : decoding_error(path, f);
2990 : os_abort();
2991 : /* LCOV_EXCL_STOP */
2992 : }
2993 :
2994 1583 : ret = sgetb32(f, &v_pos);
2995 1583 : if (ret < 0) {
2996 : /* LCOV_EXCL_START */
2997 : decoding_error(path, f);
2998 : os_abort();
2999 : /* LCOV_EXCL_STOP */
3000 : }
3001 :
3002 : /* from SnapRAID 7.0 the 'M' command includes the free space */
3003 1583 : if (c == 'M') {
3004 1583 : ret = sgetb32(f, &v_total_blocks);
3005 1583 : if (ret < 0) {
3006 : /* LCOV_EXCL_START */
3007 : decoding_error(path, f);
3008 : os_abort();
3009 : /* LCOV_EXCL_STOP */
3010 : }
3011 :
3012 1583 : ret = sgetb32(f, &v_free_blocks);
3013 1583 : if (ret < 0) {
3014 : /* LCOV_EXCL_START */
3015 : decoding_error(path, f);
3016 : os_abort();
3017 : /* LCOV_EXCL_STOP */
3018 : }
3019 :
3020 1583 : log_tag("content_data:%s:%" PRIi64 ":%" PRIi64 "\n",
3021 : esc_tag(buffer, esc_buffer),
3022 1583 : v_total_blocks * (uint64_t)state->block_size,
3023 1583 : v_free_blocks * (uint64_t)state->block_size);
3024 : } else {
3025 0 : v_total_blocks = 0;
3026 0 : v_free_blocks = 0;
3027 : }
3028 :
3029 : /* read the uuid */
3030 1583 : ret = sgetbs(f, uuid, sizeof(uuid));
3031 1583 : if (ret < 0) {
3032 : /* LCOV_EXCL_START */
3033 : decoding_error(path, f);
3034 : os_abort();
3035 : /* LCOV_EXCL_STOP */
3036 : }
3037 :
3038 1583 : log_tag("content_data_split:%s:%s\n",
3039 : esc_tag(buffer, esc_buffer),
3040 : uuid);
3041 :
3042 : /* find the disk */
3043 1583 : disk = find_disk_by_name(state, buffer);
3044 1583 : if (!disk) {
3045 : /* search by UUID if renamed */
3046 2 : disk = find_disk_by_uuid(state, uuid);
3047 2 : if (disk) {
3048 2 : log_fatal(EUSER, "WARNING! Renaming disk '%s' to '%s'\n", buffer, disk->name);
3049 :
3050 : /* write the new state with the new name */
3051 2 : state->need_write = 1;
3052 : }
3053 : }
3054 1583 : if (!disk) {
3055 : /* LCOV_EXCL_START */
3056 : decoding_error(path, f);
3057 : log_fatal(EUSER, "Disk '%s' with uuid '%s' not present in the configuration file!\n", buffer, uuid);
3058 : log_fatal(EUSER, "If you have removed it from the configuration file, please restore it\n");
3059 : /* if it's a command without UUID, it cannot autorename using UUID */
3060 : if (state->opt.skip_disk_access)
3061 : log_fatal(EUSER, "If you have renamed it, run 'sync' to update the new name\n");
3062 : exit(EXIT_FAILURE);
3063 : /* LCOV_EXCL_STOP */
3064 : }
3065 :
3066 1583 : map = map_alloc(disk->name, v_pos, v_total_blocks, v_free_blocks, uuid);
3067 :
3068 1583 : tommy_list_insert_tail(&state->maplist, &map->node, map);
3069 :
3070 : /* insert in the mapping vector */
3071 1583 : tommy_array_grow(&disk_mapping, mapping_max + 1);
3072 1583 : tommy_array_set(&disk_mapping, mapping_max, disk);
3073 1583 : ++mapping_max;
3074 1830 : } else if (c == 'P') {
3075 : /*
3076 : * From SnapRAID 7.0 the 'P' command includes the free space
3077 : * from SnapRAID 11.0 the 'P' command is deprecated by 'Q'
3078 : */
3079 : char v_uuid[UUID_MAX];
3080 : uint32_t v_level;
3081 : uint32_t v_total_blocks;
3082 : uint32_t v_free_blocks;
3083 :
3084 0 : ret = sgetb32(f, &v_level);
3085 0 : if (ret < 0) {
3086 : /* LCOV_EXCL_START */
3087 : decoding_error(path, f);
3088 : os_abort();
3089 : /* LCOV_EXCL_STOP */
3090 : }
3091 :
3092 0 : ret = sgetb32(f, &v_total_blocks);
3093 0 : if (ret < 0) {
3094 : /* LCOV_EXCL_START */
3095 : decoding_error(path, f);
3096 : os_abort();
3097 : /* LCOV_EXCL_STOP */
3098 : }
3099 :
3100 0 : ret = sgetb32(f, &v_free_blocks);
3101 0 : if (ret < 0) {
3102 : /* LCOV_EXCL_START */
3103 : decoding_error(path, f);
3104 : os_abort();
3105 : /* LCOV_EXCL_STOP */
3106 : }
3107 :
3108 0 : ret = sgetbs(f, v_uuid, sizeof(v_uuid));
3109 0 : if (ret < 0) {
3110 : /* LCOV_EXCL_START */
3111 : decoding_error(path, f);
3112 : os_abort();
3113 : /* LCOV_EXCL_STOP */
3114 : }
3115 :
3116 0 : if (v_level >= LEV_MAX) {
3117 : /* LCOV_EXCL_START */
3118 : decoding_error(path, f);
3119 : log_fatal(ECONTENT, "Invalid parity level '%u' in the configuration file!\n", v_level);
3120 : exit(EXIT_FAILURE);
3121 : /* LCOV_EXCL_STOP */
3122 : }
3123 :
3124 0 : log_tag("content_parity:%s:%" PRIi64 ":%" PRIi64 "\n",
3125 : lev_config_name(v_level),
3126 0 : v_total_blocks * (uint64_t)state->block_size,
3127 0 : v_free_blocks * (uint64_t)state->block_size);
3128 :
3129 : /* auto configure if configuration is missing */
3130 0 : if (state->no_conf) {
3131 0 : if (v_level >= state->level)
3132 0 : state->level = v_level + 1;
3133 : }
3134 :
3135 : /* if we use this parity entry */
3136 0 : if (v_level < state->level) {
3137 : /* if the configuration has more splits, keep them */
3138 0 : if (state->parity[v_level].split_mac < 1)
3139 0 : state->parity[v_level].split_mac = 1;
3140 : /* set the parity info */
3141 0 : pathcpy(state->parity[v_level].split_map[0].uuid, sizeof(state->parity[v_level].split_map[0].uuid), v_uuid);
3142 0 : state->parity[v_level].total_blocks = v_total_blocks;
3143 0 : state->parity[v_level].free_blocks = v_free_blocks;
3144 :
3145 0 : log_tag("content_parity_split:%s:%s:%s:%" PRIi64 "\n",
3146 : lev_config_name(v_level), /* P command always has a single split */
3147 0 : state->parity[v_level].split_map[0].uuid,
3148 0 : esc_tag(state->parity[v_level].split_map[0].path, esc_buffer),
3149 : state->parity[v_level].split_map[0].size);
3150 : }
3151 1830 : } else if (c == 'Q') {
3152 : /* from SnapRAID 11.0 the 'Q' command include size info and multi file support */
3153 : uint32_t v_level;
3154 : uint32_t v_total_blocks;
3155 : uint32_t v_free_blocks;
3156 : uint32_t v_split_mac;
3157 : unsigned s;
3158 :
3159 1535 : ret = sgetb32(f, &v_level);
3160 1535 : if (ret < 0) {
3161 : /* LCOV_EXCL_START */
3162 : decoding_error(path, f);
3163 : os_abort();
3164 : /* LCOV_EXCL_STOP */
3165 : }
3166 :
3167 1535 : ret = sgetb32(f, &v_total_blocks);
3168 1535 : if (ret < 0) {
3169 : /* LCOV_EXCL_START */
3170 : decoding_error(path, f);
3171 : os_abort();
3172 : /* LCOV_EXCL_STOP */
3173 : }
3174 :
3175 1535 : ret = sgetb32(f, &v_free_blocks);
3176 1535 : if (ret < 0) {
3177 : /* LCOV_EXCL_START */
3178 : decoding_error(path, f);
3179 : os_abort();
3180 : /* LCOV_EXCL_STOP */
3181 : }
3182 :
3183 1535 : ret = sgetb32(f, &v_split_mac);
3184 1535 : if (ret < 0) {
3185 : /* LCOV_EXCL_START */
3186 : decoding_error(path, f);
3187 : os_abort();
3188 : /* LCOV_EXCL_STOP */
3189 : }
3190 :
3191 1535 : if (v_level >= LEV_MAX) {
3192 : /* LCOV_EXCL_START */
3193 : decoding_error(path, f);
3194 : log_fatal(ECONTENT, "Invalid parity level '%u' in the configuration file!\n", v_level);
3195 : exit(EXIT_FAILURE);
3196 : /* LCOV_EXCL_STOP */
3197 : }
3198 :
3199 : /* auto configure if configuration is missing */
3200 1535 : if (state->no_conf) {
3201 6 : if (v_level >= state->level)
3202 5 : state->level = v_level + 1;
3203 6 : if (state->parity[v_level].split_mac < v_split_mac)
3204 6 : state->parity[v_level].split_mac = v_split_mac;
3205 : }
3206 :
3207 : /* if we use this parity entry */
3208 1535 : if (v_level < state->level) {
3209 : /* set the parity info */
3210 1267 : state->parity[v_level].total_blocks = v_total_blocks;
3211 1267 : state->parity[v_level].free_blocks = v_free_blocks;
3212 : }
3213 :
3214 1535 : log_tag("content_parity:%s:%" PRIi64 ":%" PRIi64 "\n",
3215 : lev_config_name(v_level),
3216 1535 : v_total_blocks * (uint64_t)state->block_size,
3217 1535 : v_free_blocks * (uint64_t)state->block_size);
3218 :
3219 7609 : for (s = 0; s < v_split_mac; ++s) {
3220 : char v_path[PATH_MAX];
3221 : char v_uuid[UUID_MAX];
3222 : uint64_t v_size;
3223 :
3224 6074 : ret = sgetbs(f, v_path, sizeof(v_path));
3225 6074 : if (ret < 0) {
3226 : /* LCOV_EXCL_START */
3227 : decoding_error(path, f);
3228 : os_abort();
3229 : /* LCOV_EXCL_STOP */
3230 : }
3231 :
3232 6074 : ret = sgetbs(f, v_uuid, sizeof(v_uuid));
3233 6074 : if (ret < 0) {
3234 : /* LCOV_EXCL_START */
3235 : decoding_error(path, f);
3236 : os_abort();
3237 : /* LCOV_EXCL_STOP */
3238 : }
3239 :
3240 6074 : ret = sgetb64(f, &v_size);
3241 6074 : if (ret < 0) {
3242 : /* LCOV_EXCL_START */
3243 : decoding_error(path, f);
3244 : os_abort();
3245 : /* LCOV_EXCL_STOP */
3246 : }
3247 :
3248 : /* if we use this parity entry */
3249 6074 : if (v_level < state->level) {
3250 : /* if this split was removed from the configuration */
3251 5002 : if (s >= state->parity[v_level].split_mac) {
3252 : /* if the file is used, we really need it */
3253 0 : if (v_size != 0) {
3254 : /* LCOV_EXCL_START */
3255 : decoding_error(path, f);
3256 : log_fatal(EUSER, "Parity '%s' misses used file '%u'!\n", lev_config_name(v_level), s);
3257 : log_fatal(EUSER, "If you have removed it from the configuration file, please restore it\n");
3258 : exit(EXIT_FAILURE);
3259 : /* LCOV_EXCL_STOP */
3260 : }
3261 :
3262 : /* otherwise we can drop it */
3263 0 : log_fatal(EUSER, "WARNING! Dropping from '%s' unused split '%u'\n", lev_config_name(v_level), s);
3264 : } else {
3265 : char parity_name[64];
3266 :
3267 : /* we copy the path only if without configuration file */
3268 5002 : if (state->no_conf)
3269 24 : pathcpy(state->parity[v_level].split_map[s].path, sizeof(state->parity[v_level].split_map[s].path), v_path);
3270 :
3271 : /* set the split info */
3272 5002 : pathcpy(state->parity[v_level].split_map[s].uuid, sizeof(state->parity[v_level].split_map[s].uuid), v_uuid);
3273 5002 : state->parity[v_level].split_map[s].size = v_size;
3274 :
3275 5002 : if (s == 0)
3276 1267 : pathcpy(parity_name, sizeof(parity_name), lev_config_name(v_level));
3277 : else
3278 3735 : pathprint(parity_name, sizeof(parity_name), "%s/%u", lev_config_name(v_level), s);
3279 10004 : log_tag("content_parity_split:%s:%s:%s:%" PRIi64 "\n",
3280 : parity_name,
3281 5002 : state->parity[v_level].split_map[s].uuid,
3282 5002 : esc_tag(state->parity[v_level].split_map[s].path, esc_buffer),
3283 : state->parity[v_level].split_map[s].size);
3284 : }
3285 : }
3286 : }
3287 295 : } else if (c == 'N') {
3288 : uint32_t crc_stored;
3289 : uint32_t crc_computed;
3290 :
3291 : /* get the crc before reading it from the file */
3292 294 : crc_computed = scrc(f);
3293 :
3294 294 : ret = sgetble32(f, &crc_stored);
3295 294 : if (ret < 0) {
3296 : /* LCOV_EXCL_START */
3297 : /* here don't call decoding_error() because it's too late to get the crc */
3298 : log_fatal(errno, "Error reading the CRC in '%s' at offset %" PRIi64 "\n", path, stell(f));
3299 : log_fatal(errno, "This content file is damaged! Use an alternate copy.\n");
3300 : exit(EXIT_FAILURE);
3301 : /* LCOV_EXCL_STOP */
3302 : }
3303 :
3304 294 : if (crc_stored != crc_computed) {
3305 : /* LCOV_EXCL_START */
3306 : /* here don't call decoding_error() because it's too late to get the crc */
3307 : log_fatal(ECONTENT, "CRC mismatch in '%s'\n", path);
3308 : log_fatal(ECONTENT, "The content file is damaged! Please use an alternate copy.\n");
3309 : exit(EXIT_FAILURE);
3310 : /* LCOV_EXCL_STOP */
3311 : }
3312 :
3313 294 : crc_checked = 1;
3314 : } else {
3315 : /* LCOV_EXCL_START */
3316 : decoding_error(path, f);
3317 : log_fatal(ECONTENT, "Invalid command '%c'!\n", (char)c);
3318 : os_abort();
3319 : /* LCOV_EXCL_STOP */
3320 : }
3321 : }
3322 :
3323 : /* mark all disks as multi threads */
3324 1987 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
3325 1693 : struct snapraid_disk* disk = i->data;
3326 1693 : disk->single_thread = 0;
3327 : }
3328 :
3329 294 : tommy_array_done(&disk_mapping);
3330 :
3331 294 : if (serror(f)) {
3332 : /* LCOV_EXCL_START */
3333 : log_fatal(errno, "Error reading the content file '%s' at offset %" PRIi64 "\n", path, stell(f));
3334 : exit(EXIT_FAILURE);
3335 : /* LCOV_EXCL_STOP */
3336 : }
3337 :
3338 294 : if (!crc_checked) {
3339 : /* LCOV_EXCL_START */
3340 : log_fatal(ECONTENT, "Reached the end of '%s' without finding the expected CRC\n", path);
3341 : log_fatal(ECONTENT, "This content file is truncated or damaged! Use an alternate copy.\n");
3342 : exit(EXIT_FAILURE);
3343 : /* LCOV_EXCL_STOP */
3344 : }
3345 :
3346 : /* check the file-system on all disks */
3347 294 : state_fscheck(state, "after read");
3348 :
3349 : /* check that the stored parity size matches the loaded state */
3350 294 : if (blockmax != parity_allocated_size(state)) {
3351 : /* LCOV_EXCL_START */
3352 : log_fatal(EINTERNAL, "Internal inconsistency: Parity size %u/%u in '%s' at offset %" PRIi64 "\n", blockmax, parity_allocated_size(state), path, stell(f));
3353 : if (state->opt.skip_content_check) {
3354 : log_fatal(ECONTENT, "Overriding.\n");
3355 : blockmax = parity_allocated_size(state);
3356 : } else {
3357 : exit(EXIT_FAILURE);
3358 : }
3359 : /* LCOV_EXCL_STOP */
3360 : }
3361 :
3362 294 : msg_verbose("%8u files\n", count_file);
3363 294 : msg_verbose("%8u hardlinks\n", count_hardlink);
3364 294 : msg_verbose("%8u symlinks\n", count_symlink);
3365 294 : msg_verbose("%8u empty dirs\n", count_dir);
3366 :
3367 294 : log_tag("content_info:file:%u\n", count_file);
3368 294 : log_tag("content_info:hardlink:%u\n", count_hardlink);
3369 294 : log_tag("content_info:symlink:%u\n", count_symlink);
3370 294 : log_tag("content_info:dir_empty:%u\n", count_dir);
3371 :
3372 294 : log_tag("content_info:block:%u\n", blockmax);
3373 294 : log_tag("content_info:block_bad:%u\n", count_bad);
3374 294 : log_tag("content_info:block_rehash:%u\n", count_rehash);
3375 294 : log_tag("content_info:block_unsynced:%u\n", count_unsynced);
3376 294 : log_tag("content_info:block_unscrubbed:%u\n", count_unscrubbed);
3377 :
3378 : /* store the blocks counters */
3379 294 : state->rehash_blocks = count_rehash;
3380 294 : state->bad_blocks = count_bad;
3381 294 : state->unsynced_blocks = count_unsynced;
3382 294 : state->unscrubbed_blocks = count_unscrubbed;
3383 :
3384 : /* store the bucket info list */
3385 294 : bucket_to_list(&bucket_hash, &state->bucketlist, &state->bucketcount);
3386 294 : tommy_hashdyn_done(&bucket_hash);
3387 294 : }
3388 :
3389 : struct state_write_thread_context {
3390 : struct snapraid_state* state;
3391 : #if HAVE_MT_WRITE
3392 : thread_id_t thread;
3393 : #endif
3394 : /* input */
3395 : block_off_t blockmax;
3396 : time_t info_oldest;
3397 : time_t info_now;
3398 : int info_has_rehash;
3399 : STREAM* f;
3400 : int first;
3401 :
3402 : /* output (required to postpone the output to the terminal after the latest write) */
3403 : uint32_t crc;
3404 : unsigned count_file;
3405 : unsigned count_hardlink;
3406 : unsigned count_symlink;
3407 : unsigned count_dir;
3408 : unsigned count_bad;
3409 : unsigned count_rehash;
3410 : unsigned count_unsynced;
3411 : unsigned count_unscrubbed;
3412 : };
3413 :
3414 179 : static void* state_write_thread(void* arg)
3415 : {
3416 : char esc_buffer[ESC_MAX];
3417 : char esc_buffer1[ESC_MAX];
3418 179 : struct state_write_thread_context* context = arg;
3419 179 : struct snapraid_state* state = context->state;
3420 179 : block_off_t blockmax = context->blockmax;
3421 179 : time_t info_oldest = context->info_oldest;
3422 179 : time_t info_now = context->info_now;
3423 179 : int info_has_rehash = context->info_has_rehash;
3424 179 : STREAM* f = context->f;
3425 : uint32_t crc;
3426 : uint64_t t64;
3427 : unsigned count_file;
3428 : unsigned count_hardlink;
3429 : unsigned count_symlink;
3430 : unsigned count_dir;
3431 : unsigned count_bad;
3432 : unsigned count_rehash;
3433 : unsigned count_unsynced;
3434 : unsigned count_unscrubbed;
3435 : tommy_node* i;
3436 : block_off_t idx;
3437 : block_off_t begin;
3438 : unsigned l, s;
3439 : tommy_hashdyn bucket_hash;
3440 :
3441 179 : count_file = 0;
3442 179 : count_hardlink = 0;
3443 179 : count_symlink = 0;
3444 179 : count_dir = 0;
3445 179 : count_bad = 0;
3446 179 : count_rehash = 0;
3447 179 : count_unsynced = 0;
3448 179 : count_unscrubbed = 0;
3449 179 : tommy_hashdyn_init(&bucket_hash);
3450 :
3451 : /*
3452 : * Force at least version 3 a we want to always store the parity size
3453 : *
3454 : * If there is a dealloc list, force version 4
3455 : */
3456 927 : for (i = state->disklist; i != 0; i = i->next) {
3457 800 : struct snapraid_disk* disk = i->data;
3458 800 : if (!tommy_list_empty(&disk->dealloclist))
3459 52 : break;
3460 : }
3461 :
3462 : /* write header */
3463 179 : if (i == 0)
3464 127 : swrite("SNAPCNT3\n\3\0\0", 12, f);
3465 : else
3466 52 : swrite("SNAPCNT4\n\3\0\0", 12, f);
3467 :
3468 : /* write block size and block max */
3469 179 : sputc('z', f);
3470 179 : sputb32(state->block_size, f);
3471 179 : sputc('x', f);
3472 179 : sputb32(blockmax, f);
3473 :
3474 : /* hash size */
3475 179 : sputc('y', f);
3476 179 : sputb32(BLOCK_HASH_SIZE, f);
3477 :
3478 179 : if (serror(f)) {
3479 : /* LCOV_EXCL_START */
3480 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3481 : goto bail;
3482 : /* LCOV_EXCL_STOP */
3483 : }
3484 :
3485 179 : sputc('c', f);
3486 179 : if (state->hash == HASH_MURMUR3) {
3487 107 : sputc('u', f);
3488 72 : } else if (state->hash == HASH_SPOOKY2) {
3489 72 : sputc('k', f);
3490 0 : } else if (state->hash == HASH_METRO) {
3491 0 : sputc('m', f);
3492 : } else {
3493 : /* LCOV_EXCL_START */
3494 : log_fatal(EINTERNAL, "Unexpected hash when writing the content file '%s'.\n", serrorfile(f));
3495 : goto bail;
3496 : /* LCOV_EXCL_STOP */
3497 : }
3498 179 : swrite(state->hashseed, HASH_MAX, f);
3499 179 : if (serror(f)) {
3500 : /* LCOV_EXCL_START */
3501 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3502 : goto bail;
3503 : /* LCOV_EXCL_STOP */
3504 : }
3505 :
3506 : /* previous hash only present */
3507 179 : if (state->prevhash != HASH_UNDEFINED) {
3508 : /* if at least one rehash tag found, we have to save the previous hash */
3509 3 : if (info_has_rehash) {
3510 2 : sputc('C', f);
3511 2 : if (state->prevhash == HASH_MURMUR3) {
3512 2 : sputc('u', f);
3513 0 : } else if (state->prevhash == HASH_SPOOKY2) {
3514 0 : sputc('k', f);
3515 0 : } else if (state->prevhash == HASH_METRO) {
3516 0 : sputc('m', f);
3517 : } else {
3518 : /* LCOV_EXCL_START */
3519 : log_fatal(EINTERNAL, "Unexpected prevhash when writing the content file '%s'.\n", serrorfile(f));
3520 : goto bail;
3521 : /* LCOV_EXCL_STOP */
3522 : }
3523 2 : swrite(state->prevhashseed, HASH_MAX, f);
3524 2 : if (serror(f)) {
3525 : /* LCOV_EXCL_START */
3526 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3527 : goto bail;
3528 : /* LCOV_EXCL_STOP */
3529 : }
3530 : }
3531 : }
3532 :
3533 : /* for each map */
3534 1202 : for (i = state->maplist; i != 0; i = i->next) {
3535 1023 : struct snapraid_map* map = i->data;
3536 : struct snapraid_disk* disk;
3537 :
3538 : /* find the disk for this mapping */
3539 1023 : disk = find_disk_by_name(state, map->name);
3540 1023 : if (!disk) {
3541 : /* LCOV_EXCL_START */
3542 : log_fatal(EINTERNAL, "Internal inconsistency: Unmapped disk '%s'\n", map->name);
3543 : goto bail;
3544 : /* LCOV_EXCL_STOP */
3545 : }
3546 :
3547 : /* save the mapping only if disk is mapped */
3548 1023 : if (disk->mapping_idx != -1) {
3549 931 : sputc('M', f);
3550 931 : sputbs(map->name, f);
3551 931 : sputb32(map->position, f);
3552 931 : sputb32(map->total_blocks, f);
3553 931 : sputb32(map->free_blocks, f);
3554 931 : sputbs(map->uuid, f);
3555 931 : if (context->first) {
3556 931 : log_tag("content_data:%s:%" PRIi64 ":%" PRIi64 "\n",
3557 931 : esc_tag(map->name, esc_buffer),
3558 931 : map->total_blocks * (uint64_t)state->block_size,
3559 931 : map->free_blocks * (uint64_t)state->block_size);
3560 931 : log_tag("content_data_split:%s:%s\n",
3561 931 : esc_tag(map->name, esc_buffer),
3562 931 : map->uuid);
3563 : }
3564 931 : if (serror(f)) {
3565 : /* LCOV_EXCL_START */
3566 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3567 : goto bail;
3568 : /* LCOV_EXCL_STOP */
3569 : }
3570 : }
3571 : }
3572 :
3573 : /* for each parity */
3574 1113 : for (l = 0; l < state->level; ++l) {
3575 934 : sputc('Q', f);
3576 934 : sputb32(l, f);
3577 934 : sputb32(state->parity[l].total_blocks, f);
3578 934 : sputb32(state->parity[l].free_blocks, f);
3579 934 : if (context->first) {
3580 934 : log_tag("content_parity:%s:%" PRIi64 ":%" PRIi64 "\n",
3581 : lev_config_name(l),
3582 934 : state->parity[l].total_blocks * (uint64_t)state->block_size,
3583 934 : state->parity[l].free_blocks * (uint64_t)state->block_size);
3584 : }
3585 934 : sputb32(state->parity[l].split_mac, f);
3586 4619 : for (s = 0; s < state->parity[l].split_mac; ++s) {
3587 : char parity_name[64];
3588 3685 : sputbs(state->parity[l].split_map[s].path, f);
3589 3685 : sputbs(state->parity[l].split_map[s].uuid, f);
3590 3685 : sputb64(state->parity[l].split_map[s].size, f);
3591 3685 : if (s == 0)
3592 934 : pathcpy(parity_name, sizeof(parity_name), lev_config_name(l));
3593 : else
3594 2751 : pathprint(parity_name, sizeof(parity_name), "%s/%u", lev_config_name(l), s);
3595 3685 : if (context->first) {
3596 3685 : log_tag("content_parity_split:%s:%s:%s:%" PRIi64 "\n",
3597 : parity_name,
3598 3685 : state->parity[l].split_map[s].uuid,
3599 3685 : state->parity[l].split_map[s].path,
3600 : state->parity[l].split_map[s].size);
3601 : }
3602 : }
3603 934 : if (serror(f)) {
3604 : /* LCOV_EXCL_START */
3605 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3606 : goto bail;
3607 : /* LCOV_EXCL_STOP */
3608 : }
3609 : }
3610 :
3611 : /* for each disk */
3612 1202 : for (i = state->disklist; i != 0; i = i->next) {
3613 : tommy_node* j;
3614 1023 : struct snapraid_disk* disk = i->data;
3615 :
3616 : /* if the disk is not mapped, skip it */
3617 1023 : if (disk->mapping_idx < 0)
3618 92 : continue;
3619 :
3620 : /* for each file */
3621 1964002 : for (j = disk->filelist; j != 0; j = j->next) {
3622 1963071 : struct snapraid_file* file = j->data;
3623 : uint64_t size;
3624 : uint64_t mtime_sec;
3625 : int32_t mtime_nsec;
3626 : uint64_t inode;
3627 :
3628 1963071 : size = file->size;
3629 1963071 : mtime_sec = file->mtime_sec;
3630 1963071 : mtime_nsec = file->mtime_nsec;
3631 1963071 : inode = file->inode;
3632 :
3633 1963071 : sputc('f', f);
3634 1963071 : sputb32(disk->mapping_idx, f);
3635 1963071 : sputb64(size, f);
3636 1963071 : sputb64(mtime_sec, f);
3637 : /* encode STAT_NSEC_INVALID as 0 */
3638 1963071 : if (mtime_nsec == STAT_NSEC_INVALID)
3639 0 : sputb32(0, f);
3640 : else
3641 1963071 : sputb32(mtime_nsec + 1, f);
3642 1963071 : sputb64(inode, f);
3643 1963071 : sputbs(file->sub, f);
3644 1963071 : if (serror(f)) {
3645 : /* LCOV_EXCL_START */
3646 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3647 : goto bail;
3648 : /* LCOV_EXCL_STOP */
3649 : }
3650 :
3651 : /* for all the blocks of the file */
3652 1963071 : begin = 0;
3653 3936854 : while (begin < file->blockmax) {
3654 1973783 : unsigned v_state = block_state_get(fs_file2block_get(file, begin));
3655 1973783 : block_off_t v_pos = fs_file2par_get(disk, file, begin);
3656 : uint32_t v_count;
3657 :
3658 : block_off_t end;
3659 :
3660 : /* find the end of run of blocks */
3661 1973783 : end = begin + 1;
3662 4786886 : while (end < file->blockmax) {
3663 2826549 : if (v_state != block_state_get(fs_file2block_get(file, end)))
3664 14 : break;
3665 2826535 : if (v_pos + (end - begin) != fs_file2par_get(disk, file, end))
3666 13432 : break;
3667 2813103 : ++end;
3668 : }
3669 :
3670 1973783 : switch (v_state) {
3671 1867092 : case BLOCK_STATE_BLK :
3672 1867092 : sputc('b', f);
3673 1867092 : break;
3674 72417 : case BLOCK_STATE_CHG :
3675 72417 : sputc('g', f);
3676 72417 : break;
3677 34274 : case BLOCK_STATE_REP :
3678 34274 : sputc('p', f);
3679 34274 : break;
3680 0 : default :
3681 : /* LCOV_EXCL_START */
3682 : log_fatal(EINTERNAL, "Internal inconsistency: State for block %u state %u\n", v_pos, v_state);
3683 : goto bail;
3684 : /* LCOV_EXCL_STOP */
3685 : }
3686 :
3687 1973783 : sputb32(v_pos, f);
3688 :
3689 1973783 : v_count = end - begin;
3690 1973783 : sputb32(v_count, f);
3691 :
3692 : /* write hashes */
3693 6760669 : for (idx = begin; idx < end; ++idx) {
3694 4786886 : struct snapraid_block* block = fs_file2block_get(file, idx);
3695 :
3696 4786886 : swrite(block->hash, BLOCK_HASH_SIZE, f);
3697 : }
3698 :
3699 1973783 : if (serror(f)) {
3700 : /* LCOV_EXCL_START */
3701 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3702 : goto bail;
3703 : /* LCOV_EXCL_STOP */
3704 : }
3705 :
3706 : /* next begin position */
3707 1973783 : begin = end;
3708 : }
3709 :
3710 1963071 : ++count_file;
3711 : }
3712 :
3713 : /* for each link */
3714 65406 : for (j = disk->linklist; j != 0; j = j->next) {
3715 64475 : struct snapraid_link* slink = j->data;
3716 :
3717 64475 : switch (link_flag_get(slink, FILE_IS_LINK_MASK)) {
3718 357 : case FILE_IS_HARDLINK :
3719 357 : sputc('a', f);
3720 357 : ++count_hardlink;
3721 357 : break;
3722 64118 : case FILE_IS_SYMLINK :
3723 64118 : sputc('s', f);
3724 64118 : ++count_symlink;
3725 64118 : break;
3726 : }
3727 :
3728 64475 : sputb32(disk->mapping_idx, f);
3729 64475 : sputbs(slink->sub, f);
3730 64475 : sputbs(slink->linkto, f);
3731 64475 : if (serror(f)) {
3732 : /* LCOV_EXCL_START */
3733 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3734 : goto bail;
3735 : /* LCOV_EXCL_STOP */
3736 : }
3737 : }
3738 :
3739 : /* for each dir */
3740 1406 : for (j = disk->dirlist; j != 0; j = j->next) {
3741 475 : struct snapraid_dir* dir = j->data;
3742 :
3743 475 : sputc('r', f);
3744 475 : sputb32(disk->mapping_idx, f);
3745 475 : sputbs(dir->sub, f);
3746 475 : if (serror(f)) {
3747 : /* LCOV_EXCL_START */
3748 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3749 : goto bail;
3750 : /* LCOV_EXCL_STOP */
3751 : }
3752 :
3753 475 : ++count_dir;
3754 : }
3755 :
3756 : /* deleted blocks of the disk */
3757 931 : sputc('h', f);
3758 931 : sputb32(disk->mapping_idx, f);
3759 931 : if (serror(f)) {
3760 : /* LCOV_EXCL_START */
3761 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3762 : goto bail;
3763 : /* LCOV_EXCL_STOP */
3764 : }
3765 931 : begin = 0;
3766 1971 : while (begin < blockmax) {
3767 : int is_deleted;
3768 : block_off_t end;
3769 :
3770 1040 : is_deleted = fs_is_block_deleted(disk, begin);
3771 :
3772 : /* find the end of run of blocks */
3773 1040 : end = begin + 1;
3774 1040 : while (end < blockmax
3775 5032383 : && is_deleted == fs_is_block_deleted(disk, end)
3776 : ) {
3777 5031343 : ++end;
3778 : }
3779 :
3780 1040 : sputb32(end - begin, f);
3781 :
3782 1040 : if (is_deleted) {
3783 : /* write the run of deleted blocks with hash */
3784 65 : sputc('o', f);
3785 :
3786 : /* write all the hash */
3787 18973 : while (begin < end) {
3788 18908 : struct snapraid_block* block = fs_par2block_get(disk, begin);
3789 :
3790 18908 : swrite(block->hash, BLOCK_HASH_SIZE, f);
3791 :
3792 18908 : ++begin;
3793 : }
3794 : } else {
3795 : /*
3796 : * Write the run of blocks without hash
3797 : * they can be either used or empty blocks
3798 : */
3799 975 : sputc('O', f);
3800 :
3801 : /* next begin position */
3802 975 : begin = end;
3803 : }
3804 :
3805 1040 : if (serror(f)) {
3806 : /* LCOV_EXCL_START */
3807 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3808 : goto bail;
3809 : /* LCOV_EXCL_STOP */
3810 : }
3811 : }
3812 :
3813 : /* deallocated files of the disk */
3814 931 : if (!tommy_list_empty(&disk->dealloclist)) {
3815 83 : sputc('d', f);
3816 83 : sputb32(disk->mapping_idx, f);
3817 :
3818 83 : uint32_t v_count = tommy_list_count(&disk->dealloclist);
3819 :
3820 83 : sputb32(v_count, f);
3821 83 : log_tag("content_info:dealloc:%s:%u\n", esc_tag(disk->name, esc_buffer), v_count);
3822 :
3823 : /* for each file */
3824 44062 : for (j = tommy_list_head(&disk->dealloclist); j != 0; j = j->next) {
3825 43979 : struct snapraid_dealloc* dealloc = j->data;
3826 43979 : sputbs(dealloc->sub, f);
3827 43979 : sputb64(dealloc->size, f);
3828 43979 : sputb64(dealloc->mtime_sec, f);
3829 :
3830 : /* encode STAT_NSEC_INVALID as 0 */
3831 43979 : if (dealloc->mtime_nsec == STAT_NSEC_INVALID)
3832 0 : sputb32(0, f);
3833 : else
3834 43979 : sputb32(dealloc->mtime_nsec + 1, f);
3835 :
3836 43979 : log_tag("content_info:dealloc_entry:%s:%s:%" PRIu64 ":%" PRIu64 ":%u\n", esc_tag(disk->name, esc_buffer), esc_tag(dealloc->sub, esc_buffer1), dealloc->size, dealloc->mtime_sec, dealloc->mtime_nsec);
3837 :
3838 : /* write all hashes */
3839 146906 : for (block_off_t k = 0; k < dealloc->blockmax; ++k) {
3840 102927 : unsigned char* hash = dealloc->blockhash + k * BLOCK_HASH_SIZE;
3841 :
3842 102927 : swrite(hash, BLOCK_HASH_SIZE, f);
3843 : }
3844 : }
3845 :
3846 83 : if (serror(f)) {
3847 : /* LCOV_EXCL_START */
3848 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3849 : goto bail;
3850 : /* LCOV_EXCL_STOP */
3851 : }
3852 : } else {
3853 848 : log_tag("content_info:dealloc:%s:0\n", esc_tag(disk->name, esc_buffer));
3854 : }
3855 : }
3856 :
3857 : /* write the info for each block */
3858 179 : sputc('i', f);
3859 : /* ensure to write a 64 bit time */
3860 179 : t64 = info_oldest;
3861 179 : sputb64(t64, f);
3862 179 : begin = 0;
3863 17550 : while (begin < blockmax) {
3864 : snapraid_info info;
3865 : block_off_t end;
3866 : block_off_t count;
3867 : time_t t;
3868 : unsigned flag;
3869 :
3870 17371 : info = info_get(&state->infoarr, begin);
3871 :
3872 : /* avoid this slow operation if not needed */
3873 17371 : if (context->first && fs_is_block_unsynced(state, begin))
3874 133 : ++count_unsynced;
3875 :
3876 : /* find the end of run of blocks */
3877 17371 : end = begin + 1;
3878 17371 : while (end < blockmax
3879 847980 : && info == info_get(&state->infoarr, end)
3880 : ) {
3881 830609 : if (context->first && fs_is_block_unsynced(state, end))
3882 117981 : ++count_unsynced;
3883 830609 : ++end;
3884 : }
3885 :
3886 17371 : count = end - begin;
3887 17371 : sputb32(count, f);
3888 :
3889 : /* if there is info */
3890 17371 : if (info) {
3891 : /* other flags */
3892 17342 : flag = 1; /* info is present */
3893 17342 : if (info_get_bad(info)) {
3894 1328 : flag |= 2;
3895 1328 : if (context->first)
3896 1328 : count_bad += count;
3897 : }
3898 17342 : if (info_get_rehash(info)) {
3899 4994 : flag |= 4;
3900 4994 : if (context->first)
3901 4994 : count_rehash += count;
3902 : }
3903 17342 : if (info_get_justsynced(info)) {
3904 8173 : flag |= 8;
3905 8173 : if (context->first)
3906 8173 : count_unscrubbed += count;
3907 : }
3908 17342 : sputb32(flag, f);
3909 :
3910 17342 : t = info_get_time(info);
3911 :
3912 17342 : if (context->first)
3913 17342 : bucket_insert(&bucket_hash, t, count, flag & 8);
3914 :
3915 : /* truncate any time that is in the future */
3916 17342 : if (t > info_now)
3917 0 : t = info_now;
3918 :
3919 : /* the oldest info is computed only on required blocks, so it may not be the absolute oldest */
3920 17342 : if (t < info_oldest)
3921 0 : t = 0;
3922 : else
3923 17342 : t -= info_oldest;
3924 :
3925 : /* ensure to write a 64 bit time */
3926 17342 : t64 = t;
3927 17342 : sputb64(t64, f);
3928 : } else {
3929 : /* write a special 0 flag to mark missing info */
3930 29 : sputb32(0, f);
3931 : }
3932 :
3933 17371 : if (serror(f)) {
3934 : /* LCOV_EXCL_START */
3935 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3936 : goto bail;
3937 : /* LCOV_EXCL_STOP */
3938 : }
3939 :
3940 : /* next begin position */
3941 17371 : begin = end;
3942 : }
3943 :
3944 179 : sputc('N', f);
3945 :
3946 : /* flush data written to the disk */
3947 179 : if (sflush(f)) {
3948 : /* LCOV_EXCL_START */
3949 : log_fatal(errno, "Error writing the content file '%s' (in flush before crc). %s.\n", serrorfile(f), strerror(errno));
3950 : goto bail;
3951 : /* LCOV_EXCL_STOP */
3952 : }
3953 :
3954 : /* get the file crc */
3955 179 : crc = scrc(f);
3956 :
3957 : /*
3958 : * Compare the crc of the data written to file
3959 : * with the one of the data written to the stream
3960 : */
3961 179 : if (crc != scrc_stream(f)) {
3962 : /* LCOV_EXCL_START */
3963 : log_fatal(ECONTENT, "CRC mismatch while writing the content stream.\n");
3964 : log_fatal(ECONTENT, "DANGER! Your RAM memory is faulty! DO NOT PROCEED UNTIL FIXED!\n");
3965 : log_fatal(ECONTENT, "Try running a memory test like http://www.memtest86.com/\n");
3966 : goto bail;
3967 : /* LCOV_EXCL_STOP */
3968 : }
3969 :
3970 179 : sputble32(crc, f);
3971 179 : if (serror(f)) {
3972 : /* LCOV_EXCL_START */
3973 : log_fatal(errno, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
3974 : goto bail;
3975 : /* LCOV_EXCL_STOP */
3976 : }
3977 :
3978 : /* set output variables */
3979 179 : context->crc = crc;
3980 179 : context->count_file = count_file;
3981 179 : context->count_hardlink = count_hardlink;
3982 179 : context->count_symlink = count_symlink;
3983 179 : context->count_dir = count_dir;
3984 179 : context->count_bad = count_bad;
3985 179 : context->count_rehash = count_rehash;
3986 179 : context->count_unsynced = count_unsynced;
3987 179 : context->count_unscrubbed = count_unscrubbed;
3988 :
3989 : /* store the bucket info list */
3990 179 : if (context->first)
3991 179 : bucket_to_list(&bucket_hash, &state->bucketlist, &state->bucketcount);
3992 :
3993 179 : tommy_hashdyn_done(&bucket_hash);
3994 179 : return 0;
3995 :
3996 0 : bail:
3997 0 : tommy_hashdyn_done(&bucket_hash);
3998 0 : return context;
3999 : }
4000 :
4001 179 : static void state_write_content(struct snapraid_state* state, uint32_t* out_crc)
4002 : {
4003 : #if HAVE_MT_WRITE
4004 : int fail;
4005 : int first;
4006 : #else
4007 : STREAM* f;
4008 : unsigned count_content;
4009 : unsigned k;
4010 : struct state_write_thread_context* context;
4011 : void* retval;
4012 : #endif
4013 : tommy_node* i;
4014 : block_off_t blockmax;
4015 : time_t info_oldest;
4016 : time_t info_now;
4017 : int info_has_rehash;
4018 : int mapping_idx;
4019 : block_off_t idx;
4020 : uint32_t crc;
4021 : unsigned count_file;
4022 : unsigned count_hardlink;
4023 : unsigned count_symlink;
4024 : unsigned count_dir;
4025 : unsigned count_bad;
4026 : unsigned count_rehash;
4027 : unsigned count_unsynced;
4028 : unsigned count_unscrubbed;
4029 :
4030 : /* blocks of all array */
4031 179 : blockmax = parity_allocated_size(state);
4032 :
4033 : /* check the file-system on all disks */
4034 179 : state_fscheck(state, "before write");
4035 :
4036 : /*
4037 : * Clear the info for unused blocks
4038 : * and get some other info
4039 : */
4040 179 : info_oldest = 0; /* oldest time in info */
4041 179 : info_now = time(0); /* get the present time */
4042 179 : info_has_rehash = 0; /* if there is a rehash info */
4043 848159 : for (idx = 0; idx < blockmax; ++idx) {
4044 : /* if the position is used */
4045 847980 : if (fs_position_is_required(state, idx)) {
4046 846102 : snapraid_info info = info_get(&state->infoarr, idx);
4047 :
4048 : /* only if there is some info to store */
4049 846102 : if (info) {
4050 798511 : time_t info_time = info_get_time(info);
4051 :
4052 798511 : if (!info_oldest || info_time < info_oldest)
4053 182 : info_oldest = info_time;
4054 :
4055 798511 : if (info_get_rehash(info))
4056 13912 : info_has_rehash = 1;
4057 : }
4058 : } else {
4059 : /* clear any previous info */
4060 1878 : info_set(&state->infoarr, idx, 0);
4061 :
4062 : /* and clear any deleted blocks */
4063 1878 : fs_position_clear_deleted(state, idx);
4064 : }
4065 : }
4066 :
4067 : /* map disks */
4068 179 : mapping_idx = 0;
4069 1202 : for (i = state->maplist; i != 0; i = i->next) {
4070 1023 : struct snapraid_map* map = i->data;
4071 : struct snapraid_disk* disk;
4072 :
4073 : /* find the disk for this mapping */
4074 1023 : disk = find_disk_by_name(state, map->name);
4075 1023 : if (!disk) {
4076 : /* LCOV_EXCL_START */
4077 : log_fatal(EINTERNAL, "Internal inconsistency: Unmapped disk '%s'\n", map->name);
4078 : os_abort();
4079 : /* LCOV_EXCL_STOP */
4080 : }
4081 :
4082 : /* save the mapping only for not empty disks */
4083 1023 : if (!fs_is_empty(disk, blockmax)) {
4084 : /* assign the mapping index used to identify disks */
4085 931 : disk->mapping_idx = mapping_idx;
4086 931 : ++mapping_idx;
4087 : } else {
4088 : /* mark the disk as without mapping */
4089 92 : disk->mapping_idx = -1;
4090 : }
4091 : }
4092 :
4093 : #if HAVE_MT_WRITE
4094 : /* start all writing threads */
4095 : first = 1;
4096 : i = tommy_list_head(&state->contentlist);
4097 : while (i) {
4098 : struct snapraid_content* content = i->data;
4099 : struct state_write_thread_context* context;
4100 : char tmp[PATH_MAX];
4101 : char esc_buffer[ESC_MAX];
4102 : STREAM* f;
4103 :
4104 : msg_progress("Saving state to %s...\n", content->content);
4105 : log_tag("content_write:%s\n", esc_tag(content->content, esc_buffer));
4106 :
4107 : pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
4108 :
4109 : /* ensure to delete a previous stale file */
4110 : if (remove(tmp) != 0) {
4111 : if (errno != ENOENT) {
4112 : /* LCOV_EXCL_START */
4113 : log_fatal(errno, "Error removing the stale content file '%s'. %s.\n", tmp, strerror(errno));
4114 : exit(EXIT_FAILURE);
4115 : /* LCOV_EXCL_STOP */
4116 : }
4117 : }
4118 :
4119 : f = sopen_write(tmp);
4120 : if (f == 0) {
4121 : /* LCOV_EXCL_START */
4122 : log_fatal(errno, "Error opening the temporary content file '%s'. %s.\n", tmp, strerror(errno));
4123 : exit(EXIT_FAILURE);
4124 : /* LCOV_EXCL_STOP */
4125 : }
4126 :
4127 : /* allocate the thread context */
4128 : context = malloc_nofail(sizeof(struct state_write_thread_context));
4129 : content->context = context;
4130 :
4131 : /* initialize */
4132 : context->state = state;
4133 : context->blockmax = blockmax;
4134 : context->info_oldest = info_oldest;
4135 : context->info_now = info_now;
4136 : context->info_has_rehash = info_has_rehash;
4137 : context->f = f;
4138 : context->first = first;
4139 : first = 0;
4140 :
4141 : thread_create(&context->thread, state_write_thread, context);
4142 :
4143 : i = i->next;
4144 : }
4145 :
4146 : /* join all thread */
4147 : fail = 0;
4148 : first = 1;
4149 : crc = 0;
4150 : count_file = 0;
4151 : count_hardlink = 0;
4152 : count_symlink = 0;
4153 : count_dir = 0;
4154 : count_bad = 0;
4155 : count_rehash = 0;
4156 : count_unsynced = 0;
4157 : count_unscrubbed = 0;
4158 : i = tommy_list_head(&state->contentlist);
4159 : while (i) {
4160 : struct snapraid_content* content = i->data;
4161 : struct state_write_thread_context* context = content->context;
4162 : void* retval;
4163 :
4164 : thread_join(context->thread, &retval);
4165 :
4166 : if (retval) {
4167 : /* LCOV_EXCL_START */
4168 : fail = 1;
4169 : /* LCOV_EXCL_STOP */
4170 : } else {
4171 : STREAM* f = context->f;
4172 :
4173 : /*
4174 : * Use the sequence fflush() -> fsync() -> fclose() -> rename() to ensure
4175 : * than even in a system crash event we have one valid copy of the file.
4176 : */
4177 : if (sflush(f) != 0) {
4178 : /* LCOV_EXCL_START */
4179 : log_fatal(errno, "Error writing the content file '%s', in flush(). %s.\n", serrorfile(f), strerror(errno));
4180 : exit(EXIT_FAILURE);
4181 : /* LCOV_EXCL_STOP */
4182 : }
4183 :
4184 : #if HAVE_FSYNC
4185 : if (ssync(f) != 0) {
4186 : /* LCOV_EXCL_START */
4187 : log_fatal(errno, "Error writing the content file '%s' in sync(). %s.\n", serrorfile(f), strerror(errno));
4188 : exit(EXIT_FAILURE);
4189 : /* LCOV_EXCL_STOP */
4190 : }
4191 : #endif
4192 :
4193 : if (sclose(f) != 0) {
4194 : /* LCOV_EXCL_START */
4195 : log_fatal(errno, "Error closing the content file. %s.\n", strerror(errno));
4196 : exit(EXIT_FAILURE);
4197 : /* LCOV_EXCL_STOP */
4198 : }
4199 :
4200 : if (first) {
4201 : first = 0;
4202 : crc = context->crc;
4203 : count_file = context->count_file;
4204 : count_hardlink = context->count_hardlink;
4205 : count_symlink = context->count_symlink;
4206 : count_dir = context->count_dir;
4207 : cound_bad = context->count_bad;
4208 : count_rehash = context->count_rehash;
4209 : count_unsynced = context->count_unsynced;
4210 : count_unscrubbed = context->count_unscrubbed;
4211 : } else {
4212 : if (crc != context->crc) {
4213 : /* LCOV_EXCL_START */
4214 : log_fatal(ECONTENT, "Different CRCs writing content streams.\n");
4215 : log_fatal(ECONTENT, "DANGER! Your RAM memory is broken! DO NOT PROCEED UNTIL FIXED!\n");
4216 : log_fatal(ECONTENT, "Try running a memory test like http://www.memtest86.com/\n");
4217 : exit(EXIT_FAILURE);
4218 : /* LCOV_EXCL_STOP */
4219 : }
4220 : }
4221 : }
4222 :
4223 : free(context);
4224 :
4225 : i = i->next;
4226 : }
4227 :
4228 : /* abort on failure */
4229 : if (fail) {
4230 : /* LCOV_EXCL_START */
4231 : exit(EXIT_FAILURE);
4232 : /* LCOV_EXCL_STOP */
4233 : }
4234 : #else
4235 : /* count the content files */
4236 179 : count_content = 0;
4237 179 : i = tommy_list_head(&state->contentlist);
4238 1292 : while (i) {
4239 1113 : struct snapraid_content* content = i->data;
4240 1113 : msg_progress("Saving state to %s...\n", content->content);
4241 1113 : ++count_content;
4242 1113 : i = i->next;
4243 : }
4244 :
4245 : /* open all the content files */
4246 179 : f = sopen_multi_write(count_content, STREAM_FLAGS_SEQUENTIAL | STREAM_FLAGS_CRC);
4247 179 : if (!f) {
4248 : /* LCOV_EXCL_START */
4249 : log_fatal(errno, "Error opening the content files.\n");
4250 : exit(EXIT_FAILURE);
4251 : /* LCOV_EXCL_STOP */
4252 : }
4253 :
4254 179 : k = 0;
4255 179 : i = tommy_list_head(&state->contentlist);
4256 1292 : while (i) {
4257 1113 : struct snapraid_content* content = i->data;
4258 : char tmp[PATH_MAX];
4259 1113 : pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
4260 :
4261 : /* ensure to delete a previous stale file */
4262 1113 : if (remove(tmp) != 0) {
4263 1113 : if (errno != ENOENT) {
4264 : /* LCOV_EXCL_START */
4265 : log_fatal(errno, "Error removing the stale content file '%s'. %s.\n", tmp, strerror(errno));
4266 : exit(EXIT_FAILURE);
4267 : /* LCOV_EXCL_STOP */
4268 : }
4269 : }
4270 :
4271 1113 : if (sopen_multi_file(f, k, tmp) != 0) {
4272 : /* LCOV_EXCL_START */
4273 : log_fatal(errno, "Error opening the temporary content file '%s'. %s.\n", tmp, strerror(errno));
4274 : exit(EXIT_FAILURE);
4275 : /* LCOV_EXCL_STOP */
4276 : }
4277 :
4278 1113 : ++k;
4279 1113 : i = i->next;
4280 : }
4281 :
4282 : /* allocate the thread context */
4283 179 : context = malloc_nofail(sizeof(struct state_write_thread_context));
4284 :
4285 : /* initialize */
4286 179 : context->state = state;
4287 179 : context->blockmax = blockmax;
4288 179 : context->info_oldest = info_oldest;
4289 179 : context->info_now = info_now;
4290 179 : context->info_has_rehash = info_has_rehash;
4291 179 : context->f = f;
4292 179 : context->first = 1;
4293 :
4294 179 : retval = state_write_thread(context);
4295 :
4296 : /* abort on failure */
4297 179 : if (retval) {
4298 : /* LCOV_EXCL_START */
4299 : exit(EXIT_FAILURE);
4300 : /* LCOV_EXCL_STOP */
4301 : }
4302 :
4303 : /*
4304 : * Use the sequence fflush() -> fsync() -> fclose() -> rename() to ensure
4305 : * than even in a system crash event we have one valid copy of the file.
4306 : */
4307 179 : if (sflush(f) != 0) {
4308 : /* LCOV_EXCL_START */
4309 : log_fatal(errno, "Error writing the content file '%s', in flush(). %s.\n", serrorfile(f), strerror(errno));
4310 : exit(EXIT_FAILURE);
4311 : /* LCOV_EXCL_STOP */
4312 : }
4313 :
4314 : #if HAVE_FSYNC
4315 179 : if (ssync(f) != 0) {
4316 : /* LCOV_EXCL_START */
4317 : log_fatal(errno, "Error writing the content file '%s' in sync(). %s.\n", serrorfile(f), strerror(errno));
4318 : exit(EXIT_FAILURE);
4319 : /* LCOV_EXCL_STOP */
4320 : }
4321 : #endif
4322 :
4323 179 : if (sclose(f) != 0) {
4324 : /* LCOV_EXCL_START */
4325 : log_fatal(errno, "Error closing the content file. %s.\n", strerror(errno));
4326 : exit(EXIT_FAILURE);
4327 : /* LCOV_EXCL_STOP */
4328 : }
4329 :
4330 179 : crc = context->crc;
4331 179 : count_file = context->count_file;
4332 179 : count_hardlink = context->count_hardlink;
4333 179 : count_symlink = context->count_symlink;
4334 179 : count_dir = context->count_dir;
4335 179 : count_bad = context->count_bad;
4336 179 : count_rehash = context->count_rehash;
4337 179 : count_unsynced = context->count_unsynced;
4338 179 : count_unscrubbed = context->count_unscrubbed;
4339 :
4340 179 : free(context);
4341 : #endif
4342 :
4343 179 : msg_verbose("%8u files\n", count_file);
4344 179 : msg_verbose("%8u hardlinks\n", count_hardlink);
4345 179 : msg_verbose("%8u symlinks\n", count_symlink);
4346 179 : msg_verbose("%8u empty dirs\n", count_dir);
4347 :
4348 179 : log_tag("content_info:file:%u\n", count_file);
4349 179 : log_tag("content_info:hardlink:%u\n", count_hardlink);
4350 179 : log_tag("content_info:symlink:%u\n", count_symlink);
4351 179 : log_tag("content_info:dir_empty:%u\n", count_dir);
4352 :
4353 179 : log_tag("content_info:block:%u\n", blockmax);
4354 179 : log_tag("content_info:block_bad:%u\n", count_bad);
4355 179 : log_tag("content_info:block_rehash:%u\n", count_rehash);
4356 179 : log_tag("content_info:block_unsynced:%u\n", count_unsynced);
4357 179 : log_tag("content_info:block_unscrubbed:%u\n", count_unscrubbed);
4358 :
4359 : /* store the blocks counters */
4360 179 : state->rehash_blocks = count_rehash;
4361 179 : state->bad_blocks = count_bad;
4362 179 : state->unsynced_blocks = count_unsynced;
4363 179 : state->unscrubbed_blocks = count_unscrubbed;
4364 :
4365 179 : *out_crc = crc;
4366 179 : }
4367 :
4368 4 : void state_probe(struct snapraid_state* state)
4369 : {
4370 : char path[PATH_MAX];
4371 : struct stat st;
4372 : tommy_node* node;
4373 :
4374 : /* iterate over all the available content files and check the first one present */
4375 4 : node = tommy_list_head(&state->contentlist);
4376 4 : while (node) {
4377 4 : struct snapraid_content* content = node->data;
4378 :
4379 4 : pathcpy(path, sizeof(path), content->content);
4380 :
4381 4 : if (stat(path, &st) == 0) {
4382 : /* if found stop the search */
4383 4 : break;
4384 : }
4385 :
4386 : /* next content file */
4387 0 : node = node->next;
4388 : }
4389 :
4390 : /* if not found, assume empty */
4391 4 : if (!node)
4392 0 : return;
4393 :
4394 4 : if (!state->no_conf) {
4395 : /* intentionally don't output the content path because it's not really read */
4396 4 : log_tag("content_info:probe_unixtime:%" PRId64 "\n", (int64_t)st.st_mtime);
4397 4 : log_flush();
4398 : }
4399 : }
4400 :
4401 303 : void state_read(struct snapraid_state* state)
4402 : {
4403 : STREAM* f;
4404 : char path[PATH_MAX];
4405 : struct stat st;
4406 : tommy_node* node;
4407 : int ret;
4408 : int c;
4409 :
4410 : /* iterate over all the available content files and load the first one present */
4411 303 : f = 0;
4412 303 : node = tommy_list_head(&state->contentlist);
4413 355 : while (node) {
4414 347 : struct snapraid_content* content = node->data;
4415 347 : pathcpy(path, sizeof(path), content->content);
4416 :
4417 347 : msg_progress("Loading state from %s...\n", path);
4418 :
4419 347 : f = sopen_read(path, STREAM_FLAGS_SEQUENTIAL | STREAM_FLAGS_CRC);
4420 347 : if (f != 0) {
4421 : /* if found stop the search */
4422 295 : break;
4423 : } else {
4424 : /* if it's real error of an existing file, abort */
4425 52 : if (errno != ENOENT) {
4426 : /* LCOV_EXCL_START */
4427 : log_fatal(errno, "Error opening the content file '%s'. %s.\n", path, strerror(errno));
4428 : exit(EXIT_FAILURE);
4429 : /* LCOV_EXCL_STOP */
4430 : }
4431 :
4432 : /* otherwise continue */
4433 52 : if (node->next) {
4434 44 : log_fatal(errno, "WARNING! Content file '%s' not found, attempting with another copy...\n", path);
4435 :
4436 : /* ensure to rewrite all the content files */
4437 44 : state->need_write = 1;
4438 : }
4439 : }
4440 :
4441 : /* next content file */
4442 52 : node = node->next;
4443 : }
4444 :
4445 : /* if not found, assume empty */
4446 303 : if (!f) {
4447 8 : log_fatal(EUSER, "No content file found. Assuming empty.\n");
4448 :
4449 : /* create the initial mapping */
4450 8 : state_map(state);
4451 8 : return;
4452 : }
4453 :
4454 : /* get the stat of the content file */
4455 295 : ret = fstat(shandle(f), &st);
4456 295 : if (ret != 0) {
4457 : /* LCOV_EXCL_START */
4458 : log_fatal(errno, "Error stating the content file '%s'. %s.\n", path, strerror(errno));
4459 : exit(EXIT_FAILURE);
4460 : /* LCOV_EXCL_STOP */
4461 : }
4462 :
4463 295 : if (!state->no_conf) {
4464 : char esc_buffer[ESC_MAX];
4465 294 : log_tag("content:%s\n", esc_tag(path, esc_buffer));
4466 294 : log_tag("content_info:read_unixtime:%" PRId64 "\n", (int64_t)st.st_mtime);
4467 294 : log_flush();
4468 : }
4469 :
4470 : /* go further to check other content files */
4471 1890 : while (node) {
4472 : char other_path[PATH_MAX];
4473 : struct stat other_st;
4474 1595 : struct snapraid_content* content = node->data;
4475 1595 : pathcpy(other_path, sizeof(other_path), content->content);
4476 :
4477 1595 : ret = stat(other_path, &other_st);
4478 1595 : if (ret != 0) {
4479 : /* allow missing content files, but not any other kind of error */
4480 1 : if (errno != ENOENT) {
4481 : /* LCOV_EXCL_START */
4482 : log_fatal(errno, "Error stating the content file '%s'. %s.\n", other_path, strerror(errno));
4483 : exit(EXIT_FAILURE);
4484 : /* LCOV_EXCL_STOP */
4485 : }
4486 :
4487 : /* ensure to rewrite all the content files */
4488 1 : state->need_write = 1;
4489 : } else {
4490 : /* if the size is different */
4491 1594 : if (other_st.st_size != st.st_size) {
4492 39 : log_fatal(ECONTENT, "WARNING! Content files '%s' and '%s' have a different size!\n", path, other_path);
4493 39 : log_fatal(ECONTENT, "Likely one of the two is broken!\n");
4494 :
4495 : /* ensure to rewrite all the content files */
4496 39 : state->need_write = 1;
4497 : }
4498 : }
4499 :
4500 : /* next content file */
4501 1595 : node = node->next;
4502 : }
4503 :
4504 : /*
4505 : * Start with a undefined default.
4506 : * it's for compatibility with version 1.0 where MD5 was implicit.
4507 : */
4508 295 : state->hash = HASH_UNDEFINED;
4509 :
4510 : /* start with a zero seed, it was the default in old versions */
4511 295 : memset(state->hashseed, 0, HASH_MAX);
4512 :
4513 : /* previous hash, start with an undefined value */
4514 295 : state->prevhash = HASH_UNDEFINED;
4515 :
4516 : /* intentionally not set the prevhashseed, if used valgrind will warn about it */
4517 :
4518 : /* get the first char to detect the file type */
4519 295 : c = sgetc(f);
4520 295 : sungetc(c, f);
4521 :
4522 : /* guess the file type from the first char */
4523 295 : if (c == 'S') {
4524 295 : state_read_content(state, path, f);
4525 : } else {
4526 : /* LCOV_EXCL_START */
4527 : log_fatal(EUSER, "From SnapRAID v9.0 the text content file is not supported anymore.\n");
4528 : log_fatal(EUSER, "You have first to upgrade to SnapRAID v8.1 to convert it to binary format.\n");
4529 : exit(EXIT_FAILURE);
4530 : /* LCOV_EXCL_STOP */
4531 : }
4532 :
4533 294 : sclose(f);
4534 :
4535 294 : if (state->hash == HASH_UNDEFINED) {
4536 : /* LCOV_EXCL_START */
4537 : log_fatal(EUSER, "The checksum to use is not specified.\n");
4538 : log_fatal(EUSER, "This happens because you are likely upgrading from SnapRAID 1.0.\n");
4539 : log_fatal(EUSER, "To use a new SnapRAID you must restart from scratch,\n");
4540 : log_fatal(EUSER, "deleting all the content and parity files.\n");
4541 : exit(EXIT_FAILURE);
4542 : /* LCOV_EXCL_STOP */
4543 : }
4544 :
4545 294 : if (state->unsynced_blocks)
4546 51 : msg_progress("WARNING! The latest sync was interrupted!\n");
4547 :
4548 : /* update the mapping */
4549 294 : state_map(state);
4550 :
4551 294 : state_content_check(state, path);
4552 :
4553 : /* mark that we read the content file, and it passed all the checks */
4554 294 : state->checked_read = 1;
4555 : }
4556 :
4557 : struct state_verify_thread_context {
4558 : struct snapraid_state* state;
4559 : struct snapraid_content* content;
4560 : #if HAVE_MT_VERIFY
4561 : thread_id_t thread;
4562 : #else
4563 : void* retval;
4564 : #endif
4565 : /* input */
4566 : uint32_t crc;
4567 : STREAM* f;
4568 : };
4569 :
4570 1113 : static void* state_verify_thread(void* arg)
4571 : {
4572 1113 : struct state_verify_thread_context* context = arg;
4573 1113 : struct snapraid_content* content = context->content;
4574 1113 : STREAM* f = context->f;
4575 : unsigned char buf[4];
4576 : uint32_t crc_stored;
4577 : uint32_t crc_computed;
4578 : uint64_t start;
4579 :
4580 1113 : start = os_tick_ms();
4581 :
4582 1113 : if (sdeplete(f, buf) != 0) {
4583 : /* LCOV_EXCL_START */
4584 : log_fatal(errno, "Failed to flush content file '%s'. %s.\n", serrorfile(f), strerror(errno));
4585 : return context;
4586 : /* LCOV_EXCL_STOP */
4587 : }
4588 :
4589 : /* get the stored crc from the last four bytes */
4590 1113 : crc_stored = buf[0] | (uint32_t)buf[1] << 8 | (uint32_t)buf[2] << 16 | (uint32_t)buf[3] << 24;
4591 :
4592 1113 : if (crc_stored != context->crc) {
4593 : /* LCOV_EXCL_START */
4594 : log_fatal(ECONTENT, "DANGER! Wrong stored CRC in '%s'\n", serrorfile(f));
4595 : return context;
4596 : /* LCOV_EXCL_STOP */
4597 : }
4598 :
4599 : /* get the computed crc */
4600 1113 : crc_computed = scrc(f);
4601 :
4602 : /* adjust the stored crc to include itself */
4603 1113 : crc_stored = crc32c(crc_stored, buf, 4);
4604 :
4605 1113 : if (crc_computed != crc_stored) {
4606 : /* LCOV_EXCL_START */
4607 : log_fatal(ECONTENT, "DANGER! Wrong file CRC in '%s'\n", serrorfile(f));
4608 : return context;
4609 : /* LCOV_EXCL_STOP */
4610 : }
4611 :
4612 1113 : msg_progress("Verified %s in %" PRIu64 " seconds\n", content->content, (os_tick_ms() - start) / 1000);
4613 :
4614 1113 : return 0;
4615 : }
4616 :
4617 179 : static void state_verify_content(struct snapraid_state* state, uint32_t crc)
4618 : {
4619 : tommy_node* i;
4620 : int fail;
4621 :
4622 179 : msg_progress("Verifying...\n");
4623 :
4624 : /* start all reading threads */
4625 179 : i = tommy_list_head(&state->contentlist);
4626 1292 : while (i) {
4627 1113 : struct snapraid_content* content = i->data;
4628 : struct state_verify_thread_context* context;
4629 : char tmp[PATH_MAX];
4630 : STREAM* f;
4631 :
4632 1113 : pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
4633 1113 : f = sopen_read(tmp, STREAM_FLAGS_SEQUENTIAL | STREAM_FLAGS_CRC);
4634 1113 : if (f == 0) {
4635 : /* LCOV_EXCL_START */
4636 : log_fatal(errno, "Error reopening the temporary content file '%s'. %s.\n", tmp, strerror(errno));
4637 : exit(EXIT_FAILURE);
4638 : /* LCOV_EXCL_STOP */
4639 : }
4640 :
4641 : /* allocate the thread context */
4642 1113 : context = malloc_nofail(sizeof(struct state_verify_thread_context));
4643 1113 : content->context = context;
4644 :
4645 : /* initialize */
4646 1113 : context->state = state;
4647 1113 : context->content = content;
4648 1113 : context->crc = crc;
4649 1113 : context->f = f;
4650 :
4651 : #if HAVE_MT_VERIFY
4652 1113 : thread_create(&context->thread, state_verify_thread, context);
4653 : #else
4654 : context->retval = state_verify_thread(context);
4655 : #endif
4656 :
4657 1113 : i = i->next;
4658 : }
4659 :
4660 : /* join all thread */
4661 179 : fail = 0;
4662 179 : i = tommy_list_head(&state->contentlist);
4663 1292 : while (i) {
4664 1113 : struct snapraid_content* content = i->data;
4665 1113 : struct state_verify_thread_context* context = content->context;
4666 : void* retval;
4667 :
4668 : #if HAVE_MT_VERIFY
4669 1113 : thread_join(context->thread, &retval);
4670 : #else
4671 : retval = context->retval;
4672 : #endif
4673 1113 : if (retval) {
4674 : /* LCOV_EXCL_START */
4675 : fail = 1;
4676 : /* LCOV_EXCL_STOP */
4677 : } else {
4678 1113 : STREAM* f = context->f;
4679 :
4680 1113 : if (sclose(f) != 0) {
4681 : /* LCOV_EXCL_START */
4682 : log_fatal(errno, "Error closing the content file. %s.\n", strerror(errno));
4683 : exit(EXIT_FAILURE);
4684 : /* LCOV_EXCL_STOP */
4685 : }
4686 : }
4687 :
4688 1113 : free(context);
4689 :
4690 1113 : i = i->next;
4691 : }
4692 :
4693 : /* abort on failure */
4694 179 : if (fail) {
4695 : /* LCOV_EXCL_START */
4696 : exit(EXIT_FAILURE);
4697 : /* LCOV_EXCL_STOP */
4698 : }
4699 179 : }
4700 :
4701 179 : static void state_rename_content(struct snapraid_state* state)
4702 : {
4703 : tommy_node* i;
4704 :
4705 : #if defined(_linux) /* this sequence is linux specific */
4706 : i = tommy_list_head(&state->contentlist);
4707 : while (i) {
4708 : struct snapraid_content* content = i->data;
4709 : char tmp[PATH_MAX];
4710 : char dir[PATH_MAX];
4711 : char* slash;
4712 : int handle;
4713 :
4714 : pathcpy(dir, sizeof(dir), content->content);
4715 :
4716 : slash = strrchr(tmp, '/');
4717 : if (slash)
4718 : *slash = 0;
4719 : else
4720 : pathcpy(dir, sizeof(dir), ".");
4721 :
4722 : /* open the directory to get the handle */
4723 : handle = open(dir, O_RDONLY | O_DIRECTORY);
4724 : if (handle < 0) {
4725 : /* LCOV_EXCL_START */
4726 : log_fatal(errno, "Error opening the directory '%s'. %s.\n", dir, strerror(errno));
4727 : exit(EXIT_FAILURE);
4728 : /* LCOV_EXCL_STOP */
4729 : }
4730 :
4731 : /* now rename the just written copy with the correct name */
4732 : pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
4733 : if (rename(tmp, content->content) != 0) {
4734 : /* LCOV_EXCL_START */
4735 : log_fatal(errno, "Error renaming the content file '%s' to '%s'. %s.\n", tmp, content->content, strerror(errno));
4736 : exit(EXIT_FAILURE);
4737 : /* LCOV_EXCL_STOP */
4738 : }
4739 :
4740 : /* sync the directory */
4741 : if (fsync(handle) != 0) {
4742 : /* LCOV_EXCL_START */
4743 : log_fatal(errno, "Error syncing the directory '%s'. %s.\n", dir, strerror(errno));
4744 : exit(EXIT_FAILURE);
4745 : /* LCOV_EXCL_STOP */
4746 : }
4747 :
4748 : if (close(handle) != 0) {
4749 : /* LCOV_EXCL_START */
4750 : log_fatal(errno, "Error closing the directory '%s'. %s.\n", dir, strerror(errno));
4751 : exit(EXIT_FAILURE);
4752 : /* LCOV_EXCL_STOP */
4753 : }
4754 :
4755 : i = i->next;
4756 : }
4757 : #else
4758 179 : i = tommy_list_head(&state->contentlist);
4759 1292 : while (i) {
4760 1113 : struct snapraid_content* content = i->data;
4761 : char tmp[PATH_MAX];
4762 :
4763 : /* now renames the just written copy with the correct name */
4764 1113 : pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
4765 1113 : if (rename(tmp, content->content) != 0) {
4766 : /* LCOV_EXCL_START */
4767 : log_fatal(errno, "Error renaming the content file '%s' to '%s' in rename(). %s.\n", tmp, content->content, strerror(errno));
4768 : exit(EXIT_FAILURE);
4769 : /* LCOV_EXCL_STOP */
4770 : }
4771 :
4772 1113 : i = i->next;
4773 : }
4774 : #endif
4775 179 : }
4776 :
4777 179 : void state_write(struct snapraid_state* state)
4778 : {
4779 : uint32_t crc;
4780 : time_t now;
4781 :
4782 : /* write all the content files */
4783 179 : state_write_content(state, &crc);
4784 :
4785 : /* verify the just written files */
4786 179 : state_verify_content(state, crc);
4787 :
4788 : /* rename the new files, over the old ones */
4789 179 : state_rename_content(state);
4790 :
4791 : /* log the write time of the content file */
4792 179 : now = time(0);
4793 179 : log_tag("content_info:write_unixtime:%" PRId64 "\n", (int64_t)now);
4794 179 : log_flush();
4795 :
4796 179 : state->need_write = 0; /* no write needed anymore */
4797 179 : state->checked_read = 0; /* what we wrote is not checked in read */
4798 179 : state->written = 1;
4799 179 : }
4800 :
4801 100 : void state_commit(struct snapraid_state* state)
4802 : {
4803 : tommy_node* i;
4804 :
4805 : /* for each disk */
4806 675 : for (i = state->disklist; i != 0; i = i->next) {
4807 575 : struct snapraid_disk* disk = i->data;
4808 :
4809 : /* clear the dealloc list */
4810 575 : tommy_list_foreach(&disk->dealloclist, (tommy_foreach_func*)dealloc_free);
4811 575 : tommy_list_init(&disk->dealloclist);
4812 : }
4813 100 : }
4814 :
4815 137 : void state_skip(struct snapraid_state* state)
4816 : {
4817 : tommy_node* i;
4818 :
4819 : /* for each disk */
4820 925 : for (i = state->disklist; i != 0; i = i->next) {
4821 : tommy_node* j;
4822 788 : struct snapraid_disk* disk = i->data;
4823 :
4824 788 : if (!disk->skip_access)
4825 787 : continue;
4826 :
4827 : /* for each file */
4828 1885 : for (j = tommy_list_head(&disk->filelist); j != 0; j = j->next) {
4829 1884 : struct snapraid_file* file = j->data;
4830 1884 : file_flag_set(file, FILE_IS_EXCLUDED);
4831 : }
4832 :
4833 : /* for each link */
4834 57 : for (j = tommy_list_head(&disk->linklist); j != 0; j = j->next) {
4835 56 : struct snapraid_link* slink = j->data;
4836 56 : link_flag_set(slink, FILE_IS_EXCLUDED);
4837 : }
4838 :
4839 : /* for each dir */
4840 1 : for (j = tommy_list_head(&disk->dirlist); j != 0; j = j->next) {
4841 0 : struct snapraid_dir* dir = j->data;
4842 0 : dir_flag_set(dir, FILE_IS_EXCLUDED);
4843 : }
4844 : }
4845 137 : }
4846 :
4847 137 : void state_filter(struct snapraid_state* state, tommy_list* filterlist_file, tommy_list* filterlist_disk, int filter_missing, int filter_error)
4848 : {
4849 : tommy_node* i;
4850 : unsigned l;
4851 :
4852 : /* if no filter, include all */
4853 137 : if (!filter_missing && !filter_error && tommy_list_empty(filterlist_file) && tommy_list_empty(filterlist_disk))
4854 122 : return;
4855 :
4856 15 : msg_progress("Selecting...\n");
4857 :
4858 29 : for (i = tommy_list_head(filterlist_disk); i != 0; i = i->next) {
4859 14 : struct snapraid_filter* filter = i->data;
4860 14 : msg_verbose("\t%s%s\n", filter->pattern, filter->is_disk ? "//" : "");
4861 : }
4862 18 : for (i = tommy_list_head(filterlist_file); i != 0; i = i->next) {
4863 3 : struct snapraid_filter* filter = i->data;
4864 3 : msg_verbose("\t%s%s\n", filter->pattern, filter->is_dir ? "/" : "");
4865 : }
4866 15 : if (filter_missing)
4867 2 : msg_verbose("\t<missing>\n");
4868 15 : if (filter_error)
4869 2 : msg_verbose("\t<error>\n");
4870 :
4871 : /* for each disk */
4872 90 : for (i = state->disklist; i != 0; i = i->next) {
4873 : tommy_node* j;
4874 75 : struct snapraid_disk* disk = i->data;
4875 :
4876 : /* if we filter for presence, we have to access the disk, so better to print something */
4877 75 : if (filter_missing)
4878 12 : msg_progress("Scanning disk %s...\n", disk->name);
4879 :
4880 : /* for each file */
4881 164081 : for (j = tommy_list_head(&disk->filelist); j != 0; j = j->next) {
4882 164006 : struct snapraid_file* file = j->data;
4883 :
4884 164006 : if (filter_path(filterlist_disk, 0, disk->name, file->sub) != 0
4885 85645 : || filter_path(filterlist_file, 0, disk->name, file->sub) != 0
4886 82801 : || filter_existence(filter_missing, disk->dir, file->sub) != 0
4887 43605 : || filter_correctness(filter_error, &state->infoarr, disk, file) != 0
4888 : ) {
4889 134658 : file_flag_set(file, FILE_IS_EXCLUDED);
4890 : }
4891 : }
4892 :
4893 : /* for each link */
4894 5499 : for (j = tommy_list_head(&disk->linklist); j != 0; j = j->next) {
4895 5424 : struct snapraid_link* slink = j->data;
4896 :
4897 5424 : if (filter_path(filterlist_disk, 0, disk->name, slink->sub) != 0
4898 2800 : || filter_path(filterlist_file, 0, disk->name, slink->sub) != 0
4899 2695 : || filter_existence(filter_missing, disk->dir, slink->sub) != 0
4900 : ) {
4901 4113 : link_flag_set(slink, FILE_IS_EXCLUDED);
4902 : }
4903 : }
4904 :
4905 : /* for each empty dir */
4906 107 : for (j = tommy_list_head(&disk->dirlist); j != 0; j = j->next) {
4907 32 : struct snapraid_dir* dir = j->data;
4908 :
4909 32 : if (filter_emptydir(filterlist_disk, 0, disk->name, dir->sub) != 0
4910 17 : || filter_emptydir(filterlist_file, 0, disk->name, dir->sub) != 0
4911 17 : || filter_existence(filter_missing, disk->dir, dir->sub) != 0
4912 : ) {
4913 23 : dir_flag_set(dir, FILE_IS_EXCLUDED);
4914 : }
4915 : }
4916 : }
4917 :
4918 : /* if we are filtering by disk, exclude any parity not explicitly included */
4919 15 : if (!tommy_list_empty(filterlist_disk)) {
4920 : /* for each parity disk */
4921 34 : for (l = 0; l < state->level; ++l) {
4922 : /* check if the parity is excluded by name */
4923 24 : if (filter_path(filterlist_disk, 0, lev_config_name(l), 0) != 0) {
4924 : /* excluded the parity from further operation */
4925 18 : state->parity[l].is_excluded_by_filter = 1;
4926 : }
4927 : }
4928 : } else {
4929 : /* if we are filtering by file, exclude all parity */
4930 5 : if (filter_missing || !tommy_list_empty(filterlist_file)) {
4931 : /* for each parity disk */
4932 16 : for (l = 0; l < state->level; ++l) {
4933 13 : state->parity[l].is_excluded_by_filter = 1;
4934 : }
4935 : }
4936 : }
4937 : }
4938 :
4939 254 : int state_progress_begin(struct snapraid_state* state, block_off_t blockstart, block_off_t blockmax, block_off_t countmax)
4940 : {
4941 : time_t now;
4942 :
4943 254 : if (state->opt.gui) {
4944 5 : log_tag("run:begin:%u:%u:%u\n", blockstart, blockmax, countmax);
4945 5 : log_flush();
4946 : }
4947 :
4948 254 : now = time(0);
4949 :
4950 254 : state->progress_whole_start = now;
4951 254 : state->progress_tick = 0;
4952 254 : state->progress_ptr = 0;
4953 254 : state->progress_wasted = 0;
4954 :
4955 : /* start of thermal control */
4956 254 : if (!state_thermal_begin(state, now))
4957 0 : return -1;
4958 :
4959 : /* stop if requested */
4960 254 : if (global_interrupt) {
4961 : /* LCOV_EXCL_START */
4962 : if (!state->opt.gui) {
4963 : msg_status("Not starting due to interruption\n");
4964 : }
4965 : log_tag("sigint:0: SIGINT received\n");
4966 : log_flush();
4967 : return 1;
4968 : /* LCOV_EXCL_STOP */
4969 : }
4970 :
4971 254 : return 0;
4972 : }
4973 :
4974 254 : void state_progress_end(struct snapraid_state* state, block_off_t countpos, block_off_t countmax, data_off_t countsize, const char* msg)
4975 : {
4976 254 : if (state->opt.gui) {
4977 5 : log_tag("run:end\n");
4978 5 : log_flush();
4979 249 : } else if (countmax == 0) {
4980 21 : if (state->need_write || state->written) {
4981 16 : msg_status("100%% completed\n");
4982 : } else {
4983 5 : msg_status("%s", msg);
4984 : }
4985 : } else {
4986 : time_t now;
4987 : time_t elapsed;
4988 :
4989 228 : unsigned countsize_MB = (countsize + MEGA - 1) / MEGA;
4990 :
4991 228 : now = time(0);
4992 :
4993 228 : elapsed = now - state->progress_whole_start - state->progress_wasted;
4994 :
4995 228 : msg_bar("%u%% completed, %u MB accessed", muldiv(countpos, 100, countmax), countsize_MB);
4996 :
4997 228 : msg_bar(" in %u:%02u", (unsigned)(elapsed / 3600), (unsigned)((elapsed % 3600) / 60));
4998 :
4999 228 : msg_bar("\n");
5000 228 : msg_flush();
5001 : }
5002 254 : }
5003 :
5004 1 : void state_progress_stop(struct snapraid_state* state)
5005 : {
5006 : time_t now;
5007 :
5008 1 : now = time(0);
5009 :
5010 1 : state->progress_interruption = now;
5011 1 : }
5012 :
5013 1 : void state_progress_restart(struct snapraid_state* state)
5014 : {
5015 : time_t now;
5016 :
5017 1 : now = time(0);
5018 :
5019 : /* reset the progress counter */
5020 1 : state->progress_tick = 0;
5021 1 : state->progress_ptr = 0;
5022 :
5023 1 : if (now >= state->progress_interruption) /* avoid degenerated cases when the clock is manually adjusted */
5024 1 : state->progress_wasted += now - state->progress_interruption;
5025 1 : }
5026 :
5027 : /**
5028 : * Set the latest os_tick data in the progress vector.
5029 : */
5030 551 : static void state_progress_latest(struct snapraid_state* state)
5031 : {
5032 : tommy_node* i;
5033 : unsigned l;
5034 :
5035 551 : state->progress_tick_misc[state->progress_ptr] = state->tick_misc;
5036 551 : state->progress_tick_sched[state->progress_ptr] = state->tick_sched;
5037 551 : state->progress_tick_raid[state->progress_ptr] = state->tick_raid;
5038 551 : state->progress_tick_hash[state->progress_ptr] = state->tick_hash;
5039 551 : state->progress_tick_io[state->progress_ptr] = state->tick_io;
5040 3732 : for (i = state->disklist; i != 0; i = i->next) {
5041 3181 : struct snapraid_disk* disk = i->data;
5042 3181 : disk->progress_tick[state->progress_ptr] = disk->tick;
5043 : }
5044 2933 : for (l = 0; l < state->level; ++l)
5045 2382 : state->parity[l].progress_tick[state->progress_ptr] = state->parity[l].tick;
5046 551 : }
5047 :
5048 : /**
5049 : * Number of columns
5050 : */
5051 : #define GRAPH_COLUMN 78
5052 :
5053 : /**
5054 : * Get the reference value, 0 if index is PROGRESS_MAX
5055 : */
5056 : #define ref(map, index) (index < PROGRESS_MAX ? map[index] : 0)
5057 :
5058 0 : static struct snapraid_thermal* state_thermal_find(struct snapraid_state* state, const char* name)
5059 : {
5060 : tommy_node* i;
5061 0 : struct snapraid_thermal* found = 0;
5062 :
5063 0 : for (i = tommy_list_head(&state->thermallist); i != 0; i = i->next) {
5064 0 : struct snapraid_thermal* thermal = i->data;
5065 0 : if (strcmp(thermal->name, name) == 0) {
5066 0 : if (found == 0
5067 : /* if multiple matches, return the one with highest temperature */
5068 0 : || found->latest_temperature < thermal->latest_temperature
5069 : /* or, if the temperature is equal, the one with the higher r_squared */
5070 0 : || (found->latest_temperature == thermal->latest_temperature && found->params.r_squared < thermal->params.r_squared)
5071 : ) {
5072 0 : found = thermal;
5073 : }
5074 : }
5075 : }
5076 :
5077 0 : return found;
5078 : }
5079 :
5080 : #define THERMAL_PAD " "
5081 :
5082 1 : static void state_progress_graph(struct snapraid_state* state, struct snapraid_io* io, unsigned current, unsigned oldest)
5083 : {
5084 : uint64_t v;
5085 : uint64_t tick_total;
5086 : unsigned bar;
5087 : tommy_node* i;
5088 : unsigned l;
5089 : size_t pad;
5090 : size_t pre;
5091 :
5092 1 : tick_total = 0;
5093 :
5094 1 : tick_total += state->progress_tick_misc[current] - ref(state->progress_tick_misc, oldest);
5095 1 : tick_total += state->progress_tick_sched[current] - ref(state->progress_tick_sched, oldest);
5096 1 : tick_total += state->progress_tick_raid[current] - ref(state->progress_tick_raid, oldest);
5097 1 : tick_total += state->progress_tick_hash[current] - ref(state->progress_tick_hash, oldest);
5098 1 : tick_total += state->progress_tick_io[current] - ref(state->progress_tick_io, oldest);
5099 1 : if (!tick_total)
5100 0 : return;
5101 :
5102 1 : pad = 4;
5103 7 : for (i = state->disklist; i != 0; i = i->next) {
5104 6 : struct snapraid_disk* disk = i->data;
5105 6 : size_t len = strlen(disk->name);
5106 6 : if (pad < len)
5107 1 : pad = len;
5108 : }
5109 7 : for (l = 0; l < state->level; ++l) {
5110 6 : size_t len = strlen(lev_config_name(l));
5111 6 : if (pad < len)
5112 2 : pad = len;
5113 : }
5114 :
5115 : /* extra space */
5116 1 : pad += 1;
5117 :
5118 1 : pre = 4;
5119 1 : if (pad + pre + 30 < GRAPH_COLUMN)
5120 1 : bar = GRAPH_COLUMN - pad - pre;
5121 : else
5122 0 : bar = 30; /* at least a bar of 30 */
5123 :
5124 1 : if (io) {
5125 0 : const char* legend = "cached blocks (instant, more is better)";
5126 :
5127 : /* refresh the cached blocks info */
5128 0 : io_refresh(io);
5129 :
5130 0 : printf("\n");
5131 :
5132 0 : for (i = state->disklist; i != 0; i = i->next) {
5133 0 : struct snapraid_disk* disk = i->data;
5134 0 : v = disk->cached_blocks;
5135 0 : printr(disk->name, pad);
5136 0 : printf("%4" PRIu64 " | ", v);
5137 :
5138 0 : if (disk->progress_file && disk->progress_file->sub)
5139 0 : printf("%s", disk->progress_file->sub);
5140 : else
5141 0 : printf("-");
5142 :
5143 0 : printf("\n");
5144 : }
5145 :
5146 0 : for (l = 0; l < state->level; ++l) {
5147 0 : v = state->parity[l].cached_blocks;
5148 0 : printr(lev_config_name(l), pad);
5149 0 : printf("%4" PRIu64 " | ", v);
5150 0 : printc('o', v * bar / io->io_max);
5151 0 : printf("\n");
5152 : }
5153 :
5154 0 : printc(' ', pad);
5155 0 : printf(" |_");
5156 0 : printc('_', bar);
5157 0 : printf("\n");
5158 :
5159 0 : printc(' ', 5 + pad + 1 + bar / 2 - strlen(legend) / 2);
5160 0 : printf("%s", legend);
5161 0 : printf("\n");
5162 : }
5163 :
5164 1 : printf("\n");
5165 :
5166 1 : pre = 4;
5167 1 : if (state->thermal_temperature_limit != 0)
5168 0 : pre += strlen(THERMAL_PAD);
5169 1 : if (pad + pre + 30 < GRAPH_COLUMN)
5170 1 : bar = GRAPH_COLUMN - pad - pre;
5171 : else
5172 0 : bar = 30; /* at least a bar of 30 */
5173 :
5174 7 : for (i = state->disklist; i != 0; i = i->next) {
5175 6 : struct snapraid_disk* disk = i->data;
5176 :
5177 6 : v = disk->progress_tick[current] - ref(disk->progress_tick, oldest);
5178 6 : printr(disk->name, pad);
5179 :
5180 6 : if (state->thermal_temperature_limit != 0) {
5181 0 : struct snapraid_thermal* thermal = state_thermal_find(state, disk->name);
5182 0 : if (thermal) {
5183 0 : if (thermal->params.r_squared >= THERMAL_R_SQUARED_LIMIT)
5184 0 : printf(" %2u (%2u)", thermal->latest_temperature, (int)thermal->params.t_steady);
5185 : else
5186 0 : printf(" %2u ", thermal->latest_temperature);
5187 : } else {
5188 0 : printf(THERMAL_PAD);
5189 : }
5190 : }
5191 :
5192 6 : printf("%3u%% | ", muldiv(v, 100, tick_total));
5193 6 : printc('*', v * bar / tick_total);
5194 6 : printf("\n");
5195 :
5196 : /* clear the file in progress */
5197 6 : disk->progress_file = 0;
5198 : }
5199 :
5200 7 : for (l = 0; l < state->level; ++l) {
5201 6 : v = state->parity[l].progress_tick[current] - ref(state->parity[l].progress_tick, oldest);
5202 6 : printr(lev_config_name(l), pad);
5203 :
5204 6 : if (state->thermal_temperature_limit != 0) {
5205 0 : struct snapraid_thermal* thermal = state_thermal_find(state, lev_config_name(l));
5206 0 : if (thermal) {
5207 0 : if (thermal->params.r_squared >= THERMAL_R_SQUARED_LIMIT)
5208 0 : printf(" %2u (%2u)", thermal->latest_temperature, (int)thermal->params.t_steady);
5209 : else
5210 0 : printf(" %2u ", thermal->latest_temperature);
5211 : } else {
5212 0 : printf(THERMAL_PAD);
5213 : }
5214 : }
5215 :
5216 6 : printf("%3u%% | ", muldiv(v, 100, tick_total));
5217 6 : printc('*', v * bar / tick_total);
5218 6 : printf("\n");
5219 : }
5220 :
5221 1 : v = state->progress_tick_raid[current] - ref(state->progress_tick_raid, oldest);
5222 1 : printr("raid", pad);
5223 1 : if (state->thermal_temperature_limit != 0)
5224 0 : printf(THERMAL_PAD);
5225 1 : printf("%3u%% | ", muldiv(v, 100, tick_total));
5226 1 : printc('*', v * bar / tick_total);
5227 1 : printf("\n");
5228 :
5229 1 : v = state->progress_tick_hash[current] - ref(state->progress_tick_hash, oldest);
5230 1 : printr("hash", pad);
5231 1 : if (state->thermal_temperature_limit != 0)
5232 0 : printf(THERMAL_PAD);
5233 1 : printf("%3u%% | ", muldiv(v, 100, tick_total));
5234 1 : printc('*', v * bar / tick_total);
5235 1 : printf("\n");
5236 :
5237 1 : v = state->progress_tick_sched[current] - ref(state->progress_tick_sched, oldest);
5238 1 : printr("sched", pad);
5239 1 : if (state->thermal_temperature_limit != 0)
5240 0 : printf(THERMAL_PAD);
5241 1 : printf("%3u%% | ", muldiv(v, 100, tick_total));
5242 1 : printc('*', v * bar / tick_total);
5243 1 : printf("\n");
5244 :
5245 1 : v = state->progress_tick_misc[current] - ref(state->progress_tick_misc, oldest);
5246 1 : printr("misc", pad);
5247 1 : if (state->thermal_temperature_limit != 0)
5248 0 : printf(THERMAL_PAD);
5249 1 : printf("%3u%% | ", muldiv(v, 100, tick_total));
5250 1 : printc('*', v * bar / tick_total);
5251 1 : printf("\n");
5252 :
5253 1 : printc(' ', pad);
5254 1 : if (state->thermal_temperature_limit != 0)
5255 0 : printf(THERMAL_PAD);
5256 1 : printf(" |_");
5257 1 : printc('_', bar);
5258 1 : printf("\n");
5259 :
5260 1 : if (oldest == PROGRESS_MAX) {
5261 1 : const char* legend = "wait time (total, less is better)";
5262 1 : if (state->thermal_temperature_limit != 0)
5263 0 : printf(THERMAL_PAD);
5264 1 : printc(' ', 5 + pad + 1 + bar / 2 - strlen(legend) / 2);
5265 1 : printf("%s", legend);
5266 1 : printf("\n");
5267 : } else {
5268 0 : const char* legend_d = "wait time (last %d secs, less is better)";
5269 0 : if (state->thermal_temperature_limit != 0)
5270 0 : printf(THERMAL_PAD);
5271 0 : printc(' ', 5 + pad + 1 + bar / 2 - strlen(legend_d) / 2);
5272 0 : printf(legend_d, PROGRESS_MAX);
5273 0 : printf("\n");
5274 : }
5275 :
5276 1 : printf("\n");
5277 : }
5278 :
5279 1134270 : int state_progress(struct snapraid_state* state, struct snapraid_io* io, block_off_t blockpos, block_off_t countpos, block_off_t countmax, data_off_t countsize)
5280 : {
5281 : time_t now;
5282 : int pred;
5283 :
5284 1134270 : now = time(0);
5285 :
5286 : /* thermal measure */
5287 1134270 : if (now > state->thermal_latest + THERMAL_PERIOD_SECONDS || state->opt.fake_device) {
5288 2641 : state->thermal_latest = now;
5289 2641 : state_thermal(state, now);
5290 : }
5291 :
5292 : /* previous position */
5293 1134270 : pred = state->progress_ptr + PROGRESS_MAX - 1;
5294 1134270 : if (pred >= PROGRESS_MAX)
5295 1134040 : pred -= PROGRESS_MAX;
5296 :
5297 : /* if the previous measure is different */
5298 1134270 : if (state->progress_tick == 0
5299 1134040 : || state->progress_time[pred] != now
5300 : ) {
5301 : time_t elapsed;
5302 448 : unsigned out_perc = 0;
5303 448 : unsigned out_size_speed = 0;
5304 448 : unsigned out_block_speed = 0;
5305 448 : unsigned out_cpu = 0;
5306 448 : unsigned out_eta = 0;
5307 448 : int out_temperature = 0;
5308 448 : int out_steady = 0;
5309 448 : int out_computed = 0;
5310 :
5311 : /* store the new measure */
5312 448 : state->progress_time[state->progress_ptr] = now;
5313 448 : state->progress_pos[state->progress_ptr] = countpos;
5314 448 : state->progress_size[state->progress_ptr] = countsize;
5315 448 : state_progress_latest(state);
5316 :
5317 448 : elapsed = now - state->progress_whole_start - state->progress_wasted;
5318 :
5319 : /* completion percentage */
5320 448 : if (countmax)
5321 448 : out_perc = muldiv(countpos, 100, countmax);
5322 :
5323 : /* if we have at least 5 measures */
5324 448 : if (state->progress_tick >= 5
5325 : /* or if we are running in test mode, with at least one measure */
5326 421 : || (state->opt.force_progress && state->progress_tick >= 1)
5327 : ) {
5328 : int oldest;
5329 : int past;
5330 : time_t delta_time;
5331 : block_off_t delta_pos;
5332 : data_off_t delta_size;
5333 : uint64_t tick_cpu;
5334 : uint64_t tick_total;
5335 : uint64_t delta_tick_cpu;
5336 : uint64_t delta_tick_total;
5337 : uint64_t oldest_tick_cpu;
5338 : uint64_t oldest_tick_total;
5339 : tommy_node* i;
5340 :
5341 : /* number of past measures */
5342 205 : past = state->progress_tick;
5343 :
5344 : /*
5345 : * Drop the oldest ones, to promptly
5346 : * skip the startup phase
5347 : */
5348 205 : past -= past / 5;
5349 :
5350 : /* check how much we can go in the past */
5351 205 : if (past >= PROGRESS_MAX - 1) {
5352 : /*
5353 : * The vector is filled, so we are already in position
5354 : * to get the possible oldest one
5355 : */
5356 0 : oldest = state->progress_ptr + 1;
5357 : } else {
5358 : /* go backward the number of positions selected */
5359 205 : oldest = state->progress_ptr + PROGRESS_MAX - past;
5360 : }
5361 205 : if (oldest >= PROGRESS_MAX)
5362 205 : oldest -= PROGRESS_MAX;
5363 :
5364 205 : tick_cpu = state->progress_tick_misc[state->progress_ptr]
5365 205 : + state->progress_tick_sched[state->progress_ptr]
5366 205 : + state->progress_tick_raid[state->progress_ptr]
5367 205 : + state->progress_tick_hash[state->progress_ptr];
5368 205 : tick_total = tick_cpu + state->progress_tick_io[state->progress_ptr];
5369 :
5370 205 : oldest_tick_cpu = state->progress_tick_misc[oldest]
5371 205 : + state->progress_tick_sched[oldest]
5372 205 : + state->progress_tick_raid[oldest]
5373 205 : + state->progress_tick_hash[oldest];
5374 205 : oldest_tick_total = oldest_tick_cpu + state->progress_tick_io[oldest];
5375 :
5376 205 : delta_time = now - state->progress_time[oldest];
5377 205 : delta_pos = countpos - state->progress_pos[oldest];
5378 205 : delta_size = countsize - state->progress_size[oldest];
5379 205 : delta_tick_cpu = tick_cpu - oldest_tick_cpu;
5380 205 : delta_tick_total = tick_total - oldest_tick_total;
5381 :
5382 : /* estimate the speed in MB/s */
5383 205 : if (delta_time != 0)
5384 205 : out_size_speed = (unsigned)(delta_size / MEGA / delta_time);
5385 :
5386 : /* estimate the speed in block/s */
5387 205 : if (delta_time != 0)
5388 205 : out_block_speed = (unsigned)(delta_pos / delta_time);
5389 :
5390 : /* estimate the cpu usage percentage */
5391 205 : if (delta_tick_total != 0)
5392 69 : out_cpu = muldiv(delta_tick_cpu, 100, delta_tick_total);
5393 :
5394 : /* estimate the remaining time in minutes */
5395 205 : if (delta_pos != 0)
5396 205 : out_eta = muldiv(countmax - countpos, delta_time, delta_pos);
5397 :
5398 205 : if (state->opt.force_stats) {
5399 0 : os_clear();
5400 0 : state_progress_graph(state, io, state->progress_ptr, oldest);
5401 : }
5402 :
5403 205 : if (state->thermal_temperature_limit != 0) {
5404 : /* get the max temperature */
5405 4 : out_temperature = 0;
5406 124 : for (i = tommy_list_head(&state->thermallist); i != 0; i = i->next) {
5407 120 : struct snapraid_thermal* thermal = i->data;
5408 120 : if (out_temperature < thermal->latest_temperature) {
5409 4 : out_temperature = thermal->latest_temperature;
5410 4 : if (thermal->params.r_squared >= THERMAL_R_SQUARED_LIMIT)
5411 4 : out_steady = thermal->params.t_steady;
5412 : else
5413 0 : out_steady = 0;
5414 : }
5415 : }
5416 : } else {
5417 201 : out_temperature = 0;
5418 201 : out_steady = 0;
5419 : }
5420 :
5421 : /* we have the output value */
5422 205 : out_computed = 1;
5423 : }
5424 :
5425 448 : if (state->opt.gui) {
5426 : char str_eta[32];
5427 : char str_size_speed[32];
5428 : char str_cpu[32];
5429 : char str_temp[32];
5430 : char str_steady[32];
5431 6 : if (out_computed) {
5432 1 : snprintf(str_eta, sizeof(str_eta), "%u", out_eta);
5433 1 : snprintf(str_size_speed, sizeof(str_size_speed), "%u", out_size_speed);
5434 1 : snprintf(str_cpu, sizeof(str_cpu), "%u", out_cpu);
5435 : } else {
5436 5 : str_eta[0] = 0;
5437 5 : str_size_speed[0] = 0;
5438 5 : str_cpu[0] = 0;
5439 : }
5440 6 : if (out_temperature) {
5441 0 : snprintf(str_temp, sizeof(str_eta), "%u", out_temperature);
5442 : } else {
5443 6 : str_temp[0] = 0;
5444 : }
5445 6 : if (out_steady) {
5446 0 : snprintf(str_steady, sizeof(str_steady), "%u", out_steady);
5447 : } else {
5448 6 : str_steady[0] = 0;
5449 : }
5450 6 : log_tag("run:pos:%u:%u:%" PRIu64 ":%u:%s:%s:%s:%" PRIu64 ":%s:%s\n", blockpos, countpos, countsize, out_perc, str_eta, str_size_speed, str_cpu, (uint64_t)elapsed, str_temp, str_steady);
5451 6 : log_flush();
5452 : } else {
5453 442 : msg_bar("%u%%, %u MB", out_perc, (unsigned)(countsize / MEGA));
5454 442 : if (out_computed) {
5455 204 : msg_bar(", %u MB/s", out_size_speed);
5456 204 : msg_bar(", %u stripe/s", out_block_speed);
5457 204 : msg_bar(", CPU %u%%", out_cpu);
5458 204 : if (out_temperature != 0) {
5459 4 : if (out_steady != 0)
5460 4 : msg_bar(", Tmax %u (%u)", out_temperature, out_steady);
5461 : else
5462 0 : msg_bar(", Tmax %u", out_temperature);
5463 : }
5464 204 : unsigned out_eta_minutes = out_eta / 60; /* minutes */
5465 204 : msg_bar(", %u:%02u ETA", out_eta_minutes / 60, out_eta_minutes % 60);
5466 : }
5467 442 : msg_bar("\r");
5468 442 : msg_flush();
5469 : }
5470 :
5471 : /* next position to fill */
5472 448 : ++state->progress_ptr;
5473 448 : if (state->progress_ptr >= PROGRESS_MAX)
5474 0 : state->progress_ptr -= PROGRESS_MAX;
5475 :
5476 : /* one more measure */
5477 448 : ++state->progress_tick;
5478 : }
5479 :
5480 : /* stop if requested */
5481 1134270 : if (global_interrupt) {
5482 : /* LCOV_EXCL_START */
5483 : if (!state->opt.gui) {
5484 : log_fatal(EUSER, "\n");
5485 : log_fatal(EUSER, "Stopping for interruption at block %u\n", blockpos);
5486 : }
5487 : log_tag("sigint:%u: SIGINT received\n", blockpos);
5488 : log_flush();
5489 : return 1;
5490 : /* LCOV_EXCL_STOP */
5491 : }
5492 :
5493 1134270 : return 0;
5494 : }
5495 :
5496 123 : void state_usage_waste(struct snapraid_state* state)
5497 : {
5498 123 : uint64_t now = os_tick();
5499 :
5500 123 : state->tick_last = now;
5501 123 : }
5502 :
5503 1170202 : void state_usage_misc(struct snapraid_state* state)
5504 : {
5505 1170202 : uint64_t now = os_tick();
5506 1170202 : uint64_t delta = now - state->tick_last;
5507 :
5508 : /* increment the time spent in computations */
5509 1170202 : state->tick_misc += delta;
5510 :
5511 1170202 : state->tick_last = now;
5512 1170202 : }
5513 :
5514 168059 : void state_usage_sched(struct snapraid_state* state)
5515 : {
5516 168059 : uint64_t now = os_tick();
5517 168059 : uint64_t delta = now - state->tick_last;
5518 :
5519 : /* increment the time spent in computations */
5520 168059 : state->tick_sched += delta;
5521 :
5522 168059 : state->tick_last = now;
5523 168059 : }
5524 :
5525 158455 : void state_usage_raid(struct snapraid_state* state)
5526 : {
5527 158455 : uint64_t now = os_tick();
5528 158455 : uint64_t delta = now - state->tick_last;
5529 :
5530 : /* increment the time spent in computations */
5531 158455 : state->tick_raid += delta;
5532 :
5533 158455 : state->tick_last = now;
5534 158455 : }
5535 :
5536 831491 : void state_usage_hash(struct snapraid_state* state)
5537 : {
5538 831491 : uint64_t now = os_tick();
5539 831491 : uint64_t delta = now - state->tick_last;
5540 :
5541 : /* increment the time spent in computations */
5542 831491 : state->tick_hash += delta;
5543 :
5544 831491 : state->tick_last = now;
5545 831491 : }
5546 :
5547 996594 : void state_usage_file(struct snapraid_state* state, struct snapraid_disk* disk, struct snapraid_file* file)
5548 : {
5549 : (void)state;
5550 :
5551 996594 : disk->progress_file = file;
5552 996594 : }
5553 :
5554 1001281 : void state_usage_disk(struct snapraid_state* state, struct snapraid_handle* handle_map, unsigned* waiting_map, unsigned waiting_mac)
5555 : {
5556 1001281 : uint64_t now = os_tick();
5557 1001281 : uint64_t delta = now - state->tick_last;
5558 : unsigned i;
5559 :
5560 : /* increment the time spent in the data disks */
5561 1071739 : for (i = 0; i < waiting_mac; ++i) {
5562 70458 : struct snapraid_disk* disk = handle_map[waiting_map[i]].disk;
5563 :
5564 70458 : if (!disk)
5565 0 : continue;
5566 :
5567 70458 : disk->tick += delta;
5568 : }
5569 1001281 : state->tick_io += delta;
5570 :
5571 1001281 : state->tick_last = now;
5572 1001281 : }
5573 :
5574 802781 : void state_usage_parity(struct snapraid_state* state, unsigned* waiting_map, unsigned waiting_mac)
5575 : {
5576 802781 : uint64_t now = os_tick();
5577 802781 : uint64_t delta = now - state->tick_last;
5578 : unsigned i;
5579 :
5580 : /* increment the time spent in the parity disk */
5581 821832 : for (i = 0; i < waiting_mac; ++i)
5582 19051 : state->parity[waiting_map[i]].tick += delta;
5583 802781 : state->tick_io += delta;
5584 :
5585 802781 : state->tick_last = now;
5586 802781 : }
5587 :
5588 103 : void state_usage_print(struct snapraid_state* state)
5589 : {
5590 : /* set the latest data */
5591 103 : state_progress_latest(state);
5592 :
5593 103 : if (msg_level < MSG_PROGRESS)
5594 102 : return;
5595 :
5596 : /* print a graph for it */
5597 1 : state_progress_graph(state, 0, state->progress_ptr, PROGRESS_MAX);
5598 : }
5599 :
5600 587 : void state_fscheck(struct snapraid_state* state, const char* ope)
5601 : {
5602 : tommy_node* i;
5603 :
5604 : /* check the file-system on all disks */
5605 3956 : for (i = state->disklist; i != 0; i = i->next) {
5606 3369 : struct snapraid_disk* disk = i->data;
5607 :
5608 3369 : if (fs_check(disk) != 0) {
5609 : /* LCOV_EXCL_START */
5610 : log_fatal(EINTERNAL, "Internal inconsistency: File-system check for disk '%s' %s\n", disk->name, ope);
5611 : os_abort();
5612 : /* LCOV_EXCL_STOP */
5613 : }
5614 : }
5615 587 : }
5616 :
5617 1 : void generate_configuration(const char* path)
5618 : {
5619 : struct snapraid_state state;
5620 : struct snapraid_content* content;
5621 : char esc_buffer[ESC_MAX];
5622 : unsigned l, s;
5623 : tommy_node* j;
5624 :
5625 1 : state_init(&state);
5626 :
5627 : /* mark that we are without a configuration file */
5628 1 : state.no_conf = 1;
5629 :
5630 : /* create the dummy content entry */
5631 1 : content = content_alloc(path, -1);
5632 :
5633 : /* adds the content entry */
5634 1 : tommy_list_insert_tail(&state.contentlist, &content->node, content);
5635 :
5636 : /* read the content file */
5637 1 : state_read(&state);
5638 :
5639 : /* output a dummy configuration file */
5640 1 : printf("# Configuration file generated from %s\n", path);
5641 1 : printf("\n");
5642 1 : printf("# Use this blocksize\n");
5643 1 : printf("blocksize %u\n", state.block_size / KIBI);
5644 1 : printf("\n");
5645 1 : printf("# Use this hashsize\n");
5646 1 : printf("hashsize %u\n", BLOCK_HASH_SIZE);
5647 1 : printf("\n");
5648 7 : for (l = 0; l < state.level; ++l) {
5649 6 : printf("# Set the correct path for the %s files\n", lev_name(l));
5650 6 : printf("# You had %u of them:\n", state.parity[l].split_mac);
5651 30 : for (s = 0; s < state.parity[l].split_mac; ++s) {
5652 24 : printf("# %u:\n", s);
5653 24 : printf("# PATH:");
5654 24 : if (state.parity[l].split_map[s].path[0])
5655 24 : printf("%s", state.parity[l].split_map[s].path);
5656 : else
5657 0 : printf("?");
5658 24 : printf("\n");
5659 24 : printf("# SIZE:");
5660 24 : if (state.parity[l].split_map[s].size != PARITY_SIZE_INVALID)
5661 24 : printf("%" PRIu64, state.parity[l].split_map[s].size);
5662 : else
5663 0 : printf("?");
5664 24 : printf("\n");
5665 24 : printf("# UUID:");
5666 24 : if (state.parity[l].split_map[s].uuid[0])
5667 24 : printf("%s", state.parity[l].split_map[s].uuid);
5668 : else
5669 0 : printf("?");
5670 24 : printf("\n");
5671 24 : printf("#\n");
5672 : }
5673 6 : printf("%s ENTER_HERE_THE_PARITY_FILES_COMMA_SEPARATED\n", lev_config_name(l));
5674 6 : printf("\n");
5675 : }
5676 1 : printf("# Add any other content file\n");
5677 1 : printf("content %s\n", path);
5678 1 : printf("\n");
5679 7 : for (j = state.maplist; j; j = j->next) {
5680 6 : struct snapraid_map* map = j->data;
5681 : struct snapraid_disk* disk;
5682 6 : printf("# Set the correct dir for disk '%s'\n", map->name);
5683 6 : if (map->uuid[0])
5684 6 : printf("# Disk '%s' is the one with id '%s'\n", map->name, map->uuid);
5685 6 : disk = find_disk_by_name(&state, map->name);
5686 6 : if (disk && disk->filelist) {
5687 6 : struct snapraid_file* file = disk->filelist->data;
5688 6 : if (file) {
5689 6 : printf("# and containing: %s\n", fmt_poll(disk, file->sub, esc_buffer));
5690 : }
5691 : }
5692 6 : printf("data %s ENTER_HERE_THE_DIR\n", map->name);
5693 6 : printf("\n");
5694 : }
5695 :
5696 1 : state_done(&state);
5697 1 : }
5698 :
5699 :
5700 : /**
5701 : * Flush and sync the parity files.
5702 : */
5703 101 : int state_flush(struct snapraid_state* state, struct snapraid_io* io, struct snapraid_parity_handle* parity_handle, block_off_t blockcur)
5704 : {
5705 : unsigned l;
5706 :
5707 101 : if (io)
5708 101 : io_flush(io);
5709 :
5710 : /* flush all parity handles to ensure data is written to disk */
5711 622 : for (l = 0; l < state->level; ++l) {
5712 521 : int ret = parity_sync(&parity_handle[l]);
5713 521 : if (ret == -1) {
5714 : /* LCOV_EXCL_START */
5715 : log_tag("parity_error_io:%u:%s: Sync error. %s.\n", blockcur, lev_config_name(l), strerror(errno));
5716 : log_fatal(errno, "DANGER! Unexpected input/output error in disk %s. It isn't possible to continue.\n", lev_config_name(l));
5717 : return -1;
5718 : /* LCOV_EXCL_STOP */
5719 : }
5720 : }
5721 :
5722 101 : return 0;
5723 : }
5724 :
5725 66 : void state_load_ignore_file(tommy_list* filter_list, const char* path, const char* sub)
5726 : {
5727 : STREAM* f;
5728 : int line;
5729 :
5730 66 : f = sopen_read(path, 0);
5731 66 : if (!f) {
5732 : /* LCOV_EXCL_START */
5733 : log_error(errno, "Error opening the ignore file '%s'. %s.\n", path, strerror(errno));
5734 : return;
5735 : /* LCOV_EXCL_STOP */
5736 : }
5737 :
5738 66 : line = 1;
5739 66 : while (1) {
5740 : char buffer[PATH_MAX];
5741 : int ret;
5742 : int c;
5743 :
5744 : /* skip initial spaces */
5745 132 : sgetspace(f);
5746 :
5747 : /* get the whole line */
5748 132 : ret = sgetline(f, buffer, sizeof(buffer));
5749 132 : if (ret < 0) {
5750 : /* LCOV_EXCL_START */
5751 : log_error(EUSER, "Too long line in '%s' at line %u\n", path, line);
5752 : break;
5753 : /* LCOV_EXCL_STOP */
5754 : }
5755 :
5756 132 : if (buffer[0] == 0) {
5757 : /* allow empty lines */
5758 66 : } else if (buffer[0] == '#') {
5759 : /* ignore comment lines */
5760 : } else {
5761 : struct snapraid_filter* filter;
5762 :
5763 66 : filter = filter_alloc_file(-1, sub, buffer);
5764 66 : if (!filter) {
5765 : /* LCOV_EXCL_START */
5766 : log_error(EUSER, "Invalid ignore specification '%s' in '%s' at line %u\n", buffer, path, line);
5767 : break;
5768 : /* LCOV_EXCL_STOP */
5769 : }
5770 :
5771 66 : tommy_list_insert_tail(filter_list, &filter->node, filter);
5772 : }
5773 :
5774 : /* next line */
5775 132 : c = sgeteol(f);
5776 132 : if (c == EOF) {
5777 66 : break;
5778 : }
5779 66 : if (c != '\n') {
5780 : /* LCOV_EXCL_START */
5781 : log_error(EUSER, "Extra data in '%s' at line %u\n", path, line);
5782 : break;
5783 : /* LCOV_EXCL_STOP */
5784 : }
5785 66 : ++line;
5786 : }
5787 :
5788 66 : if (serror(f)) {
5789 : /* LCOV_EXCL_START */
5790 : log_error(errno, "Error reading the ignore file '%s' at line %u\n", path, line);
5791 : /* LCOV_EXCL_STOP */
5792 : }
5793 :
5794 66 : sclose(f);
5795 : }
5796 :
5797 : #define SNAPSHOT_CONTAINER ".snapraid"
5798 : #define SNAPSHOT_PENDING "pending"
5799 : #define SNAPSHOT_STABLE "stable"
5800 :
5801 54 : static int state_snapshot_dir(const char* dir, struct snapraid_disk* disk)
5802 : {
5803 : struct stat st;
5804 :
5805 54 : if (lstat(dir, &st) != 0)
5806 9 : return -1;
5807 :
5808 45 : if (!S_ISDIR(st.st_mode))
5809 0 : return -1;
5810 :
5811 45 : if (disk) {
5812 43 : pathcpy(disk->dir, sizeof(disk->dir), dir);
5813 43 : disk->dir_device = st.st_dev;
5814 : }
5815 :
5816 45 : return 0;
5817 : }
5818 :
5819 107 : int state_snapshot_new(struct snapraid_state* state)
5820 : {
5821 107 : if (!state->snapshot)
5822 97 : return 0;
5823 :
5824 40 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
5825 30 : struct snapraid_disk* disk = i->data;
5826 : char root[PATH_MAX];
5827 : char container[PATH_MAX];
5828 : char vol[PATH_MAX];
5829 : int ret;
5830 :
5831 : /* check if it supports snapshot */
5832 30 : if (fssnapshot(disk->mount_point, root, sizeof(root)) != 0)
5833 0 : continue;
5834 :
5835 30 : size_t root_len = strlen(root);
5836 :
5837 30 : pathcpy(container, sizeof(container), root);
5838 30 : pathcat(container, sizeof(container), SNAPSHOT_CONTAINER);
5839 :
5840 : /* create the snapshot container directory if not existing */
5841 30 : ret = mkdir(container, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
5842 30 : if (ret != 0 && errno != EEXIST) {
5843 0 : log_error(errno, "Error creating '%s'. %s.\n", container, strerror(errno));
5844 0 : return -1;
5845 : }
5846 :
5847 : /* delete a potential previous pending snapshot */
5848 30 : if (fssnapshot_delete(container, SNAPSHOT_PENDING) != 0) {
5849 0 : log_fatal(errno, "Failed to delete pending snapshot in '%s'. %s.\n", container, strerror(errno));
5850 0 : return -1;
5851 : }
5852 :
5853 : /* create a new snapshot */
5854 30 : if (fssnapshot_create(root, container, SNAPSHOT_PENDING) != 0) {
5855 0 : log_fatal(errno, "Failed to create pending snapshot '%s'. %s.\n", container, strerror(errno));
5856 0 : return -1;
5857 : }
5858 :
5859 30 : msg_progress("Created disk %s pending snapshot...\n", disk->name);
5860 :
5861 : /* setup the snapshot in use */
5862 30 : pathcpy(vol, sizeof(vol), container);
5863 30 : pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING "/");
5864 30 : pathcat(vol, sizeof(vol), disk->mount_point + root_len);
5865 :
5866 30 : if (state_snapshot_dir(vol, disk) != 0) {
5867 0 : log_error(errno, "Error stating snapshot dir '%s'. %s.\n", vol, strerror(errno));
5868 0 : return -1;
5869 : }
5870 :
5871 : /* store the snapshot root */
5872 30 : pathcpy(disk->snapshot_root, sizeof(disk->snapshot_root), root);
5873 : }
5874 :
5875 10 : return 0;
5876 : }
5877 :
5878 80 : int state_snapshot_commit(struct snapraid_state* state)
5879 : {
5880 80 : if (!state->snapshot)
5881 74 : return 0;
5882 :
5883 24 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
5884 18 : struct snapraid_disk* disk = i->data;
5885 : char container[PATH_MAX];
5886 :
5887 : /* check if it supports snapshot */
5888 18 : if (disk->snapshot_root[0] == 0)
5889 0 : continue;
5890 :
5891 18 : pathcpy(container, sizeof(container), disk->snapshot_root);
5892 18 : pathcat(container, sizeof(container), SNAPSHOT_CONTAINER);
5893 :
5894 : /* delete a potential previous stable snapshot */
5895 18 : if (fssnapshot_delete(container, SNAPSHOT_STABLE) != 0) {
5896 0 : log_fatal(errno, "Failed to delete stable snapshot in '%s'. %s.\n", container, strerror(errno));
5897 0 : return -1;
5898 : }
5899 :
5900 : /* rename pending to stable */
5901 18 : if (fssnapshot_rename(container, SNAPSHOT_PENDING, SNAPSHOT_STABLE) != 0) {
5902 0 : log_fatal(errno, "Failed to rename snapshot in '%s'. %s.\n", container, strerror(errno));
5903 0 : return -1;
5904 : }
5905 :
5906 18 : msg_progress("Committed disk %s stable snapshot...\n", disk->name);
5907 : }
5908 :
5909 6 : return 0;
5910 : }
5911 :
5912 12 : void state_snapshot_read(struct snapraid_state* state)
5913 : {
5914 12 : if (!state->snapshot)
5915 11 : return;
5916 :
5917 4 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
5918 3 : struct snapraid_disk* disk = i->data;
5919 : char root[PATH_MAX];
5920 : char container[PATH_MAX];
5921 : char vol[PATH_MAX];
5922 :
5923 : /* check if it supports snapshot */
5924 3 : if (fssnapshot(disk->mount_point, root, sizeof(root)) != 0)
5925 0 : continue;
5926 :
5927 3 : size_t root_len = strlen(root);
5928 :
5929 3 : pathcpy(container, sizeof(container), root);
5930 3 : pathcat(container, sizeof(container), SNAPSHOT_CONTAINER);
5931 :
5932 3 : pathcpy(vol, sizeof(vol), container);
5933 3 : pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING "/");
5934 3 : pathcat(vol, sizeof(vol), disk->mount_point + root_len);
5935 3 : if (state_snapshot_dir(vol, disk) == 0) {
5936 0 : msg_progress("Using disk %s pending snapshot...\n", disk->name);
5937 : } else {
5938 3 : pathcpy(vol, sizeof(vol), container);
5939 3 : pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE "/");
5940 3 : pathcat(vol, sizeof(vol), disk->mount_point + root_len);
5941 3 : if (state_snapshot_dir(vol, disk) == 0) {
5942 3 : msg_progress("Using disk %s stable snapshot...\n", disk->name);
5943 : } else {
5944 : /* fallback to standard mount point */
5945 0 : msg_progress("Using disk %s live filesystem...\n", disk->name);
5946 : }
5947 : }
5948 : }
5949 : }
5950 :
5951 134 : void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_disk)
5952 : {
5953 134 : if (!state->snapshot)
5954 124 : return;
5955 :
5956 : /* if the filter disk is empty, all disks are potentially written */
5957 10 : if (tommy_list_empty(filterlist_disk)) {
5958 5 : msg_progress("Using live filesystems...\n");
5959 5 : return;
5960 : }
5961 :
5962 20 : for (tommy_node* i = state->disklist; i != 0; i = i->next) {
5963 15 : struct snapraid_disk* disk = i->data;
5964 : char root[PATH_MAX];
5965 : char container[PATH_MAX];
5966 : char vol[PATH_MAX];
5967 :
5968 : /* check if it supports snapshot */
5969 15 : if (fssnapshot(disk->mount_point, root, sizeof(root)) != 0)
5970 5 : continue;
5971 :
5972 15 : size_t root_len = strlen(root);
5973 :
5974 : /* if the disk is filtered in, it could be written, then we cannot use the snapshot */
5975 15 : if (filter_path(filterlist_disk, 0, disk->name, 0) == 0) {
5976 5 : msg_progress("Using disk %s live filesystem...\n", disk->name);
5977 5 : continue;
5978 : }
5979 :
5980 10 : pathcpy(container, sizeof(container), root);
5981 10 : pathcat(container, sizeof(container), SNAPSHOT_CONTAINER);
5982 :
5983 10 : pathcpy(vol, sizeof(vol), container);
5984 10 : pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING "/");
5985 10 : pathcat(vol, sizeof(vol), disk->mount_point + root_len);
5986 10 : if (state_snapshot_dir(vol, disk) == 0) {
5987 4 : msg_progress("Using disk %s pending snapshot...\n", disk->name);
5988 :
5989 : /* if there is a dealloc list */
5990 4 : if (!tommy_list_empty(&disk->dealloclist)) {
5991 2 : pathcpy(vol, sizeof(vol), container);
5992 2 : pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE "/");
5993 2 : pathcat(vol, sizeof(vol), disk->mount_point + root_len);
5994 :
5995 : /* if there is a previous snapshot */
5996 2 : if (state_snapshot_dir(vol, 0) == 0) {
5997 2 : msg_progress("Importing disk %s stable snapshot %" PRIu64 " deallocated files...\n", disk->name, tommy_list_count(&disk->dealloclist));
5998 :
5999 2 : state_dealloc(state, vol, &disk->dealloclist);
6000 : }
6001 : }
6002 : } else {
6003 6 : pathcpy(vol, sizeof(vol), container);
6004 6 : pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE "/");
6005 6 : pathcat(vol, sizeof(vol), disk->mount_point + root_len);
6006 6 : if (state_snapshot_dir(vol, disk) == 0) {
6007 6 : msg_progress("Using disk %s stable snapshot...\n", disk->name);
6008 : } else {
6009 : /* fallback to standard mount point */
6010 0 : msg_progress("Using disk %s live filesystem...\n", disk->name);
6011 : }
6012 : }
6013 : }
6014 : }
6015 :
|