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

yast / yast-bootloader / 18099610444

29 Sep 2025 02:00PM UTC coverage: 87.094% (-0.2%) from 87.325%
18099610444

Pull #724

github

schubi2
description changed
Pull Request #724: Update: Reports error if the bootloader is changed to grub2-bls

5 of 17 new or added lines in 2 files covered. (29.41%)

5 existing lines in 2 files now uncovered.

3489 of 4006 relevant lines covered (87.09%)

12.85 hits per line

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

93.42
/src/lib/bootloader/grub2base.rb
1
# frozen_string_literal: true
2

3
require "yast"
1✔
4
require "yast2/execute"
1✔
5
require "yast2/target_file" # adds ability to work with cfa in inst-sys
1✔
6
require "bootloader/bootloader_base"
1✔
7
require "bootloader/exceptions"
1✔
8
require "bootloader/sections"
1✔
9
require "bootloader/grub2pwd"
1✔
10
require "bootloader/udev_mapping"
1✔
11
require "bootloader/serial_console"
1✔
12
require "bootloader/language"
1✔
13
require "bootloader/os_prober"
1✔
14
require "cfa/grub2/default"
1✔
15
require "cfa/grub2/grub_cfg"
1✔
16
require "cfa/matcher"
1✔
17
require "cfa/placer"
1✔
18

19
Yast.import "Arch"
1✔
20
Yast.import "BootArch"
1✔
21
Yast.import "BootStorage"
1✔
22
Yast.import "HTML"
1✔
23
Yast.import "Initrd"
1✔
24
Yast.import "Mode"
1✔
25
Yast.import "Pkg"
1✔
26
Yast.import "Product"
1✔
27
Yast.import "ProductFeatures"
1✔
28
Yast.import "Stage"
1✔
29

30
module Bootloader
1✔
31
  # Common base for GRUB2 specialized classes
32
  # rubocop:disable Metrics/ClassLength
33
  class Grub2Base < BootloaderBase
1✔
34
    include Yast::Logger
1✔
35
    include Yast::I18n
1✔
36

37
    # @!attribute password
38
    #    @return [::Bootloader::GRUB2Pwd] stored password configuration object
39
    attr_reader :password
1✔
40

41
    attr_reader :sections
1✔
42
    # @!attribute grub_default
43
    #    @return [CFA::Grub2::Default] grub2 configuration object
44
    attr_reader :grub_default
1✔
45

46
    attr_accessor :pmbr_action
1✔
47

48
    # @!attribute trusted_boot
49
    #   @return [Boolean] current trusted boot setting
50
    attr_accessor :trusted_boot
1✔
51

52
    # @!attribute secure_boot
53
    #   @return [Boolean] current secure boot setting
54
    attr_accessor :secure_boot
1✔
55

56
    # @!attribute update_nvram
57
    #   @return [Boolean] current update nvram setting
58
    attr_accessor :update_nvram
1✔
59

60
    # @!attribute console
61
    #   @return [::Bootloader::SerialConsole] serial console or nil if none
62
    attr_reader :console
1✔
63

64
    # @!attribute stage1
65
    #   @return [::Bootloader::Stage1, nil] bootloader stage1, if one is needed
66
    attr_reader :stage1
1✔
67

68
    def initialize
1✔
69
      super
343✔
70

71
      textdomain "bootloader"
343✔
72
      @password = ::Bootloader::GRUB2Pwd.new
343✔
73
      @grub_default = ::CFA::Grub2::Default.new
343✔
74
      @sections = ::Bootloader::Sections.new
343✔
75
      @pmbr_action = :nothing
343✔
76
      @explicit_cpu_mitigations = false
343✔
77
      @update_nvram = true
343✔
78
    end
79

80
    # general functions
81

82
    def cpu_mitigations
1✔
83
      CpuMitigations.from_kernel_params(grub_default.kernel_params)
19✔
84
    end
85

86
    def explicit_cpu_mitigations
1✔
87
      @explicit_cpu_mitigations ? cpu_mitigations : nil
24✔
88
    end
89

90
    def cpu_mitigations=(value)
1✔
91
      log.info "setting mitigations to #{value}"
×
92
      @explicit_cpu_mitigations = true
×
93
      value.modify_kernel_params(grub_default.kernel_params)
×
94
    end
95

96
    def read
1✔
97
      super
11✔
98

99
      begin
100
        grub_default.load
11✔
101
      rescue Errno::ENOENT
102
        raise BrokenConfiguration, _("File /etc/default/grub missing on system")
