-
Notifications
You must be signed in to change notification settings - Fork 19
/
drive_section.rb
379 lines (330 loc) · 13.5 KB
/
drive_section.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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# encoding: utf-8
# Copyright (c) [2017] 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 "y2storage/autoinst_profile/section_with_attributes"
require "y2storage/autoinst_profile/skip_list_section"
require "y2storage/autoinst_profile/partition_section"
Yast.import "Arch"
module Y2Storage
module AutoinstProfile
# Thin object oriented layer on top of a <drive> section of the
# AutoYaST profile.
#
# More information can be found in the 'Partitioning' section of the AutoYaST documentation:
# https://www.suse.com/documentation/sles-12/singlehtml/book_autoyast/book_autoyast.html#CreateProfile.Partitioning
# Check that document for details about the semantic of every attribute.
class DriveSection < SectionWithAttributes
def self.attributes
[
{ name: :device },
{ name: :disklabel },
{ name: :enable_snapshots },
{ name: :imsmdriver },
{ name: :initialize_attr, xml_name: :initialize },
{ name: :keep_unknown_lv },
{ name: :lvm2 },
{ name: :is_lvm_vg },
{ name: :partitions },
{ name: :pesize },
{ name: :type },
{ name: :use },
{ name: :skip_list }
]
end
define_attr_accessors
# @!attribute device
# @return [String] device name
# @!attribute disklabel
# @return [String] partition table type
# @!attribute enable_snapshots
# @return [Boolean] undocumented attribute
# @!attribute imsmdriver
# @return [Symbol] undocumented attribute
# @!attribute initialize_attr
# @return [Boolean] value of the 'initialize' attribute in the profile
# (reserved name in Ruby). Whether the partition table must be wiped
# out at the beginning of the AutoYaST process.
# @!attribute keep_unknown_lv
# @return [Boolean] whether the existing logical volumes should be
# kept. Only makes sense if #type is :CT_LVM and there is a volume group
# to reuse.
# @!attribute lvm2
# @return [Boolean] undocumented attribute
# @!attribute is_lvm_vg
# @return [Boolean] undocumented attribute
# @!attribute partitions
# @return [Array<PartitionSection>] a list of <partition> entries
# @!attribute pesize
# @return [String] size of the LVM PE
# @!attribute type
# @return [Symbol] :CT_DISK or :CT_LVM
# @!attribute use
# @return [String,Array<Integer>] strategy AutoYaST will use to partition the disk
# @!attribute skip_list
# @return [Array<SkipListSection] collection of <skip_list> entries
# Constructor
#
# @param parent [#parent,#section_name] parent section
def initialize(parent = nil)
@parent = parent
@partitions = []
@skip_list = SkipListSection.new([])
end
# Method used by {.new_from_hashes} to populate the attributes.
#
# It only enforces default values for #type (:CT_DISK) and #use ("all")
# since the {AutoinstProposal} algorithm relies on them.
#
# @param hash [Hash] see {.new_from_hashes}
def init_from_hashes(hash)
super
@type ||= default_type_for(hash)
@use = use_value_from_string(hash["use"]) if hash["use"]
@partitions = partitions_from_hash(hash)
@skip_list = SkipListSection.new_from_hashes(hash.fetch("skip_list", []), self)
end
def default_type_for(hash)
return :CT_MD if hash["device"] == "/dev/md"
:CT_DISK
end
# Clones a drive into an AutoYaST profile section by creating an instance
# of this class from the information in a block device.
#
# @see PartitioningSection.new_from_storage for more details
#
# @param device [BlkDevice] a block device that can be cloned into a
# <drive> section, like a disk, a DASD or an LVM volume group.
# @return [DriveSection]
def self.new_from_storage(device)
result = new
# So far, only disks (and DASD) are supported
initialized = result.init_from_device(device)
initialized ? result : nil
end
# Method used by {.new_from_storage} to populate the attributes when
# cloning a device.
#
# As usual, it keeps the behavior of the old clone functionality, check
# the implementation of this class for details.
#
# @param device [BlkDevice] a block device that can be cloned into a
# <drive> section, like a disk, a DASD or an LVM volume group.
# @return [Boolean] if attributes were successfully read; false otherwise.
def init_from_device(device)
if device.is?(:software_raid)
init_from_md(device)
elsif device.is?(:lvm_vg)
init_from_vg(device)
else
init_from_disk(device)
end
end
# Device name to be used for the real MD device
#
# @see PartitionSection#name_for_md for details
#
# @return [String] MD RAID device name
def name_for_md
# TODO: a proper profile will always include one partition for each MD
# drive, but as soon as we introduce error handling and reporting we
# should do something if #partitions is empty (wrong profile).
partitions.first.name_for_md
end
# Content of the section in the format used by the AutoYaST modules
#
# @return [Hash] each element of the hash corresponds to one of the
# attributes defined in the section. Blank attributes are not
# included.
def to_hashes
hash = super
hash["use"] = use.join(",") if use.is_a?(Array)
hash
end
# Return section name
#
# @return [String] "drives"
def section_name
"drives"
end
protected
# Method used by {.new_from_storage} to populate the attributes when
# cloning a disk or DASD device.
#
# As usual, it keeps the behavior of the old clone functionality, check
# the implementation of this class for details.
#
# @param disk [Y2Storage::Disk, Y2Storage::Dasd] Disk
# @return [Boolean]
def init_from_disk(disk)
return false if disk.partitions.empty?
@type = :CT_DISK
# FIXME: could anyone with knowledge leave a comment why s390 is special here?
@device = Yast::Arch.s390 ? disk.udev_full_paths.first : disk.name
# if disk.udev_full_paths.first is nil go for disk.name anyway
@device ||= disk.name
@disklabel = disk.partition_table.type.to_s
@partitions = partitions_from_disk(disk)
return false if @partitions.empty?
@enable_snapshots = enabled_snapshots?(disk.partitions.map(&:filesystem))
@partitions.each { |i| i.create = false } if reuse_partitions?(disk)
# Same logic followed by the old exporter
@use =
if disk.partitions.any? { |i| windows?(i) }
@partitions.map(&:partition_nr)
else
"all"
end
true
end
# Method used by {.new_from_storage} to populate the attributes when
# cloning a volume group.
#
# @param vg [Y2Storage::LvmVg] Volume group
# @return [Boolean]
def init_from_vg(vg)
return false if vg.lvm_lvs.empty?
@type = :CT_LVM
@device = vg.name
@partitions = partitions_from_collection(vg.lvm_lvs)
return false if @partitions.empty?
@enable_snapshots = enabled_snapshots?(vg.lvm_lvs.map(&:filesystem))
@pesize = vg.extent_size.to_i.to_s
true
end
# Method used by {.new_from_storage} to populate the attributes when
# cloning a MD RAID.
#
# AutoYaST does not support multiple partitions on a MD RAID.
#
# @param md [Y2Storage::Md] RAID
# @return [Boolean]
def init_from_md(md)
@type = :CT_MD
@device = "/dev/md"
@partitions = partitions_from_collection([md])
@enable_snapshots = enabled_snapshots?([md.filesystem])
true
end
def partitions_from_hash(hash)
return [] unless hash["partitions"]
hash["partitions"].map { |part| PartitionSection.new_from_hashes(part, self) }
end
def partitions_from_disk(disk)
collection = disk.partitions.reject { |p| skip_partition?(p) }
partitions_from_collection(collection.sort_by(&:number))
end
def partitions_from_collection(collection)
collection.each_with_object([]) do |storage_partition, result|
partition = PartitionSection.new_from_storage(storage_partition)
next unless partition
result << partition
end
end
# Whether AutoYaST considers a partition to be part of a Windows
# installation and not directly relevant for the system being
# cloned.
#
# NOTE: to ensure backward compatibility, this method implements the
# logic present in the old AutoYaST exporter that used to live in
# AutoinstPartPlan#ReadHelper.
# https://github.com/yast/yast-autoinstallation/blob/47c24fb98e074f5b6432f3a4f8b9421362ee29cc/src/modules/AutoinstPartPlan.rb#L345
# Check the comments in the code to know more about what is checked
# and why.
#
# @param partition [Y2Storage::Partition]
# @return [Boolean]
def windows?(partition)
# Only partitions with a typical Windows ID are considered
return false unless partition.id.is?(:windows_system)
# If the partition is mounted in /boot*, then it doesn't fully
# belong to Windows, it's also relevant for the current system
if partition.filesystem_mountpoint && partition.filesystem_mountpoint.include?("/boot")
return false
end
# Surprinsingly enough, partitions with the boot flag are discarded
# as Windows partitions (btw, we expect better compatibility checking
# only for the corresponding flag on MSDOS partition tables, leaving
# out Partition#legacy_boot?, although we cannot be sure).
#
# This extra criteria of checking the boot flag was introduced in
# commit 795a18a795cd45d7e5f4d (January 2017) in order to fix
# bsc#192342. The PPC bootloader was switching the id of the partition
# from id 0x41 (PReP) to id 0x06 (FAT16) and as a result the AutoYaST
# exporter was ignoring the partition (considering it to be a Windows
# partition). Very likely, the intention of the fix was just to stop
# considering such FAT16+boot partitions as part of Windows.
# Unfortunately, the introduced fix affected all Windows-related ids,
# not only FAT16.
# That side effect has been there for 10+ years, so let's keep it.
!partition.boot?
end
# Whether a given partition should be ignored when cloning the devicegraph
# into a profile section.
#
# @return [Boolean] true if the partition is extended or considered to be
# a Windows system (see #windows?)
def skip_partition?(partition)
partition.type.is?(:extended) || windows?(partition)
end
# Whether all partitions in the drive should have "create" set to false
# (so no new partitions will be actually created in the target system).
#
# NOTE: This implements logic that was present in the old exporter and
# returns true if there is a Windows partition (see {#windows?}) that is
# placed in the disk after any non-Windows partition.
#
# @param disk [Y2Storage::Partitionable]
# @return [Boolean]
def reuse_partitions?(disk)
linux_already_found = false
disk.partitions.sort_by { |i| i.region.start }.each do |part|
next if part.type.is?(:extended)
if windows?(part)
return true if linux_already_found
else
linux_already_found = true
end
end
false
end
# Return value for the "use" element
#
# If the given string is a comma separated list of numbers, it will
# return an array containing those numbers. Otherwise, the original
# value will be returned.
#
# @return [String,Array<Integer>]
def use_value_from_string(use)
return use unless use =~ /(\d+,?)+/
use.split(",").select { |n| n =~ /\d+/ }.map(&:to_i)
end
# Determine whether snapshots are enabled
#
# Currently AutoYaST does not support enabling/disabling snapshots
# for a partition but for the whole disk/volume group.
#
# @param filesystems [Array<Y2Storage::Filesystem>] Filesystems to evaluate
# @return [Boolean] true if snapshots are enabled
def enabled_snapshots?(filesystems)
filesystems.any? { |f| f.respond_to?(:snapshots?) && f.snapshots? }
end
end
end
end