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

yast / yast-bootloader / 8848983076

26 Apr 2024 01:21PM UTC coverage: 87.52% (+0.1%) from 87.387%
8848983076

Pull #700

github

web-flow
Merge a3e40b0cc into 3a16f9bd6
Pull Request #700: Kernel parameters for systemd-boot

108 of 119 new or added lines in 6 files covered. (90.76%)

68 existing lines in 4 files now uncovered.

3247 of 3710 relevant lines covered (87.52%)

13.03 hits per line

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

96.32
/src/modules/BootStorage.rb
1
# frozen_string_literal: true
2

3
# File:
4
#      modules/BootStorage.ycp
5
#
6
# Module:
7
#      Bootloader installation and configuration
8
#
9
# Summary:
10
#      Module includes specific functions for handling storage data.
11
#      The idea is handling all storage data necessary for bootloader
12
#      in one module.
13
#
14
# Authors:
15
#      Jozef Uhliarik <juhliarik@suse.cz>
16
#
17
#
18
#
19
#
20
require "yast"
1✔
21
require "storage"
1✔
22
require "y2storage"
1✔
23
require "bootloader/udev_mapping"
1✔
24
require "bootloader/exceptions"
1✔
25

26
module Yast
1✔
27
  class BootStorageClass < Module
1✔
28
    include Yast::Logger
1✔
29

30
    # Moint point for /boot. If there is not separated /boot, / is used instead.
31
    # @return [Y2Storage::Filesystem]
32
    def boot_filesystem
1✔
33
      detect_disks
213✔
34

35
      @boot_fs
212✔
36
    end
37

38
    # Moint point for /.
39
    # @return [Y2Storage::Filesystem]
40
    def root_filesystem
1✔
41
      detect_disks
6✔
42

43
      @root_fs
6✔
44
    end
45

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

49
      Yast.import "Arch"
1✔
50
      Yast.import "Mode"
1✔
51

52
      # FATE#305008: Failover boot configurations for md arrays with redundancy
53
      # list <string> includes physical disks used for md raid
54

55
      @md_physical_disks = []
1✔
56

57
      # Revision to recognize if cached values are still valid
58
      @storage_revision = nil
1✔
59
    end
60

61
    def storage_changed?
1✔
62
      @storage_revision != Y2Storage::StorageManager.instance.staging_revision
220✔
63
    end
64

65
    def staging
1✔
66
      Y2Storage::StorageManager.instance.staging
260✔
67
    end
68

69
    def storage_read?
1✔
70
      !@storage_revision.nil?
18✔
71
    end
72

73
    # Returns if any of boot disks has gpt
74
    def gpt_boot_disk?
1✔
75
      boot_disks.any? { |d| d.gpt? }
6✔
76
    end
77

78
    # Returns detected gpt disks
79
    # @param devices[Array<String>] devices to inspect, can be disk, partition or its udev links
80
    # @return [Array<String>] gpt disks only
81
    def gpt_disks(devices)
1✔
82
      targets = devices.map do |dev_name|
1✔
83
        staging.find_by_any_name(dev_name) or handle_unknown_device(dev_name)
1✔
84
      end
UNCOV
85
      boot_disks = targets.each_with_object([]) { |t, r| r.concat(stage1_disks_for(t)) }
×
86

UNCOV
87
      result = boot_disks.select { |disk| disk.gpt? }
×
88

UNCOV
89
      log.info "Found these gpt boot disks: #{result.inspect}"
×
90

UNCOV
91
      result.map(&:name)
×
92
    end
93

94
    # FIXME: merge with BootSupportCheck
95
    # Check if the bootloader can be installed at all with current configuration
96
    # @return [Boolean] true if it can
97
    def bootloader_installable?
1✔
UNCOV
98
      true
×
99
    end
100

101
    # Sets properly boot, root and mbr disk.
102
    # resets disk configuration. Clears cache from #detect_disks
103
    def reset_disks
1✔
104
      @boot_fs = nil
15✔
105
      @root_fs = nil
15✔
106
    end
107

108
    def prep_partitions
1✔
109
      partitions = Y2Storage::Partitionable.all(staging).map(&:prep_partitions).flatten
1✔
110
      log.info "detected prep partitions #{partitions.inspect}"
1✔
111
      partitions
1✔
112
    end
113

114
    # Get map of swap partitions
115
    # @return a map where key is partition name and value its size in KiB
116
    def available_swap_partitions
1✔
117
      ret = {}
8✔
118

119
      mounted = Y2Storage::MountPoint.find_by_path(staging, Y2Storage::MountPoint::SWAP_PATH.to_s)
8✔
120
      if mounted.empty?
8✔
121
        log.info "No mounted swap found, using fallback."
1✔
122
        staging.filesystems.select { |f| f.type.is?(:swap) }.each do |swap|
