/
BootStorage.rb
320 lines (265 loc) · 10.2 KB
/
BootStorage.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# frozen_string_literal: true
# File:
# modules/BootStorage.ycp
#
# Module:
# Bootloader installation and configuration
#
# Summary:
# Module includes specific functions for handling storage data.
# The idea is handling all storage data necessary for bootloader
# in one module.
#
# Authors:
# Jozef Uhliarik <juhliarik@suse.cz>
#
#
#
#
require "yast"
require "storage"
require "y2storage"
require "bootloader/udev_mapping"
require "bootloader/exceptions"
module Yast
class BootStorageClass < Module
include Yast::Logger
# Moint point for /boot. If there is not separated /boot, / is used instead.
# @return [Y2Storage::Filesystem]
def boot_filesystem
detect_disks
@boot_fs
end
def main
textdomain "bootloader"
Yast.import "Arch"
Yast.import "Mode"
# FATE#305008: Failover boot configurations for md arrays with redundancy
# list <string> includes physical disks used for md raid
@md_physical_disks = []
# Revision to recognize if cached values are still valid
@storage_revision = nil
end
def storage_changed?
@storage_revision != Y2Storage::StorageManager.instance.staging_revision
end
def staging
Y2Storage::StorageManager.instance.staging
end
def storage_read?
!@storage_revision.nil?
end
# Returns if any of boot disks has gpt
def gpt_boot_disk?
boot_disks.any? { |d| d.gpt? }
end
# Returns detected gpt disks
# @param devices[Array<String>] devices to inspect, can be disk, partition or its udev links
# @return [Array<String>] gpt disks only
def gpt_disks(devices)
targets = devices.map do |dev_name|
staging.find_by_any_name(dev_name) or handle_unknown_device(dev_name)
end
boot_disks = targets.each_with_object([]) { |t, r| r.concat(stage1_disks_for(t)) }
result = boot_disks.select { |disk| disk.gpt? }
log.info "Found these gpt boot disks: #{result.inspect}"
result.map(&:name)
end
# FIXME: merge with BootSupportCheck
# Check if the bootloader can be installed at all with current configuration
# @return [Boolean] true if it can
def bootloader_installable?
true
end
# Sets properly boot, root and mbr disk.
# resets disk configuration. Clears cache from #detect_disks
def reset_disks
@boot_fs = nil
end
def prep_partitions
partitions = Y2Storage::Partitionable.all(staging).map(&:prep_partitions).flatten
log.info "detected prep partitions #{partitions.inspect}"
partitions
end
# Get map of swap partitions
# @return a map where key is partition name and value its size in KiB
def available_swap_partitions
ret = {}
mounted = Y2Storage::MountPoint.find_by_path(staging, Y2Storage::MountPoint::SWAP_PATH.to_s)
if mounted.empty?
log.info "No mounted swap found, using fallback."
staging.filesystems.select { |f| f.type.is?(:swap) }.each do |swap|
blk_device = swap.blk_devices[0]
ret[blk_device.name] = blk_device.size.to_i / 1024
end
else
log.info "Mounted swap found: #{mounted.inspect}"
mounted.each do |mp|
blk_device = mp.filesystem.blk_devices[0]
ret[blk_device.name] = blk_device.size.to_i / 1024
end
end
log.info "Available swap partitions: #{ret}"
ret
end
def encrypted_boot?
fs = boot_filesystem
log.info "boot mp = #{fs.inspect}"
# check if fs is on an encryption
result = fs.ancestors.any? { |a| a.is?(:encryption) }
log.info "encrypted_boot? = #{result}"
result
end
# Find the devices (disks or partitions)
# to whose boot records we should put stage1.
#
# In simple setups it will be one device, but for RAIDs and LVMs and other
# multi-device setups we need to put stage1 to *all* the underlying boot
# records so that the (Legacy) BIOS does not have a chance to pick
# an empty BR to boot from. See bsc#1072908.
#
# @param [String] dev_name device name including udev links
# @return [Array<Y2Storage::Device>] list of suitable devices
def stage1_devices_for_name(dev_name)
device = staging.find_by_any_name(dev_name)
handle_unknown_device(dev_name) unless device
if device.is?(:partition) || device.is?(:filesystem)
stage1_partitions_for(device)
else
stage1_disks_for(device)
end
end
# Find the partitions to whose boot records we should put stage1.
# (In simple setups it will be one partition)
# @param [Y2Storage::Device] device to check
# eg. a Y2Storage::Filesystems::Base (for a new installation)
# or a Y2Storage::Partition (for an upgrade)
# @return [Array<Y2Storage::Device>] devices suitable for stage1
def stage1_partitions_for(device)
# so how to do search? at first find first partition with parents
# that is on disk or multipath (as ancestors method is not sorted)
partitions = select_ancestors(device) do |ancestor|
if ancestor.is?(:partition)
partitionable = ancestor.partitionable
partitionable.is?(:disk) || partitionable.is?(:multipath) || partitionable.is?(:bios_raid)
else
false
end
end
partitions.uniq!
log.info "stage1 partitions for #{device.inspect} are #{partitions.inspect}"
partitions
end
# If the passed partition is a logical one (sda7),
# return its extended "parent" (sda4), otherwise return the argument
def extended_for_logical(partition)
partition.type.is?(:logical) ? extended_partition(partition) : partition
end
# Find the disks to whose MBRs we should put stage1.
# (In simple setups it will be one disk)
# @param [Y2Storage::Device] device to check
# eg. a Y2Storage::Filesystems::Base (for a new installation)
# or a Y2Storage::Disk (for an upgrade)
# @return [Array<Y2Storage::Device>] devices suitable for stage1
def stage1_disks_for(device)
# Usually we want just the ancestors, but in the upgrade case
# we may start with just 1 of multipath wires and have to
# traverse descendants to find the Y2Storage::Multipath to use.
component = [device] + device.ancestors + device.descendants
# The simple case: just get the disks.
disks = component.select { |a| a.is?(:disk) }
# Eg. 2 Disks are parents of 1 Multipath, the disks are just "wires"
# to the real disk.
multipaths = component.select { |a| a.is?(:multipath) }
multipath_wires = multipaths.each_with_object([]) { |m, r| r.concat(m.parents) }
log.info "multipath devices #{multipaths.inspect} and its wires #{multipath_wires.inspect}"
# And same for bios raids
bios_raids = component.select { |a| a.is?(:bios_raid) }
# raid can be more complex, so we need not only direct parents but all
# ancestors involved in RAID
raid_members = bios_raids.each_with_object([]) { |m, r| r.concat(m.ancestors) }
log.info "bios_raids devices #{bios_raids.inspect} and its members #{raid_members.inspect}"
result = multipaths + disks + bios_raids - multipath_wires - raid_members
log.info "stage1 disks for #{device.inspect} are #{result.inspect}"
result
end
# shortcut to get stage1 disks for /boot
def boot_disks
stage1_disks_for(boot_filesystem)
end
# shortcut to get stage1 partitions for /boot
def boot_partitions
stage1_partitions_for(boot_filesystem)
end
private
def detect_disks
return if @boot_fs && !storage_changed? # quit if already detected
@boot_fs = find_mountpoint("/boot")
@boot_fs ||= find_mountpoint("/")
raise ::Bootloader::NoRoot, "Missing '/' mount point" unless @boot_fs
log.info "boot fs #{@boot_fs.inspect}"
@storage_revision = Y2Storage::StorageManager.instance.staging_revision
end
def extended_partition(partition)
part = partition.partitionable.partitions.find { |p| p.type.is?(:extended) }
return nil unless part
log.info "Using extended partition instead: #{part.inspect}"
part
end
# Find the filesystem mounted to given mountpoint.
#
# If the mountpoint is assigned to a Btrfs subvolume, it returns the
# corresponding filesystem. That's specially relevant in cases like
# bsc#1151748 or bsc#1124581, in which libstorage-ng gets confused by
# non-standard Btrfs configurations.
#
# @param mountpoint [String]
# @return [Y2Storage::Filesystems::Base, nil] nil if nothing is mounted in
# the given location
def find_mountpoint(mountpoint)
mp = Y2Storage::MountPoint.all(staging).find { |m| m.path == mountpoint }
return nil unless mp
mp.filesystem
end
# In a device graph, starting at *device* (inclusive), find the parents
# that match *predicate*.
# NOTE that once the predicate matches, the search stops **for that node**
# but continues for other nodes.
# @param device [Y2Storage::Device] starting point
# @yieldparam [Y2Storage::Device]
# @yieldreturn [Boolean]
# @return [Array<Y2Storage::Device>]
def select_ancestors(device, &predicate)
results = []
to_process = [device]
until to_process.empty?
candidate = to_process.pop
if predicate.call(candidate)
results << candidate
next # done with this branch but continue on other branches
end
to_process.concat(candidate.parents)
end
results
end
# Handle an "unknown device" error: Raise an appropriate exception.
# @param dev_name [String]
def handle_unknown_device(dev_name)
# rubocop:disable Style/GuardClause
#
# I flatly refuse to make my code LESS readable because of a third-rate
# check tool. This is the CLASSIC use case for if...else, even if this
# mindless rubocop thinks otherwise.
#
# 2019-02-06 shundhammer
if dev_name =~ %r{/by-path/} # bsc#1122008, bsc#1116305
raise ::Bootloader::BrokenByPathDeviceName, dev_name
else
raise ::Bootloader::BrokenConfiguration, "Unknown device #{dev_name}"
end
# rubocop:enable Style/GuardClause
end
end
BootStorage = BootStorageClass.new
BootStorage.main
end