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