10✔
123
          blk_device = swap.blk_devices[0]
1✔
124
          ret[blk_device.name] = blk_device.size.to_i / 1024
1✔
125
        end
126
      else
127
        log.info "Mounted swap found: #{mounted.inspect}"
7✔
128
        mounted.each do |mp|
7✔
129
          blk_device = mp.filesystem.blk_devices[0]
7✔
130
          ret[blk_device.name] = blk_device.size.to_i / 1024
7✔
131
        end
132
      end
133

134
      log.info "Available swap partitions: #{ret}"
8✔
135
      ret
8✔
136
    end
137

138
    # Ram size in KiB
139
    #
140
    # @return [Intenger]
141
    def ram_size
1✔
142
      Y2Storage::StorageManager.instance.arch.ram_size / 1024
8✔
143
    end
144

145
    def encrypted_boot?
1✔
146
      fs = boot_filesystem
46✔
147
      log.info "boot mp = #{fs.inspect}"
46✔
148
      # check if fs is on an encryption
149
      result = fs.ancestors.any? { |a| a.is?(:encryption) }
186✔
150

151
      log.info "encrypted_boot? = #{result}"
46✔
152

153
      result
46✔
154
    end
155

156
    # Find the devices (disks or partitions)
157
    # to whose boot records we should put stage1.
158
    #
159
    # In simple setups it will be one device, but for RAIDs and LVMs and other
160
    # multi-device setups we need to put stage1 to *all* the underlying boot
161
    # records so that the (Legacy) BIOS does not have a chance to pick
162
    # an empty BR to boot from. See bsc#1072908.
163
    #
164
    # @param [String] dev_name device name including udev links
165
    # @return [Array<Y2Storage::Device>] list of suitable devices
166
    def stage1_devices_for_name(dev_name)
1✔
167
      device = staging.find_by_any_name(dev_name)
18✔
168
      handle_unknown_device(dev_name) unless device
18✔
169

170
      if device.is?(:partition) || device.is?(:filesystem)
16✔
171
        stage1_partitions_for(device)
4✔
172
      else
173
        stage1_disks_for(device)
12✔
174
      end
175
    end
176

177
    # Find the partitions to whose boot records we should put stage1.
178
    # (In simple setups it will be one partition)
179
    # @param [Y2Storage::Device] device to check
180
    #   eg. a Y2Storage::Filesystems::Base (for a new installation)
181
    #   or a Y2Storage::Partition (for an upgrade)
182
    # @return [Array<Y2Storage::Device>] devices suitable for stage1
183
    def stage1_partitions_for(device)
1✔
184
      # so how to do search? at first find first partition with parents
185
      # that is on disk or multipath (as ancestors method is not sorted)
186
      partitions = select_ancestors(device) do |ancestor|
49✔
187
        if ancestor.is?(:partition)
98✔
188
          partitionable = ancestor.partitionable
52✔
189
          partitionable.is?(:disk) || partitionable.is?(:multipath) || partitionable.is?(:bios_raid)
52✔
190
        else
191
          false
46✔
192
        end
193
      end
194

195
      partitions.uniq!
49✔
196

197
      log.info "stage1 partitions for #{device.inspect} are #{partitions.inspect}"
49✔
198

199
      partitions
49✔
200
    end
201

202
    # If the passed partition is a logical one (sda7),
203
    # return its extended "parent" (sda4), otherwise return the argument
204
    def extended_for_logical(partition)
1✔
205
      partition.type.is?(:logical) ? extended_partition(partition) : partition
21✔
206
    end
207

208
    # Find the disks to whose MBRs we should put stage1.
209
    # (In simple setups it will be one disk)
210
    # @param [Y2Storage::Device] device to check
211
    #   eg. a Y2Storage::Filesystems::Base (for a new installation)
212
    #   or a Y2Storage::Disk (for an upgrade)
213
    # @return [Array<Y2Storage::Device>] devices suitable for stage1
214
    def stage1_disks_for(device)
1✔
215
      # Usually we want just the ancestors, but in the upgrade case
216
      # we may start with just 1 of multipath wires and have to
217
      # traverse descendants to find the Y2Storage::Multipath to use.
218
      component = [device] + device.ancestors + device.descendants
95✔
219

220
      # The simple case: just get the disks.
221
      disks = component.select { |a| a.is?(:disk) }
927✔
222
      # Eg. 2 Disks are parents of 1 Multipath, the disks are just "wires"
223
      # to the real disk.
224
      multipaths = component.select { |a| a.is?(:multipath) }
927✔
225
      multipath_wires = multipaths.each_with_object([]) { |m, r| r.concat(m.parents) }
95✔
226
      log.info "multipath devices #{multipaths.inspect} and its wires #{multipath_wires.inspect}"
95✔
227

228
      # And same for bios raids
