• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-bootloader / 14854565708

06 May 2025 07:59AM UTC coverage: 87.379% (-0.04%) from 87.417%
14854565708

Pull #716

github

web-flow
Merge f1f4ee92d into d2595d11d
Pull Request #716: Unifying Bootloader Options for systemd-boot and grub2-bls

92 of 119 new or added lines in 10 files covered. (77.31%)

7 existing lines in 1 file now uncovered.

3434 of 3930 relevant lines covered (87.38%)

12.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.98
/src/lib/bootloader/grub2_widgets.rb
1
# frozen_string_literal: true
2

3
require "yast"
1✔
4

5
require "bootloader/generic_widgets"
1✔
6
require "bootloader/device_map_dialog"
1✔
7
require "bootloader/serial_console"
1✔
8
require "bootloader/cpu_mitigations"
1✔
9
require "bootloader/systeminfo"
1✔
10
require "bootloader/os_prober"
1✔
11
require "bootloader/device_path"
1✔
12
require "bootloader/pmbr"
1✔
13
require "cfa/matcher"
1✔
14

15
Yast.import "Initrd"
1✔
16
Yast.import "Label"
1✔
17
Yast.import "Report"
1✔
18
Yast.import "UI"
1✔
19
Yast.import "Mode"
1✔
20
Yast.import "Arch"
1✔
21

22
module Bootloader
1✔
23
  module Grub2Widget
1✔
24
    # Adds to generic widget grub2 specific helpers
25
    module Grub2Helper
1✔
26
      def grub_default
1✔
27
        BootloaderFactory.current.grub_default
72✔
28
      end
29

30
      def stage1
1✔
31
        BootloaderFactory.current.stage1
9✔
32
      end
33

34
      def password
1✔
35
        BootloaderFactory.current.password
36✔
36
      end
37

38
      def grub2
1✔
39
        BootloaderFactory.current
20✔
40
      end
41
    end
42

43
    # Represents bootloader timeout value
44
    class TimeoutWidget < CWM::IntField
1✔
45
      include Grub2Helper
1✔
46

47
      def initialize(hidden_menu_widget)
1✔
48
        textdomain "bootloader"
11✔
49

50
        super()
11✔
51

52
        @minimum = -1
11✔
53
        @maximum = 600
11✔
54
        @hidden_menu_widget = hidden_menu_widget
11✔
55
      end
56

57
      attr_reader :minimum, :maximum
1✔
58

59
      def label
1✔
60
        _("&Timeout in Seconds")
1✔
61
      end
62

63
      def help
1✔
64
        _("<p><b>Timeout in Seconds</b>\n" \
1✔
65
          "specifies the time the boot loader will wait until the default kernel is loaded.</p>\n")
66
      end
67

68
      def init
1✔
69
        self.value = if grub_default.hidden_timeout && grub_default.hidden_timeout.to_i > 0
2✔
70
          grub_default.hidden_timeout.to_i
1✔
71
        else
72
          grub_default.timeout.to_i
1✔
73
        end
74
      end
75

76
      def store
1✔
77
        if @hidden_menu_widget.is_a?(CWM::Empty)
4✔
78
          grub_default.timeout = value.to_s
×
79
        elsif @hidden_menu_widget.checked?
4✔
80
          grub_default.hidden_timeout = value.to_s
2✔
81
          grub_default.timeout = "0"
2✔
82
        else
83
          grub_default.hidden_timeout = "0"
2✔
84
          grub_default.timeout = value.to_s
2✔
85
        end
86
      end
87
    end
88

89
    # Represents decision if bootloader need activated partition
90
    class ActivateWidget < CWM::CheckBox
1✔
91
      include Grub2Helper
1✔
92

93
      def initialize
1✔
94
        textdomain "bootloader"
5✔
95

96
        super
5✔
97
      end
98

99
      def label
1✔
100
        _("Set &active Flag in Partition Table for Boot Partition")
1✔
101
      end
102

103
      def help
1✔
104
        _(
1✔
105
          "<p><b>Set Active Flag in Partition Table for Boot Partition</b>\n" \
106
          "specifies whether the partition containing " \
107
          "the boot loader will have the \"active\" flag." \
108
          " The generic MBR code will then\n" \
109
          "boot the active partition. Older BIOSes require one partition to be active even\n" \
110
          "if the boot loader is installed in the MBR.</p>"
111
        )
112
      end
113

114
      def init
1✔
115
        self.value = stage1.activate?
1✔
116
      end
117

118
      def store
1✔
119
        stage1.activate = checked?
1✔
120
      end
121
    end
122

123
    # Represents decision if generic MBR have to be installed on disk
124
    class GenericMBRWidget < CWM::CheckBox
