]>
Commit | Line | Data |
---|---|---|
5362c05c AL |
1 | use std::collections::HashSet; |
2 | ||
3 | use crate::options::{BtrfsRaidLevel, Disk, ZfsRaidLevel}; | |
4 | use crate::setup::BootType; | |
5 | ||
6 | /// Checks a list of disks for duplicate entries, using their index as key. | |
7 | /// | |
8 | /// # Arguments | |
9 | /// | |
10 | /// * `disks` - A list of disks to check for duplicates. | |
a83d1c96 | 11 | pub fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { |
5362c05c AL |
12 | let mut set = HashSet::new(); |
13 | ||
14 | for disk in disks { | |
15 | if !set.insert(&disk.index) { | |
16 | return Err(disk); | |
17 | } | |
18 | } | |
19 | ||
20 | Ok(()) | |
21 | } | |
22 | ||
23 | /// Simple wrapper which returns an descriptive error if the list of disks is too short. | |
24 | /// | |
25 | /// # Arguments | |
26 | /// | |
27 | /// * `disks` - A list of disks to check the lenght of. | |
28 | /// * `min` - Minimum number of disks | |
a83d1c96 | 29 | pub fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { |
5362c05c AL |
30 | if disks.len() < min { |
31 | Err(format!("Need at least {min} disks")) | |
32 | } else { | |
33 | Ok(()) | |
34 | } | |
35 | } | |
36 | ||
37 | /// Checks all disks for legacy BIOS boot compatibility and reports an error as appropriate. 4Kn | |
38 | /// disks are generally broken with legacy BIOS and cannot be booted from. | |
39 | /// | |
40 | /// # Arguments | |
41 | /// | |
42 | /// * `runinfo` - `RuntimeInfo` instance of currently running system | |
43 | /// * `disks` - List of disks designated as bootdisk targets. | |
a83d1c96 | 44 | pub fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { |
5362c05c AL |
45 | let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false); |
46 | ||
47 | if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) { | |
48 | return Err("Booting from 4Kn drive in legacy BIOS mode is not supported."); | |
49 | } | |
50 | ||
51 | Ok(()) | |
52 | } | |
53 | ||
54 | /// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum | |
55 | /// number of disks. | |
56 | /// | |
57 | /// # Arguments | |
58 | /// | |
59 | /// * `level` - The targeted ZFS RAID level by the user. | |
60 | /// * `disks` - List of disks designated as RAID targets. | |
a83d1c96 | 61 | pub fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { |
5362c05c AL |
62 | // See also Proxmox/Install.pm:get_zfs_raid_setup() |
63 | ||
64 | let check_mirror_size = |disk1: &Disk, disk2: &Disk| { | |
65 | if (disk1.size - disk2.size).abs() > disk1.size / 10. { | |
66 | Err(format!( | |
67 | "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}" | |
68 | )) | |
69 | } else { | |
70 | Ok(()) | |
71 | } | |
72 | }; | |
73 | ||
74 | match level { | |
75 | ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, | |
76 | ZfsRaidLevel::Raid1 => { | |
77 | check_raid_min_disks(disks, 2)?; | |
78 | for disk in disks { | |
79 | check_mirror_size(&disks[0], disk)?; | |
80 | } | |
81 | } | |
82 | ZfsRaidLevel::Raid10 => { | |
83 | check_raid_min_disks(disks, 4)?; | |
984be2a5 CH |
84 | |
85 | if disks.len() % 2 != 0 { | |
86 | return Err(format!( | |
87 | "Needs an even number of disks, currently selected: {}", | |
88 | disks.len(), | |
89 | )); | |
90 | } | |
91 | ||
5362c05c AL |
92 | // Pairs need to have the same size |
93 | for i in (0..disks.len()).step_by(2) { | |
94 | check_mirror_size(&disks[i], &disks[i + 1])?; | |
95 | } | |
96 | } | |
97 | // For RAID-Z: minimum disks number is level + 2 | |
98 | ZfsRaidLevel::RaidZ => { | |
99 | check_raid_min_disks(disks, 3)?; | |
100 | for disk in disks { | |
101 | check_mirror_size(&disks[0], disk)?; | |
102 | } | |
103 | } | |
104 | ZfsRaidLevel::RaidZ2 => { | |
105 | check_raid_min_disks(disks, 4)?; | |
106 | for disk in disks { | |
107 | check_mirror_size(&disks[0], disk)?; | |
108 | } | |
109 | } | |
110 | ZfsRaidLevel::RaidZ3 => { | |
111 | check_raid_min_disks(disks, 5)?; | |
112 | for disk in disks { | |
113 | check_mirror_size(&disks[0], disk)?; | |
114 | } | |
115 | } | |
116 | } | |
117 | ||
118 | Ok(()) | |
119 | } | |
120 | ||
121 | /// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum | |
122 | /// number of disks. | |
123 | /// | |
124 | /// # Arguments | |
125 | /// | |
126 | /// * `level` - The targeted Btrfs RAID level by the user. | |
127 | /// * `disks` - List of disks designated as RAID targets. | |
a83d1c96 | 128 | pub fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { |
5362c05c AL |
129 | // See also Proxmox/Install.pm:get_btrfs_raid_setup() |
130 | ||
131 | match level { | |
132 | BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, | |
133 | BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?, | |
134 | BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?, | |
135 | } | |
136 | ||
137 | Ok(()) | |
138 | } | |
139 | ||
140 | #[cfg(test)] | |
141 | mod tests { | |
142 | use super::*; | |
143 | ||
144 | fn dummy_disk(index: usize) -> Disk { | |
145 | Disk { | |
146 | index: index.to_string(), | |
147 | path: format!("/dev/dummy{index}"), | |
148 | model: Some("Dummy disk".to_owned()), | |
149 | size: 1024. * 1024. * 1024. * 8., | |
150 | block_size: Some(512), | |
151 | } | |
152 | } | |
153 | ||
154 | fn dummy_disks(num: usize) -> Vec<Disk> { | |
155 | (0..num).map(dummy_disk).collect() | |
156 | } | |
157 | ||
158 | #[test] | |
159 | fn duplicate_disks() { | |
160 | assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok()); | |
161 | assert_eq!( | |
162 | check_for_duplicate_disks(&[ | |
163 | dummy_disk(0), | |
164 | dummy_disk(1), | |
165 | dummy_disk(2), | |
166 | dummy_disk(2), | |
167 | dummy_disk(3), | |
168 | ]), | |
169 | Err(&dummy_disk(2)), | |
170 | ); | |
171 | } | |
172 | ||
173 | #[test] | |
174 | fn raid_min_disks() { | |
175 | let disks = dummy_disks(10); | |
176 | ||
177 | assert!(check_raid_min_disks(&disks[..1], 2).is_err()); | |
178 | assert!(check_raid_min_disks(&disks[..1], 1).is_ok()); | |
179 | assert!(check_raid_min_disks(&disks, 1).is_ok()); | |
180 | } | |
181 | ||
182 | #[test] | |
183 | fn bios_boot_compat_4kn() { | |
184 | for i in 0..10 { | |
185 | let mut disks = dummy_disks(10); | |
186 | disks[i].block_size = Some(4096); | |
187 | ||
188 | // Must fail if /any/ of the disks are 4Kn | |
189 | assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err()); | |
190 | // For UEFI, we allow it for every configuration | |
191 | assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok()); | |
192 | } | |
193 | } | |
194 | ||
195 | #[test] | |
196 | fn btrfs_raid() { | |
197 | let disks = dummy_disks(10); | |
198 | ||
199 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err()); | |
200 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok()); | |
201 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok()); | |
202 | ||
203 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err()); | |
204 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err()); | |
205 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok()); | |
206 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok()); | |
207 | ||
208 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err()); | |
209 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err()); | |
210 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok()); | |
211 | assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok()); | |
212 | } | |
213 | ||
214 | #[test] | |
215 | fn zfs_raid() { | |
216 | let disks = dummy_disks(10); | |
217 | ||
218 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err()); | |
219 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok()); | |
220 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok()); | |
221 | ||
222 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err()); | |
223 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok()); | |
224 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok()); | |
225 | ||
226 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err()); | |
227 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok()); | |
228 | assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok()); | |
229 | ||
230 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err()); | |
231 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err()); | |
232 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok()); | |
233 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok()); | |
234 | ||
235 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err()); | |
236 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err()); | |
237 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok()); | |
238 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok()); | |
239 | ||
240 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err()); | |
241 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err()); | |
242 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok()); | |
243 | assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok()); | |
244 | } | |
245 | } |