/
base.rb
225 lines (194 loc) · 7.3 KB
/
base.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
# Copyright (c) [2015] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.
require "yast"
require "yast/i18n"
require "y2storage/disk_size"
require "y2storage/filesystems/type"
require "y2storage/planned"
require "y2storage/boot_requirements_strategies/analyzer"
require "y2storage/exceptions"
require "y2storage/volume_specification"
require "y2storage/setup_error"
require "y2storage/volume_specification_builder"
module Y2Storage
module BootRequirementsStrategies
class Error < Y2Storage::Error
end
# Base class for the strategies used to calculate the boot partitioning
# requirements
class Base
include Yast::Logger
include Yast::I18n
extend Forwardable
def_delegators :@analyzer,
:root_filesystem, :boot_disk, :boot_ptable_type?, :free_mountpoint?,
:root_in_lvm?, :root_in_software_raid?, :encrypted_root?, :btrfs_root?,
:root_fs_can_embed_grub?, :boot_in_lvm?,
:boot_in_thin_lvm?, :boot_in_bcache?, :boot_in_software_raid?, :encrypted_boot?,
:boot_fs_can_embed_grub?, :boot_filesystem_type, :boot_encryption_type,
:esp_in_lvm?, :esp_in_software_raid?, :esp_in_software_raid1?, :encrypted_esp?
# Constructor
#
# @see [BootRequirementsChecker#devicegraph]
# @see [BootRequirementsChecker#planned_devices]
# @see [BootRequirementsChecker#boot_disk_name]
def initialize(devicegraph, planned_devices, boot_disk_name)
textdomain "storage"
@devicegraph = devicegraph
@analyzer = Analyzer.new(devicegraph, planned_devices, boot_disk_name)
log.info "boot disk: #{boot_disk.inspect}"
end
# Partitions that should be created to boot the system
#
# @param target [Symbol] :desired, :min
#
# @return [Array<Planned::Partition>]
def needed_partitions(target)
planned_partitions = []
planned_partitions << boot_partition(target) if boot_partition_needed? && boot_partition_missing?
planned_partitions
end
# All boot warnings detected in the setup, for example, when required partition is too small
#
# @note This method should be overloaded for derived classes.
#
# @see SetupError
#
# @return [Array<SetupError>]
def warnings
res = []
if !encrypted_for_grub?
error_message =
_(
"The boot loader cannot access the file system mounted at /boot. " \
"Only LUKS1 encryption is supported."
)
res << SetupError.new(message: error_message)
end
res
end
# All fatal boot errors detected in the setup, for example, when a / partition
# is missing
#
# @note This method can be overloaded for derived classes.
#
# @see SetupError
#
# @return [Array<SetupError>]
def errors
res = []
if root_filesystem_missing?
error_message = _("There is no device mounted at '/'")
res << SetupError.new(message: error_message)
end
if too_small_boot?
error_message =
_("The device mounted at '/boot' does not have enough space to contain a kernel.")
res << SetupError.new(message: error_message)
end
if boot_in_thin_lvm?
error_message =
_("The device mounted at '/boot' cannot be in a thinly provisioned LVM VG.")
res << SetupError.new(message: error_message)
end
if boot_in_bcache?
error_message =
_("The device mounted at '/boot' cannot be in a BCache.")
res << SetupError.new(message: error_message)
end
res
end
protected
# @return [Devicegraph]
attr_reader :devicegraph
# @return [BootRequirementsStrategies::Analyzer]
attr_reader :analyzer
# Whether there is not root
#
# @return [Boolean] true if there is no root; false otherwise.
def root_filesystem_missing?
root_filesystem.nil?
end
def boot_partition_needed?
!encrypted_for_grub?
end
def too_small_boot?
# for other partitions it is not needed as packager check disk usage, but /boot is special
# as it contain initrd that is generated and also bootloader code.
filesystem = devicegraph.filesystems.find { |f| f.mount_path == "/boot" }
return false unless filesystem
# it is not 100% exact for new fs, but good estimation
filesystem.free_space < boot_volume.min_size
end
def boot_partition_missing?
free_mountpoint?("/boot")
end
# @return [VolumeSpecification]
def boot_volume
@boot_volume ||= volume_specification_for("/boot")
end
# @return [VolumeSpecification,nil]
def volume_specification_for(mount_point)
VolumeSpecificationBuilder.new.for(mount_point)
end
# @return [Planned::Partition]
def boot_partition(target)
planned_partition = create_planned_partition(boot_volume, target)
planned_partition.disk = boot_disk.name
planned_partition
end
# Create a planned partition from a volume specification
#
# @param volume [VolumeSpecification]
# @param target [Symbol] :desired, :min
#
# @return [VolumeSpecification]
def create_planned_partition(volume, target)
planned_partition = Planned::Partition.new(volume.mount_point, volume.fs_type)
planned_partition.min_size = (target == :min) ? volume.min_size : volume.desired_size
planned_partition.max_size = volume.max_size
planned_partition.partition_id = volume.partition_id
planned_partition.weight = analyzer.max_planned_weight || 0.0
planned_partition
end
# Whether there is no partition that matches the volume
#
# @param volume [VolumeSpecification]
# @return [Boolean] true if there is no partition; false otherwise.
def missing_partition_for?(volume)
Partition.all(devicegraph).none? { |p| p.match_volume?(volume) }
end
# Specific error when the boot disk cannot be detected
#
# @return [SetupError]
def unknown_boot_disk_error
# TRANSLATORS: error message
error_message = _("Boot requirements cannot be determined because there is no '/' mount point")
SetupError.new(message: error_message)
end
# Whether the boot device is encrypted and grub can decrypt it
#
# @return [Boolean] true if grub can decrypt boot device (or it is unencrypted)
def encrypted_for_grub?
t = boot_encryption_type
t.is?(:none) || t.is?(:luks1)
end
end
end
end