1✔
125
      include Grub2Helper
1✔
126

127
      def initialize
1✔
128
        textdomain "bootloader"
5✔
129

130
        super
5✔
131
      end
132

133
      def label
1✔
134
        _("Write &generic Boot Code to MBR")
1✔
135
      end
136

137
      def help
1✔
138
        _(
1✔
139
          "<p><b>Write generic Boot Code to MBR</b> replace the master boot" \
140
          " record of your disk with generic code (OS independent code which\n" \
141
          "boots the active partition).</p>"
142
        )
143
      end
144

145
      def init
1✔
146
        self.value = stage1.generic_mbr?
1✔
147
      end
148

149
      def store
1✔
150
        stage1.generic_mbr = checked?
1✔
151
      end
152
    end
153

154
    # Represents decision if menu should be hidden or visible
155
    class HiddenMenuWidget < CWM::CheckBox
1✔
156
      include Grub2Helper
1✔
157

158
      def initialize
1✔
159
        textdomain "bootloader"
5✔
160

161
        super
5✔
162
      end
163

164
      def label
1✔
165
        _("&Hide Menu on Boot")
1✔
166
      end
167

168
      def help
1✔
169
        _(
1✔
170
          "<p>Selecting <b>Hide Menu on Boot</b> will hide the boot menu.</p>"
171
        )
172
      end
173

174
      def init
1✔
175
        self.value = grub_default.hidden_timeout && grub_default.hidden_timeout.to_i > 0
1✔
176
      end
177
    end
178

179
    # Represents if os prober should be run
180
    class OSProberWidget < CWM::CheckBox
1✔
181
      include Grub2Helper
1✔
182

183
      def initialize
1✔
184
        textdomain "bootloader"
5✔
185

186
        super
5✔
187
      end
188

189
      def label
1✔
190
        _("Pro&be Foreign OS")
1✔
191
      end
192

193
      def help
1✔
194
        _(
1✔
195
          "<p><b>Probe Foreign OS</b> by means of os-prober for multiboot with " \
196
          "other foreign distribution </p>"
197
        )
198
      end
199

200
      def init
1✔
201
        self.value = grub_default.os_prober.enabled?
1✔
202
      end
203

204
      def store
1✔
205
        grub_default.os_prober.value = checked?
1✔
206
      end
207
    end
208

209
    # Represents switcher for Trusted Boot
210
    class TrustedBootWidget < CWM::CheckBox
1✔
211
      include Grub2Helper
1✔
212

213
      def initialize
1✔
214
        textdomain "bootloader"
8✔
215

216
        super
8✔
217
      end
218

219
      def label
1✔
220
        _("&Trusted Boot Support")
2✔
221
      end
222

223
      def help
1✔
224
        res = _("<p><b>Trusted Boot</b> " \
2✔
225
                "means measuring the integrity of the boot process,\n" \
226
                "with the help from the hardware (a TPM, Trusted Platform Module,\n" \
227
                "chip).\n")
228
        if grub2.name == "grub2"
2✔
229
          res += _("First you need to make sure Trusted Boot is enabled in the BIOS\n" \
1✔
230
                   "setup (the setting may be named \"Security Chip\", for example).\n")
231
        end
232

233
        res += "</p>"
2✔
234

235
        res
2✔
236
      end
237

238
      def init
1✔
239
        self.value = grub2.trusted_boot
×
240
      end
241

242
      def store
1✔
243
        grub2.trusted_boot = value
×
244
      end
245

246
      def validate
1✔
247
        return true if Yast::Mode.config || !value || ["grub2-efi",
2✔
248
                                                       "grub2-bls"].include?(grub2.name)
249

250
        tpm_files = Dir.glob("/sys/**/pcrs")
1✔
251
        if !tpm_files.empty? && !File.read(tpm_files[0], 1).nil?
1✔
252
          # check for file size does not work, since FS reports it 4096
253
          # even if the file is in fact empty and a single byte cannot
254
          # be read, therefore testing real reading (details: bsc#994556)
255
          return true
×
256
        end
257

258
        Yast::Popup.ContinueCancel(_("Trusted Platform Module not found.\n" \
1✔
259
                                     "Make sure it is enabled in BIOS.\n" \
260
                                     "The system will not boot otherwise."))
261
      end
262
    end
263

264
    # Represents switcher for NVRAM update
265
    class UpdateNvramWidget < CWM::CheckBox
1✔
266
      include Grub2Helper
1✔
267

268
      def initialize
1✔
269
        textdomain "bootloader"
6✔
270

271
        super
6✔
272
      end
273

274
      def label
1✔
275
        _("Update &NVRAM Entry")
1✔
276
      end
277

278
      def help
