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

yast / yast-bootloader / 13052830653

30 Jan 2025 12:56PM UTC coverage: 87.442% (-0.07%) from 87.51%
13052830653

Pull #711

github

web-flow
Merge 5ce39ce5a into 0a277a1b6
Pull Request #711: Calling -sdbootutil set-timeout- with the correct parameters

1 of 1 new or added line in 1 file covered. (100.0%)

4 existing lines in 3 files now uncovered.

3405 of 3894 relevant lines covered (87.44%)

13.02 hits per line

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

68.22
/src/modules/Bootloader.rb
1
# frozen_string_literal: true
2

3
# File:
4
#      modules/Bootloader.rb
5
#
6
# Module:
7
#      Bootloader installation and configuration
8
#
9
# Summary:
10
#      Bootloader installation and configuration base module
11
#
12
# Authors:
13
#      Jiri Srain <jsrain@suse.cz>
14
#      Olaf Dabrunz <od@suse.de>
15
#
16
# $Id$
17
#
18
require "yast"
1✔
19
require "yast2/popup"
1✔
20
require "bootloader/exceptions"
1✔
21
require "bootloader/sysconfig"
1✔
22
require "bootloader/bootloader_factory"
1✔
23
require "bootloader/autoyast_converter"
1✔
24
require "bootloader/autoinst_profile/bootloader_section"
1✔
25
require "bootloader/systemdboot"
1✔
26
require "installation/autoinst_issues/invalid_value"
1✔
27
require "cfa/matcher"
1✔
28

29
Yast.import "Arch"
1✔
30
Yast.import "BootStorage"
1✔
31
Yast.import "Initrd"
1✔
32
Yast.import "Installation"
1✔
33
Yast.import "Mode"
1✔
34
Yast.import "Package"
1✔
35
Yast.import "Progress"
1✔
36
Yast.import "Report"
1✔
37
Yast.import "Stage"
1✔
38
Yast.import "UI"
1✔
39

40
module Yast
1✔
41
  class BootloaderClass < Module
1✔
42
    include Yast::Logger
1✔
43

44
    BOOLEAN_MAPPING = { true => :present, false => :missing }.freeze
1✔
45

46
    def main
1✔
47
      textdomain "bootloader"
1✔
48

49
      # installation proposal help variables
50

51
      # Configuration was changed during inst. proposal if true
52
      @proposed_cfg_changed = false
1✔
53

54
      # old vga value handling function
55

56
      # old value of vga parameter of default bootloader section
57
      @old_vga = nil
1✔
58

59
      # general functions
60

61
      @test_abort = nil
1✔
62
    end
63

64
    # Check whether abort was pressed
65
    # @return [Boolean] true if abort was pressed
66
    def testAbort
1✔
67
      return false if Mode.commandline
32✔
68

69
      UI.PollInput == :abort
32✔
70
    end
71

72
    # Export bootloader settings to a map
73
    # @return bootloader settings
74
    def Export
1✔
75
      config = ::Bootloader::BootloaderFactory.current
×
76
      config.read if !config.read? && !config.proposed?
×
77
      result = ::Bootloader::AutoyastConverter.export(config)
×
78

79
      log.info "autoyast map for bootloader: #{result.inspect}"
×
80

81
      result
×
82
    end
83

84
    # Import settings from a map
85
    # @param [Hash] data map of bootloader settings
86
    # @return [Boolean] true on success
87
    def Import(data)
1✔
88
      factory = ::Bootloader::BootloaderFactory
2✔
89
      bootloader_section = ::Bootloader::AutoinstProfile::BootloaderSection.new_from_hashes(data)
2✔
90

91
      imported_configuration = import_bootloader(bootloader_section)
2✔
92
      return false if imported_configuration.nil?
2✔
93

94
      factory.clear_cache
×
95

96
      proposed_configuration = factory.bootloader_by_name(imported_configuration.name)
×
97
      unless Mode.config # no AutoYaST configuration mode
×
98
        proposed_configuration.propose
×
99
        proposed_configuration.merge(imported_configuration)
×
100
      end
101
      factory.current = proposed_configuration
×
102

103
      # mark that it is not clear proposal (bsc#1081967)
104
      Yast::Bootloader.proposed_cfg_changed = true
×
105

106
      true
×
107
    end
108

109
    # Read settings from disk
110
    # @return [Boolean] true on success
111
    def Read
1✔
112
      log.info "Reading configuration"
16✔
113
      # run Progress bar
