]> git.proxmox.com Git - pve-installer.git/blame - proxmox-tui-installer/src/views/bootdisk.rs
tui: bootdisk: refactor Rc<RefCell<..>> type into custom type
[pve-installer.git] / proxmox-tui-installer / src / views / bootdisk.rs
CommitLineData
86c48f76 1use std::{cell::RefCell, marker::PhantomData, rc::Rc};
32368ac3 2
af0dfe0e 3use cursive::{
ed191e60 4 view::{Nameable, Resizable, ViewWrapper},
45082a3c 5 views::{
2379338c
CH
6 Button, Dialog, DummyView, LinearLayout, NamedView, PaddedView, Panel, ScrollView,
7 SelectView, TextView,
45082a3c 8 },
7393ed88 9 Cursive, View,
af0dfe0e 10};
32368ac3
WB
11
12use super::{DiskSizeEditView, FormView, IntegerEditView};
86c48f76
AL
13use crate::options::FS_TYPES;
14use crate::InstallerState;
15
16use proxmox_installer_common::{
feea90d4
CH
17 disk_checks::{
18 check_btrfs_raid_config, check_disks_4kn_legacy_boot, check_for_duplicate_disks,
19 check_zfs_raid_config,
20 },
ed37980f 21 options::{
feea90d4
CH
22 AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, Disk, FsType,
23 LvmBootdiskOptions, ZfsBootdiskOptions, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS,
ed37980f 24 },
13490d13 25 setup::{BootType, ProductConfig, ProxmoxProduct, RuntimeInfo},
32368ac3 26};
af0dfe0e 27
521dfff5
CH
28/// OpenZFS specifies 64 MiB as the absolute minimum:
29/// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max
30const ZFS_ARC_MIN_SIZE_MIB: usize = 64; // MiB
31
d81cffcb
CH
32/// Convience wrapper when needing to take a (interior-mutable) reference to `BootdiskOptions`.
33/// Interior mutability is safe for this case, as it is completely single-threaded.
34pub type BootdiskOptionsRef = Rc<RefCell<BootdiskOptions>>;
35
ed191e60 36pub struct BootdiskOptionsView {
af0dfe0e 37 view: LinearLayout,
d81cffcb 38 advanced_options: BootdiskOptionsRef,
fea97303 39 boot_type: BootType,
ed191e60
CH
40}
41
42impl BootdiskOptionsView {
521dfff5 43 pub fn new(siv: &mut Cursive, runinfo: &RuntimeInfo, options: &BootdiskOptions) -> Self {
b977af78
CH
44 let bootdisk_form = FormView::new()
45 .child(
46 "Target harddisk",
47 SelectView::new()
48 .popup()
521dfff5 49 .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))),
b977af78 50 )
66baa275 51 .with_name("bootdisk-options-target-disk");
ed191e60 52
e14adfe3
CH
53 let product_conf = siv
54 .user_data::<InstallerState>()
55 .map(|state| state.setup_info.config.clone())
56 .unwrap(); // Safety: InstallerState must always be set
57
7393ed88 58 let advanced_options = Rc::new(RefCell::new(options.clone()));
ed191e60
CH
59
60 let advanced_button = LinearLayout::horizontal()
61 .child(DummyView.full_width())
62 .child(Button::new("Advanced options", {
521dfff5 63 let runinfo = runinfo.clone();
ed191e60
CH
64 let options = advanced_options.clone();
65 move |siv| {
e14adfe3 66 siv.add_layer(advanced_options_view(
521dfff5 67 &runinfo,
e14adfe3
CH
68 options.clone(),
69 product_conf.clone(),
70 ));
ed191e60
CH
71 }
72 }));
73
74 let view = LinearLayout::vertical()
b977af78 75 .child(bootdisk_form)
ed191e60
CH
76 .child(DummyView)
77 .child(advanced_button);
78
fea97303
CH
79 let boot_type = siv
80 .user_data::<InstallerState>()
81 .map(|state| state.runtime_info.boot_type)
82 .unwrap_or(BootType::Bios);
83
ed191e60
CH
84 Self {
85 view,
86 advanced_options,
fea97303 87 boot_type,
ed191e60
CH
88 }
89 }
90
994c4ff0 91 pub fn get_values(&mut self) -> Result<BootdiskOptions, String> {
7393ed88 92 let mut options = (*self.advanced_options).clone().into_inner();
ed191e60 93
7393ed88
CH
94 if [FsType::Ext4, FsType::Xfs].contains(&options.fstype) {
95 let disk = self
96 .view
994c4ff0
CH
97 .get_child_mut(0)
98 .and_then(|v| v.downcast_mut::<NamedView<FormView>>())
99 .map(NamedView::<FormView>::get_mut)
100 .and_then(|v| v.get_value::<SelectView<Disk>, _>(0))
69f162a6 101 .ok_or("failed to retrieve bootdisk")?;
ed191e60 102
7393ed88
CH
103 options.disks = vec![disk];
104 }
105
fea97303 106 check_disks_4kn_legacy_boot(self.boot_type, &options.disks)?;
994c4ff0 107 Ok(options)
ed191e60 108 }
af0dfe0e
CH
109}
110
ed191e60
CH
111impl ViewWrapper for BootdiskOptionsView {
112 cursive::wrap_impl!(self.view: LinearLayout);
113}
114
115struct AdvancedBootdiskOptionsView {
116 view: LinearLayout,
117}
af0dfe0e 118
ed191e60 119impl AdvancedBootdiskOptionsView {
521dfff5 120 fn new(runinfo: &RuntimeInfo, options: &BootdiskOptions, product_conf: ProductConfig) -> Self {
e14adfe3
CH
121 let filter_btrfs =
122 |fstype: &&FsType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() };
a96dc916 123
66baa275
CH
124 let fstype_select = SelectView::new()
125 .popup()
a96dc916
TL
126 .with_all(
127 FS_TYPES
128 .iter()
129 .filter(filter_btrfs)
130 .map(|t| (t.to_string(), *t)),
131 )
66baa275
CH
132 .selected(
133 FS_TYPES
134 .iter()
a96dc916 135 .filter(filter_btrfs)
66baa275
CH
136 .position(|t| *t == options.fstype)
137 .unwrap_or_default(),
138 )
521dfff5 139 .on_submit(Self::fstype_on_submit);
af0dfe0e 140
7393ed88 141 let mut view = LinearLayout::vertical()
ed191e60 142 .child(DummyView.full_width())
66baa275 143 .child(FormView::new().child("Filesystem", fstype_select))
7393ed88
CH
144 .child(DummyView.full_width());
145
146 match &options.advanced {
e8a7b9ba
CH
147 AdvancedBootdiskOptions::Lvm(lvm) => {
148 view.add_child(LvmBootdiskOptionsView::new(lvm, &product_conf))
149 }
7393ed88 150 AdvancedBootdiskOptions::Zfs(zfs) => {
521dfff5 151 view.add_child(ZfsBootdiskOptionsView::new(runinfo, zfs, &product_conf))
7393ed88 152 }
56a304d5 153 AdvancedBootdiskOptions::Btrfs(btrfs) => {
521dfff5 154 view.add_child(BtrfsBootdiskOptionsView::new(&runinfo.disks, btrfs))
56a304d5 155 }
7393ed88 156 };
af0dfe0e
CH
157
158 Self { view }
159 }
160
521dfff5 161 fn fstype_on_submit(siv: &mut Cursive, fstype: &FsType) {
e8a7b9ba 162 let state = siv.user_data::<InstallerState>().unwrap();
13490d13 163 let runinfo = state.runtime_info.clone();
e8a7b9ba 164 let product_conf = state.setup_info.config.clone();
e14adfe3 165
7393ed88
CH
166 siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| {
167 if let Some(AdvancedBootdiskOptionsView { view }) =
ce0b5865 168 view.get_content_mut().downcast_mut()
7393ed88
CH
169 {
170 view.remove_child(3);
171 match fstype {
e8a7b9ba 172 FsType::Ext4 | FsType::Xfs => view.add_child(
521dfff5 173 LvmBootdiskOptionsView::new_with_defaults(&runinfo.disks[0], &product_conf),
e8a7b9ba 174 ),
13490d13
CH
175 FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new_with_defaults(
176 &runinfo,
177 &product_conf,
178 )),
e8a7b9ba 179 FsType::Btrfs(_) => {
521dfff5 180 view.add_child(BtrfsBootdiskOptionsView::new_with_defaults(&runinfo.disks))
e8a7b9ba 181 }
7393ed88
CH
182 }
183 }
184 });
185
186 siv.call_on_name(
187 "bootdisk-options-target-disk",
66baa275
CH
188 |view: &mut FormView| match fstype {
189 FsType::Ext4 | FsType::Xfs => {
190 view.replace_child(
191 0,
192 SelectView::new()
193 .popup()
521dfff5 194 .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))),
66baa275
CH
195 );
196 }
197 other => view.replace_child(0, TextView::new(other.to_string())),
7393ed88
CH
198 },
199 );
200 }
201
fea97303 202 fn get_values(&mut self) -> Result<BootdiskOptions, String> {
ed191e60
CH
203 let fstype = self
204 .view
597d10f6
CH
205 .get_child(1)
206 .and_then(|v| v.downcast_ref::<FormView>())
207 .and_then(|v| v.get_value::<SelectView<FsType>, _>(0))
208 .ok_or("Failed to retrieve filesystem type".to_owned())?;
ed191e60 209
597d10f6
CH
210 let advanced = self
211 .view
212 .get_child_mut(3)
213 .ok_or("Failed to retrieve advanced bootdisk options view".to_owned())?;
7393ed88
CH
214
215 if let Some(view) = advanced.downcast_mut::<LvmBootdiskOptionsView>() {
597d10f6
CH
216 let advanced = view
217 .get_values()
218 .map(AdvancedBootdiskOptions::Lvm)
219 .ok_or("Failed to retrieve advanced bootdisk options")?;
220
221 Ok(BootdiskOptions {
7393ed88
CH
222 disks: vec![],
223 fstype,
597d10f6 224 advanced,
7393ed88
CH
225 })
226 } else if let Some(view) = advanced.downcast_mut::<ZfsBootdiskOptionsView>() {
597d10f6
CH
227 let (disks, advanced) = view
228 .get_values()
229 .ok_or("Failed to retrieve advanced bootdisk options")?;
ed191e60 230
ed37980f 231 if let FsType::Zfs(level) = fstype {
fea97303 232 check_zfs_raid_config(level, &disks).map_err(|err| format!("{fstype}: {err}"))?;
ed37980f
CH
233 }
234
597d10f6 235 Ok(BootdiskOptions {
7393ed88
CH
236 disks,
237 fstype,
238 advanced: AdvancedBootdiskOptions::Zfs(advanced),
239 })
56a304d5 240 } else if let Some(view) = advanced.downcast_mut::<BtrfsBootdiskOptionsView>() {
597d10f6
CH
241 let (disks, advanced) = view
242 .get_values()
243 .ok_or("Failed to retrieve advanced bootdisk options")?;
56a304d5 244
ed37980f
CH
245 if let FsType::Btrfs(level) = fstype {
246 check_btrfs_raid_config(level, &disks).map_err(|err| format!("{fstype}: {err}"))?;
247 }
248
597d10f6 249 Ok(BootdiskOptions {
56a304d5
CH
250 disks,
251 fstype,
252 advanced: AdvancedBootdiskOptions::Btrfs(advanced),
253 })
7393ed88 254 } else {
597d10f6 255 Err("Invalid bootdisk view state".to_owned())
7393ed88 256 }
af0dfe0e
CH
257 }
258}
259
ed191e60 260impl ViewWrapper for AdvancedBootdiskOptionsView {
af0dfe0e
CH
261 cursive::wrap_impl!(self.view: LinearLayout);
262}
263
264struct LvmBootdiskOptionsView {
cd6d2d24 265 view: FormView,
e14adfe3 266 has_extra_fields: bool,
af0dfe0e
CH
267}
268
269impl LvmBootdiskOptionsView {
e8a7b9ba
CH
270 fn new(options: &LvmBootdiskOptions, product_conf: &ProductConfig) -> Self {
271 let show_extra_fields = product_conf.product == ProxmoxProduct::PVE;
272
ed191e60 273 // TODO: Set maximum accordingly to disk size
cd6d2d24
CH
274 let view = FormView::new()
275 .child(
276 "Total size",
859e3478
CH
277 DiskSizeEditView::new()
278 .content(options.total_size)
279 .max_value(options.total_size),
cd6d2d24 280 )
af0dfe0e 281 .child(
cd6d2d24 282 "Swap size",
409fc0fd 283 DiskSizeEditView::new_emptyable().content_maybe(options.swap_size),
af0dfe0e 284 )
6719eda6 285 .child_conditional(
e14adfe3 286 show_extra_fields,
cd6d2d24 287 "Maximum root volume size",
409fc0fd 288 DiskSizeEditView::new_emptyable().content_maybe(options.max_root_size),
af0dfe0e 289 )
6719eda6 290 .child_conditional(
e14adfe3 291 show_extra_fields,
cd6d2d24 292 "Maximum data volume size",
409fc0fd 293 DiskSizeEditView::new_emptyable().content_maybe(options.max_data_size),
cd6d2d24
CH
294 )
295 .child(
296 "Minimum free LVM space",
409fc0fd 297 DiskSizeEditView::new_emptyable().content_maybe(options.min_lvm_free),
af0dfe0e
CH
298 );
299
e14adfe3
CH
300 Self {
301 view,
302 has_extra_fields: show_extra_fields,
303 }
af0dfe0e
CH
304 }
305
e8a7b9ba
CH
306 fn new_with_defaults(disk: &Disk, product_conf: &ProductConfig) -> Self {
307 Self::new(&LvmBootdiskOptions::defaults_from(disk), product_conf)
308 }
309
af0dfe0e 310 fn get_values(&mut self) -> Option<LvmBootdiskOptions> {
e14adfe3
CH
311 let min_lvm_free_id = if self.has_extra_fields { 4 } else { 2 };
312
313 let max_root_size = self
314 .has_extra_fields
315 .then(|| self.view.get_value::<DiskSizeEditView, _>(2))
316 .flatten();
317 let max_data_size = self
318 .has_extra_fields
319 .then(|| self.view.get_value::<DiskSizeEditView, _>(3))
320 .flatten();
321
af0dfe0e 322 Some(LvmBootdiskOptions {
cd6d2d24 323 total_size: self.view.get_value::<DiskSizeEditView, _>(0)?,
409fc0fd 324 swap_size: self.view.get_value::<DiskSizeEditView, _>(1),
6719eda6
TL
325 max_root_size,
326 max_data_size,
327 min_lvm_free: self.view.get_value::<DiskSizeEditView, _>(min_lvm_free_id),
af0dfe0e
CH
328 })
329 }
330}
331
332impl ViewWrapper for LvmBootdiskOptionsView {
cd6d2d24 333 cursive::wrap_impl!(self.view: FormView);
af0dfe0e 334}
ed191e60 335
ee1437bb 336struct MultiDiskOptionsView<T> {
56a304d5 337 view: LinearLayout,
ee1437bb 338 phantom: PhantomData<T>,
56a304d5
CH
339}
340
ee1437bb 341impl<T: View> MultiDiskOptionsView<T> {
9e5cf6b6 342 fn new(avail_disks: &[Disk], selected_disks: &[usize], options_view: T) -> Self {
429718b8
CH
343 let mut selectable_disks = avail_disks
344 .iter()
345 .map(|d| (d.to_string(), Some(d.clone())))
346 .collect::<Vec<(String, Option<Disk>)>>();
347
348 selectable_disks.push(("-- do not use --".to_owned(), None));
349
d36c96af 350 let mut disk_form = FormView::new();
9e5cf6b6 351 for (i, _) in avail_disks.iter().enumerate() {
d36c96af 352 disk_form.add_child(
56a304d5
CH
353 &format!("Harddisk {i}"),
354 SelectView::new()
355 .popup()
429718b8 356 .with_all(selectable_disks.clone())
cc655f8b 357 .selected(selected_disks[i]),
d36c96af 358 );
56a304d5
CH
359 }
360
2379338c 361 let mut disk_select_view = LinearLayout::vertical()
cdba54ce 362 .child(TextView::new("Disk setup").center())
d36c96af 363 .child(DummyView)
2379338c
CH
364 .child(ScrollView::new(disk_form.with_name("multidisk-disk-form")));
365
366 if avail_disks.len() > 3 {
367 let do_not_use_index = selectable_disks.len() - 1;
368 let deselect_all_button = Button::new("Deselect all", move |siv| {
369 siv.call_on_name("multidisk-disk-form", |view: &mut FormView| {
370 view.call_on_childs(&|v: &mut SelectView<Option<Disk>>| {
371 // As there is no .on_select() callback defined on the
372 // SelectView's, the returned callback here can be safely
373 // ignored.
374 v.set_selection(do_not_use_index);
375 });
376 });
377 });
378
379 disk_select_view.add_child(PaddedView::lrtb(
380 0,
381 0,
382 1,
383 0,
384 LinearLayout::horizontal()
385 .child(DummyView.full_width())
386 .child(deselect_all_button),
387 ));
388 }
d36c96af 389
56a304d5 390 let options_view = LinearLayout::vertical()
cdba54ce 391 .child(TextView::new("Advanced options").center())
56a304d5 392 .child(DummyView)
ee1437bb 393 .child(options_view);
56a304d5
CH
394
395 let view = LinearLayout::horizontal()
396 .child(disk_select_view)
397 .child(DummyView.fixed_width(3))
398 .child(options_view);
399
ee1437bb 400 Self {
82cc9fc4 401 view: LinearLayout::vertical().child(view),
ee1437bb
CH
402 phantom: PhantomData,
403 }
56a304d5
CH
404 }
405
7f273738 406 fn top_panel(mut self, view: impl View) -> Self {
82cc9fc4
CH
407 if self.has_top_panel() {
408 self.view.remove_child(0);
409 }
7f273738 410
82cc9fc4 411 self.view.insert_child(0, Panel::new(view));
7f273738
CH
412 self
413 }
414
cc655f8b
SS
415 ///
416 /// This function returns a tuple of vectors. The first vector contains the currently selected
417 /// disks in order of their selection slot. Empty slots are filtered out. The second vector
418 /// contains indices of each slot's selection, which enables us to restore the selection even
419 /// for empty slots.
420 ///
421 fn get_disks_and_selection(&mut self) -> Option<(Vec<Disk>, Vec<usize>)> {
56a304d5 422 let mut disks = vec![];
82cc9fc4
CH
423 let view_top_index = usize::from(self.has_top_panel());
424
d36c96af
CH
425 let disk_form = self
426 .view
2379338c
CH
427 .get_child_mut(view_top_index)?
428 .downcast_mut::<LinearLayout>()?
429 .get_child_mut(0)?
430 .downcast_mut::<LinearLayout>()?
431 .get_child_mut(2)?
432 .downcast_mut::<ScrollView<NamedView<FormView>>>()?
433 .get_inner_mut()
434 .get_mut();
56a304d5 435
cc655f8b
SS
436 let mut selected_disks = Vec::new();
437
d36c96af 438 for i in 0..disk_form.len() {
429718b8 439 let disk = disk_form.get_value::<SelectView<Option<Disk>>, _>(i)?;
56a304d5 440
429718b8
CH
441 // `None` means no disk was selected for this slot
442 if let Some(disk) = disk {
443 disks.push(disk);
444 }
cc655f8b
SS
445
446 selected_disks.push(
447 disk_form
448 .get_child::<SelectView<Option<Disk>>>(i)?
449 .selected_id()?,
450 );
56a304d5
CH
451 }
452
cc655f8b 453 Some((disks, selected_disks))
ee1437bb 454 }
56a304d5 455
ee1437bb 456 fn inner_mut(&mut self) -> Option<&mut T> {
82cc9fc4
CH
457 let view_top_index = usize::from(self.has_top_panel());
458
ee1437bb 459 self.view
82cc9fc4
CH
460 .get_child_mut(view_top_index)?
461 .downcast_mut::<LinearLayout>()?
56a304d5 462 .get_child_mut(2)?
ee1437bb
CH
463 .downcast_mut::<LinearLayout>()?
464 .get_child_mut(2)?
465 .downcast_mut::<T>()
466 }
82cc9fc4
CH
467
468 fn has_top_panel(&self) -> bool {
469 // The root view should only ever have one or two children
470 assert!([1, 2].contains(&self.view.len()));
471
472 self.view.len() == 2
473 }
ee1437bb
CH
474}
475
476impl<T: 'static> ViewWrapper for MultiDiskOptionsView<T> {
477 cursive::wrap_impl!(self.view: LinearLayout);
478}
479
480struct BtrfsBootdiskOptionsView {
d36c96af 481 view: MultiDiskOptionsView<FormView>,
ee1437bb
CH
482}
483
484impl BtrfsBootdiskOptionsView {
ee1437bb
CH
485 fn new(disks: &[Disk], options: &BtrfsBootdiskOptions) -> Self {
486 let view = MultiDiskOptionsView::new(
487 disks,
cc655f8b 488 &options.selected_disks,
d36c96af 489 FormView::new().child("hdsize", DiskSizeEditView::new().content(options.disk_size)),
7f273738
CH
490 )
491 .top_panel(TextView::new("Btrfs integration is a technology preview!").center());
ee1437bb
CH
492
493 Self { view }
494 }
495
e8a7b9ba
CH
496 fn new_with_defaults(disks: &[Disk]) -> Self {
497 Self::new(disks, &BtrfsBootdiskOptions::defaults_from(disks))
498 }
499
ee1437bb 500 fn get_values(&mut self) -> Option<(Vec<Disk>, BtrfsBootdiskOptions)> {
cc655f8b 501 let (disks, selected_disks) = self.view.get_disks_and_selection()?;
d36c96af 502 let disk_size = self.view.inner_mut()?.get_value::<DiskSizeEditView, _>(0)?;
56a304d5 503
cc655f8b
SS
504 Some((
505 disks,
506 BtrfsBootdiskOptions {
507 disk_size,
508 selected_disks,
509 },
510 ))
56a304d5
CH
511 }
512}
513
514impl ViewWrapper for BtrfsBootdiskOptionsView {
d36c96af 515 cursive::wrap_impl!(self.view: MultiDiskOptionsView<FormView>);
56a304d5
CH
516}
517
7393ed88 518struct ZfsBootdiskOptionsView {
d36c96af 519 view: MultiDiskOptionsView<FormView>,
7393ed88
CH
520}
521
522impl ZfsBootdiskOptionsView {
523 // TODO: Re-apply previous disk selection from `options` correctly
521dfff5
CH
524 fn new(
525 runinfo: &RuntimeInfo,
526 options: &ZfsBootdiskOptions,
527 product_conf: &ProductConfig,
528 ) -> Self {
529 let is_pve = product_conf.product == ProxmoxProduct::PVE;
530
d36c96af
CH
531 let inner = FormView::new()
532 .child("ashift", IntegerEditView::new().content(options.ashift))
533 .child(
7393ed88
CH
534 "compress",
535 SelectView::new()
536 .popup()
537 .with_all(ZFS_COMPRESS_OPTIONS.iter().map(|o| (o.to_string(), *o)))
538 .selected(
539 ZFS_COMPRESS_OPTIONS
540 .iter()
541 .position(|o| *o == options.compress)
542 .unwrap_or_default(),
543 ),
d36c96af
CH
544 )
545 .child(
7393ed88
CH
546 "checksum",
547 SelectView::new()
548 .popup()
549 .with_all(ZFS_CHECKSUM_OPTIONS.iter().map(|o| (o.to_string(), *o)))
550 .selected(
551 ZFS_CHECKSUM_OPTIONS
552 .iter()
553 .position(|o| *o == options.checksum)
554 .unwrap_or_default(),
555 ),
d36c96af
CH
556 )
557 .child("copies", IntegerEditView::new().content(options.copies))
521dfff5
CH
558 .child_conditional(
559 is_pve,
560 "ARC max size",
561 IntegerEditView::new_with_suffix("MiB")
562 .max_value(runinfo.total_memory)
563 .content(options.arc_max),
564 )
d36c96af 565 .child("hdsize", DiskSizeEditView::new().content(options.disk_size));
7393ed88 566
521dfff5 567 let view = MultiDiskOptionsView::new(&runinfo.disks, &options.selected_disks, inner)
7f273738
CH
568 .top_panel(TextView::new(
569 "ZFS is not compatible with hardware RAID controllers, for details see the documentation."
570 ).center());
571
572 Self { view }
7393ed88
CH
573 }
574
13490d13
CH
575 fn new_with_defaults(runinfo: &RuntimeInfo, product_conf: &ProductConfig) -> Self {
576 Self::new(
521dfff5 577 runinfo,
13490d13 578 &ZfsBootdiskOptions::defaults_from(runinfo, product_conf),
521dfff5 579 product_conf,
13490d13 580 )
e8a7b9ba
CH
581 }
582
7393ed88 583 fn get_values(&mut self) -> Option<(Vec<Disk>, ZfsBootdiskOptions)> {
cc655f8b 584 let (disks, selected_disks) = self.view.get_disks_and_selection()?;
d36c96af 585 let view = self.view.inner_mut()?;
521dfff5
CH
586 let has_arc_max = view.len() >= 6;
587 let disk_size_index = if has_arc_max { 5 } else { 4 };
7393ed88 588
d36c96af
CH
589 let ashift = view.get_value::<IntegerEditView, _>(0)?;
590 let compress = view.get_value::<SelectView<_>, _>(1)?;
591 let checksum = view.get_value::<SelectView<_>, _>(2)?;
592 let copies = view.get_value::<IntegerEditView, _>(3)?;
521dfff5
CH
593 let disk_size = view.get_value::<DiskSizeEditView, _>(disk_size_index)?;
594
595 let arc_max = if has_arc_max {
596 view.get_value::<IntegerEditView, _>(4)?
597 .max(ZFS_ARC_MIN_SIZE_MIB)
598 } else {
599 0 // use built-in ZFS default value
600 };
7393ed88
CH
601
602 Some((
603 disks,
604 ZfsBootdiskOptions {
605 ashift,
606 compress,
607 checksum,
608 copies,
521dfff5 609 arc_max,
7393ed88 610 disk_size,
cc655f8b 611 selected_disks,
7393ed88
CH
612 },
613 ))
614 }
615}
616
617impl ViewWrapper for ZfsBootdiskOptionsView {
d36c96af 618 cursive::wrap_impl!(self.view: MultiDiskOptionsView<FormView>);
7393ed88
CH
619}
620
e14adfe3 621fn advanced_options_view(
521dfff5 622 runinfo: &RuntimeInfo,
d81cffcb 623 options_ref: BootdiskOptionsRef,
e14adfe3
CH
624 product_conf: ProductConfig,
625) -> impl View {
7393ed88 626 Dialog::around(AdvancedBootdiskOptionsView::new(
521dfff5 627 runinfo,
d81cffcb 628 &(*options_ref).borrow(),
e14adfe3 629 product_conf,
7393ed88
CH
630 ))
631 .title("Advanced bootdisk options")
632 .button("Ok", {
d81cffcb 633 let options_ref = options_ref.clone();
7393ed88
CH
634 move |siv| {
635 let options = siv
636 .call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| {
637 view.get_content_mut()
ed37980f 638 .downcast_mut::<AdvancedBootdiskOptionsView>()
fea97303 639 .map(AdvancedBootdiskOptionsView::get_values)
7393ed88
CH
640 })
641 .flatten();
642
597d10f6
CH
643 let options = match options {
644 Some(Ok(options)) => options,
645 Some(Err(err)) => {
646 siv.add_layer(Dialog::info(err));
647 return;
648 }
649 None => {
650 siv.add_layer(Dialog::info("Failed to retrieve bootdisk options view"));
651 return;
652 }
653 };
654
4f2c43ee
CH
655 if let Err(duplicate) = check_for_duplicate_disks(&options.disks) {
656 siv.add_layer(Dialog::info(format!(
657 "Cannot select same disk twice: {duplicate}"
658 )));
659 return;
5ed7f495
TL
660 }
661
7393ed88 662 siv.pop_layer();
597d10f6 663 *(*options_ref).borrow_mut() = options;
7393ed88
CH
664 }
665 })
666 .with_name("advanced-bootdisk-options-dialog")
fccf6bb0 667 .max_size((120, 40))
ed191e60 668}