1✔
279
        _("<p><b>Update NVRAM Entry</b> will add nvram entry for the bootloader\n" \
1✔
280
          "in the firmware.\n" \
281
          "This is usually desirable unless you want to preserve specific settings\n" \
282
          "or need to work around firmware issues.</p>\n")
283
      end
284

285
      def init
1✔
286
        self.value = grub2.update_nvram
2✔
287
      end
288

289
      def store
1✔
290
        grub2.update_nvram = value
2✔
291
      end
292
    end
293

294
    # Represents grub password protection widget
295
    class GrubPasswordWidget < CWM::CustomWidget
1✔
296
      include Grub2Helper
1✔
297

298
      def initialize
1✔
299
        textdomain "bootloader"
25✔
300

301
        super
25✔
302
      end
303

304
      MASKED_PASSWORD = "**********"
1✔
305

306
      def contents
1✔
307
        HBox(
1✔
308
          CheckBoxFrame(
309
            Id(:use_pas),
310
            _("Prot&ect Boot Loader with Password"),
311
            true,
312
            VBox(
313
              HBox(
314
                HSpacing(2),
315
                # TRANSLATORS: checkbox entry
316
                CheckBox(Id(:unrestricted_pw), _("P&rotect Entry Modification Only")),
317
                HStretch()
318
              ),
319
              HBox(
320
                HSpacing(2),
321
                # TRANSLATORS: text entry, please keep it short
322
                Password(Id(:pw1), Opt(:hstretch), _("&Password for GRUB2 User 'root'")),
323
                # text entry
324
                HSpacing(2),
325
                Password(Id(:pw2), Opt(:hstretch), _("Re&type Password")),
326
                HStretch()
327
              )
328
            )
329
          )
330
        )
331
      end
332

333
      def validate
1✔
334
        return true unless Yast::UI.QueryWidget(Id(:use_pas), :Value)
4✔
335

336
        if Yast::UI.QueryWidget(Id(:pw1), :Value) == ""
3✔
337
          Yast::Report.Error(_("The password must not be empty."))
1✔
338
          Yast::UI.SetFocus(Id(:pw1))
1✔
339
          return false
1✔
340
        end
341
        if Yast::UI.QueryWidget(Id(:pw1), :Value) == Yast::UI.QueryWidget(Id(:pw2), :Value)
2✔
342
          return true
1✔
343
        end
344

345
        Yast::Report.Error(_(
1✔
346
                             "'Password' and 'Retype password'\ndo not match. Retype the password."
347
                           ))
348
        Yast::UI.SetFocus(Id(:pw1))
1✔
349
        false
1✔
350
      end
351

352
      def init
1✔
353
        enabled = password.used?
9✔
354
        # read state on disk only if not already set by user (bnc#900026)
355
        value = (enabled && password.password?) ? MASKED_PASSWORD : ""
9✔
356

357
        Yast::UI.ChangeWidget(Id(:use_pas), :Value, enabled)
9✔
358
        Yast::UI.ChangeWidget(Id(:pw1), :Enabled, enabled)
9✔
359
        Yast::UI.ChangeWidget(Id(:pw1), :Value, value)
9✔
360
        Yast::UI.ChangeWidget(Id(:pw2), :Enabled, enabled)
9✔
361
        Yast::UI.ChangeWidget(Id(:pw2), :Value, value)
9✔
362
        Yast::UI.ChangeWidget(Id(:unrestricted_pw), :Enabled, enabled)
9✔
363
        Yast::UI.ChangeWidget(Id(:unrestricted_pw), :Value, password.unrestricted?)
9✔
364
      end
365

366
      def handle(event)
1✔
367
        return unless event["ID"] == :use_pas
3✔
368

369
        enabled = Yast::UI.QueryWidget(Id(:use_pas), :Value)
2✔
370
        Yast::UI.ChangeWidget(Id(:unrestricted_pw), :Enabled, enabled)
2✔
371
        Yast::UI.ChangeWidget(Id(:pw1), :Enabled, enabled)
2✔
372
        Yast::UI.ChangeWidget(Id(:pw2), :Enabled, enabled)
2✔
373

374
        nil
2✔
375
      end
376

377
      def store
1✔
378
        usepass = Yast::UI.QueryWidget(Id(:use_pas), :Value)
6✔
379
        matcher = CFA::Matcher.new(key: "rd.shell")
6✔
380
        grub_default.kernel_params.remove_parameter(matcher)
6✔
381
        if !usepass
6✔
382
          password.used = false
2✔
383
          return
2✔
384
        end
385

386
        password.used = true
4✔
387

388
        value = Yast::UI.QueryWidget(Id(:pw1), :Value)