114
      stages = [
115
        # progress stage, text in dialog (short, infinitiv)
116
        _("Check boot loader"),
16✔
117
        # progress stage, text in dialog (short, infinitiv)
118
        _("Load boot loader settings")
119
      ]
120
      titles = [
121
        # progress step, text in dialog (short)
122
        _("Checking boot loader..."),
16✔
123
        # progress step, text in dialog (short)
124
        _("Reading partitioning..."),
125
        # progress step, text in dialog (short)
126
        _("Loading boot loader settings...")
127
      ]
128
      # dialog header
129
      Progress.New(
16✔
130
        _("Initializing Boot Loader Configuration"),
131
        " ",
132
        3,
133
        stages,
134
        titles,
135
        ""
136
      )
137

138
      Progress.NextStage
16✔
139
      return false if testAbort
16✔
140

141
      Progress.NextStage
16✔
142
      return false if testAbort
16✔
143

144
      begin
145
        ::Bootloader::BootloaderFactory.current.read
16✔
146
      rescue ::Bootloader::UnsupportedBootloader => e
147
        ret = Yast::Report.AnyQuestion(_("Unsupported Bootloader"),
×
148
          _("Unsupported bootloader '%s' detected. Use proposal of supported configuration instead?") %
149
            e.bootloader_name,
150
          _("Use"),
151
          _("Quit"),
152
          :yes) # focus proposing new one
153
        return false unless ret
×
154

155
        ::Bootloader::BootloaderFactory.current = ::Bootloader::BootloaderFactory.proposed
×
156
        ::Bootloader::BootloaderFactory.current.propose
×
157
      rescue ::Bootloader::BrokenConfiguration, ::Bootloader::UnsupportedOption => e
158
        msg = if e.is_a?(::Bootloader::BrokenConfiguration)
×
159
          # TRANSLATORS: %s stands for readon why yast cannot process it
160
          _("YaST cannot process current bootloader configuration (%s). " \
×
161
            "Propose new configuration from scratch?") % e.reason
162
        else
163
          e.message
×
164
        end
165

166
        ret = Yast::Report.AnyQuestion(_("Unsupported Configuration"),
×
167
          # TRANSLATORS: %s stands for readon why yast cannot process it
168
          msg,
169
          _("Propose"),
170
          _("Quit"),
171
          :yes) # focus proposing new one
172
        return false unless ret
×
173

174
        ::Bootloader::BootloaderFactory.current = ::Bootloader::BootloaderFactory.proposed
×
175
        ::Bootloader::BootloaderFactory.current.propose
×
176
      rescue Errno::EACCES
177
        # If the access to any needed file (e.g., grub.cfg when using GRUB bootloader) is not
178
        # allowed, just abort the execution. Using Yast::Confirm.MustBeRoot early in the
179
        # wizard/client is not enough since it allows continue.
180

181
        Yast2::Popup.show(
×
182
          # TRANSLATORS: pop-up message, beware the line breaks
183
          _("The module is running without enough privileges to perform all possible actions.\n\n" \
184
            "Cannot continue. Please, try again as root."),
185
          headline: :error
186
        )
187

188
        return false
×
189
      end
190

191
      Progress.Finish
16✔
192

193
      true
16✔
194
    end
195

196
    # Reset bootloader settings
197
    def Reset
1✔
198
      return if Mode.autoinst
×
199

200
      log.info "Resetting configuration"
×
201

202
      ::Bootloader::BootloaderFactory.clear_cache
×
203
      if Stage.initial
×
204
        config = ::Bootloader::BootloaderFactory.proposed
×
205
        config.propose
×
206
      else
207
        config = ::Bootloader::BootloaderFactory.system
×
208
        config.read
×
209
      end
210
      ::Bootloader::BootloaderFactory.current = config
×
UNCOV
211
      nil
×
212
    end
213

214
    # Propose bootloader settings
215
    def Propose
1✔
216
      log.info "Proposing configuration"
×
217
      ::Bootloader::BootloaderFactory.current.propose
×
218

219
      log.info "Proposed settings: #{Export()}"
×
220

UNCOV
221
      nil
×
222
    end
223

224
    # Display bootloader summary
225
    # @return a list of summary lines
226
    def Summary(simple_mode: false)
1✔
227
      # kokso: additional warning that root partition is nfs type -> bootloader will not be installed
228
      if BootStorage.boot_filesystem.is?(:nfs)
×
229
        log.info "Bootloader::Summary() -> Boot partition is nfs type, bootloader will not be installed."