1✔
103
      end
104

105
      grub_cfg = CFA::Grub2::GrubCfg.new
10✔
106
      begin
107
        grub_cfg.load
10✔
108
      rescue Errno::ENOENT
109
        # there may not need to be grub.cfg generated (bnc#976534),(bsc#1124064)
110
        log.info "/boot/grub2/grub.cfg is missing. Defaulting to empty one."
1✔
111
      end
112
      @sections = ::Bootloader::Sections.new(grub_cfg)
10✔
113
      log.info "grub sections: #{@sections.all}"
10✔
114

115
      self.trusted_boot = Systeminfo.trusted_boot_active?
10✔
116
      self.secure_boot = Systeminfo.secure_boot_active?
10✔
117
      self.update_nvram = Systeminfo.update_nvram_active?
10✔
118
    end
119

120
    def write(etc_only: false)
1✔
121
      super
15✔
122

123
      log.info "writing /etc/default/grub #{grub_default.inspect}"
15✔
124
      grub_default.save
15✔
125
      @sections.write
15✔
126
      @password.write
15✔
127
      return if etc_only
15✔
128

129
      Yast::Execute.on_target("/usr/sbin/grub2-mkconfig", "-o", "/boot/grub2/grub.cfg",
11✔
130
        env: systemwide_locale)
131
    end
132

133
    def propose
1✔
134
      super
46✔
135

136
      propose_os_probing
46✔
137
      propose_terminal
46✔
138
      propose_timeout
46✔
139
      propose_encrypted
46✔
140
      propose_grub_default
46✔
141
      propose_serial
46✔
142
      propose_xen_hypervisor
46✔
143

144
      self.trusted_boot = false
46✔
145
      self.secure_boot = Systeminfo.secure_boot_supported?
46✔
146
      self.update_nvram = true
46✔
147
    end
148

149
    def merge(other)
1✔
150
      super
23✔
151

152
      merge_grub_default(other)
23✔
153
      merge_password(other)
23✔
154
      merge_pmbr_action(other)
23✔
155
      merge_sections(other)
23✔
156

157
      self.trusted_boot = other.trusted_boot unless other.trusted_boot.nil?
23✔
158
      self.secure_boot = other.secure_boot unless other.secure_boot.nil?
23✔
159
      self.update_nvram = other.update_nvram unless other.update_nvram.nil?
23✔
160
    end
161

162
    def packages
1✔
163
      res = super
55✔
164
      res << OsProber.package_name if include_os_prober_package?
55✔
165
      res
55✔
166
    end
167

168
    # Checks if the os-prober package should be included.
169
    #
170
    # This default implementation checks if os-prober is supported on the
171
    # current architecture (all except s/390) and if the package is available
172
    # (not all products include it).
173
    #
174
    # @return [Boolean] true if the os-prober package should be included; false otherwise.
175
    def include_os_prober_package?
1✔
176
      OsProber.arch_supported? && OsProber.package_available?
55✔
177
    end
178

179
    def enable_serial_console(console_arg_string)
1✔
180
      @console = SerialConsole.load_from_console_args(console_arg_string)
4✔
181
      raise ::Bootloader::InvalidSerialConsoleArguments unless @console
4✔
182

183
      grub_default.serial_console = console.console_args
4✔
184

185
      placer = CFA::ReplacePlacer.new(serial_console_matcher)
4✔
186
      kernel_params = grub_default.kernel_params
4✔
187
      kernel_params.add_parameter("console", console.kernel_args, placer)
4✔
188
    end
189

190
    def disable_serial_console
1✔
191
      @console = nil
2✔
192
      grub_default.kernel_params.remove_parameter(serial_console_matcher)
2✔
193
      grub_default.serial_console = ""
2✔
194
    end
195

196
    def serial_console?
1✔
197
      !console.nil?
52✔
198
    end
199

200
  private
1✔
201

202
    def systemwide_locale
1✔
203
      begin
204
        language = ::Bootloader::Language.new
11✔
205
        language.load
11✔
206
      rescue Errno::ENOENT
207
        log.info "/etc/sysconfig/language does not exist. Using current locale"
11✔
208
        return {}
11✔
209
      end
210

UNCOV
211
      lang = language.rc_lang || "C"
×
212

UNCOV
213
      log.info "System language is #{lang}"
×
214

UNCOV
215
      { "LC_MESSAGES" => nil, "LC_ALL" => nil, "LANGUAGE" => nil, "LANG" => lang }
×
216
    end
217

218
    def merge_pmbr_action(other)