4✔
389
        # special value as we do not know password, so it mean user do not change it
390
        password.password = value if value != MASKED_PASSWORD
4✔
391

392
        value = Yast::UI.QueryWidget(Id(:unrestricted_pw), :Value)
4✔
393
        grub_default.kernel_params.add_parameter("rd.shell", "0") if value
4✔
394
        password.unrestricted = value
4✔
395
      end
396

397
      def help
1✔
398
        _(
1✔
399
          "<p><b>Protect Boot Loader with Password</b>\n" \
400
          "at boot time, modifying or even booting any entry will require the" \
401
          " password. If <b>Protect Entry Modification Only</b> is checked then " \
402
          "booting any entry is not restricted but modifying entries requires " \
403
          "the password (which is the way GRUB 1 behaved). As side-effect of " \
404
          "this option, rd.shell=0 is added to kernel parameters, to prevent " \
405
          "an unauthorized access to the initrd shell. " \
406
          "YaST will only accept the password if you repeat it in " \
407
          "<b>Retype Password</b>. The password applies to the GRUB2 user 'root' " \
408
          "which is distinct from the Linux 'root'. YaST currently does not support " \
409
          "other GRUB2 users. If you need them, use a separate GRUB2 script.</p>"
410
        )
411
      end
412
    end
413

414
    # Represents graphical and serial console setup for bootloader
415
    #
416
    # Allows to configure terminal for grub. It can configure grub
417
    # to use either graphical terminal, console or console over serial line.
418
    #
419
    # Graphical or serial terminal has to be selected explicitly. Either
420
    # one of them or both at once.
421
    # Native console is configured as a fallback when nothing else is selected.
422
    class ConsoleWidget < CWM::CustomWidget
1✔
423
      include Grub2Helper
1✔
424

425
      def initialize
1✔
426
        textdomain "bootloader"
21✔
427

428
        super
21✔
429
      end
430

431
      def contents
1✔
432
        VBox(
1✔
433
          graphical_console_frame,
434
          serial_console_frame
435
        )
436
      end
437

438
      def help
1✔
439
        # Translators: do not translate the quoted parts like "unit"
440
        _(
×
441
          "<p><b>Graphical console</b> when checked it allows to use various " \
442
          "display resolutions. The <tt>auto</tt> option tries to find " \
443
          "the best one when booting starts.</p>\n" \
444
          "<p><b>Serial console</b> when checked it redirects the boot output " \
445
          "to a serial device like <tt>ttyS0</tt>. " \
446
          "At least the <tt>--unit</tt> option has to be specified, " \
447
          "and the complete syntax is <tt>%s</tt>. " \
448
          "Other parts are optional and if not set, a default is used. " \
449
          "<tt>NUM</tt> in commands stands for a positive number like 8. " \
450
          "Example parameters are <tt>serial --speed=38400 --unit=0</tt>.</p>"
451
        ) % syntax
452
      end
453

454
      def init
1✔
455
        init_console
6✔
456
        init_gfxterm
5✔
457

458
        Yast::UI.ChangeWidget(Id(:theme), :Value, grub_default.theme || "")
5✔
459
      rescue RuntimeError
460
        raise ::Bootloader::UnsupportedOption, "GRUB_TERMINAL"
1✔
461
      end
462

463
      def validate
1✔
464
        if Yast::UI.QueryWidget(Id(:console_frame), :Value)
4✔
465
          console_value = Yast::UI.QueryWidget(Id(:console_args), :Value)
3✔
466
          if console_value.strip.empty?
3✔
467
            Yast::Report.Error(
1✔
468
              _("To enable serial console you must provide the corresponding arguments.")
469
            )
470
            Yast::UI.SetFocus(Id(:console_args))
1✔
471
            return false
1✔
472
          end
473
          if ::Bootloader::SerialConsole.load_from_console_args(console_value).nil?
2✔
474
            # Translators: do not translate "unit"
475
            msg = _("To enable the serial console you must provide the corresponding arguments.\n" \
1✔
476
                    "The \"unit\" argument is required, the complete syntax is:\n%s") % syntax
477
            Yast::Report.Error(msg)
1✔
478
            Yast::UI.SetFocus(Id(:console_args))
1✔
479
            return false
1✔
480
          end
481
        end
482
        true
2✔
483
      end
484

485
      def store
1✔
486
        use_serial = Yast::UI.QueryWidget(Id(:console_frame), :Value)
6✔
487
        use_gfxterm = Yast::UI.QueryWidget(Id(:gfxterm_frame), :Value)
6✔
488
        use_console = !use_serial && !use_gfxterm
6✔
489

490
        grub_default.terminal = []
6✔
491
        grub_default.terminal = [:gfxterm] if use_gfxterm