×
230
        return [_("The boot partition is of type NFS. Bootloader cannot be installed.")]
×
231
      end
232

233
      ::Bootloader::BootloaderFactory.current.summary(simple_mode: simple_mode)
×
234
    end
235

236
    # Update the whole configuration
237
    # @return [Boolean] true on success
238
    def Update
1✔
239
      Write() # write also reads the configuration and updates it
×
240
    end
241

242
    # Write bootloader settings to disk
243
    # @return [Boolean] true on success
244
    def Write
1✔
245
      ReadOrProposeIfNeeded()
3✔
246

247
      mark_as_changed
3✔
248

249
      log.info "Writing bootloader configuration"
3✔
250

251
      stages = [
252
        _("Prepare system"),
3✔
253
        _("Create initrd"),
254
        _("Save boot loader configuration")
255
      ]
256
      titles = [
257
        _("Preparing system..."),
3✔
258
        _("Creating initrd..."),
259
        _("Saving boot loader configuration...")
260
      ]
261

262
      if Mode.normal
3✔
263
        Progress.New(_("Saving Boot Loader Configuration"), " ", stages.size, stages, titles, "")
3✔
264
        Progress.NextStage
3✔
265
      else
266
        Progress.Title(titles[0])
×
267
      end
268

269
      # Prepare system
270
      progress_state = Progress.set(false)
3✔
271
      if !::Bootloader::BootloaderFactory.current.prepare
3✔
272
        log.error("System could not be prepared successfully, required packages were not installed")
3✔
273
        Yast2::Popup.show(_("Cannot continue without install required packages"))
3✔
274
        return false
3✔
275
      end
276
      Progress.set(progress_state)
×
277

278
      transactional = Package.IsTransactionalSystem
×
279

280
      # Create initrd
281
      Progress.NextStage
×
282
      Progress.Title(titles[1]) unless Mode.normal
×
283

284
      write_initrd || log.error("Error occurred while creating initrd") if !transactional
×
285

286
      # Save boot loader configuration
287
      Progress.NextStage
×
288
      Progress.Title(titles[2]) unless Mode.normal
×
289
      ::Bootloader::BootloaderFactory.current.write(etc_only: transactional)
×
290
      if transactional
×
291
        # all writing to target is done in specific transactional command
292
        Yast::Execute.on_target!("transactional-update", "--continue", "bootloader")
×
293
      end
294

295
      true
×
296
    end
297

298
    # return default section label
299
    # @return [String] default section label
300
    def getDefaultSection
1✔
301
      ReadOrProposeIfNeeded()
×
302

303
      bootloader = Bootloader::BootloaderFactory.current
×
304
      return "" unless bootloader.respond_to?(:sections)
×
305

306
      bootloader.sections.default
×
307
    end
308

309
    FLAVOR_KERNEL_LINE_MAP = {
1✔
310
      :common    => "append",
311
      :xen_guest => "xen_append",
312
      :xen_host  => "xen_kernel_append"
313
    }.freeze
314

315
    # Gets value for given parameter in kernel parameters for given flavor.
316
    # @param [Symbol] flavor flavor of kernel, for possible values see #modify_kernel_param
317
    # @param [String] key of parameter on kernel command line
318
    # @return [String,:missing,:present] Returns string for parameters with value,
319
    #   `:missing` if key is not there and `:present` for parameters without value.
320
    #
321
    # @example get crashkernel parameter to common kernel
322
    #   Bootloader.kernel_param(:common, "crashkernel")
323
    #   => "256M@64B"
324
    #
325
    # @example get cio_ignore parameter for xen_host kernel when missing
326
    #   Bootloader.kernel_param(:xen_host, "cio_ignore")
327
    #   => :missing
328
    #
329
    # @example get verbose parameter for xen_guest which is there
330
    #   Bootloader.kernel_param(:xen_guest, "verbose")
331
    #   => :present
332
    #
333

334
    def kernel_param(flavor, key)
1✔
335
      if flavor == :recovery
4✔
336
        log.warn "Using deprecated recovery flavor"
×
337
        return :missing
×
338
      end
339

340
      current_bl = ::Bootloader::BootloaderFactory.current
4✔
341
      if current_bl.is_a?(::Bootloader::SystemdBoot)
4✔
342
        # systemd-boot
343
        kernel_params = current_bl.kernel_params
×
344
      elsif current_bl.respond_to?(:grub_default)
4✔
345
        # all grub bootloader types
346
        grub_default = current_bl.grub_default