1✔
219
      log.info "merging pmbr action. own #{@pmbr_action}, other #{other.pmbr_action}"
24✔
220
      @pmbr_action = other.pmbr_action if other.pmbr_action
24✔
221
    end
222

223
    def merge_sections(other)
1✔
224
      return if !other.sections.default || other.sections.default.empty?
23✔
225

226
      sections.default = other.sections.default
1✔
227
    end
228

229
    def merge_password(other)
1✔
230
      @password = other.password
23✔
231
    end
232

233
    KERNEL_FLAVORS_METHODS = [:kernel_params, :xen_hypervisor_params, :xen_kernel_params].freeze
1✔
234

235
    def merge_grub_default(other)
1✔
236
      default = grub_default
24✔
237
      other_default = other.grub_default
24✔
238

239
      log.info "before merge default #{default.inspect}"
24✔
240
      log.info "before merge other #{other_default.inspect}"
24✔
241

242
      KERNEL_FLAVORS_METHODS.each do |method|
24✔
243
        merge_kernel_params(method, other_default)
72✔
244
      end
245

246
      merge_attributes(default, other_default)
24✔
247

248
      # explicitly set mitigations means overwrite of our
249
      if other.explicit_cpu_mitigations
24✔
250
        log.info "merging cpu_mitigations"
×
251
        self.cpu_mitigations = other.cpu_mitigations
×
252
      end
253
      log.info "mitigations after merge #{cpu_mitigations}"
24✔
254

255
      log.info "after merge default #{default.inspect}"
24✔
256
    end
257

258
    def merge_kernel_params(method, other_default)
1✔
259
      other_params = other_default.public_send(method)
72✔
260
      default_params = grub_default.public_send(method)
72✔
261
      return if other_params.empty?
72✔
262

263
      default_serialize = default_params.serialize
2✔
264
      # handle specially noresume as it should lead to remove all other resume
265
      default_serialize.gsub!(/resume=\S+/, "") if other_params.parameter("noresume")
2✔
266
      # prevent double cpu_mitigations params
267
      default_serialize.gsub!(/mitigations=\S+/, "") if other_params.parameter("mitigations")
2✔
268

269
      new_kernel_params = "#{default_serialize} #{other_params.serialize}"
2✔
270
      # deduplicate identicatel parameter. Keep always the last one ( so reverse is needed ).
271
      new_params = new_kernel_params.split.reverse.uniq.reverse.join(" ")
2✔
272

273
      default_params.replace(new_params)
2✔
274
    end
275

276
    def merge_attributes(default, other)
1✔
277
      # string attributes
278
      [:serial_console, :timeout, :hidden_timeout, :distributor,
24✔
279
       :gfxmode, :theme, :default].each do |attr|
280
        val = other.public_send(attr)
168✔
281
        default.public_send("#{attr}=".to_sym, val) if val
168✔
282
      end
283

284
      # array attributes with multiple values allowed
285
      [:terminal].each do |attr|
24✔
286
        val = other.public_send(attr)
24✔
287
        default.public_send("#{attr}=".to_sym, val) if val
24✔
288
      end
289

290
      # specific attributes that are not part of cfa
291
      ["SUSE_BTRFS_SNAPSHOT_BOOTING", "GRUB_GFXPAYLOAD_LINUX", "GRUB_USE_LINUXEFI"].each do |attr|
24✔
292
        val = other.generic_get(attr)
72✔
293
        grub_default.generic_set(attr, val) if val
72✔
294
      end
295

296
      # boolean attributes, instance of {CFA::Boolean}
297
      [:os_prober, :cryptodisk].each do |attr|
24✔
298
        val = other.public_send(attr)
48✔
299
        default.public_send(attr).value = val.enabled? if val.defined?
48✔
300
      end
301
    end
302

303
    def serial_console_matcher
1✔
304
      CFA::Matcher.new(key: "console", value_matcher: /tty(S|AMA)/)
6✔
305
    end
306

307
    def propose_os_probing
1✔
308
      os_prober = grub_default.os_prober
46✔
309
      return if os_prober.defined?
46✔
310

311
      # s390 do not have os_prober, see bnc#868909#c2
312
      # ppc have slow os_prober, see boo#931653
313
      disable_os_prober = (Yast::Arch.s390 || Yast::Arch.ppc) ||
38✔
314
        Yast::ProductFeatures.GetBooleanFeature("globals", "disable_os_prober")
315
      if disable_os_prober
38✔
316
        os_prober.disable
10✔
317
      else
318
        os_prober.enable
28✔
319
      end
320
    end
321

322
    def propose_terminal
1✔
323
      begin
324
        return if grub_default.terminal
46✔
325
      rescue RuntimeError => e
326
        log.info "Proposing terminal again due to #{e}"
×
327
      end
328

329
      # for ppc: Boards with graphics are rare and those are PowerNV, where
330
      # modules are not used, see bsc#911682
331
      grub_default.terminal = (Yast::Arch.s390 || Yast::Arch.ppc) ? [:console] : [:gfxterm]
38✔
332
      grub_default.generic_set("GRUB_GFXPAYLOAD_LINUX", "text") if Yast::Arch.ppc
38✔
333
    end
334

335
    def propose_timeout
1✔
336
      grub_default.hidden_timeout = "0"
46✔
337

338
      return if grub_default.timeout
46✔
339

340
      grub_default.timeout = Yast::ProductFeatures.GetIntegerFeature("globals", "boot_timeout").to_s
38✔
341
    end
342

343
    def propose_serial
1✔
344
      @console = SerialConsole.load_from_kernel_args(grub_default.kernel_params)
46✔
345
      return unless @console
46✔
346

347
      grub_default.serial_console = console.console_args
6✔
348
      propose_xen_serial
6✔
349
    end
350

351
    def propose_xen_serial
1✔
352
      return unless serial_console?
6✔
353

354
      grub_default.xen_kernel_params.replace(console.xen_kernel_args)
6✔
355
      grub_default.xen_hypervisor_params.replace(console.xen_hypervisor_args)
6✔
356
    end
357

358
    def propose_xen_hypervisor
1✔
359
      return if serial_console?
46✔
360
      return if Dir["/dev/fb*"].empty?
40✔
361

362
      matcher = CFA::Matcher.new(key: "vga")
1✔
363
      placer = CFA::ReplacePlacer.new(matcher)
1✔
364
      grub_default.xen_hypervisor_params.add_parameter("vga", "gfx-1024x768x16", placer)
1✔
365
    end
366

367
    def propose_encrypted
1✔
368
      grub_default.cryptodisk.value = !!Yast::BootStorage.encrypted_boot?
46✔
369
    end
370

371
    def propose_grub_default
1✔
372
      if grub_default.kernel_params.empty?
46✔
373
        kernel_line = Yast::BootArch.DefaultKernelParams(Yast::BootStorage.propose_resume)
46✔
374
        grub_default.kernel_params.replace(kernel_line)
46✔
375
      end
376
      grub_default.gfxmode ||= "auto"
46✔
377
      grub_default.recovery_entry.disable unless grub_default.recovery_entry.defined?
46✔
378
      grub_default.distributor ||= ""
46✔
379
      grub_default.default = "saved"
46✔
380
      # always propose true as grub2 itself detect if btrfs used
381
      grub_default.generic_set("SUSE_BTRFS_SNAPSHOT_BOOTING", "true")
46✔
382
    end
383

384
    # Secure boot setting shown in summary screen.
385
    #
386
    # @return [String]
387
    def secure_boot_summary
1✔
388
      link = if secure_boot
3✔
389
        "<a href=\"disable_secure_boot\">(#{_("disable")})</a>"
×
390
      else
391
        "<a href=\"enable_secure_boot\">(#{_("enable")})</a>"
3✔
392
      end
393

394
      "#{_("Secure Boot:")} #{status_string(secure_boot)} #{link}"
3✔
395
    end
396

397
    # Trusted boot setting shown in summary screen.
398
    #
399
    # @return [String]
400
    def trusted_boot_summary
1✔
401
      link = if trusted_boot
×
402
        "<a href=\"disable_trusted_boot\">(#{_("disable")})</a>"
×
403
      else
404
        "<a href=\"enable_trusted_boot\">(#{_("enable")})</a>"
×
405
      end
406

407
      "#{_("Trusted Boot:")} #{status_string(trusted_boot)} #{link}"
×
408
    end
409

410
    # Update nvram shown in summary screen
411
    #
412
    # @return [String]
413
    def update_nvram_summary
1✔
414
      link = if update_nvram
3✔
415
        "<a href=\"disable_update_nvram\">(#{_("disable")})</a>"
3✔
416
      else
417
        "<a href=\"enable_update_nvram\">(#{_("enable")})</a>"
×
418
      end
419

420
      "#{_("Update NVRAM:")} #{status_string(update_nvram)} #{link}"
3✔
421
    end
422
  end
423
  # rubocop:enable Metrics/ClassLength
424
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