6✔
492

493
        if use_serial
6✔
494
          console_value = Yast::UI.QueryWidget(Id(:console_args), :Value)
2✔
495
          BootloaderFactory.current.enable_serial_console(console_value)
2✔
496
        elsif use_console
4✔
497
          grub_default.terminal = [:console]
3✔
498
        end
499

500
        mode = Yast::UI.QueryWidget(Id(:gfxmode), :Value)
6✔
501
        grub_default.gfxmode = mode if mode != ""
6✔
502

503
        theme = Yast::UI.QueryWidget(Id(:theme), :Value)
6✔
504
        grub_default.theme = theme if theme != ""
6✔
505
      end
506

507
      def handle(event)
1✔
508
        return if event["ID"] != :browsegfx
3✔
509

510
        theme_dir = "/boot/grub2/themes/openSUSE"
2✔
511
        theme_dir = "/boot/grub2" unless ::Dir.exist?(theme_dir)
2✔
512

513
        file = Yast::UI.AskForExistingFile(
2✔
514
          theme_dir,
515
          "*.txt",
516
          _("Choose new graphical theme file")
517
        )
518

519
        Yast::UI.ChangeWidget(Id(:theme), :Value, file) if file
2✔
520

521
        nil
2✔
522
      end
523

524
    private
1✔
525

526
      # Initializates serial console specific widgets
527
      def init_console
1✔
528
        enable = grub_default.terminal.include?(:serial) if grub_default.terminal
6✔
529
        Yast::UI.ChangeWidget(Id(:console_frame), :Value, enable)
5✔
530
        args = grub_default.serial_console || ""
5✔
531
        Yast::UI.ChangeWidget(Id(:console_args), :Value, args)
5✔
532
      end
533

534
      # Initializates gfxterm specific widgets
535
      def init_gfxterm
1✔
536
        enable = grub_default.terminal.include?(:gfxterm) if grub_default.terminal
5✔
537
        Yast::UI.ChangeWidget(Id(:gfxterm_frame), :Value, enable)
5✔
538

539
        Yast::UI.ChangeWidget(Id(:gfxmode), :Items, vga_modes_items)
5✔
540
        mode = grub_default.gfxmode
5✔
541

542
        # there's mode specified, use it
543
        Yast::UI.ChangeWidget(Id(:gfxmode), :Value, mode) if mode && mode != ""
5✔
544
      end
545

546
      # Explanation for help and error messages
547
      def syntax
1✔
548
        # Translators: NUM is an abbreviation for "number",
549
        # to be substituted in a command like
550
        # "serial --unit=NUM --speed=NUM --parity={odd|even|no} --word=NUM --stop=NUM"
551
        # so do not use punctuation
552
        n = _("NUM")
1✔
553
        "serial --unit=#{n} --speed=#{n} --parity={odd|even|no} --word=#{n} --stop=#{n}"
1✔
554
      end
555

556
      def graphical_console_frame
1✔
557
        CheckBoxFrame(
1✔
558
          Id(:gfxterm_frame),
559
          _("&Graphical console"),
560
          true,
561
          HBox(
562
            HSpacing(2),
563
            ComboBox(
564
              Id(:gfxmode), Opt(:editable, :hstretch), _("&Console resolution")
565
            ),
566
            HBox(
567
              Left(
568
                InputField(
569
                  Id(:theme), Opt(:hstretch), _("&Console theme")
570
                )
571
              ),
572
              VBox(
573
                Left(Label("")),
574
                Left(
575
                  PushButton(Id(:browsegfx), Opt(:notify), Yast::Label.BrowseButton)
576
                )
577
              )
578
            ),
579
            HStretch()
580
          )
581
        )
582
      end
583

584
      def vga_modes_items
1✔
585
        return @vga_modes if @vga_modes
5✔
586

587
        @vga_modes = Yast::Initrd.VgaModes
5✔
588

589
        @vga_modes.sort! do |a, b|
5✔
590
          res = a["width"] <=> b["width"]
285✔
591
          res = a["height"] <=> b["height"] if res.zero?
285✔
592

593
          res
285✔
594
        end
595

596
        @vga_modes.map! { |a| "#{a["width"]}x#{a["height"]}" }
135✔
597
        @vga_modes.uniq!
5✔
598

599
        @vga_modes.map! { |m| Item(Id(m), m) }
50✔
600
        @vga_modes.unshift(Item(Id("auto"), _("Autodetect by grub2")))
5✔
601

602
        @vga_modes
5✔
603
      end
604

605
      def serial_console_frame