4✔
347
        kernel_params = case flavor
4✔
348
        when :common then grub_default.kernel_params
4✔
349
        when :xen_guest then grub_default.xen_kernel_params
×
350
        when :xen_host then grub_default.xen_hypervisor_params
×
351
        else raise ArgumentError, "Unknown flavor #{flavor}"
×
352
        end
353
      else
354
        return :missing
×
355
      end
356

357
      ReadOrProposeIfNeeded() # ensure we have some data
4✔
358

359
      res = kernel_params.parameter(key)
4✔
360

361
      BOOLEAN_MAPPING[res] || res
4✔
362
    end
363

364
    # Modify kernel parameters for installed kernels according to values
365
    # @param [Array]  args parameters to modify. Last parameter is hash with keys
366
    #   and its values, keys are strings and values are `:present`, `:missing` or
367
    #   string value. Other parameters specify which kernel flavors are affected.
368
    #   Known values are:
369
    #     - `:common` for non-specific flavor
370
    #     - `:recovery` DEPRECATED: no longer use
371
    #     - `:xen_guest` for xen guest kernels
372
    #     - `:xen_host` for xen host kernels
373
    # @return [Boolean] true if params were modified; false otherwise.
374
    #
375
    # @example add crashkernel parameter to common kernel and xen guest
376
    #   Bootloader.modify_kernel_params(:common, :xen_guest, "crashkernel" => "256M@64M")
377
    #
378
    # @example same as before just with array passing
379
    #   targets = [:common, :xen_guest]
380
    #   Bootloader.modify_kernel_params(targets, "crashkernel" => "256M@64M")
381
    #
382
    # @example remove cio_ignore parameter for common kernel only
383
    #   Bootloader.modify_kernel_params("cio_ignore" => :missing)
384
    #
385
    # @example add cio_ignore parameter for xen host kernel
386
    #   Bootloader.modify_kernel_params(:xen_host, "cio_ignore" => :present)
387
    #
388
    def modify_kernel_params(*args)
1✔
389
      ReadOrProposeIfNeeded() # ensure we have data to modify
9✔
390
      current_bl = ::Bootloader::BootloaderFactory.current
9✔
391
      # currently only grub2 bootloader and systemd-boot supported
392
      if !current_bl.respond_to?(:grub_default) && !current_bl.is_a?(::Bootloader::SystemdBoot)
9✔
393
        return :missing
×
394
      end
395

396
      values = args.pop
9✔
397
      raise ArgumentError, "Missing parameters to modify #{args.inspect}" if !values.is_a? Hash
9✔
398

399
      args = [:common] if args.empty? # by default change common kernels only
8✔
400
      args = args.first if args.first.is_a? Array # support array like syntax
8✔
401

402
      if args.include?(:recovery)
8✔
403
        args.delete(:recovery)
×
404
        log.warn "recovery flavor is deprecated and not set"
×
405
      end
406

407
      remap_values = BOOLEAN_MAPPING.invert
8✔
408
      values.each_key do |key|
8✔
409
        values[key] = remap_values[values[key]] if remap_values.key?(values[key])
8✔
410
      end
411

412
      if current_bl.is_a?(::Bootloader::SystemdBoot)
8✔
413
        params = [current_bl.kernel_params]
×
414
      else
415
        grub_default = current_bl.grub_default
8✔
416
        params = args.map do |flavor|
8✔
417
          case flavor
10✔
418
          when :common then grub_default.kernel_params
4✔
419
          when :xen_guest then grub_default.xen_kernel_params
3✔
420
          when :xen_host then grub_default.xen_hypervisor_params
2✔
421
          else raise ArgumentError, "Unknown flavor #{flavor}"
1✔
422
          end
423
        end
424
      end
425

426
      changed = false
7✔
427
      values.each do |key, value|
7✔
428
        params.each do |param|
7✔
429
          old_val = param.parameter(key)
9✔
430
          next if old_val == value
9✔
431

432
          changed = true
9✔
433
          # at first clean old entries
434
          matcher = CFA::Matcher.new(key: key)
9✔
435
          param.remove_parameter(matcher)
9✔
436

437
          case value
9✔
438
          when false then next # already done
1✔
439
          when Array
440
            value.each { |val| param.add_parameter(key, val) }
3✔
441
          else
442
            param.add_parameter(key, value)
7✔
443
          end
444
        end
445
      end
446

447
      changed
7✔
448
    end
449

450
    # Get currently used bootloader, detect if not set yet
