1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::{
fmt,
io::{self, Read, Seek, SeekFrom},
str::from_utf8,
};
use byteorder::{ByteOrder, LittleEndian};
use crc::crc32;
use serde_json::Value;
use devicemapper::{Sectors, IEC, SECTOR_SIZE};
use crate::{
engine::{
strat_engine::{
metadata::sizes::{
static_header_size, BDAExtendedSize, BlockdevSize, MDADataSize, MDASize,
ReservedSize,
},
writing::SyncAll,
},
types::{DevUuid, PoolUuid},
},
stratis::{ErrorEnum, StratisError, StratisResult},
};
const RESERVED_SECTORS: Sectors = Sectors(3 * IEC::Mi / (SECTOR_SIZE as u64)); // = 3 MiB
const STRAT_MAGIC: &[u8] = b"!Stra0tis\x86\xff\x02^\x41rh";
const STRAT_SIGBLOCK_VERSION: u8 = 1;
/// Data structure to hold results of reading and parsing a signature buffer.
/// Invariant: bytes is Err <-> header == None, because if there was an error
/// reading the data then there is no point in parsing.
#[derive(Debug)]
pub struct StaticHeaderResult {
/// The bytes read
pub bytes: StratisResult<Box<[u8; bytes!(static_header_size::SIGBLOCK_SECTORS)]>>,
/// The header parsed from the bytes
pub header: Option<StratisResult<Option<StaticHeader>>>,
}
impl PartialEq for StaticHeaderResult {
fn eq(&self, other: &Self) -> bool {
match (self.header.as_ref(), other.header.as_ref()) {
(Some(Ok(Some(sh0))), Some(Ok(Some(sh1)))) => sh0 == sh1,
_ => false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MetadataLocation {
Both,
First,
Second,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct StratisIdentifiers {
pub pool_uuid: PoolUuid,
pub device_uuid: DevUuid,
}
impl StratisIdentifiers {
pub fn new(pool_uuid: PoolUuid, device_uuid: DevUuid) -> StratisIdentifiers {
StratisIdentifiers {
pool_uuid,
device_uuid,
}
}
}
impl fmt::Display for StratisIdentifiers {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Stratis pool UUID: \"{}\", Stratis device UUID: \"{}\"",
self.pool_uuid, self.device_uuid,
)
}
}
impl<'a> Into<Value> for &'a StratisIdentifiers {
fn into(self) -> Value {
json!({
"pool_uuid": Value::from(self.pool_uuid.to_string()),
"device_uuid": Value::from(self.device_uuid.to_string())
})
}
}
/// Get a Stratis pool UUID and device UUID from any device.
/// If there is an error while obtaining these values return the error.
/// If the device does not appear to be a Stratis device, return None.
pub fn device_identifiers<F>(f: &mut F) -> StratisResult<Option<StratisIdentifiers>>
where
F: Read + Seek + SyncAll,
{
let read_results = StaticHeader::read_sigblocks(f);
StaticHeader::repair_sigblocks(f, read_results, StaticHeader::write_header)
.map(|sh| sh.map(|sh| sh.identifiers))
}
/// Remove Stratis identifying information from device.
pub fn disown_device<F>(f: &mut F) -> StratisResult<()>
where
F: Seek + SyncAll,
{
StaticHeader::wipe(f)
}
#[derive(Debug, Eq, PartialEq)]
pub struct StaticHeader {
pub blkdev_size: BlockdevSize,
pub identifiers: StratisIdentifiers,
pub mda_size: MDASize,
pub reserved_size: ReservedSize,
pub flags: u64,
/// Seconds portion of DateTime<Utc> value.
pub initialization_time: u64,
}
impl StaticHeader {
pub fn new(
identifiers: StratisIdentifiers,
mda_data_size: MDADataSize,
blkdev_size: BlockdevSize,
initialization_time: u64,
) -> StaticHeader {
StaticHeader {
blkdev_size,
identifiers,
mda_size: mda_data_size.region_size().mda_size(),
reserved_size: ReservedSize::new(RESERVED_SECTORS),
flags: 0,
initialization_time,
}
}
/// Read the data at both signature block locations.
///
/// Return the data from each location as an array of bytes
/// or an error if the read fails. The values are returned
/// in the same order in which they occur on the device.
///
/// Read the contents of each signature block separately,
/// as this increases the probability that at least one read
/// will not fail.
fn read<F>(
f: &mut F,
) -> (
io::Result<[u8; bytes!(static_header_size::SIGBLOCK_SECTORS)]>,
io::Result<[u8; bytes!(static_header_size::SIGBLOCK_SECTORS)]>,
)
where
F: Read + Seek,
{
let mut buf_loc_1 = [0u8; bytes!(static_header_size::SIGBLOCK_SECTORS)];
let mut buf_loc_2 = [0u8; bytes!(static_header_size::SIGBLOCK_SECTORS)];
fn read_sector_at_offset<F>(f: &mut F, offset: usize, mut buf: &mut [u8]) -> io::Result<()>
where
F: Read + Seek,
{
f.seek(SeekFrom::Start(offset as u64))
.and_then(|_| f.read_exact(&mut buf))
}
(
read_sector_at_offset(
f,
bytes!(static_header_size::FIRST_SIGBLOCK_START_SECTORS),
&mut buf_loc_1,
)
.map(|_| buf_loc_1),
read_sector_at_offset(
f,
bytes!(static_header_size::SECOND_SIGBLOCK_START_SECTORS),
&mut buf_loc_2,
)
.map(|_| buf_loc_2),
)
}
// Writes signature_block according to the value of which.
// If first location is specified, write zeroes to empty regions in the
// first 8 sectors. If the second location is specified, writes zeroes to empty
// regions in the second 8 sectors.
pub fn write<F>(&self, f: &mut F, which: MetadataLocation) -> io::Result<()>
where
F: Seek + SyncAll,
{
let signature_block = self.sigblock_to_buf();
let zeroed = [0u8; bytes!(static_header_size::POST_SIGBLOCK_PADDING_SECTORS)];
f.seek(SeekFrom::Start(0))?;
// Write to a static header region in the static header.
fn write_region<F>(f: &mut F, signature_block: &[u8], zeroed: &[u8]) -> io::Result<()>
where
F: Seek + SyncAll,
{
f.write_all(&zeroed[..bytes!(static_header_size::PRE_SIGBLOCK_PADDING_SECTORS)])?;
f.write_all(signature_block)?;
f.write_all(&zeroed[..bytes!(static_header_size::POST_SIGBLOCK_PADDING_SECTORS)])?;
f.sync_all()?;
Ok(())
}
if which == MetadataLocation::Both || which == MetadataLocation::First {
write_region(f, &signature_block, &zeroed)?;
} else {
f.seek(SeekFrom::Start(
bytes!(static_header_size::SIGBLOCK_REGION_SECTORS) as u64,
))?;
}
if which == MetadataLocation::Both || which == MetadataLocation::Second {
write_region(f, &signature_block, &zeroed)?;
}
Ok(())
}
pub fn bda_extended_size(&self) -> BDAExtendedSize {
BDAExtendedSize::new(self.mda_size.bda_size().sectors() + self.reserved_size.sectors())
}
/// Read a pair of headers from device.
///
/// Return the StaticHeaders and corresponding
/// array of bytes in the form of a tuple of StaticHeaderResults.
/// If read successfully, StaticHeaderResult with contain a reference
/// to the buffer that was read, and the resulting StaticHeader.
/// If reading a buffer fails, StaticHeaderResult will contain an error
/// in the bytes buffer, and None for the header.
pub fn read_sigblocks<F>(f: &mut F) -> (StaticHeaderResult, StaticHeaderResult)
where
F: Read + Seek,
{
let (maybe_buf_1, maybe_buf_2) = StaticHeader::read(f);
(
match maybe_buf_1 {
Ok(buf) => StaticHeaderResult {
bytes: Ok(Box::new(buf)),
header: Some(StaticHeader::sigblock_from_buf(&buf)),
},
Err(err) => StaticHeaderResult {
bytes: Err(err.into()),
header: None,
},
},
match maybe_buf_2 {
Ok(buf) => StaticHeaderResult {
bytes: Ok(Box::new(buf)),
header: Some(StaticHeader::sigblock_from_buf(&buf)),
},
Err(err) => StaticHeaderResult {
bytes: Err(err.into()),
header: None,
},
},
)
}
/// Writes the specified static header
/// to a specified repair location. Used to update
/// corrupted or outdated static headers.
pub fn write_header<F>(
f: &mut F,
sh: StaticHeader,
repair_location: MetadataLocation,
) -> StratisResult<Option<StaticHeader>>
where
F: Seek + SyncAll,
{
sh.write(f, repair_location)?;
Ok(Some(sh))
}
/// Replacement function for write_header
/// for cases when writing repairs to corrupted
/// sigblocks is not desired
pub fn do_nothing<F>(
_f: &mut F,
sh: StaticHeader,
_repair_location: MetadataLocation,
) -> StratisResult<Option<StaticHeader>>
where
F: Seek + SyncAll,
{
Ok(Some(sh))
}
/// Try to find a valid StaticHeader on a device.
/// Pass StaticHeader::write_header as closure in order to
/// repair header in the case of an ill-formed, unreadable, or stale signature block,
/// or pass StaticHeader::do_nothing in order to leave the header unchanged.
/// Return the latest copy that validates as a Stratis BDA, however verify both
/// copies and if one validates but one does not, re-write the one that is incorrect or leave
/// it be, depending on the closure parameter. If both
/// copies are valid, but one is newer than the other, rewrite the older one to match or leave
/// it be depending on the closure paraemter.
/// Return None if it's not a Stratis device.
/// Return an error if the metadata seems to indicate that the device is
/// a Stratis device, but no well-formed signature block could be read.
/// Return an error if neither sigblock location can be read.
/// Return an error if the sigblocks differ in some unaccountable way.
/// Returns an error if a write intended to repair an ill-formed,
/// unreadable, or stale signature block failed.
pub fn repair_sigblocks<C, F>(
f: &mut F,
read_results: (StaticHeaderResult, StaticHeaderResult),
closure: C,
) -> StratisResult<Option<StaticHeader>>
where
F: Read + Seek + SyncAll,
C: Fn(&mut F, StaticHeader, MetadataLocation) -> StratisResult<Option<StaticHeader>>,
{
// Action taken when one sigblock is interpreted as invalid.
//
// If the other sigblock is interpreted as a Stratis header, attempts repair
// of the invalid sigblock, returning an error if that fails, otherwise returning
// the valid sigblock.
//
// In all other cases, return the error associated with the invalid sigblock.
let ok_err_static_header_handling = |f: &mut F,
maybe_sh: Option<StaticHeader>,
sh_error: StratisError,
repair_location: MetadataLocation|
-> StratisResult<Option<StaticHeader>> {
if let Some(sh) = maybe_sh {
closure(f, sh, repair_location)
} else {
Err(sh_error)
}
};
// Action taken when both signature blocks are interpreted as valid
// Stratis headers.
//
// If the contents of the signature blocks are equivalent,
// return valid static header result.
//
// If the contents of the signature blocks are not equivalent,
// overwrite the older block with the contents of the newer one,
// or return an error if the blocks have the same initialization time.
let compare_headers = |f: &mut F,
sh_1: StaticHeader,
sh_2: StaticHeader|
-> StratisResult<Option<StaticHeader>> {
if sh_1 == sh_2 {
Ok(Some(sh_1))
} else if sh_1.initialization_time == sh_2.initialization_time {
let err_str = "Appeared to be a Stratis device, but signature blocks disagree.";
Err(StratisError::Engine(ErrorEnum::Invalid, err_str.into()))
} else if sh_1.initialization_time > sh_2.initialization_time {
closure(f, sh_1, MetadataLocation::Second)
} else {
closure(f, sh_2, MetadataLocation::First)
}
};
// Action taken when both sigblock locations are analyzed without encountering an error.
//
// If both sigblocks are interpreted as a Stratis headers,
// compare contents of static headers.
//
// If only a single sigblock is interpreted as a Stratis header,
// overwrite the other sigblock with the contents of the valid
// Stratis header sigblock.
//
// If neither sigblock is a valid Stratis header,
// return Ok(None)
let ok_ok_static_header_handling = |f: &mut F,
maybe_sh1: Option<StaticHeader>,
maybe_sh2: Option<StaticHeader>|
-> StratisResult<Option<StaticHeader>> {
match (maybe_sh1, maybe_sh2) {
(Some(loc_1), Some(loc_2)) => compare_headers(f, loc_1, loc_2),
(None, None) => Ok(None),
(Some(loc_1), None) => closure(f, loc_1, MetadataLocation::Second),
(None, Some(loc_2)) => closure(f, loc_2, MetadataLocation::First),
}
};
// Action taken when there was an I/O error reading the other sigblock.
//
// * If this sigblock region is interpreted as having no siglblock, it returns None.
// * If this sigblock region has a valid sigblock, attempts repair of the other
// sigblock region with the valid sigblock, returning the valid sigblock
// if the repair succeeds, otherwise returning an error.
// * If this sigblock appears to be invalid, return the error encountered when
// reading the sigblock.
let copy_ok_err_handling = |f: &mut F,
maybe_sh: StratisResult<Option<StaticHeader>>,
repair_location: MetadataLocation|
-> StratisResult<Option<StaticHeader>> {
match maybe_sh {
Ok(loc) => {
if let Some(ref sh) = loc {
sh.write(f, repair_location)?;
}
Ok(loc)
}
Err(e) => Err(e),
}
};
match read_results {
(
StaticHeaderResult {
header: Some(maybe_sh_1),
bytes: Ok(_),
},
StaticHeaderResult {
header: Some(maybe_sh_2),
bytes: Ok(_),
},
) => match (maybe_sh_1, maybe_sh_2) {
(Ok(loc_1), Ok(loc_2)) => ok_ok_static_header_handling(f, loc_1, loc_2),
(Ok(loc_1), Err(loc_2)) => {
ok_err_static_header_handling(f, loc_1, loc_2, MetadataLocation::Second)
}
(Err(loc_1), Ok(loc_2)) => {
ok_err_static_header_handling(f, loc_2, loc_1, MetadataLocation::First)
}
(Err(_), Err(_)) => {
let err_str = "Appeared to be a Stratis device, but no valid sigblock found";
Err(StratisError::Engine(ErrorEnum::Invalid, err_str.into()))
}
},
(
StaticHeaderResult {
header: Some(maybe_sh_1),
bytes: Ok(_),
},
StaticHeaderResult {
header: None,
bytes: Err(_),
},
) => copy_ok_err_handling(f, maybe_sh_1, MetadataLocation::Second),
(
StaticHeaderResult {
header: None,
bytes: Err(_),
},
StaticHeaderResult {
header: Some(maybe_sh_2),
bytes: Ok(_),
},
) => copy_ok_err_handling(f, maybe_sh_2, MetadataLocation::First),
(
StaticHeaderResult {
header: None,
bytes: Err(_),
},
StaticHeaderResult {
header: None,
bytes: Err(_),
},
) => {
let err_str = "Unable to read data at sigblock locations.";
Err(StratisError::Engine(ErrorEnum::Invalid, err_str.into()))
}
(_, _) => unreachable!("header == None <-> bytes is Err(_)"),
}
}
/// Generate a buf suitable for writing to blockdev
fn sigblock_to_buf(&self) -> [u8; bytes!(static_header_size::SIGBLOCK_SECTORS)] {
let mut buf = [0u8; bytes!(static_header_size::SIGBLOCK_SECTORS)];
buf[4..20].clone_from_slice(STRAT_MAGIC);
LittleEndian::write_u64(&mut buf[20..28], *self.blkdev_size.sectors());
buf[28] = STRAT_SIGBLOCK_VERSION;
buf[32..64].clone_from_slice(uuid_to_string!(self.identifiers.pool_uuid).as_bytes());
buf[64..96].clone_from_slice(uuid_to_string!(self.identifiers.device_uuid).as_bytes());
LittleEndian::write_u64(&mut buf[96..104], *self.mda_size.sectors());
LittleEndian::write_u64(&mut buf[104..112], *self.reserved_size.sectors());
LittleEndian::write_u64(&mut buf[120..128], self.initialization_time);
let hdr_crc =
crc32::checksum_castagnoli(&buf[4..bytes!(static_header_size::SIGBLOCK_SECTORS)]);
LittleEndian::write_u32(&mut buf[..4], hdr_crc);
buf
}
/// Parse a buffer to a StaticHeader.
/// Return None if no stratis magic number found.
/// Return an error if stored checksum and calculated checksum do not
/// match.
/// Return an error if the version number is not expected.
fn sigblock_from_buf(buf: &[u8]) -> StratisResult<Option<StaticHeader>> {
assert_eq!(buf.len(), bytes!(static_header_size::SIGBLOCK_SECTORS));
if &buf[4..20] != STRAT_MAGIC {
return Ok(None);
}
let crc = crc32::checksum_castagnoli(&buf[4..bytes!(static_header_size::SIGBLOCK_SECTORS)]);
if crc != LittleEndian::read_u32(&buf[..4]) {
return Err(StratisError::Engine(
ErrorEnum::Invalid,
"header CRC invalid".into(),
));
}
let blkdev_size = BlockdevSize::new(Sectors(LittleEndian::read_u64(&buf[20..28])));
let version = buf[28];
if version != STRAT_SIGBLOCK_VERSION {
return Err(StratisError::Engine(
ErrorEnum::Invalid,
format!("Unknown sigblock version: {}", version),
));
}
let pool_uuid = PoolUuid::parse_str(from_utf8(&buf[32..64])?)?;
let dev_uuid = DevUuid::parse_str(from_utf8(&buf[64..96])?)?;
let mda_size = MDASize(Sectors(LittleEndian::read_u64(&buf[96..104])));
Ok(Some(StaticHeader {
identifiers: StratisIdentifiers::new(pool_uuid, dev_uuid),
blkdev_size,
mda_size,
reserved_size: ReservedSize::new(Sectors(LittleEndian::read_u64(&buf[104..112]))),
flags: 0,
initialization_time: LittleEndian::read_u64(&buf[120..128]),
}))
}
/// Zero out the entire static header region on the designated file.
pub fn wipe<F>(f: &mut F) -> StratisResult<()>
where
F: Seek + SyncAll,
{
let zeroed = [0u8; bytes!(static_header_size::STATIC_HEADER_SECTORS)];
f.seek(SeekFrom::Start(0))?;
f.write_all(&zeroed)?;
f.sync_all()?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use std::io::Cursor;
use proptest::{option, prelude::BoxedStrategy, strategy::Strategy};
use chrono::Utc;
use devicemapper::{Bytes, Sectors, IEC};
use crate::engine::strat_engine::metadata::sizes::{static_header_size, MDADataSize};
use super::*;
proptest! {
#[test]
/// Construct an arbitrary StaticHeader object.
/// Verify that the "memory buffer" is unowned.
/// Initialize a static header.
/// Verify that Stratis buffer validates.
/// Wipe the static header.
/// Verify that the buffer is again unowned.
fn test_ownership(ref sh in static_header_strategy()) {
let buf_size = bytes!(static_header_size::STATIC_HEADER_SECTORS);
let mut buf = Cursor::new(vec![0; buf_size]);
let (s1, s2) = StaticHeader::read_sigblocks(&mut buf);
prop_assert_eq!(s1.header.unwrap().unwrap(), None);
prop_assert_eq!(s2.header.unwrap().unwrap(), None);
sh.write(&mut buf, MetadataLocation::Both).unwrap();
let (s1, s2) = StaticHeader::read_sigblocks(&mut buf);
prop_assert!(s1.header.unwrap().unwrap().map(|new_sh| new_sh.identifiers == sh.identifiers).unwrap_or(false));
prop_assert!(s2.header.unwrap().unwrap().map(|new_sh| new_sh.identifiers == sh.identifiers).unwrap_or(false));
StaticHeader::wipe(&mut buf).unwrap();
let (s1, s2) = StaticHeader::read_sigblocks(&mut buf);
prop_assert_eq!(s1.header.unwrap().unwrap(), None);
prop_assert_eq!(s2.header.unwrap().unwrap(), None);
}
}
/// Return a static header with random block device and MDA size.
/// The block device is less than the minimum, for efficiency in testing.
pub fn random_static_header(blkdev_size: u64, mda_size_factor: u32) -> StaticHeader {
let pool_uuid = PoolUuid::new_v4();
let dev_uuid = DevUuid::new_v4();
let mda_data_size =
MDADataSize::new(MDADataSize::default().bytes() + Bytes::from(mda_size_factor * 4));
let blkdev_size = (Bytes::from(IEC::Mi) + Sectors(blkdev_size).bytes()).sectors();
StaticHeader::new(
StratisIdentifiers::new(pool_uuid, dev_uuid),
mda_data_size,
BlockdevSize::new(blkdev_size),
Utc::now().timestamp() as u64,
)
}
/// Make a static header strategy
pub fn static_header_strategy() -> BoxedStrategy<StaticHeader> {
(0..64u64, 0..64u32)
.prop_map(|(b, m)| random_static_header(b, m))
.boxed()
}
proptest! {
#[test]
/// Verify correct reading of the static header if only one of
/// the two static headers is corrupted. Verify expected behavior
/// if both are corrupted, which varies depending on whether the
/// Stratis magic number or some other part of the header is corrupted.
fn test_corrupted_sigblock_recovery(primary in option::of(0..bytes!(static_header_size::SIGBLOCK_SECTORS)),
secondary in option::of(0..bytes!(static_header_size::SIGBLOCK_SECTORS))) {
// Corrupt a byte at the specified position.
fn corrupt_byte<F>(f: &mut F, position: u64) -> io::Result<()>
where
F: Read + Seek + SyncAll,
{
let mut byte_to_corrupt = [0; 1];
f.seek(SeekFrom::Start(position))
.and_then(|_| f.read_exact(&mut byte_to_corrupt))?;
byte_to_corrupt[0] = !byte_to_corrupt[0];
f.seek(SeekFrom::Start(position))
.and_then(|_| f.write_all(&byte_to_corrupt))
.and_then(|_| f.sync_all())
}
let sh = random_static_header(10000, 4);
let buf_size = bytes!(static_header_size::STATIC_HEADER_SECTORS);
let mut reference_buf = Cursor::new(vec![0; buf_size]);
sh.write(&mut reference_buf, MetadataLocation::Both).unwrap();
let mut buf = Cursor::new(vec![0; buf_size]);
sh.write(&mut buf, MetadataLocation::Both).unwrap();
if let Some(index) = primary {
corrupt_byte(
&mut buf,
(bytes!(static_header_size::FIRST_SIGBLOCK_START_SECTORS) + index) as u64,
)
.unwrap();
}
if let Some(index) = secondary {
corrupt_byte(
&mut buf,
(bytes!(static_header_size::SECOND_SIGBLOCK_START_SECTORS) + index) as u64,
)
.unwrap();
}
let read_results = StaticHeader::read_sigblocks(&mut buf);
let setup_result = StaticHeader::repair_sigblocks(&mut buf, read_results, StaticHeader::write_header);
match (primary, secondary) {
(Some(p_index), Some(s_index)) => {
match (p_index, s_index) {
// Both magic locations are corrupted, conclusion is
// that this is not a Stratis static header.
(4..=19, 4..=19) => {
prop_assert!(setup_result.is_ok());
prop_assert_eq!(setup_result.unwrap(), None);
}
// Both sigblocks were corrupted, but at least one
// was recognized as a Stratis sigblock.
_ => {
prop_assert!(setup_result.is_err());
}
}
// No healing was attempted.
prop_assert_ne!(reference_buf.get_ref(), buf.get_ref());
}
// Only one header was corrupted, so the other was healed.
_ => {
prop_assert!(setup_result.is_ok());
prop_assert_eq!(setup_result.unwrap(), Some(sh));
prop_assert_eq!(reference_buf.get_ref(), buf.get_ref());
}
}
}
}
proptest! {
#[test]
/// Construct an arbitrary StaticHeader object.
/// Write it to a buffer, read it out and make sure you get the same thing.
fn static_header(ref sh1 in static_header_strategy()) {
let buf = sh1.sigblock_to_buf();
let sh2 = StaticHeader::sigblock_from_buf(&buf).unwrap().unwrap();
prop_assert_eq!(sh1.identifiers, sh2.identifiers);
prop_assert_eq!(sh1.blkdev_size, sh2.blkdev_size);
prop_assert_eq!(sh1.mda_size, sh2.mda_size);
prop_assert_eq!(sh1.reserved_size, sh2.reserved_size);
prop_assert_eq!(sh1.flags, sh2.flags);
prop_assert_eq!(sh1.initialization_time, sh2.initialization_time);
}
}
#[test]
/// Test that the newer sigblock is copied to the older sigblock's location
/// if the sigblock's timestamps don't match but they are otherwise
/// identical.
fn test_rewrite_older_sigblock() {
let sh = random_static_header(10000, 4);
let ts = Utc::now().timestamp() as u64;
let sh_older = StaticHeader {
initialization_time: ts,
..sh
};
let sh_newer = StaticHeader {
initialization_time: ts + 1,
..sh
};
assert_ne!(sh_older, sh_newer);
let buf_size = bytes!(static_header_size::STATIC_HEADER_SECTORS);
let mut reference_buf = Cursor::new(vec![0; buf_size]);
sh_newer
.write(&mut reference_buf, MetadataLocation::Both)
.unwrap();
// Test the StaticHeader::repair_sigblocks method by writing the older
// signature block to the specified older location and the newer
// sigblock to the specified newer location, then calling
// StaticHeader::repair_sigblocks, which should return without
// error with the newer sigblock. As a side-effect, it should
// overwrite the location of the older sigblock with the value of
// the newer sigblock, since StaticHeader::write_header was provided
// as an argument.
let test_rewrite = |sh_older: &StaticHeader,
sh_newer: &StaticHeader,
older_location: MetadataLocation,
newer_location: MetadataLocation| {
let mut buf = Cursor::new(vec![0; buf_size]);
sh_older.write(&mut buf, older_location).unwrap();
sh_newer.write(&mut buf, newer_location).unwrap();
assert_ne!(buf.get_ref(), reference_buf.get_ref());
let read_results = StaticHeader::read_sigblocks(&mut buf);
assert_eq!(
StaticHeader::repair_sigblocks(&mut buf, read_results, StaticHeader::write_header)
.unwrap()
.as_ref(),
Some(sh_newer)
);
assert_eq!(buf.get_ref(), reference_buf.get_ref());
};
test_rewrite(
&sh_older,
&sh_newer,
MetadataLocation::First,
MetadataLocation::Second,
);
test_rewrite(
&sh_older,
&sh_newer,
MetadataLocation::Second,
MetadataLocation::First,
);
}
}