1✔
606
        CheckBoxFrame(
1✔
607
          Id(:console_frame),
608
          _("&Serial console"),
609
          true,
610
          HBox(
611
            HSpacing(2),
612
            InputField(
613
              Id(:console_args),
614
              Opt(:hstretch),
615
              _("&Console arguments")
616
            ),
617
            HStretch()
618
          )
619
        )
620
      end
621
    end
622

623
    # Represents stage1 location for bootloader
624
    class LoaderLocationWidget < CWM::CustomWidget
1✔
625
      include Grub2Helper
1✔
626

627
      def contents
1✔
628
        textdomain "bootloader"
1✔
629

630
        VBox(
1✔
631
          Frame(
632
            _("Boot Code Location"),
633
            HBox(
634
              HSpacing(1),
635
              VBox(*location_checkboxes),
636
              HSpacing(1)
637
            )
638
          ),
639
          VSpacing(1)
640
        )
641
      end
642

643
      def handle(event)
1✔
644
        return unless event["ID"] == :custom
1✔
645

646
        checked = Yast::UI.QueryWidget(Id(:custom), :Value)
×
647
        Yast::UI.ChangeWidget(Id(:custom_list), :Enabled, checked)
×
648

649
        nil
×
650
      end
651

652
      def init
1✔
653
        if locations.include?(:boot)
×
654
          Yast::UI.ChangeWidget(Id(:boot), :Value,
×
655
            stage1.boot_partition?)
656
        end
657
        if locations.include?(:logical)
×
658
          Yast::UI.ChangeWidget(Id(:logical), :Value, stage1.boot_partition?)
×
659
        end
660
        if locations.include?(:extended)
×
661
          Yast::UI.ChangeWidget(Id(:extended), :Value, stage1.extended_boot_partition?)
×
662
        end
663
        Yast::UI.ChangeWidget(Id(:mbr), :Value, stage1.mbr?) if locations.include?(:mbr)
×
664

665
        init_custom_devices(stage1.custom_devices)
×
666
      end
667

668
      def store
1✔
669
        stage1.clear_devices
×
670
        locations.each { |l| add_location(l) }
×
671

672
        return unless Yast::UI.QueryWidget(:custom, :Value)
×
673

674
        devs = Yast::UI.QueryWidget(:custom_list, :Value)
×
675
        devs.split(",").each do |dev|
×
676
          stage1.add_device(DevicePath.new(dev).path)
×
677
        end
678
      end
679

680
      def validate
1✔
681
        return true if !Yast::UI.QueryWidget(:custom, :Value)
1✔
682

683
        devs = Yast::UI.QueryWidget(:custom_list, :Value)
×
684

685
        if devs.strip.empty?
×
686
          Yast::Report.Error(_("Custom boot device has to be specified if checked"))
×
687
          Yast::UI.SetFocus(Id(:custom_list))
×
688
          return false
×
689
        end
690

691
        invalid_devs = invalid_custom_devices(devs)
×
692
        if !invalid_devs.empty?
×
693
          ret = Yast::Popup.ContinueCancel(
×
694
            format(
695
              _(
696
                "These custom devices can be invalid: %s." \
697
                "Please check if exist and spelled correctly." \
698
                "Do you want to continue?"
699
              ),
700
              invalid_devs.join(", ")
701
            )
702
          )
703

704
          if !ret
×
705
            Yast::UI.SetFocus(Id(:custom_list))
×
706
            return false
×
707
          end
708
        end
709

710
        true
×
711
      end
712

713
    private
1✔
714

715
      def add_location(id)
1✔
716
        return unless Yast::UI.QueryWidget(Id(id), :Value)
×
717

718
        case id
×
719
        when :boot, :logical
720
          stage1.boot_partition_names.each { |d| stage1.add_udev_device(d) }
×
721
        when :extended
722
          stage1.extended_boot_partitions_names.each { |d| stage1.add_udev_device(d) }
×
723
        when :mbr
724
          stage1.boot_disk_names.each { |d| stage1.add_udev_device(d) }
×
725
        end
726
      end
727

728
      def init_custom_devices(custom_devices)
1✔
729
        if custom_devices.empty?
×
730
          Yast::UI.ChangeWidget(:custom, :Value, false)
×
731
          Yast::UI.ChangeWidget(:custom_list, :Enabled, false)
×
732
        else
733
          Yast::UI.ChangeWidget(:custom, :Value, true)
×
734
          Yast::UI.ChangeWidget(:custom_list, :Enabled, true)
×
735
          Yast::UI.ChangeWidget(:custom_list, :Value, custom_devices.join(","))
×
736
        end
737
      end
738

739
      # Checks list of custom devices
740
      #
741
      # @param devs_list[String] comma separated list of device definitions
742
      #
743
      # @return [Array<String>] devices which didn't pass validation