451
    # @return [String] botloader type
452
    def getLoaderType
1✔
453
      ::Bootloader::BootloaderFactory.current.name
×
454
    end
455

456
    # Check whether settings were read or proposed, if not, decide
457
    # what to do and read or propose settings
458
    def ReadOrProposeIfNeeded
1✔
459
      current_bl = ::Bootloader::BootloaderFactory.current
22✔
460
      return if current_bl.read? || current_bl.proposed?
22✔
461

462
      if Mode.config || (Stage.initial && !Mode.update)
20✔
463
        Propose()
2✔
464
      else
465
        progress_orig = Progress.set(false)
18✔
466
        if Stage.initial && Mode.update
18✔
467
          # SCR has been currently set to inst-sys. So we have
468
          # set the SCR to installed system in order to read
469
          # grub settings
470
          old_SCR = WFM.SCRGetDefault
1✔
471
          new_SCR = WFM.SCROpen("chroot=#{Yast::Installation.destdir}:scr",
1✔
472
            false)
473
          WFM.SCRSetDefault(new_SCR)
1✔
474
        end
475
        Read()
18✔
476
        if Stage.initial && Mode.update
18✔
477
          # settings have been read from the target system
478
          current_bl.read
1✔
479
          # reset target system to inst-sys
480
          WFM.SCRSetDefault(old_SCR)
1✔
481
          WFM.SCRClose(new_SCR)
1✔
482
        end
483
        Progress.set(progress_orig)
18✔
484
      end
485
    end
486

487
  private
1✔
488

489
    def mark_as_changed
1✔
490
      # always run mkinitrd at the end of S/390 installation (bsc#933177)
491
      # otherwise cio_ignore settings are not honored in initrd
492
      Initrd.changed = true if Arch.s390 && Stage.initial
3✔
493
    end
494

495
    NONSPLASH_VGA_VALUES = ["", "false", "ask"].freeze
1✔
496

497
    # regenerates initrd if needed
498
    # @return boolean true if succeed
499
    def write_initrd
1✔
500
      return true unless Initrd.changed
×
501

502
      # save initrd
503
      Initrd.Write
×
504
    end
505

506
    # @param section [AutoinstProfile::BootloaderSection] Bootloader section
507
    def import_bootloader(section)
1✔
508
      ::Bootloader::AutoyastConverter.import(section)
2✔
509
    rescue ::Bootloader::UnsupportedBootloader => e
510
      Yast.import "AutoInstall"
2✔
511

512
      possible_values = ::Bootloader::BootloaderFactory.supported_names +
2✔
513
        [::Bootloader::BootloaderFactory::DEFAULT_KEYWORD]
514
      Yast::AutoInstall.issues_list.add(
2✔
515
        ::Installation::AutoinstIssues::InvalidValue,
516
        section,
517
        "loader_type",
518
        e.bootloader_name,
519
        _("The selected bootloader is not supported on this architecture. Possible values: ") +
520
        possible_values.join(", "),
521
        :fatal
522
      )
523
      nil
2✔
524
    end
525

526
    publish :function => :Export, :type => "map ()"
1✔
527
    publish :function => :Import, :type => "boolean (map)"
1✔
528
    publish :function => :Propose, :type => "void ()"
1✔
529
    publish :function => :Read, :type => "boolean ()"
1✔
530
    publish :function => :Reset, :type => "void ()"
1✔
531
    publish :function => :Write, :type => "boolean ()"
1✔
532
    publish :function => :getDefaultSection, :type => "string ()"
1✔
533
    publish :function => :getLoaderType, :type => "string ()"
1✔
534
    publish :variable => :proposed_cfg_changed, :type => "boolean"
1✔
535
    publish :function => :blRead, :type => "boolean (boolean, boolean)"
1✔
536
    publish :function => :blSave, :type => "boolean (boolean, boolean, boolean)"
1✔
537
    publish :function => :blWidgetMaps, :type => "map <string, map <string, any>> ()"
1✔
538
    publish :function => :blDialogs, :type => "map <string, symbol ()> ()"
1✔
539
    publish :variable => :test_abort, :type => "boolean ()"
1✔
540
    publish :function => :Summary, :type => "list <string> ()"
1✔
541
    publish :function => :Update, :type => "boolean ()"
1✔
542
    publish :function => :WriteInstallation, :type => "boolean ()"
1✔
543
  end
544

545
  Bootloader = BootloaderClass.new
1✔
546
  Bootloader.main
1✔
547
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