229
      bios_raids = component.select { |a| a.is?(:bios_raid) }
927✔
230
      # raid can be more complex, so we need not only direct parents but all
231
      # ancestors involved in RAID
232
      raid_members = bios_raids.each_with_object([]) { |m, r| r.concat(m.ancestors) }
96✔
233
      log.info "bios_raids devices #{bios_raids.inspect} and its members #{raid_members.inspect}"
95✔
234

235
      result = multipaths + disks + bios_raids - multipath_wires - raid_members
95✔
236

237
      log.info "stage1 disks for #{device.inspect} are #{result.inspect}"
95✔
238

239
      result
95✔
240
    end
241

242
    # shortcut to get stage1 disks for /boot
243
    def boot_disks
1✔
244
      stage1_disks_for(boot_filesystem)
76✔
245
    end
246

247
    # shortcut to get stage1 partitions for /boot
248
    def boot_partitions
1✔
249
      stage1_partitions_for(boot_filesystem)
40✔
250
    end
251

252
    # shortcut to get stage1 partitions for /
253
    def root_partitions
1✔
254
      stage1_partitions_for(root_filesystem)
5✔
255
    end
256

257
  private
1✔
258

259
    def detect_disks
1✔
260
      return if @boot_fs && @root_fs && !storage_changed? # quit if already detected
218✔
261

262
      @root_fs = find_mountpoint("/")
113✔
263
      @boot_fs = find_mountpoint("/boot")
113✔
264
      @boot_fs ||= @root_fs
113✔
265

266
      raise ::Bootloader::NoRoot, "Missing '/' mount point" unless @boot_fs && @root_fs
113✔
267

268
      log.info "boot fs #{@boot_fs.inspect}"
113✔
269
      log.info "root fs #{@root_fs.inspect}"
113✔
270

271
      @storage_revision = Y2Storage::StorageManager.instance.staging_revision
113✔
272
    end
273

274
    def extended_partition(partition)
1✔
275
      part = partition.partitionable.partitions.find { |p| p.type.is?(:extended) }
6✔
276
      return nil unless part
2✔
277

278
      log.info "Using extended partition instead: #{part.inspect}"
2✔
279
      part
2✔
280
    end
281

282
    # Find the filesystem mounted to given mountpoint.
283
    #
284
    # If the mountpoint is assigned to a Btrfs subvolume, it returns the
285
    # corresponding filesystem. That's specially relevant in cases like
286
    # bsc#1151748 or bsc#1124581, in which libstorage-ng gets confused by
287
    # non-standard Btrfs configurations.
288
    #
289
    # @param mountpoint [String]
290
    # @return [Y2Storage::Filesystems::Base, nil] nil if nothing is mounted in
291
    #   the given location
292
    def find_mountpoint(mountpoint)
1✔
293
      mp = Y2Storage::MountPoint.all(staging).find { |m| m.path == mountpoint }
732✔
294
      return nil unless mp
226✔
295

296
      mp.filesystem
116✔
297
    end
298

299
    # In a device graph, starting at *device* (inclusive), find the parents
300
    # that match *predicate*.
301
    # NOTE that once the predicate matches, the search stops **for that node**
302
    # but continues for other nodes.
303
    # @param device [Y2Storage::Device] starting point
304
    # @yieldparam [Y2Storage::Device]
305
    # @yieldreturn [Boolean]
306
    # @return [Array<Y2Storage::Device>]
307
    def select_ancestors(device, &predicate)
1✔
308
      results = []
49✔
309
      to_process = [device]
49✔
310
      until to_process.empty?
49✔
311
        candidate = to_process.pop
98✔
312
        if predicate.call(candidate)
98✔
313
          results << candidate
52✔
314
          next # done with this branch but continue on other branches
52✔
315
        end
316
        to_process.concat(candidate.parents)
46✔
317
      end
318
      results
49✔
319
    end
320

321
    # Handle an "unknown device" error: Raise an appropriate exception.
322
    # @param dev_name [String]
323
    def handle_unknown_device(dev_name)
1✔
324
      # rubocop:disable Style/GuardClause
325
      #
326
      # I flatly refuse to make my code LESS readable because of a third-rate
327
      # check tool. This is the CLASSIC use case for if...else, even if this
328
      # mindless rubocop thinks otherwise.
329
      #
330
      # 2019-02-06 shundhammer
331

332
      if dev_name =~ %r{/by-path/} # bsc#1122008, bsc#1116305
3✔
333
        raise ::Bootloader::BrokenByPathDeviceName, dev_name
1✔
334
      else
335
        raise ::Bootloader::BrokenConfiguration, "Unknown device #{dev_name}"
2✔
336
      end
337
      # rubocop:enable Style/GuardClause
338
    end
339
  end
340

341
  BootStorage = BootStorageClass.new
1✔
342
  BootStorage.main
1✔
343
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