744
      def invalid_custom_devices(devs_list)
1✔
745
        # almost any byte sequence is potentially valid path in unix like systems
746
        # AY profile can be generated for whatever system so we cannot decite if
747
        # particular byte sequence is valid or not
748
        return [] if Yast::Mode.config
×
749

750
        devs_list.split(",").reject do |d|
×
751
          dev_path = DevicePath.new(d)
×
752

753
          if Yast::Mode.installation
×
754
            # uuids are generated later by mkfs, so not known in time of installation
755
            # so whatever can be true
756
            dev_path.uuid? || dev_path.valid?
×
757
          else
758
            dev_path.valid?
×
759
          end
760
        end
761
      end
762

763
      def locations
1✔
764
        @locations ||= stage1.available_locations
4✔
765
      end
766

767
      def location_checkboxes
1✔
768
        checkboxes = []
1✔
769
        # TRANSLATORS: %s is used to specify exact devices
770
        add_checkbox(checkboxes, :boot,
1✔
771
          format(_("Wri&te to Partition (%s)"), stage1.boot_partition_names.join(", ")))
772
        # TRANSLATORS: %s is used to specify exact devices
773
        add_checkbox(checkboxes, :logical,
1✔
774
          format(_("Wri&te to Logical Partition (%s)"), stage1.boot_partition_names.join(", ")))
775
        # TRANSLATORS: %s is used to specify exact devices
776
        add_checkbox(checkboxes, :extended,
1✔
777
          format(_("Write to &Extended Partition (%s)"),
778
            stage1.extended_boot_partitions_names.join(", ")))
779
        # TRANSLATORS: %s is used to specify exact devices
780
        add_checkbox(checkboxes, :mbr,
1✔
781
          format(_("Write to &Master Boot Record (%s)"), stage1.boot_disk_names.join(", ")))
782

783
        checkboxes.concat(custom_partition_content)
1✔
784
      end
785

786
      def add_checkbox(checkboxes, id, title)
1✔
787
        checkboxes << Left(CheckBox(Id(id), title)) if locations.include?(id)
4✔
788
      end
789

790
      def custom_partition_content
1✔
791
        [
792
          Left(CheckBox(Id(:custom), Opt(:notify), _("C&ustom Boot Partition"))),
1✔
793
          Left(InputField(Id(:custom_list), Opt(:hstretch), ""))
794
        ]
795
      end
796
    end
797

798
    # Represents button that open Device Map edit dialog
799
    class DeviceMapWidget < ::CWM::PushButton
1✔
800
      include Grub2Helper
1✔
801

802
      def label
1✔
803
        textdomain "bootloader"
1✔
804

805
        _("&Edit Disk Boot Order")
1✔
806
      end
807

808
      def help
1✔
809
        textdomain "bootloader"
1✔
810

811
        _(
1✔
812
          "<p><b>Edit Disk Boot Order</b>\n" \
813
          "allows to specify the order of the disks according to the order in BIOS. Use\n" \
814
          "the <b>Up</b> and <b>Down</b> buttons to reorder the disks.\n" \
815
          "To add a disk, push <b>Add</b>.\n" \
816
          "To remove a disk, push <b>Remove</b>.</p>"
817
        )
818
      end
819

820
      def handle
1✔
821
        DeviceMapDialog.run(grub2.device_map)
1✔
822

823
        nil
1✔
824
      end
825
    end
826

827
    # represents Tab with kernel related configuration
828
    class KernelTab < CWM::Tab
1✔
829
      include Grub2Helper
1✔
830

831
      def label
1✔
832
        textdomain "bootloader"
1✔
833

834
        _("&Kernel Parameters")
1✔
835
      end
836

837
      def contents
1✔
838
        VBox(
1✔
839
          VSpacing(1),
840
          MarginBox(1, 0.5, KernelAppendWidget.new),
841
          MarginBox(1, 0.5, Left(CpuMitigationsWidget.new)),
842
          MarginBox(1, 0.5, console_widget),
843
          VStretch()
844
        )
845
      end
846

847
    private
1✔
848

849
      def console_widget
1✔
850
        if Systeminfo.console_supported?(grub2.name)
1✔
851
          ConsoleWidget.new
1✔
852
        else
853
          CWM::Empty.new("console")
×
854
        end
855
      end
856
    end
857

858
    # Represent tab with options related to stage1 location and bootloader type
859
    class BootCodeTab < CWM::Tab
1✔
860
      include Grub2Helper
1✔
861

862
      def label
1✔
863
        textdomain "bootloader"
1✔
864

865
        _("Boot Co&de Options")
1✔
866
      end
867

868
      def contents
1✔
869
        VBox(
1✔
870
          VSpacing(1),
871
          HBox(
872
            HSpacing(1),
873
            Left(LoaderTypeWidget.new)
874
          ),
875
          VSpacing(1),
876
          *widgets,
877
          VSpacing(1),
878
          pmbr_widget,
879
          device_map_button,
880
          VStretch()
881
        )
882
      end
883

884
    private
1✔
885

886
      def widgets
1✔
887
        w = []
1✔
888
        w << LoaderLocationWidget.new if loader_location_widget?
1✔
889

890
        if generic_mbr_widget?
1✔
891
          w << ActivateWidget.new
1✔
892
          w << GenericMBRWidget.new
1✔
893
        end
894

895
        w << SecureBootWidget.new if secure_boot_widget?
1✔
896
        w << TrustedBootWidget.new if trusted_boot_widget?
1✔
897
        w << UpdateNvramWidget.new if update_nvram_widget?
1✔
898

899
        w.map do |widget|
1✔
900
          MarginBox(horizontal_margin, 0, Left(widget))
3✔
901
        end
902
      end
903

904
      def pmbr_widget
1✔
905
        return Empty() unless pmbr_widget?
1✔
906

907
        MarginBox(1, 0, Left(PMBRWidget.new))
1✔
908
      end
909

910
      def device_map_button
1✔
911
        return Empty() unless device_map_button?
1✔
912

913
        MarginBox(1, 0, Left(DeviceMapWidget.new))
1✔
914
      end
915

916
      def horizontal_margin
1✔
917
        @horizontal_margin ||= Yast::UI.TextMode ? 1 : 1.5
3✔
918
      end
919

920
      def loader_location_widget?
1✔
921
        Systeminfo.loader_location_available?(grub2.name)
1✔
922
      end
923

924
      def generic_mbr_widget?
1✔
925
        Systeminfo.generic_mbr_available?(grub2.name)
1✔
926
      end
927

928
      def secure_boot_widget?
1✔
929
        Systeminfo.secure_boot_available?(grub2.name)
1✔
930
      end
931

932
      def trusted_boot_widget?
1✔
933
        Systeminfo.trusted_boot_available?(grub2.name)
1✔
934
      end
935

936
      def update_nvram_widget?
1✔
937
        Systeminfo.nvram_available?(grub2.name)
1✔
938
      end
939

940
      def pmbr_widget?
1✔
941
        Pmbr.available?
1✔
942
      end
943

944
      def device_map_button?
1✔
945
        Systeminfo.device_map?(grub2.name)
1✔
946
      end
947
    end
948

949
    # Represents bootloader specific options like its timeout,
950
    # default section or password protection
951
    class BootloaderTab < CWM::Tab
1✔
952
      include Grub2Helper
1✔
953

954
      def label
1✔
955
        textdomain "bootloader"
1✔
956

957
        _("Boot&loader Options")
1✔
958
      end
959

960
      def contents
1✔
961
        timeout_widget = if Systeminfo.bls_timeout_supported?(grub2.name)
1✔
NEW
962
          ::Bootloader::BlsWidget::TimeoutWidget.new
×
963
        else
964
          TimeoutWidget.new(hidden_menu_widget)
1✔
965
        end
966
        VBox(
1✔
967
          VSpacing(2),
968
          HBox(
969
            HSpacing(1),
970
            timeout_widget,
971
            HSpacing(1),
972
            VBox(
973
              os_prober_widget,
974
              VSpacing(1),
975
              Left(hidden_menu_widget)
976
            ),
977
            HSpacing(1)
978
          ),
979
          VSpacing(1),
980
          MarginBox(1, 1, MinWidth(1, DefaultSectionWidget.new)),
981
          MarginBox(1, 1, grub_password_widget),
982
          VStretch()
983
        )
984
      end
985

986
    private
1✔
987

988
      def grub_password_widget
1✔
989
        if Systeminfo.password_supported?(grub2.name)
1✔
990
          GrubPasswordWidget.new
1✔
991
        else
992
          CWM::Empty.new("password_widget")
×
993
        end
994
      end
995

996
      def hidden_menu_widget
1✔
997
        if Systeminfo.hiding_menu_supported?(grub2.name)
2✔
998
          HiddenMenuWidget.new
2✔
999
        else
1000
          CWM::Empty.new("hidden_menu")
×
1001
        end
1002
      end
1003

1004
      def os_prober_widget
1✔
1005
        # Checks !Arch.s390, not grub2-bls  and if package is available
1006
        if OsProber.available?(grub2.name)
1✔
1007
          Left(OSProberWidget.new)
1✔
1008
        else
1009
          CWM::Empty.new("os_prober")
×
1010
        end
1011
      end
1012
    end
1013
  end
1014
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc