1 |
charliebrady |
1.1 |
# __init__.py |
2 |
|
|
# Entry point for anaconda's storage configuration module. |
3 |
|
|
# |
4 |
|
|
# Copyright (C) 2009 Red Hat, Inc. |
5 |
|
|
# |
6 |
|
|
# This copyrighted material is made available to anyone wishing to use, |
7 |
|
|
# modify, copy, or redistribute it subject to the terms and conditions of |
8 |
|
|
# the GNU General Public License v.2, or (at your option) any later version. |
9 |
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
10 |
|
|
# ANY WARRANTY expressed or implied, including the implied warranties of |
11 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
12 |
|
|
# Public License for more details. You should have received a copy of the |
13 |
|
|
# GNU General Public License along with this program; if not, write to the |
14 |
|
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 |
|
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the |
16 |
|
|
# source code or documentation are not subject to the GNU General Public |
17 |
|
|
# License and may only be used or replicated with the express permission of |
18 |
|
|
# Red Hat, Inc. |
19 |
|
|
# |
20 |
|
|
# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> |
21 |
|
|
# |
22 |
|
|
|
23 |
|
|
import os |
24 |
|
|
import time |
25 |
|
|
import stat |
26 |
|
|
import errno |
27 |
|
|
import sys |
28 |
|
|
import statvfs |
29 |
|
|
|
30 |
|
|
import nss.nss |
31 |
|
|
import parted |
32 |
|
|
|
33 |
|
|
import isys |
34 |
|
|
import iutil |
35 |
|
|
from constants import * |
36 |
|
|
from pykickstart.constants import * |
37 |
|
|
from flags import flags |
38 |
|
|
|
39 |
|
|
import storage_log |
40 |
|
|
from errors import * |
41 |
|
|
from devices import * |
42 |
|
|
from devicetree import DeviceTree |
43 |
|
|
from deviceaction import * |
44 |
|
|
from formats import getFormat |
45 |
|
|
from formats import get_device_format_class |
46 |
|
|
from formats import get_default_filesystem_type |
47 |
|
|
from devicelibs.lvm import safeLvmName |
48 |
|
|
from devicelibs.dm import name_from_dm_node |
49 |
|
|
from devicelibs.crypto import generateBackupPassphrase |
50 |
|
|
from devicelibs.mpath import MultipathConfigWriter |
51 |
|
|
from devicelibs.edd import get_edd_dict |
52 |
|
|
from udev import * |
53 |
|
|
import iscsi |
54 |
|
|
import fcoe |
55 |
|
|
import zfcp |
56 |
|
|
import dasd |
57 |
|
|
|
58 |
|
|
import shelve |
59 |
|
|
import contextlib |
60 |
|
|
|
61 |
|
|
import gettext |
62 |
|
|
_ = lambda x: gettext.ldgettext("anaconda", x) |
63 |
|
|
|
64 |
|
|
import logging |
65 |
|
|
log = logging.getLogger("storage") |
66 |
|
|
|
67 |
|
|
def storageInitialize(anaconda, examine_all=True): |
68 |
|
|
""" Initialize the storage system |
69 |
|
|
|
70 |
|
|
Setting examine_all to False will use clearPartType to |
71 |
|
|
determine which disks will be used. This should be set to False |
72 |
|
|
for kickstart and True for everything else. |
73 |
|
|
""" |
74 |
|
|
storage = anaconda.id.storage |
75 |
|
|
storage.shutdown() |
76 |
|
|
|
77 |
|
|
if anaconda.dir == DISPATCH_BACK: |
78 |
|
|
return |
79 |
|
|
|
80 |
|
|
# touch /dev/.in_sysinit so that /lib/udev/rules.d/65-md-incremental.rules |
81 |
|
|
# does not mess with any mdraid sets |
82 |
|
|
open("/dev/.in_sysinit", "w") |
83 |
|
|
|
84 |
|
|
# XXX I don't understand why I have to do this, but this is needed to |
85 |
|
|
# populate the udev db |
86 |
|
|
udev_trigger(subsystem="block", action="change") |
87 |
|
|
|
88 |
|
|
|
89 |
|
|
anaconda.intf.resetInitializeDiskQuestion() |
90 |
|
|
anaconda.intf.resetReinitInconsistentLVMQuestion() |
91 |
|
|
|
92 |
|
|
# Set up the protected partitions list now. |
93 |
|
|
if anaconda.protected: |
94 |
|
|
storage.protectedDevSpecs.extend(anaconda.protected) |
95 |
|
|
storage.reset(examine_all=examine_all) |
96 |
|
|
|
97 |
|
|
if not flags.livecdInstall and not storage.protectedDevices: |
98 |
|
|
if anaconda.id.getUpgrade(): |
99 |
|
|
return |
100 |
|
|
else: |
101 |
|
|
anaconda.intf.messageWindow(_("Unknown Device"), |
102 |
|
|
_("The installation source given by device %s " |
103 |
|
|
"could not be found. Please check your " |
104 |
|
|
"parameters and try again.") % anaconda.protected, |
105 |
|
|
type="custom", custom_buttons = [_("_Exit installer")]) |
106 |
|
|
sys.exit(1) |
107 |
|
|
else: |
108 |
|
|
storage.reset(examine_all=examine_all) |
109 |
|
|
|
110 |
|
|
if not storage.disks: |
111 |
|
|
custom_buttons=[_("_Try again"), _("_Exit installer")] |
112 |
|
|
if anaconda.dispatch and anaconda.dispatch.canGoBack(): |
113 |
|
|
custom_buttons = [_("_Back"), _("_Exit installer")] |
114 |
|
|
rc = anaconda.intf.messageWindow(_("No disks found"), |
115 |
|
|
_("No usable disks have been found."), |
116 |
|
|
type="custom", |
117 |
|
|
custom_buttons=custom_buttons, default=0) |
118 |
|
|
if rc == 0: |
119 |
|
|
if anaconda.dispatch and anaconda.dispatch.canGoBack(): |
120 |
|
|
return DISPATCH_BACK |
121 |
|
|
else: |
122 |
|
|
return storageInitialize(anaconda) |
123 |
|
|
sys.exit(1) |
124 |
|
|
|
125 |
|
|
# dispatch.py helper function |
126 |
|
|
def storageComplete(anaconda): |
127 |
|
|
if anaconda.dir == DISPATCH_BACK: |
128 |
|
|
rc = anaconda.intf.messageWindow(_("Installation cannot continue."), |
129 |
|
|
_("The storage configuration you have " |
130 |
|
|
"chosen has already been activated. You " |
131 |
|
|
"can no longer return to the disk editing " |
132 |
|
|
"screen. Would you like to continue with " |
133 |
|
|
"the installation process?"), |
134 |
|
|
type = "yesno") |
135 |
|
|
if rc == 0: |
136 |
|
|
sys.exit(0) |
137 |
|
|
return DISPATCH_FORWARD |
138 |
|
|
|
139 |
|
|
devs = anaconda.id.storage.devicetree.getDevicesByType("luks/dm-crypt") |
140 |
|
|
existing_luks = False |
141 |
|
|
new_luks = False |
142 |
|
|
for dev in devs: |
143 |
|
|
if dev.exists: |
144 |
|
|
existing_luks = True |
145 |
|
|
else: |
146 |
|
|
new_luks = True |
147 |
|
|
|
148 |
|
|
if (anaconda.id.storage.encryptedAutoPart or new_luks) and \ |
149 |
|
|
not anaconda.id.storage.encryptionPassphrase: |
150 |
|
|
while True: |
151 |
|
|
(passphrase, retrofit) = anaconda.intf.getLuksPassphrase(preexist=existing_luks) |
152 |
|
|
if passphrase: |
153 |
|
|
anaconda.id.storage.encryptionPassphrase = passphrase |
154 |
|
|
anaconda.id.storage.encryptionRetrofit = retrofit |
155 |
|
|
break |
156 |
|
|
else: |
157 |
|
|
rc = anaconda.intf.messageWindow(_("Encrypt device?"), |
158 |
|
|
_("You specified block device encryption " |
159 |
|
|
"should be enabled, but you have not " |
160 |
|
|
"supplied a passphrase. If you do not " |
161 |
|
|
"go back and provide a passphrase, " |
162 |
|
|
"block device encryption will be " |
163 |
|
|
"disabled."), |
164 |
|
|
type="custom", |
165 |
|
|
custom_buttons=[_("Back"), _("Continue")], |
166 |
|
|
default=0) |
167 |
|
|
if rc == 1: |
168 |
|
|
log.info("user elected to not encrypt any devices.") |
169 |
|
|
undoEncryption(anaconda.id.storage) |
170 |
|
|
anaconda.id.storage.encryptedAutoPart = False |
171 |
|
|
break |
172 |
|
|
|
173 |
|
|
if anaconda.id.storage.encryptionPassphrase: |
174 |
|
|
for dev in anaconda.id.storage.devices: |
175 |
|
|
if dev.format.type == "luks" and not dev.format.exists and \ |
176 |
|
|
not dev.format.hasKey: |
177 |
|
|
dev.format.passphrase = anaconda.id.storage.encryptionPassphrase |
178 |
|
|
|
179 |
|
|
map(lambda d: anaconda.id.storage.services.update(d.services), |
180 |
|
|
anaconda.id.storage.fsset.devices) |
181 |
|
|
|
182 |
|
|
if anaconda.isKickstart: |
183 |
|
|
return |
184 |
|
|
|
185 |
|
|
# Warn the user if they are trying to boot a GPT disk on a non-EFI system |
186 |
|
|
# This may or may not work -- we have no way to tell, so just warn them |
187 |
|
|
bootdisk = anaconda.id.bootloader.drivelist[0] |
188 |
|
|
bootdisk = anaconda.id.storage.devicetree.getDeviceByName(bootdisk) |
189 |
|
|
|
190 |
|
|
if not iutil.isEfi() and bootdisk and bootdisk.format \ |
191 |
|
|
and bootdisk.format.type == 'disklabel' \ |
192 |
|
|
and bootdisk.format.labelType == 'gpt': |
193 |
|
|
warning = _("\n\nWARNING:\n" |
194 |
|
|
"You are using a GPT bootdisk on a non-EFI " |
195 |
|
|
"system. This may not work, depending on your BIOS's " |
196 |
|
|
"support for booting from GPT disks.") |
197 |
|
|
log.warning("Using a GPT bootdisk on non-EFI system") |
198 |
|
|
else: |
199 |
|
|
warning = "" |
200 |
|
|
|
201 |
|
|
# Prevent users from installing on s390x with (a) no /boot volume, (b) the |
202 |
|
|
# root volume on LVM, and (c) the root volume not restricted to a single |
203 |
|
|
# PV |
204 |
|
|
# NOTE: There is not really a way for users to create a / volume |
205 |
|
|
# restricted to a single PV. The backend support is there, but there are |
206 |
|
|
# no UI hook-ups to drive that functionality, but I do not personally |
207 |
|
|
# care. --dcantrell |
208 |
|
|
if iutil.isS390() and \ |
209 |
|
|
not anaconda.id.storage.mountpoints.has_key('/boot') and \ |
210 |
|
|
anaconda.id.storage.mountpoints['/'].type == 'lvmlv' and \ |
211 |
|
|
not anaconda.id.storage.mountpoints['/'].singlePV: |
212 |
|
|
rc = anaconda.intf.messageWindow(_("Missing /boot Volume"), |
213 |
|
|
_("This platform requires /boot on " |
214 |
|
|
"a dedicated partition or logical " |
215 |
|
|
"volume. If you do not want a " |
216 |
|
|
"/boot volume, you must place / " |
217 |
|
|
"on a dedicated non-LVM " |
218 |
|
|
"partition."), |
219 |
|
|
type="custom", custom_icon="error", |
220 |
|
|
custom_buttons=[_("Go _back"), |
221 |
|
|
_("_Exit installer")], |
222 |
|
|
default=0) |
223 |
|
|
if rc == 0: |
224 |
|
|
return DISPATCH_BACK |
225 |
|
|
sys.exit(1) |
226 |
|
|
|
227 |
|
|
rc = anaconda.intf.messageWindow(_("Writing storage configuration to disk"), |
228 |
wellsi |
1.2 |
_("This option performs a new install of " |
229 |
|
|
"SME Server. All hard drives, including " |
230 |
|
|
"removable media, will be re-partitioned and formatted. Proceed? |
231 |
charliebrady |
1.1 |
"%s") % (warning), |
232 |
|
|
type = "custom", custom_icon="warning", |
233 |
|
|
custom_buttons=[_("Go _back"), |
234 |
|
|
_("_Write changes to disk")], |
235 |
|
|
default = 0) |
236 |
|
|
|
237 |
|
|
# Make sure that all is down, even the disks that we setup after popluate. |
238 |
|
|
anaconda.id.storage.devicetree.teardownAll() |
239 |
|
|
|
240 |
|
|
if rc == 0: |
241 |
|
|
return DISPATCH_BACK |
242 |
|
|
|
243 |
|
|
def writeEscrowPackets(anaconda): |
244 |
|
|
escrowDevices = filter(lambda d: d.format.type == "luks" and \ |
245 |
|
|
d.format.escrow_cert, |
246 |
|
|
anaconda.id.storage.devices) |
247 |
|
|
|
248 |
|
|
if not escrowDevices: |
249 |
|
|
return |
250 |
|
|
|
251 |
|
|
log.debug("escrow: writeEscrowPackets start") |
252 |
|
|
|
253 |
|
|
wait_win = anaconda.intf.waitWindow(_("Running..."), |
254 |
|
|
_("Storing encryption keys")) |
255 |
|
|
|
256 |
|
|
nss.nss.nss_init_nodb() # Does nothing if NSS is already initialized |
257 |
|
|
|
258 |
|
|
backupPassphrase = generateBackupPassphrase() |
259 |
|
|
try: |
260 |
|
|
for device in escrowDevices: |
261 |
|
|
log.debug("escrow: device %s: %s" % |
262 |
|
|
(repr(device.path), repr(device.format.type))) |
263 |
|
|
device.format.escrow(anaconda.rootPath + "/root", |
264 |
|
|
backupPassphrase) |
265 |
|
|
|
266 |
|
|
wait_win.pop() |
267 |
|
|
except (IOError, RuntimeError) as e: |
268 |
|
|
wait_win.pop() |
269 |
|
|
anaconda.intf.messageWindow(_("Error"), |
270 |
|
|
_("Error storing an encryption key: " |
271 |
|
|
"%s\n") % str(e), type="custom", |
272 |
|
|
custom_icon="error", |
273 |
|
|
custom_buttons=[_("_Exit installer")]) |
274 |
|
|
sys.exit(1) |
275 |
|
|
|
276 |
|
|
log.debug("escrow: writeEscrowPackets done") |
277 |
|
|
|
278 |
|
|
|
279 |
|
|
def undoEncryption(storage): |
280 |
|
|
for device in storage.devicetree.getDevicesByType("luks/dm-crypt"): |
281 |
|
|
if device.exists: |
282 |
|
|
continue |
283 |
|
|
|
284 |
|
|
slave = device.slave |
285 |
|
|
format = device.format |
286 |
|
|
|
287 |
|
|
# set any devices that depended on the luks device to now depend on |
288 |
|
|
# the former slave device |
289 |
|
|
for child in storage.devicetree.getChildren(device): |
290 |
|
|
child.parents.remove(device) |
291 |
|
|
device.removeChild() |
292 |
|
|
child.parents.append(slave) |
293 |
|
|
|
294 |
|
|
storage.devicetree.registerAction(ActionDestroyFormat(device)) |
295 |
|
|
storage.devicetree.registerAction(ActionDestroyDevice(device)) |
296 |
|
|
storage.devicetree.registerAction(ActionDestroyFormat(slave)) |
297 |
|
|
storage.devicetree.registerAction(ActionCreateFormat(slave, format)) |
298 |
|
|
|
299 |
|
|
class Storage(object): |
300 |
|
|
def __init__(self, anaconda): |
301 |
|
|
self.anaconda = anaconda |
302 |
|
|
|
303 |
|
|
# storage configuration variables |
304 |
|
|
self.ignoreDiskInteractive = False |
305 |
|
|
self.ignoredDisks = [] |
306 |
|
|
self.exclusiveDisks = [] |
307 |
|
|
self.doAutoPart = False |
308 |
|
|
self.clearPartType = None |
309 |
|
|
self.clearPartDisks = [] |
310 |
|
|
self.clearPartChoice = None |
311 |
|
|
self.encryptedAutoPart = False |
312 |
|
|
self.encryptionPassphrase = None |
313 |
|
|
self.encryptionCipher = None |
314 |
|
|
self.autoPartEscrowCert = None |
315 |
|
|
self.autoPartAddBackupPassphrase = False |
316 |
|
|
self.encryptionRetrofit = False |
317 |
|
|
self.reinitializeDisks = False |
318 |
|
|
self.zeroMbr = None |
319 |
|
|
self.protectedDevSpecs = [] |
320 |
|
|
self.autoPartitionRequests = [] |
321 |
|
|
self.eddDict = {} |
322 |
|
|
self.mpathFriendlyNames = True |
323 |
|
|
|
324 |
|
|
self.__luksDevs = {} |
325 |
|
|
|
326 |
|
|
self.iscsi = iscsi.iscsi() |
327 |
|
|
self.fcoe = fcoe.fcoe() |
328 |
|
|
self.zfcp = zfcp.ZFCP() |
329 |
|
|
self.dasd = dasd.DASD() |
330 |
|
|
|
331 |
|
|
self._nextID = 0 |
332 |
|
|
self.defaultFSType = get_default_filesystem_type() |
333 |
|
|
self.defaultBootFSType = get_default_filesystem_type(boot=True) |
334 |
|
|
self._dumpFile = "/tmp/storage.state" |
335 |
|
|
|
336 |
|
|
# these will both be empty until our reset method gets called |
337 |
|
|
self.devicetree = DeviceTree(intf=self.anaconda.intf, |
338 |
|
|
ignored=self.ignoredDisks, |
339 |
|
|
exclusive=self.exclusiveDisks, |
340 |
|
|
type=self.clearPartType, |
341 |
|
|
clear=self.clearPartDisks, |
342 |
|
|
reinitializeDisks=self.reinitializeDisks, |
343 |
|
|
protected=self.protectedDevSpecs, |
344 |
|
|
zeroMbr=self.zeroMbr, |
345 |
|
|
passphrase=self.encryptionPassphrase, |
346 |
|
|
luksDict=self.__luksDevs, |
347 |
|
|
iscsi=self.iscsi, |
348 |
|
|
dasd=self.dasd, |
349 |
|
|
mpathFriendlyNames=self.mpathFriendlyNames) |
350 |
|
|
self.fsset = FSSet(self.devicetree, self.anaconda.rootPath) |
351 |
|
|
self.services = set() |
352 |
|
|
|
353 |
|
|
def doIt(self): |
354 |
|
|
self.devicetree.processActions() |
355 |
|
|
self.doEncryptionPassphraseRetrofits() |
356 |
|
|
|
357 |
|
|
# now set the boot partition's flag |
358 |
|
|
try: |
359 |
|
|
boot = self.anaconda.platform.bootDevice() |
360 |
|
|
if boot.type == "mdarray": |
361 |
|
|
bootDevs = boot.parents |
362 |
|
|
else: |
363 |
|
|
bootDevs = [boot] |
364 |
|
|
except DeviceError: |
365 |
|
|
bootDevs = [] |
366 |
|
|
else: |
367 |
|
|
for dev in bootDevs: |
368 |
|
|
if hasattr(dev, "bootable"): |
369 |
|
|
# Dos labels can only have one partition marked as active |
370 |
|
|
# and unmarking ie the windows partition is not a good idea |
371 |
|
|
skip = False |
372 |
|
|
if dev.disk.format.partedDisk.type == "msdos": |
373 |
|
|
for p in dev.disk.format.partedDisk.partitions: |
374 |
|
|
if p.type == parted.PARTITION_NORMAL and \ |
375 |
|
|
p.getFlag(parted.PARTITION_BOOT): |
376 |
|
|
skip = True |
377 |
|
|
break |
378 |
|
|
if skip: |
379 |
|
|
log.info("not setting boot flag on %s as there is" |
380 |
|
|
"another active partition" % dev.name) |
381 |
|
|
continue |
382 |
|
|
log.info("setting boot flag on %s" % dev.name) |
383 |
|
|
dev.bootable = True |
384 |
|
|
dev.disk.setup() |
385 |
|
|
dev.disk.format.commitToDisk() |
386 |
|
|
|
387 |
|
|
self.dumpState("final") |
388 |
|
|
|
389 |
|
|
@property |
390 |
|
|
def nextID(self): |
391 |
|
|
id = self._nextID |
392 |
|
|
self._nextID += 1 |
393 |
|
|
return id |
394 |
|
|
|
395 |
|
|
def shutdown(self): |
396 |
|
|
try: |
397 |
|
|
self.devicetree.teardownAll() |
398 |
|
|
except Exception as e: |
399 |
|
|
log.error("failure tearing down device tree: %s" % e) |
400 |
|
|
|
401 |
|
|
# TODO: iscsi.shutdown() |
402 |
|
|
|
403 |
|
|
def reset(self, examine_all=False): |
404 |
|
|
""" Reset storage configuration to reflect actual system state. |
405 |
|
|
|
406 |
|
|
Setting examine_all will cause it to examine all devices regardless |
407 |
|
|
of the clearPartType setting which could cause it to skip some |
408 |
|
|
devices. This is useful when looking for existing partitions to be |
409 |
|
|
upgraded. |
410 |
|
|
|
411 |
|
|
This should rescan from scratch but not clobber user-obtained |
412 |
|
|
information like passphrases, iscsi config, &c |
413 |
|
|
|
414 |
|
|
""" |
415 |
|
|
# save passphrases for luks devices so we don't have to reprompt |
416 |
|
|
self.encryptionPassphrase = None |
417 |
|
|
for device in self.devices: |
418 |
|
|
if device.format.type == "luks" and device.format.exists: |
419 |
|
|
self.__luksDevs[device.format.uuid] = device.format._LUKS__passphrase |
420 |
|
|
|
421 |
|
|
prog = self.anaconda.intf.progressWindow(_("Examining Devices"), |
422 |
|
|
_("Examining storage devices"), |
423 |
|
|
100, 0.03, pulse=True) |
424 |
|
|
self.iscsi.startup(self.anaconda.intf) |
425 |
|
|
self.fcoe.startup(self.anaconda.intf) |
426 |
|
|
self.zfcp.startup(self.anaconda.intf) |
427 |
|
|
self.dasd.startup(self.anaconda.intf, self.exclusiveDisks, self.zeroMbr) |
428 |
|
|
if examine_all: |
429 |
|
|
clearPartType = CLEARPART_TYPE_NONE |
430 |
|
|
else: |
431 |
|
|
clearPartType = self.clearPartType |
432 |
|
|
|
433 |
|
|
if self.dasd: |
434 |
|
|
# Reset the internal dasd list (823534) |
435 |
|
|
self.dasd.clear_device_list() |
436 |
|
|
|
437 |
|
|
self.devicetree = DeviceTree(intf=self.anaconda.intf, |
438 |
|
|
ignored=self.ignoredDisks, |
439 |
|
|
exclusive=self.exclusiveDisks, |
440 |
|
|
type=clearPartType, |
441 |
|
|
clear=self.clearPartDisks, |
442 |
|
|
reinitializeDisks=self.reinitializeDisks, |
443 |
|
|
protected=self.protectedDevSpecs, |
444 |
|
|
zeroMbr=self.zeroMbr, |
445 |
|
|
passphrase=self.encryptionPassphrase, |
446 |
|
|
luksDict=self.__luksDevs, |
447 |
|
|
iscsi=self.iscsi, |
448 |
|
|
dasd=self.dasd, |
449 |
|
|
mpathFriendlyNames=self.mpathFriendlyNames) |
450 |
|
|
self.devicetree.populate(prog) |
451 |
|
|
self.fsset = FSSet(self.devicetree, self.anaconda.rootPath) |
452 |
|
|
self.eddDict = get_edd_dict(self.partitioned) |
453 |
|
|
self.anaconda.id.rootParts = None |
454 |
|
|
self.anaconda.id.upgradeRoot = None |
455 |
|
|
self.dumpState("initial") |
456 |
|
|
prog.pop() |
457 |
|
|
|
458 |
|
|
@property |
459 |
|
|
def devices(self): |
460 |
|
|
""" A list of all the devices in the device tree. """ |
461 |
|
|
devices = self.devicetree.devices |
462 |
|
|
devices.sort(key=lambda d: d.name) |
463 |
|
|
return devices |
464 |
|
|
|
465 |
|
|
@property |
466 |
|
|
def disks(self): |
467 |
|
|
""" A list of the disks in the device tree. |
468 |
|
|
|
469 |
|
|
Ignored disks are not included, as are disks with no media present. |
470 |
|
|
|
471 |
|
|
This is based on the current state of the device tree and |
472 |
|
|
does not necessarily reflect the actual on-disk state of the |
473 |
|
|
system's disks. |
474 |
|
|
""" |
475 |
|
|
disks = [] |
476 |
|
|
for device in self.devicetree.devices: |
477 |
|
|
if device.isDisk: |
478 |
|
|
if not device.mediaPresent: |
479 |
|
|
log.info("Skipping disk: %s: No media present" % device.name) |
480 |
|
|
continue |
481 |
|
|
disks.append(device) |
482 |
|
|
disks.sort(key=lambda d: d.name, cmp=self.compareDisks) |
483 |
|
|
return disks |
484 |
|
|
|
485 |
|
|
@property |
486 |
|
|
def partitioned(self): |
487 |
|
|
""" A list of the partitioned devices in the device tree. |
488 |
|
|
|
489 |
|
|
Ignored devices are not included, nor disks with no media present. |
490 |
|
|
|
491 |
|
|
Devices of types for which partitioning is not supported are also |
492 |
|
|
not included. |
493 |
|
|
|
494 |
|
|
This is based on the current state of the device tree and |
495 |
|
|
does not necessarily reflect the actual on-disk state of the |
496 |
|
|
system's disks. |
497 |
|
|
""" |
498 |
|
|
partitioned = [] |
499 |
|
|
for device in self.devicetree.devices: |
500 |
|
|
if not device.partitioned: |
501 |
|
|
continue |
502 |
|
|
|
503 |
|
|
if not device.mediaPresent: |
504 |
|
|
log.info("Skipping device: %s: No media present" % device.name) |
505 |
|
|
continue |
506 |
|
|
|
507 |
|
|
partitioned.append(device) |
508 |
|
|
|
509 |
|
|
partitioned.sort(key=lambda d: d.name) |
510 |
|
|
return partitioned |
511 |
|
|
|
512 |
|
|
@property |
513 |
|
|
def partitions(self): |
514 |
|
|
""" A list of the partitions in the device tree. |
515 |
|
|
|
516 |
|
|
This is based on the current state of the device tree and |
517 |
|
|
does not necessarily reflect the actual on-disk state of the |
518 |
|
|
system's disks. |
519 |
|
|
""" |
520 |
|
|
partitions = self.devicetree.getDevicesByInstance(PartitionDevice) |
521 |
|
|
partitions.sort(key=lambda d: d.name) |
522 |
|
|
return partitions |
523 |
|
|
|
524 |
|
|
@property |
525 |
|
|
def vgs(self): |
526 |
|
|
""" A list of the LVM Volume Groups in the device tree. |
527 |
|
|
|
528 |
|
|
This is based on the current state of the device tree and |
529 |
|
|
does not necessarily reflect the actual on-disk state of the |
530 |
|
|
system's disks. |
531 |
|
|
""" |
532 |
|
|
vgs = self.devicetree.getDevicesByType("lvmvg") |
533 |
|
|
vgs.sort(key=lambda d: d.name) |
534 |
|
|
return vgs |
535 |
|
|
|
536 |
|
|
@property |
537 |
|
|
def lvs(self): |
538 |
|
|
""" A list of the LVM Logical Volumes in the device tree. |
539 |
|
|
|
540 |
|
|
This is based on the current state of the device tree and |
541 |
|
|
does not necessarily reflect the actual on-disk state of the |
542 |
|
|
system's disks. |
543 |
|
|
""" |
544 |
|
|
lvs = self.devicetree.getDevicesByType("lvmlv") |
545 |
|
|
lvs.sort(key=lambda d: d.name) |
546 |
|
|
return lvs |
547 |
|
|
|
548 |
|
|
@property |
549 |
|
|
def pvs(self): |
550 |
|
|
""" A list of the LVM Physical Volumes in the device tree. |
551 |
|
|
|
552 |
|
|
This is based on the current state of the device tree and |
553 |
|
|
does not necessarily reflect the actual on-disk state of the |
554 |
|
|
system's disks. |
555 |
|
|
""" |
556 |
|
|
devices = self.devicetree.devices |
557 |
|
|
pvs = [d for d in devices if d.format.type == "lvmpv"] |
558 |
|
|
pvs.sort(key=lambda d: d.name) |
559 |
|
|
return pvs |
560 |
|
|
|
561 |
|
|
def unusedPVs(self, vg=None): |
562 |
|
|
unused = [] |
563 |
|
|
for pv in self.pvs: |
564 |
|
|
used = False |
565 |
|
|
for _vg in self.vgs: |
566 |
|
|
if _vg.dependsOn(pv) and _vg != vg: |
567 |
|
|
used = True |
568 |
|
|
break |
569 |
|
|
elif _vg == vg: |
570 |
|
|
break |
571 |
|
|
if not used: |
572 |
|
|
unused.append(pv) |
573 |
|
|
return unused |
574 |
|
|
|
575 |
|
|
@property |
576 |
|
|
def mdarrays(self): |
577 |
|
|
""" A list of the MD arrays in the device tree. |
578 |
|
|
|
579 |
|
|
This is based on the current state of the device tree and |
580 |
|
|
does not necessarily reflect the actual on-disk state of the |
581 |
|
|
system's disks. |
582 |
|
|
""" |
583 |
|
|
arrays = self.devicetree.getDevicesByType("mdarray") |
584 |
|
|
arrays.sort(key=lambda d: d.name) |
585 |
|
|
return arrays |
586 |
|
|
|
587 |
|
|
@property |
588 |
|
|
def mdcontainers(self): |
589 |
|
|
""" A list of the MD containers in the device tree. """ |
590 |
|
|
arrays = self.devicetree.getDevicesByType("mdcontainer") |
591 |
|
|
arrays.sort(key=lambda d: d.name) |
592 |
|
|
return arrays |
593 |
|
|
|
594 |
|
|
@property |
595 |
|
|
def mdmembers(self): |
596 |
|
|
""" A list of the MD member devices in the device tree. |
597 |
|
|
|
598 |
|
|
This is based on the current state of the device tree and |
599 |
|
|
does not necessarily reflect the actual on-disk state of the |
600 |
|
|
system's disks. |
601 |
|
|
""" |
602 |
|
|
devices = self.devicetree.devices |
603 |
|
|
members = [d for d in devices if d.format.type == "mdmember"] |
604 |
|
|
members.sort(key=lambda d: d.name) |
605 |
|
|
return members |
606 |
|
|
|
607 |
|
|
def unusedMDMembers(self, array=None): |
608 |
|
|
unused = [] |
609 |
|
|
for member in self.mdmembers: |
610 |
|
|
used = False |
611 |
|
|
for _array in self.mdarrays + self.mdcontainers: |
612 |
|
|
if _array.dependsOn(member) and _array != array: |
613 |
|
|
used = True |
614 |
|
|
break |
615 |
|
|
elif _array == array: |
616 |
|
|
break |
617 |
|
|
if not used: |
618 |
|
|
unused.append(member) |
619 |
|
|
return unused |
620 |
|
|
|
621 |
|
|
@property |
622 |
|
|
def unusedMDMinors(self): |
623 |
|
|
""" Return a list of unused minors for use in RAID. """ |
624 |
|
|
raidMinors = range(0,32) |
625 |
|
|
for array in self.mdarrays + self.mdcontainers: |
626 |
|
|
if array.minor is not None and array.minor in raidMinors: |
627 |
|
|
raidMinors.remove(array.minor) |
628 |
|
|
return raidMinors |
629 |
|
|
|
630 |
|
|
@property |
631 |
|
|
def swaps(self): |
632 |
|
|
""" A list of the swap devices in the device tree. |
633 |
|
|
|
634 |
|
|
This is based on the current state of the device tree and |
635 |
|
|
does not necessarily reflect the actual on-disk state of the |
636 |
|
|
system's disks. |
637 |
|
|
""" |
638 |
|
|
devices = self.devicetree.devices |
639 |
|
|
swaps = [d for d in devices if d.format.type == "swap"] |
640 |
|
|
swaps.sort(key=lambda d: d.name) |
641 |
|
|
return swaps |
642 |
|
|
|
643 |
|
|
@property |
644 |
|
|
def protectedDevices(self): |
645 |
|
|
devices = self.devicetree.devices |
646 |
|
|
protected = [d for d in devices if d.protected] |
647 |
|
|
protected.sort(key=lambda d: d.name) |
648 |
|
|
return protected |
649 |
|
|
|
650 |
|
|
def exceptionDisks(self): |
651 |
|
|
""" Return a list of removable devices to save exceptions to. |
652 |
|
|
|
653 |
|
|
FIXME: This raises the problem that the device tree can be |
654 |
|
|
in a state that does not reflect that actual current |
655 |
|
|
state of the system at any given point. |
656 |
|
|
|
657 |
|
|
We need a way to provide direct scanning of disks, |
658 |
|
|
partitions, and filesystems without relying on the |
659 |
|
|
larger objects' correctness. |
660 |
|
|
|
661 |
|
|
Also, we need to find devices that have just been made |
662 |
|
|
available for the purpose of storing the exception |
663 |
|
|
report. |
664 |
|
|
""" |
665 |
|
|
# When a usb is connected from before the start of the installation, |
666 |
|
|
# it is not correctly detected. |
667 |
|
|
udev_trigger(subsystem="block", action="change") |
668 |
|
|
self.reset() |
669 |
|
|
|
670 |
|
|
dests = [] |
671 |
|
|
|
672 |
|
|
for disk in self.disks: |
673 |
|
|
if not disk.removable and \ |
674 |
|
|
disk.format is not None and \ |
675 |
|
|
disk.format.mountable: |
676 |
|
|
dests.append([disk.path, disk.name]) |
677 |
|
|
|
678 |
|
|
for part in self.partitions: |
679 |
|
|
if not part.disk.removable: |
680 |
|
|
continue |
681 |
|
|
|
682 |
|
|
elif part.partedPartition.active and \ |
683 |
|
|
not part.partedPartition.getFlag(parted.PARTITION_RAID) and \ |
684 |
|
|
not part.partedPartition.getFlag(parted.PARTITION_LVM) and \ |
685 |
|
|
part.format is not None and part.format.mountable: |
686 |
|
|
dests.append([part.path, part.name]) |
687 |
|
|
|
688 |
|
|
return dests |
689 |
|
|
|
690 |
|
|
def deviceImmutable(self, device, ignoreProtected=False): |
691 |
|
|
""" Return any reason the device cannot be modified/removed. |
692 |
|
|
|
693 |
|
|
Return False if the device can be removed. |
694 |
|
|
|
695 |
|
|
Devices that cannot be removed include: |
696 |
|
|
|
697 |
|
|
- protected partitions |
698 |
|
|
- devices that are part of an md array or lvm vg |
699 |
|
|
- extended partition containing logical partitions that |
700 |
|
|
meet any of the above criteria |
701 |
|
|
|
702 |
|
|
""" |
703 |
|
|
if not isinstance(device, Device): |
704 |
|
|
raise ValueError("arg1 (%s) must be a Device instance" % device) |
705 |
|
|
|
706 |
|
|
if not ignoreProtected and device.protected: |
707 |
|
|
return _("This partition is holding the data for the hard " |
708 |
|
|
"drive install.") |
709 |
|
|
elif isinstance(device, PartitionDevice) and device.isProtected: |
710 |
|
|
# LDL formatted DASDs always have one partition, you'd have to |
711 |
|
|
# reformat the DASD in CDL mode to get rid of it |
712 |
|
|
return _("You cannot delete a partition of a LDL formatted " |
713 |
|
|
"DASD.") |
714 |
|
|
elif device.format.type == "mdmember": |
715 |
|
|
for array in self.mdarrays + self.mdcontainers: |
716 |
|
|
if array.dependsOn(device): |
717 |
|
|
if array.minor is not None: |
718 |
|
|
return _("This device is part of the RAID " |
719 |
|
|
"device %s.") % (array.path,) |
720 |
|
|
else: |
721 |
|
|
return _("This device is part of a RAID device.") |
722 |
|
|
elif device.format.type == "lvmpv": |
723 |
|
|
for vg in self.vgs: |
724 |
|
|
if vg.dependsOn(device): |
725 |
|
|
if vg.name is not None: |
726 |
|
|
return _("This device is part of the LVM " |
727 |
|
|
"volume group '%s'.") % (vg.name,) |
728 |
|
|
else: |
729 |
|
|
return _("This device is part of a LVM volume " |
730 |
|
|
"group.") |
731 |
|
|
elif device.format.type == "luks": |
732 |
|
|
try: |
733 |
|
|
luksdev = self.devicetree.getChildren(device)[0] |
734 |
|
|
except IndexError: |
735 |
|
|
pass |
736 |
|
|
else: |
737 |
|
|
return self.deviceImmutable(luksdev) |
738 |
|
|
elif isinstance(device, PartitionDevice) and device.isExtended: |
739 |
|
|
reasons = {} |
740 |
|
|
for dep in self.deviceDeps(device): |
741 |
|
|
reason = self.deviceImmutable(dep) |
742 |
|
|
if reason: |
743 |
|
|
reasons[dep.path] = reason |
744 |
|
|
if reasons: |
745 |
|
|
msg = _("This device is an extended partition which " |
746 |
|
|
"contains logical partitions that cannot be " |
747 |
|
|
"deleted:\n\n") |
748 |
|
|
for dev in reasons: |
749 |
|
|
msg += "%s: %s" % (dev, reasons[dev]) |
750 |
|
|
return msg |
751 |
|
|
|
752 |
|
|
if device.immutable: |
753 |
|
|
return device.immutable |
754 |
|
|
|
755 |
|
|
return False |
756 |
|
|
|
757 |
|
|
def deviceDeps(self, device): |
758 |
|
|
return self.devicetree.getDependentDevices(device) |
759 |
|
|
|
760 |
|
|
def newPartition(self, *args, **kwargs): |
761 |
|
|
""" Return a new PartitionDevice instance for configuring. """ |
762 |
|
|
if kwargs.has_key("fmt_type"): |
763 |
|
|
kwargs["format"] = getFormat(kwargs.pop("fmt_type"), |
764 |
|
|
mountpoint=kwargs.pop("mountpoint", |
765 |
|
|
None), |
766 |
|
|
**kwargs.pop("fmt_args", {})) |
767 |
|
|
|
768 |
|
|
if kwargs.has_key("disks"): |
769 |
|
|
parents = kwargs.pop("disks") |
770 |
|
|
if isinstance(parents, Device): |
771 |
|
|
kwargs["parents"] = [parents] |
772 |
|
|
else: |
773 |
|
|
kwargs["parents"] = parents |
774 |
|
|
|
775 |
|
|
if kwargs.has_key("name"): |
776 |
|
|
name = kwargs.pop("name") |
777 |
|
|
else: |
778 |
|
|
name = "req%d" % self.nextID |
779 |
|
|
|
780 |
|
|
return PartitionDevice(name, *args, **kwargs) |
781 |
|
|
|
782 |
|
|
def newMDArray(self, *args, **kwargs): |
783 |
|
|
""" Return a new MDRaidArrayDevice instance for configuring. """ |
784 |
|
|
if kwargs.has_key("fmt_type"): |
785 |
|
|
kwargs["format"] = getFormat(kwargs.pop("fmt_type"), |
786 |
|
|
mountpoint=kwargs.pop("mountpoint", |
787 |
|
|
None)) |
788 |
|
|
|
789 |
|
|
if kwargs.has_key("minor"): |
790 |
|
|
kwargs["minor"] = int(kwargs["minor"]) |
791 |
|
|
else: |
792 |
|
|
kwargs["minor"] = self.unusedMDMinors[0] |
793 |
|
|
|
794 |
|
|
if kwargs.has_key("name"): |
795 |
|
|
name = kwargs.pop("name") |
796 |
|
|
else: |
797 |
|
|
name = "md%d" % kwargs["minor"] |
798 |
|
|
|
799 |
|
|
return MDRaidArrayDevice(name, *args, **kwargs) |
800 |
|
|
|
801 |
|
|
def newVG(self, *args, **kwargs): |
802 |
|
|
""" Return a new LVMVolumeGroupDevice instance. """ |
803 |
|
|
pvs = kwargs.pop("pvs", []) |
804 |
|
|
for pv in pvs: |
805 |
|
|
if pv not in self.devices: |
806 |
|
|
raise ValueError("pv is not in the device tree") |
807 |
|
|
|
808 |
|
|
if kwargs.has_key("name"): |
809 |
|
|
name = kwargs.pop("name") |
810 |
|
|
safe_name = safeLvmName(name) |
811 |
|
|
if safe_name != name: |
812 |
|
|
log.warning("using '%s' instead of specified name '%s'" |
813 |
|
|
% (safe_name, name)) |
814 |
|
|
name = safe_name |
815 |
|
|
else: |
816 |
|
|
name = self.createSuggestedVGName(self.anaconda.id.network) |
817 |
|
|
|
818 |
|
|
if name in [d.name for d in self.devices]: |
819 |
|
|
raise ValueError("name already in use") |
820 |
|
|
|
821 |
|
|
return LVMVolumeGroupDevice(name, pvs, *args, **kwargs) |
822 |
|
|
|
823 |
|
|
def newLV(self, *args, **kwargs): |
824 |
|
|
""" Return a new LVMLogicalVolumeDevice instance. """ |
825 |
|
|
if kwargs.has_key("vg"): |
826 |
|
|
vg = kwargs.pop("vg") |
827 |
|
|
|
828 |
|
|
mountpoint = kwargs.pop("mountpoint", None) |
829 |
|
|
if kwargs.has_key("fmt_type"): |
830 |
|
|
kwargs["format"] = getFormat(kwargs.pop("fmt_type"), |
831 |
|
|
mountpoint=mountpoint) |
832 |
|
|
|
833 |
|
|
if kwargs.has_key("name"): |
834 |
|
|
name = kwargs.pop("name") |
835 |
|
|
# make sure the specified name is sensible |
836 |
|
|
safe_name = safeLvmName(name) |
837 |
|
|
if safe_name != name: |
838 |
|
|
log.warning("using '%s' instead of specified name '%s'" |
839 |
|
|
% (safe_name, name)) |
840 |
|
|
name = safe_name |
841 |
|
|
else: |
842 |
|
|
if kwargs.get("format") and kwargs["format"].type == "swap": |
843 |
|
|
swap = True |
844 |
|
|
else: |
845 |
|
|
swap = False |
846 |
|
|
name = self.createSuggestedLVName(vg, |
847 |
|
|
swap=swap, |
848 |
|
|
mountpoint=mountpoint) |
849 |
|
|
|
850 |
|
|
if name in [d.name for d in self.devices]: |
851 |
|
|
raise ValueError("name already in use") |
852 |
|
|
|
853 |
|
|
return LVMLogicalVolumeDevice(name, vg, *args, **kwargs) |
854 |
|
|
|
855 |
|
|
def createDevice(self, device): |
856 |
|
|
""" Schedule creation of a device. |
857 |
|
|
|
858 |
|
|
TODO: We could do some things here like assign the next |
859 |
|
|
available raid minor if one isn't already set. |
860 |
|
|
""" |
861 |
|
|
self.devicetree.registerAction(ActionCreateDevice(device)) |
862 |
|
|
if device.format.type: |
863 |
|
|
self.devicetree.registerAction(ActionCreateFormat(device)) |
864 |
|
|
|
865 |
|
|
def destroyDevice(self, device): |
866 |
|
|
""" Schedule destruction of a device. """ |
867 |
|
|
if device.format.exists and device.format.type: |
868 |
|
|
# schedule destruction of any formatting while we're at it |
869 |
|
|
self.devicetree.registerAction(ActionDestroyFormat(device)) |
870 |
|
|
|
871 |
|
|
action = ActionDestroyDevice(device) |
872 |
|
|
self.devicetree.registerAction(action) |
873 |
|
|
|
874 |
|
|
def formatDevice(self, device, format): |
875 |
|
|
""" Schedule formatting of a device. """ |
876 |
|
|
self.devicetree.registerAction(ActionDestroyFormat(device)) |
877 |
|
|
self.devicetree.registerAction(ActionCreateFormat(device, format)) |
878 |
|
|
|
879 |
|
|
def formatByDefault(self, device): |
880 |
|
|
"""Return whether the device should be reformatted by default.""" |
881 |
|
|
formatlist = ['/boot', '/var', '/tmp', '/usr'] |
882 |
|
|
exceptlist = ['/home', '/usr/local', '/opt', '/var/www'] |
883 |
|
|
|
884 |
|
|
if not device.format.linuxNative: |
885 |
|
|
return False |
886 |
|
|
|
887 |
|
|
if device.format.mountable: |
888 |
|
|
if not device.format.mountpoint: |
889 |
|
|
return False |
890 |
|
|
|
891 |
|
|
if device.format.mountpoint == "/" or \ |
892 |
|
|
device.format.mountpoint in formatlist: |
893 |
|
|
return True |
894 |
|
|
|
895 |
|
|
for p in formatlist: |
896 |
|
|
if device.format.mountpoint.startswith(p): |
897 |
|
|
for q in exceptlist: |
898 |
|
|
if device.format.mountpoint.startswith(q): |
899 |
|
|
return False |
900 |
|
|
return True |
901 |
|
|
elif device.format.type == "swap": |
902 |
|
|
return True |
903 |
|
|
|
904 |
|
|
# be safe for anything else and default to off |
905 |
|
|
return False |
906 |
|
|
|
907 |
|
|
def extendedPartitionsSupported(self): |
908 |
|
|
""" Return whether any disks support extended partitions.""" |
909 |
|
|
for disk in self.partitioned: |
910 |
|
|
if disk.format.partedDisk.supportsFeature(parted.DISK_TYPE_EXTENDED): |
911 |
|
|
return True |
912 |
|
|
return False |
913 |
|
|
|
914 |
|
|
def createSuggestedVGName(self, network): |
915 |
|
|
""" Return a reasonable, unused VG name. """ |
916 |
|
|
# try to create a volume group name incorporating the hostname |
917 |
|
|
hn = network.hostname |
918 |
|
|
vgnames = [vg.name for vg in self.vgs] |
919 |
|
|
if hn is not None and hn != '': |
920 |
|
|
if hn == 'localhost' or hn == 'localhost.localdomain': |
921 |
|
|
vgtemplate = "VolGroup" |
922 |
|
|
elif hn.find('.') != -1: |
923 |
|
|
template = "vg_%s" % (hn.split('.')[0].lower(),) |
924 |
|
|
vgtemplate = safeLvmName(template) |
925 |
|
|
else: |
926 |
|
|
template = "vg_%s" % (hn.lower(),) |
927 |
|
|
vgtemplate = safeLvmName(template) |
928 |
|
|
else: |
929 |
|
|
vgtemplate = "VolGroup" |
930 |
|
|
|
931 |
|
|
if vgtemplate not in vgnames and \ |
932 |
|
|
vgtemplate not in lvm.lvm_vg_blacklist: |
933 |
|
|
return vgtemplate |
934 |
|
|
else: |
935 |
|
|
i = 0 |
936 |
|
|
while 1: |
937 |
|
|
tmpname = "%s%02d" % (vgtemplate, i,) |
938 |
|
|
if not tmpname in vgnames and \ |
939 |
|
|
tmpname not in lvm.lvm_vg_blacklist: |
940 |
|
|
break |
941 |
|
|
|
942 |
|
|
i += 1 |
943 |
|
|
if i > 99: |
944 |
|
|
tmpname = "" |
945 |
|
|
|
946 |
|
|
return tmpname |
947 |
|
|
|
948 |
|
|
def createSuggestedLVName(self, vg, swap=None, mountpoint=None): |
949 |
|
|
""" Return a suitable, unused name for a new logical volume. """ |
950 |
|
|
# FIXME: this is not at all guaranteed to work |
951 |
|
|
if mountpoint: |
952 |
|
|
# try to incorporate the mountpoint into the name |
953 |
|
|
if mountpoint == '/': |
954 |
|
|
lvtemplate = 'lv_root' |
955 |
|
|
else: |
956 |
|
|
if mountpoint.startswith("/"): |
957 |
|
|
template = "lv_%s" % mountpoint[1:] |
958 |
|
|
else: |
959 |
|
|
template = "lv_%s" % (mountpoint,) |
960 |
|
|
|
961 |
|
|
lvtemplate = safeLvmName(template) |
962 |
|
|
else: |
963 |
|
|
if swap: |
964 |
|
|
if len([s for s in self.swaps if s in vg.lvs]): |
965 |
|
|
idx = len([s for s in self.swaps if s in vg.lvs]) |
966 |
|
|
while True: |
967 |
|
|
lvtemplate = "lv_swap%02d" % idx |
968 |
|
|
if lvtemplate in [lv.lvname for lv in vg.lvs]: |
969 |
|
|
idx += 1 |
970 |
|
|
else: |
971 |
|
|
break |
972 |
|
|
else: |
973 |
|
|
lvtemplate = "lv_swap" |
974 |
|
|
else: |
975 |
|
|
idx = len(vg.lvs) |
976 |
|
|
while True: |
977 |
|
|
lvtemplate = "LogVol%02d" % idx |
978 |
|
|
if lvtemplate in [l.lvname for l in vg.lvs]: |
979 |
|
|
idx += 1 |
980 |
|
|
else: |
981 |
|
|
break |
982 |
|
|
|
983 |
|
|
return lvtemplate |
984 |
|
|
|
985 |
|
|
def doEncryptionPassphraseRetrofits(self): |
986 |
|
|
""" Add the global passphrase to all preexisting LUKS devices. |
987 |
|
|
|
988 |
|
|
This establishes a common passphrase for all encrypted devices |
989 |
|
|
in the system so that users only have to enter one passphrase |
990 |
|
|
during system boot. |
991 |
|
|
""" |
992 |
|
|
if not self.encryptionRetrofit: |
993 |
|
|
return |
994 |
|
|
|
995 |
|
|
for device in self.devices: |
996 |
|
|
if device.format.type == "luks" and \ |
997 |
|
|
device.format._LUKS__passphrase != self.encryptionPassphrase: |
998 |
|
|
log.info("adding new passphrase to preexisting encrypted " |
999 |
|
|
"device %s" % device.path) |
1000 |
|
|
try: |
1001 |
|
|
device.format.addPassphrase(self.encryptionPassphrase) |
1002 |
|
|
except CryptoError: |
1003 |
|
|
log.error("failed to add new passphrase to existing " |
1004 |
|
|
"device %s" % device.path) |
1005 |
|
|
|
1006 |
|
|
def sanityCheck(self): |
1007 |
|
|
""" Run a series of tests to verify the storage configuration. |
1008 |
|
|
|
1009 |
|
|
This function is called at the end of partitioning so that |
1010 |
|
|
we can make sure you don't have anything silly (like no /, |
1011 |
|
|
a really small /, etc). Returns (errors, warnings) where |
1012 |
|
|
each is a list of strings. |
1013 |
|
|
""" |
1014 |
|
|
checkSizes = [('/usr', 250), ('/tmp', 50), ('/var', 384), |
1015 |
|
|
('/home', 100), ('/boot', 75)] |
1016 |
|
|
warnings = [] |
1017 |
|
|
errors = [] |
1018 |
|
|
|
1019 |
|
|
mustbeonlinuxfs = ['/', '/var', '/tmp', '/usr', '/home', '/usr/share', '/usr/lib'] |
1020 |
|
|
mustbeonroot = ['/bin','/dev','/sbin','/etc','/lib','/root', '/mnt', 'lost+found', '/proc'] |
1021 |
|
|
|
1022 |
|
|
filesystems = self.mountpoints |
1023 |
|
|
root = self.fsset.rootDevice |
1024 |
|
|
swaps = self.fsset.swapDevices |
1025 |
|
|
try: |
1026 |
|
|
boot = self.anaconda.platform.bootDevice() |
1027 |
|
|
except DeviceError: |
1028 |
|
|
boot = None |
1029 |
|
|
|
1030 |
|
|
if not root: |
1031 |
|
|
errors.append(_("You have not defined a root partition (/), " |
1032 |
|
|
"which is required for installation of %s " |
1033 |
|
|
"to continue.") % (productName,)) |
1034 |
|
|
|
1035 |
|
|
if root and root.size < 250: |
1036 |
|
|
warnings.append(_("Your root partition is less than 250 " |
1037 |
|
|
"megabytes which is usually too small to " |
1038 |
|
|
"install %s.") % (productName,)) |
1039 |
|
|
|
1040 |
|
|
if (root and |
1041 |
|
|
root.size < self.anaconda.backend.getMinimumSizeMB("/")): |
1042 |
|
|
errors.append(_("Your / partition is less than %(min)s " |
1043 |
|
|
"MB which is lower than recommended " |
1044 |
|
|
"for a normal %(productName)s install.") |
1045 |
|
|
% {'min': self.anaconda.backend.getMinimumSizeMB("/"), |
1046 |
|
|
'productName': productName}) |
1047 |
|
|
|
1048 |
|
|
if root and root.format.type == "xfs": |
1049 |
|
|
errors.append(_("Placing the root partition on an XFS " |
1050 |
|
|
"filesystem is not supported in %s.") % |
1051 |
|
|
productName) |
1052 |
|
|
|
1053 |
|
|
# livecds have to have the rootfs type match up |
1054 |
|
|
if (root and |
1055 |
|
|
self.anaconda.backend.rootFsType and |
1056 |
|
|
root.format.type != self.anaconda.backend.rootFsType): |
1057 |
|
|
errors.append(_("Your / partition does not match the " |
1058 |
|
|
"the live image you are installing from. " |
1059 |
|
|
"It must be formatted as %s.") |
1060 |
|
|
% (self.anaconda.backend.rootFsType,)) |
1061 |
|
|
|
1062 |
|
|
for (mount, size) in checkSizes: |
1063 |
|
|
if mount in filesystems and filesystems[mount].size < size: |
1064 |
|
|
warnings.append(_("Your %(mount)s partition is less than " |
1065 |
|
|
"%(size)s megabytes which is lower than " |
1066 |
|
|
"recommended for a normal %(productName)s " |
1067 |
|
|
"install.") |
1068 |
|
|
% {'mount': mount, 'size': size, |
1069 |
|
|
'productName': productName}) |
1070 |
|
|
|
1071 |
|
|
for (mount, device) in filesystems.items(): |
1072 |
|
|
problem = filesystems[mount].checkSize() |
1073 |
|
|
if problem: |
1074 |
|
|
errors.append(_("Your %s partition is too %s for %s formatting " |
1075 |
|
|
"(allowable size is %d MB to %d MB)") |
1076 |
|
|
% (mount, problem, device.format.name, |
1077 |
|
|
device.minSize, device.maxSize)) |
1078 |
|
|
|
1079 |
|
|
usb_disks = [] |
1080 |
|
|
firewire_disks = [] |
1081 |
|
|
for disk in self.disks: |
1082 |
|
|
if isys.driveUsesModule(disk.name, ["usb-storage", "ub"]): |
1083 |
|
|
usb_disks.append(disk) |
1084 |
|
|
elif isys.driveUsesModule(disk.name, ["sbp2", "firewire-sbp2"]): |
1085 |
|
|
firewire_disks.append(disk) |
1086 |
|
|
|
1087 |
|
|
uses_usb = False |
1088 |
|
|
uses_firewire = False |
1089 |
|
|
for device in filesystems.values(): |
1090 |
|
|
for disk in usb_disks: |
1091 |
|
|
if device.dependsOn(disk): |
1092 |
|
|
uses_usb = True |
1093 |
|
|
break |
1094 |
|
|
|
1095 |
|
|
for disk in firewire_disks: |
1096 |
|
|
if device.dependsOn(disk): |
1097 |
|
|
uses_firewire = True |
1098 |
|
|
break |
1099 |
|
|
|
1100 |
|
|
if uses_usb: |
1101 |
|
|
warnings.append(_("Installing on a USB device. This may " |
1102 |
|
|
"or may not produce a working system.")) |
1103 |
|
|
if uses_firewire: |
1104 |
|
|
warnings.append(_("Installing on a FireWire device. This may " |
1105 |
|
|
"or may not produce a working system.")) |
1106 |
|
|
|
1107 |
|
|
errors.extend(self.anaconda.platform.checkBootRequest(boot)) |
1108 |
|
|
|
1109 |
|
|
if not swaps: |
1110 |
|
|
if iutil.memInstalled() < isys.EARLY_SWAP_RAM: |
1111 |
|
|
errors.append(_("You have not specified a swap partition. " |
1112 |
|
|
"Due to the amount of memory present, a " |
1113 |
|
|
"swap partition is required to complete " |
1114 |
|
|
"installation.")) |
1115 |
|
|
else: |
1116 |
|
|
warnings.append(_("You have not specified a swap partition. " |
1117 |
|
|
"Although not strictly required in all cases, " |
1118 |
|
|
"it will significantly improve performance " |
1119 |
|
|
"for most installations.")) |
1120 |
|
|
no_uuid = [s for s in swaps if s.format.exists and not s.format.uuid] |
1121 |
|
|
if no_uuid: |
1122 |
|
|
warnings.append(_("At least one of your swap devices does not have " |
1123 |
|
|
"a UUID, which is common in swap space created " |
1124 |
|
|
"using older versions of mkswap. These devices " |
1125 |
|
|
"will be referred to by device path in " |
1126 |
|
|
"/etc/fstab, which is not ideal since device " |
1127 |
|
|
"paths can change under a variety of " |
1128 |
|
|
"circumstances. ")) |
1129 |
|
|
|
1130 |
|
|
for (mountpoint, dev) in filesystems.items(): |
1131 |
|
|
if mountpoint in mustbeonroot: |
1132 |
|
|
errors.append(_("This mount point is invalid. The %s directory must " |
1133 |
|
|
"be on the / file system.") % mountpoint) |
1134 |
|
|
|
1135 |
|
|
if mountpoint in mustbeonlinuxfs and (not dev.format.mountable or not dev.format.linuxNative): |
1136 |
|
|
errors.append(_("The mount point %s must be on a linux file system.") % mountpoint) |
1137 |
|
|
|
1138 |
|
|
return (errors, warnings) |
1139 |
|
|
|
1140 |
|
|
def isProtected(self, device): |
1141 |
|
|
""" Return True is the device is protected. """ |
1142 |
|
|
return device.protected |
1143 |
|
|
|
1144 |
|
|
def checkNoDisks(self): |
1145 |
|
|
"""Check that there are valid disk devices.""" |
1146 |
|
|
if not self.disks: |
1147 |
|
|
self.anaconda.intf.messageWindow(_("No Drives Found"), |
1148 |
|
|
_("An error has occurred - no valid devices were " |
1149 |
|
|
"found on which to create new file systems. " |
1150 |
|
|
"Please check your hardware for the cause " |
1151 |
|
|
"of this problem.")) |
1152 |
|
|
return True |
1153 |
|
|
return False |
1154 |
|
|
|
1155 |
|
|
def dumpState(self, suffix): |
1156 |
|
|
""" Dump the current device list to the storage shelf. """ |
1157 |
|
|
key = "devices.%d.%s" % (time.time(), suffix) |
1158 |
|
|
with contextlib.closing(shelve.open(self._dumpFile)) as shelf: |
1159 |
|
|
shelf[key] = [d.dict for d in self.devices] |
1160 |
|
|
|
1161 |
|
|
def write(self, instPath): |
1162 |
|
|
self.fsset.write(instPath) |
1163 |
|
|
self.iscsi.write(instPath, self.anaconda) |
1164 |
|
|
self.fcoe.write(instPath, self.anaconda) |
1165 |
|
|
self.zfcp.write(instPath) |
1166 |
|
|
self.dasd.write(instPath) |
1167 |
|
|
|
1168 |
|
|
def writeKS(self, f): |
1169 |
|
|
def useExisting(lst): |
1170 |
|
|
foundCreateDevice = False |
1171 |
|
|
foundCreateFormat = False |
1172 |
|
|
|
1173 |
|
|
for l in lst: |
1174 |
|
|
if isinstance(l, ActionCreateDevice): |
1175 |
|
|
foundCreateDevice = True |
1176 |
|
|
elif isinstance(l, ActionCreateFormat): |
1177 |
|
|
foundCreateFormat = True |
1178 |
|
|
|
1179 |
|
|
return (foundCreateFormat and not foundCreateDevice) |
1180 |
|
|
|
1181 |
|
|
log.warning("Storage.writeKS not completely implemented") |
1182 |
|
|
f.write("# The following is the partition information you requested\n") |
1183 |
|
|
f.write("# Note that any partitions you deleted are not expressed\n") |
1184 |
|
|
f.write("# here so unless you clear all partitions first, this is\n") |
1185 |
|
|
f.write("# not guaranteed to work\n") |
1186 |
|
|
|
1187 |
|
|
# clearpart |
1188 |
|
|
if self.clearPartType is None or self.clearPartType == CLEARPART_TYPE_NONE: |
1189 |
|
|
args = ["--none"] |
1190 |
|
|
elif self.clearPartType == CLEARPART_TYPE_LINUX: |
1191 |
|
|
args = ["--linux"] |
1192 |
|
|
else: |
1193 |
|
|
args = ["--all"] |
1194 |
|
|
|
1195 |
|
|
if self.clearPartDisks: |
1196 |
|
|
args += ["--drives=%s" % ",".join(self.clearPartDisks)] |
1197 |
|
|
if self.reinitializeDisks: |
1198 |
|
|
args += ["--initlabel"] |
1199 |
|
|
|
1200 |
|
|
f.write("#clearpart %s\n" % " ".join(args)) |
1201 |
|
|
|
1202 |
|
|
# ignoredisks |
1203 |
|
|
if self.ignoreDiskInteractive: |
1204 |
|
|
f.write("#ignoredisk --interactive\n") |
1205 |
|
|
elif self.ignoredDisks: |
1206 |
|
|
f.write("#ignoredisk --drives=%s\n" % ",".join(self.ignoredDisks)) |
1207 |
|
|
elif self.exclusiveDisks: |
1208 |
|
|
f.write("#ignoredisk --only-use=%s\n" % ",".join(self.exclusiveDisks)) |
1209 |
|
|
|
1210 |
|
|
# the various partitioning commands |
1211 |
|
|
dict = {} |
1212 |
|
|
actions = filter(lambda x: x.device.format.type != "luks", |
1213 |
|
|
self.devicetree.findActions(type="create")) |
1214 |
|
|
|
1215 |
|
|
for action in actions: |
1216 |
|
|
if dict.has_key(action.device.path): |
1217 |
|
|
dict[action.device.path].append(action) |
1218 |
|
|
else: |
1219 |
|
|
dict[action.device.path] = [action] |
1220 |
|
|
|
1221 |
|
|
for device in [d for d in self.devices if d.format.type != "luks"]: |
1222 |
|
|
# If there's no action for the given device, it must be one |
1223 |
|
|
# we are reusing. |
1224 |
|
|
if not dict.has_key(device.path): |
1225 |
|
|
noformat = True |
1226 |
|
|
preexisting = True |
1227 |
|
|
else: |
1228 |
|
|
noformat = False |
1229 |
|
|
preexisting = useExisting(dict[device.path]) |
1230 |
|
|
|
1231 |
|
|
device.writeKS(f, preexisting=preexisting, noformat=noformat) |
1232 |
|
|
f.write("\n") |
1233 |
|
|
|
1234 |
|
|
self.iscsi.writeKS(f) |
1235 |
|
|
self.fcoe.writeKS(f) |
1236 |
|
|
self.zfcp.writeKS(f) |
1237 |
|
|
f.write("\n") |
1238 |
|
|
|
1239 |
|
|
def turnOnSwap(self, upgrading=None): |
1240 |
|
|
self.fsset.turnOnSwap(self.anaconda, upgrading=upgrading) |
1241 |
|
|
|
1242 |
|
|
def mountFilesystems(self, raiseErrors=None, readOnly=None, skipRoot=False): |
1243 |
|
|
self.fsset.mountFilesystems(self.anaconda, raiseErrors=raiseErrors, |
1244 |
|
|
readOnly=readOnly, skipRoot=skipRoot) |
1245 |
|
|
|
1246 |
|
|
def umountFilesystems(self, ignoreErrors=True, swapoff=True): |
1247 |
|
|
self.fsset.umountFilesystems(ignoreErrors=ignoreErrors, swapoff=swapoff) |
1248 |
|
|
|
1249 |
|
|
def parseFSTab(self): |
1250 |
|
|
self.fsset.parseFSTab() |
1251 |
|
|
|
1252 |
|
|
def mkDevRoot(self): |
1253 |
|
|
self.fsset.mkDevRoot() |
1254 |
|
|
|
1255 |
|
|
def createSwapFile(self, device, size): |
1256 |
|
|
self.fsset.createSwapFile(device, size) |
1257 |
|
|
|
1258 |
|
|
@property |
1259 |
|
|
def fsFreeSpace(self): |
1260 |
|
|
return self.fsset.fsFreeSpace() |
1261 |
|
|
|
1262 |
|
|
@property |
1263 |
|
|
def mtab(self): |
1264 |
|
|
return self.fsset.mtab() |
1265 |
|
|
|
1266 |
|
|
@property |
1267 |
|
|
def mountpoints(self): |
1268 |
|
|
return self.fsset.mountpoints |
1269 |
|
|
|
1270 |
|
|
@property |
1271 |
|
|
def migratableDevices(self): |
1272 |
|
|
return self.fsset.migratableDevices |
1273 |
|
|
|
1274 |
|
|
@property |
1275 |
|
|
def rootDevice(self): |
1276 |
|
|
return self.fsset.rootDevice |
1277 |
|
|
|
1278 |
|
|
def compareDisks(self, first, second): |
1279 |
|
|
if self.eddDict.has_key(first) and self.eddDict.has_key(second): |
1280 |
|
|
one = self.eddDict[first] |
1281 |
|
|
two = self.eddDict[second] |
1282 |
|
|
if (one < two): |
1283 |
|
|
return -1 |
1284 |
|
|
elif (one > two): |
1285 |
|
|
return 1 |
1286 |
|
|
|
1287 |
|
|
# if one is in the BIOS and the other not prefer the one in the BIOS |
1288 |
|
|
if self.eddDict.has_key(first): |
1289 |
|
|
return -1 |
1290 |
|
|
if self.eddDict.has_key(second): |
1291 |
|
|
return 1 |
1292 |
|
|
|
1293 |
|
|
if first.startswith("hd"): |
1294 |
|
|
type1 = 0 |
1295 |
|
|
elif first.startswith("sd"): |
1296 |
|
|
type1 = 1 |
1297 |
|
|
elif (first.startswith("vd") or first.startswith("xvd")): |
1298 |
|
|
type1 = -1 |
1299 |
|
|
else: |
1300 |
|
|
type1 = 2 |
1301 |
|
|
|
1302 |
|
|
if second.startswith("hd"): |
1303 |
|
|
type2 = 0 |
1304 |
|
|
elif second.startswith("sd"): |
1305 |
|
|
type2 = 1 |
1306 |
|
|
elif (second.startswith("vd") or second.startswith("xvd")): |
1307 |
|
|
type2 = -1 |
1308 |
|
|
else: |
1309 |
|
|
type2 = 2 |
1310 |
|
|
|
1311 |
|
|
if (type1 < type2): |
1312 |
|
|
return -1 |
1313 |
|
|
elif (type1 > type2): |
1314 |
|
|
return 1 |
1315 |
|
|
else: |
1316 |
|
|
len1 = len(first) |
1317 |
|
|
len2 = len(second) |
1318 |
|
|
|
1319 |
|
|
if (len1 < len2): |
1320 |
|
|
return -1 |
1321 |
|
|
elif (len1 > len2): |
1322 |
|
|
return 1 |
1323 |
|
|
else: |
1324 |
|
|
if (first < second): |
1325 |
|
|
return -1 |
1326 |
|
|
elif (first > second): |
1327 |
|
|
return 1 |
1328 |
|
|
|
1329 |
|
|
return 0 |
1330 |
|
|
|
1331 |
|
|
def getReleaseString(mountpoint): |
1332 |
|
|
relName = None |
1333 |
|
|
relVer = None |
1334 |
|
|
|
1335 |
|
|
filename = "%s/etc/redhat-release" % mountpoint |
1336 |
|
|
if os.access(filename, os.R_OK): |
1337 |
|
|
with open(filename) as f: |
1338 |
|
|
try: |
1339 |
|
|
relstr = f.readline().strip() |
1340 |
|
|
except (IOError, AttributeError): |
1341 |
|
|
relstr = "" |
1342 |
|
|
|
1343 |
|
|
# get the release name and version |
1344 |
|
|
# assumes that form is something |
1345 |
|
|
# like "Red Hat Linux release 6.2 (Zoot)" |
1346 |
|
|
(product, sep, version) = relstr.partition(" release ") |
1347 |
|
|
if sep: |
1348 |
|
|
relName = product |
1349 |
|
|
relVer = version.split()[0] |
1350 |
|
|
|
1351 |
|
|
return (relName, relVer) |
1352 |
|
|
|
1353 |
|
|
def findExistingRootDevices(anaconda, upgradeany=False): |
1354 |
|
|
""" Return a list of all root filesystems in the device tree. """ |
1355 |
|
|
rootDevs = [] |
1356 |
|
|
|
1357 |
|
|
if not os.path.exists(anaconda.rootPath): |
1358 |
|
|
iutil.mkdirChain(anaconda.rootPath) |
1359 |
|
|
|
1360 |
|
|
roots = [] |
1361 |
|
|
for device in anaconda.id.storage.devicetree.leaves: |
1362 |
|
|
if not device.format.linuxNative or not device.format.mountable: |
1363 |
|
|
continue |
1364 |
|
|
|
1365 |
|
|
if device.protected: |
1366 |
|
|
# can't upgrade the part holding hd: media so why look at it? |
1367 |
|
|
continue |
1368 |
|
|
|
1369 |
|
|
try: |
1370 |
|
|
device.setup() |
1371 |
|
|
except Exception as e: |
1372 |
|
|
log.warning("setup of %s failed: %s" % (device.name, e)) |
1373 |
|
|
continue |
1374 |
|
|
|
1375 |
|
|
try: |
1376 |
|
|
device.format.mount(options="ro", mountpoint=anaconda.rootPath) |
1377 |
|
|
except Exception as e: |
1378 |
|
|
log.warning("mount of %s as %s failed: %s" % (device.name, |
1379 |
|
|
device.format.type, |
1380 |
|
|
e)) |
1381 |
|
|
device.teardown() |
1382 |
|
|
continue |
1383 |
|
|
|
1384 |
|
|
if os.access(anaconda.rootPath + "/etc/fstab", os.R_OK): |
1385 |
|
|
(product, version) = getReleaseString(anaconda.rootPath) |
1386 |
|
|
if upgradeany or \ |
1387 |
|
|
anaconda.id.instClass.productUpgradable(product, version): |
1388 |
|
|
rootDevs.append((device, "%s %s" % (product, version))) |
1389 |
|
|
else: |
1390 |
|
|
log.info("product %s version %s found on %s is not upgradable" |
1391 |
|
|
% (product, version, device.name)) |
1392 |
|
|
|
1393 |
|
|
# this handles unmounting the filesystem |
1394 |
|
|
device.teardown(recursive=True) |
1395 |
|
|
|
1396 |
|
|
return rootDevs |
1397 |
|
|
|
1398 |
|
|
def mountExistingSystem(anaconda, rootEnt, |
1399 |
|
|
allowDirty=None, warnDirty=None, |
1400 |
|
|
readOnly=None): |
1401 |
|
|
""" Mount filesystems specified in rootDevice's /etc/fstab file. """ |
1402 |
|
|
rootDevice = rootEnt[0] |
1403 |
|
|
rootPath = anaconda.rootPath |
1404 |
|
|
fsset = anaconda.id.storage.fsset |
1405 |
|
|
if readOnly: |
1406 |
|
|
readOnly = "ro" |
1407 |
|
|
else: |
1408 |
|
|
readOnly = "" |
1409 |
|
|
|
1410 |
|
|
if rootDevice.protected and os.path.ismount("/mnt/isodir"): |
1411 |
|
|
isys.mount("/mnt/isodir", |
1412 |
|
|
rootPath, |
1413 |
|
|
fstype=rootDevice.format.type, |
1414 |
|
|
bindMount=True) |
1415 |
|
|
else: |
1416 |
|
|
rootDevice.setup() |
1417 |
|
|
rootDevice.format.mount(chroot=rootPath, |
1418 |
|
|
mountpoint="/", |
1419 |
|
|
options="ro") |
1420 |
|
|
|
1421 |
|
|
fsset.parseFSTab() |
1422 |
|
|
|
1423 |
|
|
# check for dirty filesystems |
1424 |
|
|
dirtyDevs = [] |
1425 |
|
|
for device in fsset.mountpoints.values(): |
1426 |
|
|
if not hasattr(device.format, "isDirty"): |
1427 |
|
|
continue |
1428 |
|
|
|
1429 |
|
|
try: |
1430 |
|
|
device.setup() |
1431 |
|
|
except DeviceError as e: |
1432 |
|
|
# we'll catch this in the main loop |
1433 |
|
|
continue |
1434 |
|
|
|
1435 |
|
|
if device.format.isDirty: |
1436 |
|
|
log.info("%s contains a dirty %s filesystem" % (device.path, |
1437 |
|
|
device.format.type)) |
1438 |
|
|
dirtyDevs.append(device.path) |
1439 |
|
|
|
1440 |
|
|
messageWindow = anaconda.intf.messageWindow |
1441 |
|
|
if not allowDirty and dirtyDevs: |
1442 |
|
|
messageWindow(_("Dirty File Systems"), |
1443 |
|
|
_("The following file systems for your Linux system " |
1444 |
|
|
"were not unmounted cleanly. Please boot your " |
1445 |
|
|
"Linux installation, let the file systems be " |
1446 |
|
|
"checked and shut down cleanly to upgrade.\n" |
1447 |
|
|
"%s") % "\n".join(dirtyDevs)) |
1448 |
|
|
anaconda.id.storage.devicetree.teardownAll() |
1449 |
|
|
sys.exit(0) |
1450 |
|
|
elif warnDirty and dirtyDevs: |
1451 |
|
|
rc = messageWindow(_("Dirty File Systems"), |
1452 |
|
|
_("The following file systems for your Linux " |
1453 |
|
|
"system were not unmounted cleanly. Would " |
1454 |
|
|
"you like to mount them anyway?\n" |
1455 |
|
|
"%s") % "\n".join(dirtyDevs), |
1456 |
|
|
type = "yesno") |
1457 |
|
|
if rc == 0: |
1458 |
|
|
return -1 |
1459 |
|
|
|
1460 |
|
|
log.info("All is well mounting the rest of referenced filesystems") |
1461 |
|
|
if not readOnly: |
1462 |
|
|
log.info("Remounting /mnt/sysimage read-write") |
1463 |
|
|
rootDevice.format.remount(options="rw") |
1464 |
|
|
fsset.mountFilesystems(anaconda, readOnly=readOnly, skipRoot=True) |
1465 |
|
|
|
1466 |
|
|
|
1467 |
|
|
class BlkidTab(object): |
1468 |
|
|
""" Dictionary-like interface to blkid.tab with device path keys """ |
1469 |
|
|
def __init__(self, chroot=""): |
1470 |
|
|
self.chroot = chroot |
1471 |
|
|
self.devices = {} |
1472 |
|
|
|
1473 |
|
|
def parse(self): |
1474 |
|
|
path = "%s/etc/blkid/blkid.tab" % self.chroot |
1475 |
|
|
log.debug("parsing %s" % path) |
1476 |
|
|
with open(path) as f: |
1477 |
|
|
for line in f.readlines(): |
1478 |
|
|
# this is pretty ugly, but an XML parser is more work than |
1479 |
|
|
# is justifiable for this purpose |
1480 |
|
|
if not line.startswith("<device "): |
1481 |
|
|
continue |
1482 |
|
|
|
1483 |
|
|
line = line[len("<device "):-len("</device>\n")] |
1484 |
|
|
(data, sep, device) = line.partition(">") |
1485 |
|
|
if not device: |
1486 |
|
|
continue |
1487 |
|
|
|
1488 |
|
|
self.devices[device] = {} |
1489 |
|
|
for pair in data.split(): |
1490 |
|
|
try: |
1491 |
|
|
(key, value) = pair.split("=") |
1492 |
|
|
except ValueError: |
1493 |
|
|
continue |
1494 |
|
|
|
1495 |
|
|
self.devices[device][key] = value[1:-1] # strip off quotes |
1496 |
|
|
|
1497 |
|
|
def __getitem__(self, key): |
1498 |
|
|
return self.devices[key] |
1499 |
|
|
|
1500 |
|
|
def get(self, key, default=None): |
1501 |
|
|
return self.devices.get(key, default) |
1502 |
|
|
|
1503 |
|
|
|
1504 |
|
|
class CryptTab(object): |
1505 |
|
|
""" Dictionary-like interface to crypttab entries with map name keys """ |
1506 |
|
|
def __init__(self, devicetree, blkidTab=None, chroot=""): |
1507 |
|
|
self.devicetree = devicetree |
1508 |
|
|
self.blkidTab = blkidTab |
1509 |
|
|
self.chroot = chroot |
1510 |
|
|
self.mappings = {} |
1511 |
|
|
|
1512 |
|
|
def parse(self, chroot=""): |
1513 |
|
|
""" Parse /etc/crypttab from an existing installation. """ |
1514 |
|
|
if not chroot or not os.path.isdir(chroot): |
1515 |
|
|
chroot = "" |
1516 |
|
|
|
1517 |
|
|
path = "%s/etc/crypttab" % chroot |
1518 |
|
|
log.debug("parsing %s" % path) |
1519 |
|
|
with open(path) as f: |
1520 |
|
|
if not self.blkidTab: |
1521 |
|
|
try: |
1522 |
|
|
self.blkidTab = BlkidTab(chroot=chroot) |
1523 |
|
|
self.blkidTab.parse() |
1524 |
|
|
except Exception: |
1525 |
|
|
self.blkidTab = None |
1526 |
|
|
|
1527 |
|
|
for line in f.readlines(): |
1528 |
|
|
(line, pound, comment) = line.partition("#") |
1529 |
|
|
fields = line.split() |
1530 |
|
|
if not 2 <= len(fields) <= 4: |
1531 |
|
|
continue |
1532 |
|
|
elif len(fields) == 2: |
1533 |
|
|
fields.extend(['none', '']) |
1534 |
|
|
elif len(fields) == 3: |
1535 |
|
|
fields.append('') |
1536 |
|
|
|
1537 |
|
|
(name, devspec, keyfile, options) = fields |
1538 |
|
|
|
1539 |
|
|
# resolve devspec to a device in the tree |
1540 |
|
|
device = self.devicetree.resolveDevice(devspec, |
1541 |
|
|
blkidTab=self.blkidTab) |
1542 |
|
|
if device: |
1543 |
|
|
self.mappings[name] = {"device": device, |
1544 |
|
|
"keyfile": keyfile, |
1545 |
|
|
"options": options} |
1546 |
|
|
|
1547 |
|
|
def populate(self): |
1548 |
|
|
""" Populate the instance based on the device tree's contents. """ |
1549 |
|
|
for device in self.devicetree.devices: |
1550 |
|
|
# XXX should we put them all in there or just the ones that |
1551 |
|
|
# are part of a device containing swap or a filesystem? |
1552 |
|
|
# |
1553 |
|
|
# Put them all in here -- we can filter from FSSet |
1554 |
|
|
if device.format.type != "luks": |
1555 |
|
|
continue |
1556 |
|
|
|
1557 |
|
|
key_file = device.format.keyFile |
1558 |
|
|
if not key_file: |
1559 |
|
|
key_file = "none" |
1560 |
|
|
|
1561 |
|
|
options = device.format.options |
1562 |
|
|
if not options: |
1563 |
|
|
options = "" |
1564 |
|
|
|
1565 |
|
|
self.mappings[device.format.mapName] = {"device": device, |
1566 |
|
|
"keyfile": key_file, |
1567 |
|
|
"options": options} |
1568 |
|
|
|
1569 |
|
|
def crypttab(self): |
1570 |
|
|
""" Write out /etc/crypttab """ |
1571 |
|
|
crypttab = "" |
1572 |
|
|
for name in self.mappings: |
1573 |
|
|
entry = self[name] |
1574 |
|
|
crypttab += "%s UUID=%s %s %s\n" % (name, |
1575 |
|
|
entry['device'].format.uuid, |
1576 |
|
|
entry['keyfile'], |
1577 |
|
|
entry['options']) |
1578 |
|
|
return crypttab |
1579 |
|
|
|
1580 |
|
|
def __getitem__(self, key): |
1581 |
|
|
return self.mappings[key] |
1582 |
|
|
|
1583 |
|
|
def get(self, key, default=None): |
1584 |
|
|
return self.mappings.get(key, default) |
1585 |
|
|
|
1586 |
|
|
def get_containing_device(path, devicetree): |
1587 |
|
|
""" Return the device that a path resides on. """ |
1588 |
|
|
if not os.path.exists(path): |
1589 |
|
|
return None |
1590 |
|
|
|
1591 |
|
|
st = os.stat(path) |
1592 |
|
|
major = os.major(st.st_dev) |
1593 |
|
|
minor = os.minor(st.st_dev) |
1594 |
|
|
link = "/sys/dev/block/%s:%s" % (major, minor) |
1595 |
|
|
if not os.path.exists(link): |
1596 |
|
|
return None |
1597 |
|
|
|
1598 |
|
|
try: |
1599 |
|
|
device_name = os.path.basename(os.readlink(link)) |
1600 |
|
|
except Exception: |
1601 |
|
|
return None |
1602 |
|
|
|
1603 |
|
|
if device_name.startswith("dm-"): |
1604 |
|
|
# have I told you lately that I love you, device-mapper? |
1605 |
|
|
device_name = name_from_dm_node(device_name) |
1606 |
|
|
|
1607 |
|
|
return devicetree.getDeviceByName(device_name) |
1608 |
|
|
|
1609 |
|
|
|
1610 |
|
|
class FSSet(object): |
1611 |
|
|
""" A class to represent a set of filesystems. """ |
1612 |
|
|
def __init__(self, devicetree, rootpath): |
1613 |
|
|
self.devicetree = devicetree |
1614 |
|
|
self.rootpath = rootpath |
1615 |
|
|
self.cryptTab = None |
1616 |
|
|
self.blkidTab = None |
1617 |
|
|
self.origFStab = None |
1618 |
|
|
self.active = False |
1619 |
|
|
self._dev = None |
1620 |
|
|
self._devpts = None |
1621 |
|
|
self._sysfs = None |
1622 |
|
|
self._proc = None |
1623 |
|
|
self._devshm = None |
1624 |
|
|
self.preserveLines = [] # lines we just ignore and preserve |
1625 |
|
|
|
1626 |
|
|
@property |
1627 |
|
|
def sysfs(self): |
1628 |
|
|
if not self._sysfs: |
1629 |
|
|
self._sysfs = NoDevice(format=getFormat("sysfs", |
1630 |
|
|
device="sys", |
1631 |
|
|
mountpoint="/sys")) |
1632 |
|
|
return self._sysfs |
1633 |
|
|
|
1634 |
|
|
@property |
1635 |
|
|
def dev(self): |
1636 |
|
|
if not self._dev: |
1637 |
|
|
self._dev = DirectoryDevice("/dev", format=getFormat("bind", |
1638 |
|
|
device="/dev", |
1639 |
|
|
mountpoint="/dev", |
1640 |
|
|
exists=True), |
1641 |
|
|
exists=True) |
1642 |
|
|
|
1643 |
|
|
return self._dev |
1644 |
|
|
|
1645 |
|
|
@property |
1646 |
|
|
def devpts(self): |
1647 |
|
|
if not self._devpts: |
1648 |
|
|
self._devpts = NoDevice(format=getFormat("devpts", |
1649 |
|
|
device="devpts", |
1650 |
|
|
mountpoint="/dev/pts")) |
1651 |
|
|
return self._devpts |
1652 |
|
|
|
1653 |
|
|
@property |
1654 |
|
|
def proc(self): |
1655 |
|
|
if not self._proc: |
1656 |
|
|
self._proc = NoDevice(format=getFormat("proc", |
1657 |
|
|
device="proc", |
1658 |
|
|
mountpoint="/proc")) |
1659 |
|
|
return self._proc |
1660 |
|
|
|
1661 |
|
|
@property |
1662 |
|
|
def devshm(self): |
1663 |
|
|
if not self._devshm: |
1664 |
|
|
self._devshm = NoDevice(format=getFormat("tmpfs", |
1665 |
|
|
device="tmpfs", |
1666 |
|
|
mountpoint="/dev/shm")) |
1667 |
|
|
return self._devshm |
1668 |
|
|
|
1669 |
|
|
@property |
1670 |
|
|
def devices(self): |
1671 |
|
|
return sorted(self.devicetree.devices, key=lambda d: d.path) |
1672 |
|
|
|
1673 |
|
|
@property |
1674 |
|
|
def mountpoints(self): |
1675 |
|
|
filesystems = {} |
1676 |
|
|
for device in self.devices: |
1677 |
|
|
if device.format.mountable and device.format.mountpoint: |
1678 |
|
|
filesystems[device.format.mountpoint] = device |
1679 |
|
|
return filesystems |
1680 |
|
|
|
1681 |
|
|
def _parseOneLine(self, (devspec, mountpoint, fstype, options, dump, passno)): |
1682 |
|
|
# find device in the tree |
1683 |
|
|
device = self.devicetree.resolveDevice(devspec, |
1684 |
|
|
cryptTab=self.cryptTab, |
1685 |
|
|
blkidTab=self.blkidTab) |
1686 |
|
|
if device: |
1687 |
|
|
# fall through to the bottom of this block |
1688 |
|
|
pass |
1689 |
|
|
elif devspec.startswith("/dev/loop"): |
1690 |
|
|
# FIXME: create devices.LoopDevice |
1691 |
|
|
log.warning("completely ignoring your loop mount") |
1692 |
|
|
elif ":" in devspec and fstype.startswith("nfs"): |
1693 |
|
|
# NFS -- preserve but otherwise ignore |
1694 |
|
|
device = NFSDevice(devspec, |
1695 |
|
|
format=getFormat(fstype, |
1696 |
|
|
device=devspec)) |
1697 |
|
|
elif devspec.startswith("/") and fstype == "swap": |
1698 |
|
|
# swap file |
1699 |
|
|
device = FileDevice(devspec, |
1700 |
|
|
parents=get_containing_device(devspec, self.devicetree), |
1701 |
|
|
format=getFormat(fstype, |
1702 |
|
|
device=devspec, |
1703 |
|
|
exists=True), |
1704 |
|
|
exists=True) |
1705 |
|
|
elif fstype == "bind" or "bind" in options: |
1706 |
|
|
# bind mount... set fstype so later comparison won't |
1707 |
|
|
# turn up false positives |
1708 |
|
|
fstype = "bind" |
1709 |
|
|
|
1710 |
|
|
# This is probably not going to do anything useful, so we'll |
1711 |
|
|
# make sure to try again from FSSet.mountFilesystems. The bind |
1712 |
|
|
# mount targets should be accessible by the time we try to do |
1713 |
|
|
# the bind mount from there. |
1714 |
|
|
parents = get_containing_device(devspec, self.devicetree) |
1715 |
|
|
device = DirectoryDevice(devspec, parents=parents, exists=True) |
1716 |
|
|
device.format = getFormat("bind", |
1717 |
|
|
device=device.path, |
1718 |
|
|
exists=True) |
1719 |
|
|
elif mountpoint in ("/proc", "/sys", "/dev/shm", "/dev/pts"): |
1720 |
|
|
# drop these now -- we'll recreate later |
1721 |
|
|
return None |
1722 |
|
|
else: |
1723 |
|
|
# nodev filesystem -- preserve or drop completely? |
1724 |
|
|
format = getFormat(fstype) |
1725 |
|
|
if devspec == "none" or \ |
1726 |
|
|
isinstance(format, get_device_format_class("nodev")): |
1727 |
|
|
device = NoDevice(format=format) |
1728 |
|
|
else: |
1729 |
|
|
device = StorageDevice(devspec, format=format) |
1730 |
|
|
|
1731 |
|
|
if device is None: |
1732 |
|
|
log.error("failed to resolve %s (%s) from fstab" % (devspec, |
1733 |
|
|
fstype)) |
1734 |
|
|
raise UnrecognizedFSTabEntryError() |
1735 |
|
|
|
1736 |
|
|
if device.format.type is None: |
1737 |
|
|
log.info("Unrecognized filesystem type for %s (%s)" |
1738 |
|
|
% (device.name, fstype)) |
1739 |
|
|
raise UnrecognizedFSTabEntryError() |
1740 |
|
|
|
1741 |
|
|
# make sure, if we're using a device from the tree, that |
1742 |
|
|
# the device's format we found matches what's in the fstab |
1743 |
|
|
fmt = getFormat(fstype, device=device.path) |
1744 |
|
|
ftype = getattr(fmt, "mountType", fmt.type) |
1745 |
|
|
dtype = getattr(device.format, "mountType", device.format.type) |
1746 |
|
|
if ftype != dtype: |
1747 |
|
|
raise StorageError("scanned format (%s) differs from fstab " |
1748 |
|
|
"format (%s)" % (dtype, ftype)) |
1749 |
|
|
del ftype |
1750 |
|
|
del dtype |
1751 |
|
|
|
1752 |
|
|
if device.format.mountable: |
1753 |
|
|
device.format.mountpoint = mountpoint |
1754 |
|
|
device.format.mountopts = options |
1755 |
|
|
|
1756 |
|
|
# is this useful? |
1757 |
|
|
try: |
1758 |
|
|
device.format.options = options |
1759 |
|
|
except AttributeError: |
1760 |
|
|
pass |
1761 |
|
|
|
1762 |
|
|
return device |
1763 |
|
|
|
1764 |
|
|
def parseFSTab(self, chroot=None): |
1765 |
|
|
""" parse /etc/fstab |
1766 |
|
|
|
1767 |
|
|
preconditions: |
1768 |
|
|
all storage devices have been scanned, including filesystems |
1769 |
|
|
postconditions: |
1770 |
|
|
|
1771 |
|
|
FIXME: control which exceptions we raise |
1772 |
|
|
|
1773 |
|
|
XXX do we care about bind mounts? |
1774 |
|
|
how about nodev mounts? |
1775 |
|
|
loop mounts? |
1776 |
|
|
""" |
1777 |
|
|
if not chroot or not os.path.isdir(chroot): |
1778 |
|
|
chroot = self.rootpath |
1779 |
|
|
|
1780 |
|
|
path = "%s/etc/fstab" % chroot |
1781 |
|
|
if not os.access(path, os.R_OK): |
1782 |
|
|
# XXX should we raise an exception instead? |
1783 |
|
|
log.info("cannot open %s for read" % path) |
1784 |
|
|
return |
1785 |
|
|
|
1786 |
|
|
blkidTab = BlkidTab(chroot=chroot) |
1787 |
|
|
try: |
1788 |
|
|
blkidTab.parse() |
1789 |
|
|
log.debug("blkid.tab devs: %s" % blkidTab.devices.keys()) |
1790 |
|
|
except Exception as e: |
1791 |
|
|
log.info("error parsing blkid.tab: %s" % e) |
1792 |
|
|
blkidTab = None |
1793 |
|
|
|
1794 |
|
|
cryptTab = CryptTab(self.devicetree, blkidTab=blkidTab, chroot=chroot) |
1795 |
|
|
try: |
1796 |
|
|
cryptTab.parse(chroot=chroot) |
1797 |
|
|
log.debug("crypttab maps: %s" % cryptTab.mappings.keys()) |
1798 |
|
|
except Exception as e: |
1799 |
|
|
log.info("error parsing crypttab: %s" % e) |
1800 |
|
|
cryptTab = None |
1801 |
|
|
|
1802 |
|
|
self.blkidTab = blkidTab |
1803 |
|
|
self.cryptTab = cryptTab |
1804 |
|
|
|
1805 |
|
|
with open(path) as f: |
1806 |
|
|
log.debug("parsing %s" % path) |
1807 |
|
|
|
1808 |
|
|
lines = f.readlines() |
1809 |
|
|
|
1810 |
|
|
# save the original file |
1811 |
|
|
self.origFStab = ''.join(lines) |
1812 |
|
|
|
1813 |
|
|
for line in lines: |
1814 |
|
|
# strip off comments |
1815 |
|
|
(line, pound, comment) = line.partition("#") |
1816 |
|
|
fields = line.split() |
1817 |
|
|
|
1818 |
|
|
if not 4 <= len(fields) <= 6: |
1819 |
|
|
continue |
1820 |
|
|
elif len(fields) == 4: |
1821 |
|
|
fields.extend([0, 0]) |
1822 |
|
|
elif len(fields) == 5: |
1823 |
|
|
fields.append(0) |
1824 |
|
|
|
1825 |
|
|
(devspec, mountpoint, fstype, options, dump, passno) = fields |
1826 |
|
|
|
1827 |
|
|
try: |
1828 |
|
|
device = self._parseOneLine((devspec, mountpoint, fstype, options, dump, passno)) |
1829 |
|
|
except UnrecognizedFSTabEntryError: |
1830 |
|
|
# just write the line back out as-is after upgrade |
1831 |
|
|
self.preserveLines.append(line) |
1832 |
|
|
continue |
1833 |
|
|
except Exception as e: |
1834 |
|
|
raise Exception("fstab entry %s is malformed: %s" % (devspec, e)) |
1835 |
|
|
|
1836 |
|
|
if not device: |
1837 |
|
|
continue |
1838 |
|
|
|
1839 |
|
|
if device not in self.devicetree.devices: |
1840 |
|
|
try: |
1841 |
|
|
self.devicetree._addDevice(device) |
1842 |
|
|
except ValueError: |
1843 |
|
|
# just write duplicates back out post-install |
1844 |
|
|
self.preserveLines.append(line) |
1845 |
|
|
|
1846 |
|
|
def fsFreeSpace(self, chroot=None): |
1847 |
|
|
if not chroot: |
1848 |
|
|
chroot = self.rootpath |
1849 |
|
|
|
1850 |
|
|
space = [] |
1851 |
|
|
for device in self.devices: |
1852 |
|
|
if not device.format.mountable or \ |
1853 |
|
|
not device.format.mountpoint or \ |
1854 |
|
|
not device.format.status: |
1855 |
|
|
continue |
1856 |
|
|
|
1857 |
|
|
path = "%s/%s" % (chroot, device.format.mountpoint) |
1858 |
|
|
|
1859 |
|
|
ST_RDONLY = 1 # this should be in python's posix module |
1860 |
|
|
if not os.path.exists(path) or os.statvfs(path)[statvfs.F_FLAG] & ST_RDONLY: |
1861 |
|
|
continue |
1862 |
|
|
|
1863 |
|
|
try: |
1864 |
|
|
space.append((device.format.mountpoint, |
1865 |
|
|
isys.pathSpaceAvailable(path))) |
1866 |
|
|
except SystemError: |
1867 |
|
|
log.error("failed to calculate free space for %s" % (device.format.mountpoint,)) |
1868 |
|
|
|
1869 |
|
|
space.sort(key=lambda s: s[1]) |
1870 |
|
|
return space |
1871 |
|
|
|
1872 |
|
|
def mtab(self): |
1873 |
|
|
format = "%s %s %s %s 0 0\n" |
1874 |
|
|
mtab = "" |
1875 |
|
|
devices = self.mountpoints.values() + self.swapDevices |
1876 |
|
|
devices.extend([self.devshm, self.devpts, self.sysfs, self.proc]) |
1877 |
|
|
devices.sort(key=lambda d: getattr(d.format, "mountpoint", None)) |
1878 |
|
|
for device in devices: |
1879 |
|
|
if not device.format.status: |
1880 |
|
|
continue |
1881 |
|
|
if not device.format.mountable: |
1882 |
|
|
continue |
1883 |
|
|
if device.format.mountpoint: |
1884 |
|
|
options = device.format.mountopts |
1885 |
|
|
if options: |
1886 |
|
|
options = options.replace("defaults,", "") |
1887 |
|
|
options = options.replace("defaults", "") |
1888 |
|
|
|
1889 |
|
|
if options: |
1890 |
|
|
options = "rw," + options |
1891 |
|
|
else: |
1892 |
|
|
options = "rw" |
1893 |
|
|
mtab = mtab + format % (device.path, |
1894 |
|
|
device.format.mountpoint, |
1895 |
|
|
device.format.type, |
1896 |
|
|
options) |
1897 |
|
|
return mtab |
1898 |
|
|
|
1899 |
|
|
def turnOnSwap(self, anaconda, upgrading=None): |
1900 |
|
|
def swapErrorDialog(msg, device): |
1901 |
|
|
if not anaconda.intf: |
1902 |
|
|
sys.exit(0) |
1903 |
|
|
|
1904 |
|
|
buttons = [_("Skip"), _("Format"), _("_Exit")] |
1905 |
|
|
ret = anaconda.intf.messageWindow(_("Error"), msg, type="custom", |
1906 |
|
|
custom_buttons=buttons, |
1907 |
|
|
custom_icon="warning") |
1908 |
|
|
|
1909 |
|
|
if ret == 0: |
1910 |
|
|
self.devicetree._removeDevice(device) |
1911 |
|
|
return False |
1912 |
|
|
elif ret == 1: |
1913 |
|
|
device.format.create(force=True) |
1914 |
|
|
return True |
1915 |
|
|
else: |
1916 |
|
|
sys.exit(0) |
1917 |
|
|
|
1918 |
|
|
for device in self.swapDevices: |
1919 |
|
|
if isinstance(device, FileDevice): |
1920 |
|
|
# set up FileDevices' parents now that they are accessible |
1921 |
|
|
targetDir = "%s/%s" % (anaconda.rootPath, device.path) |
1922 |
|
|
parent = get_containing_device(targetDir, self.devicetree) |
1923 |
|
|
if not parent: |
1924 |
|
|
log.error("cannot determine which device contains " |
1925 |
|
|
"directory %s" % device.path) |
1926 |
|
|
device.parents = [] |
1927 |
|
|
self.devicetree._removeDevice(device) |
1928 |
|
|
continue |
1929 |
|
|
else: |
1930 |
|
|
device.parents = [parent] |
1931 |
|
|
|
1932 |
|
|
while True: |
1933 |
|
|
try: |
1934 |
|
|
device.setup() |
1935 |
|
|
device.format.setup() |
1936 |
|
|
except OldSwapError: |
1937 |
|
|
msg = _("The swap device:\n\n %s\n\n" |
1938 |
|
|
"is an old-style Linux swap partition. If " |
1939 |
|
|
"you want to use this device for swap space, " |
1940 |
|
|
"you must reformat as a new-style Linux swap " |
1941 |
|
|
"partition.") \ |
1942 |
|
|
% device.path |
1943 |
|
|
|
1944 |
|
|
if swapErrorDialog(msg, device): |
1945 |
|
|
continue |
1946 |
|
|
except SuspendError: |
1947 |
|
|
if upgrading: |
1948 |
|
|
msg = _("The swap device:\n\n %s\n\n" |
1949 |
|
|
"in your /etc/fstab file is currently in " |
1950 |
|
|
"use as a software suspend device, " |
1951 |
|
|
"which means your system is hibernating. " |
1952 |
|
|
"To perform an upgrade, please shut down " |
1953 |
|
|
"your system rather than hibernating it.") \ |
1954 |
|
|
% device.path |
1955 |
|
|
else: |
1956 |
|
|
msg = _("The swap device:\n\n %s\n\n" |
1957 |
|
|
"in your /etc/fstab file is currently in " |
1958 |
|
|
"use as a software suspend device, " |
1959 |
|
|
"which means your system is hibernating. " |
1960 |
|
|
"If you are performing a new install, " |
1961 |
|
|
"make sure the installer is set " |
1962 |
|
|
"to format all swap devices.") \ |
1963 |
|
|
% device.path |
1964 |
|
|
|
1965 |
|
|
if swapErrorDialog(msg, device): |
1966 |
|
|
continue |
1967 |
|
|
except UnknownSwapError: |
1968 |
|
|
msg = _("The swap device:\n\n %s\n\n" |
1969 |
|
|
"does not contain a supported swap volume. In " |
1970 |
|
|
"order to continue installation, you will need " |
1971 |
|
|
"to format the device or skip it.") \ |
1972 |
|
|
% device.path |
1973 |
|
|
|
1974 |
|
|
if swapErrorDialog(msg, device): |
1975 |
|
|
continue |
1976 |
|
|
except DeviceError as (msg, name): |
1977 |
|
|
if anaconda.intf: |
1978 |
|
|
if upgrading: |
1979 |
|
|
err = _("Error enabling swap device %(name)s: " |
1980 |
|
|
"%(msg)s\n\n" |
1981 |
|
|
"The /etc/fstab on your upgrade partition " |
1982 |
|
|
"does not reference a valid swap " |
1983 |
|
|
"device.\n\nPress OK to exit the " |
1984 |
|
|
"installer") % {'name': name, 'msg': msg} |
1985 |
|
|
else: |
1986 |
|
|
err = _("Error enabling swap device %(name)s: " |
1987 |
|
|
"%(msg)s\n\n" |
1988 |
|
|
"This most likely means this swap " |
1989 |
|
|
"device has not been initialized.\n\n" |
1990 |
|
|
"Press OK to exit the installer.") % \ |
1991 |
|
|
{'name': name, 'msg': msg} |
1992 |
|
|
anaconda.intf.messageWindow(_("Error"), err) |
1993 |
|
|
sys.exit(0) |
1994 |
|
|
|
1995 |
|
|
break |
1996 |
|
|
|
1997 |
|
|
def mountFilesystems(self, anaconda, raiseErrors=None, readOnly=None, |
1998 |
|
|
skipRoot=False): |
1999 |
|
|
intf = anaconda.intf |
2000 |
|
|
devices = self.mountpoints.values() + self.swapDevices |
2001 |
|
|
devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc]) |
2002 |
|
|
devices.sort(key=lambda d: getattr(d.format, "mountpoint", None)) |
2003 |
|
|
|
2004 |
|
|
for device in devices: |
2005 |
|
|
if not device.format.mountable or not device.format.mountpoint: |
2006 |
|
|
continue |
2007 |
|
|
|
2008 |
|
|
if skipRoot and device.format.mountpoint == "/": |
2009 |
|
|
continue |
2010 |
|
|
|
2011 |
|
|
options = device.format.options |
2012 |
|
|
if "noauto" in options.split(","): |
2013 |
|
|
continue |
2014 |
|
|
|
2015 |
|
|
if device.format.type == "bind" and device != self.dev: |
2016 |
|
|
# set up the DirectoryDevice's parents now that they are |
2017 |
|
|
# accessible |
2018 |
|
|
# |
2019 |
|
|
# -- bind formats' device and mountpoint are always both |
2020 |
|
|
# under the chroot. no exceptions. none, damn it. |
2021 |
|
|
targetDir = "%s/%s" % (anaconda.rootPath, device.path) |
2022 |
|
|
parent = get_containing_device(targetDir, self.devicetree) |
2023 |
|
|
if not parent: |
2024 |
|
|
log.error("cannot determine which device contains " |
2025 |
|
|
"directory %s" % device.path) |
2026 |
|
|
device.parents = [] |
2027 |
|
|
self.devicetree._removeDevice(device) |
2028 |
|
|
continue |
2029 |
|
|
else: |
2030 |
|
|
device.parents = [parent] |
2031 |
|
|
|
2032 |
|
|
try: |
2033 |
|
|
device.setup() |
2034 |
|
|
except Exception as msg: |
2035 |
|
|
# FIXME: need an error popup |
2036 |
|
|
continue |
2037 |
|
|
|
2038 |
|
|
if readOnly: |
2039 |
|
|
options = "%s,%s" % (options, readOnly) |
2040 |
|
|
|
2041 |
|
|
try: |
2042 |
|
|
device.format.setup(options=options, |
2043 |
|
|
chroot=anaconda.rootPath) |
2044 |
|
|
except OSError as e: |
2045 |
|
|
log.error("OSError: (%d) %s" % (e.errno, e.strerror)) |
2046 |
|
|
|
2047 |
|
|
if intf: |
2048 |
|
|
if e.errno == errno.EEXIST: |
2049 |
|
|
intf.messageWindow(_("Invalid mount point"), |
2050 |
|
|
_("An error occurred when trying " |
2051 |
|
|
"to create %s. Some element of " |
2052 |
|
|
"this path is not a directory. " |
2053 |
|
|
"This is a fatal error and the " |
2054 |
|
|
"install cannot continue.\n\n" |
2055 |
|
|
"Press <Enter> to exit the " |
2056 |
|
|
"installer.") |
2057 |
|
|
% (device.format.mountpoint,)) |
2058 |
|
|
else: |
2059 |
|
|
na = {'mountpoint': device.format.mountpoint, |
2060 |
|
|
'msg': e.strerror} |
2061 |
|
|
intf.messageWindow(_("Invalid mount point"), |
2062 |
|
|
_("An error occurred when trying " |
2063 |
|
|
"to create %(mountpoint)s: " |
2064 |
|
|
"%(msg)s. This is " |
2065 |
|
|
"a fatal error and the install " |
2066 |
|
|
"cannot continue.\n\n" |
2067 |
|
|
"Press <Enter> to exit the " |
2068 |
|
|
"installer.") % na) |
2069 |
|
|
sys.exit(0) |
2070 |
|
|
except SystemError as (num, msg): |
2071 |
|
|
log.error("SystemError: (%d) %s" % (num, msg) ) |
2072 |
|
|
|
2073 |
|
|
if raiseErrors: |
2074 |
|
|
raise |
2075 |
|
|
if intf and not device.format.linuxNative: |
2076 |
|
|
na = {'path': device.path, |
2077 |
|
|
'mountpoint': device.format.mountpoint} |
2078 |
|
|
ret = intf.messageWindow(_("Unable to mount filesystem"), |
2079 |
|
|
_("An error occurred mounting " |
2080 |
|
|
"device %(path)s as " |
2081 |
|
|
"%(mountpoint)s. You may " |
2082 |
|
|
"continue installation, but " |
2083 |
|
|
"there may be problems.") % na, |
2084 |
|
|
type="custom", |
2085 |
|
|
custom_icon="warning", |
2086 |
|
|
custom_buttons=[_("_Exit installer"), |
2087 |
|
|
_("_Continue")]) |
2088 |
|
|
|
2089 |
|
|
if ret == 0: |
2090 |
|
|
sys.exit(0) |
2091 |
|
|
else: |
2092 |
|
|
continue |
2093 |
|
|
|
2094 |
|
|
sys.exit(0) |
2095 |
|
|
except FSError as msg: |
2096 |
|
|
log.error("FSError: %s" % msg) |
2097 |
|
|
|
2098 |
|
|
if intf: |
2099 |
|
|
na = {'path': device.path, |
2100 |
|
|
'mountpoint': device.format.mountpoint, |
2101 |
|
|
'msg': msg} |
2102 |
|
|
intf.messageWindow(_("Unable to mount filesystem"), |
2103 |
|
|
_("An error occurred mounting " |
2104 |
|
|
"device %(path)s as %(mountpoint)s: " |
2105 |
|
|
"%(msg)s. This is " |
2106 |
|
|
"a fatal error and the install " |
2107 |
|
|
"cannot continue.\n\n" |
2108 |
|
|
"Press <Enter> to exit the " |
2109 |
|
|
"installer.") % na) |
2110 |
|
|
sys.exit(0) |
2111 |
|
|
|
2112 |
|
|
self.active = True |
2113 |
|
|
|
2114 |
|
|
def umountFilesystems(self, ignoreErrors=True, swapoff=True): |
2115 |
|
|
devices = self.mountpoints.values() + self.swapDevices |
2116 |
|
|
devices.extend([self.dev, self.devshm, self.devpts, self.sysfs, self.proc]) |
2117 |
|
|
devices.sort(key=lambda d: getattr(d.format, "mountpoint", None)) |
2118 |
|
|
devices.reverse() |
2119 |
|
|
for device in devices: |
2120 |
|
|
if not device.format.mountable and \ |
2121 |
|
|
(device.format.type != "swap" or swapoff): |
2122 |
|
|
continue |
2123 |
|
|
|
2124 |
|
|
device.format.teardown() |
2125 |
|
|
device.teardown() |
2126 |
|
|
|
2127 |
|
|
self.active = False |
2128 |
|
|
|
2129 |
|
|
def createSwapFile(self, device, size, rootPath=None): |
2130 |
|
|
""" Create and activate a swap file under rootPath. """ |
2131 |
|
|
if not rootPath: |
2132 |
|
|
rootPath = self.rootpath |
2133 |
|
|
|
2134 |
|
|
filename = "/SWAP" |
2135 |
|
|
count = 0 |
2136 |
|
|
basedir = os.path.normpath("%s/%s" % (rootPath, |
2137 |
|
|
device.format.mountpoint)) |
2138 |
|
|
while os.path.exists("%s/%s" % (basedir, filename)) or \ |
2139 |
|
|
self.devicetree.getDeviceByName(filename): |
2140 |
|
|
file = os.path.normpath("%s/%s" % (basedir, filename)) |
2141 |
|
|
count += 1 |
2142 |
|
|
filename = "/SWAP-%d" % count |
2143 |
|
|
|
2144 |
|
|
dev = FileDevice(filename, |
2145 |
|
|
size=size, |
2146 |
|
|
parents=[device], |
2147 |
|
|
format=getFormat("swap", device=filename)) |
2148 |
|
|
dev.create() |
2149 |
|
|
dev.setup() |
2150 |
|
|
dev.format.create() |
2151 |
|
|
dev.format.setup() |
2152 |
|
|
# nasty, nasty |
2153 |
|
|
self.devicetree._addDevice(dev) |
2154 |
|
|
|
2155 |
|
|
def mkDevRoot(self, instPath=None): |
2156 |
|
|
if not instPath: |
2157 |
|
|
instPath = self.rootpath |
2158 |
|
|
|
2159 |
|
|
root = self.rootDevice |
2160 |
|
|
dev = "%s/%s" % (instPath, root.path) |
2161 |
|
|
if not os.path.exists("%s/dev/root" %(instPath,)) and os.path.exists(dev): |
2162 |
|
|
rdev = os.stat(dev).st_rdev |
2163 |
|
|
os.mknod("%s/dev/root" % (instPath,), stat.S_IFBLK | 0600, rdev) |
2164 |
|
|
|
2165 |
|
|
@property |
2166 |
|
|
def swapDevices(self): |
2167 |
|
|
swaps = [] |
2168 |
|
|
for device in self.devices: |
2169 |
|
|
if device.format.type == "swap": |
2170 |
|
|
swaps.append(device) |
2171 |
|
|
return swaps |
2172 |
|
|
|
2173 |
|
|
@property |
2174 |
|
|
def rootDevice(self): |
2175 |
|
|
for path in ["/", self.rootpath]: |
2176 |
|
|
for device in self.devices: |
2177 |
|
|
try: |
2178 |
|
|
mountpoint = device.format.mountpoint |
2179 |
|
|
except AttributeError: |
2180 |
|
|
mountpoint = None |
2181 |
|
|
|
2182 |
|
|
if mountpoint == path: |
2183 |
|
|
return device |
2184 |
|
|
|
2185 |
|
|
@property |
2186 |
|
|
def migratableDevices(self): |
2187 |
|
|
""" List of devices whose filesystems can be migrated. """ |
2188 |
|
|
migratable = [] |
2189 |
|
|
for device in self.devices: |
2190 |
|
|
if device.format.migratable and device.format.exists: |
2191 |
|
|
migratable.append(device) |
2192 |
|
|
|
2193 |
|
|
return migratable |
2194 |
|
|
|
2195 |
|
|
def write(self, instPath=None): |
2196 |
|
|
""" write out all config files based on the set of filesystems """ |
2197 |
|
|
if not instPath: |
2198 |
|
|
instPath = self.rootpath |
2199 |
|
|
|
2200 |
|
|
# /etc/fstab |
2201 |
|
|
fstab_path = os.path.normpath("%s/etc/fstab" % instPath) |
2202 |
|
|
fstab = self.fstab() |
2203 |
|
|
open(fstab_path, "w").write(fstab) |
2204 |
|
|
|
2205 |
|
|
# /etc/crypttab |
2206 |
|
|
crypttab_path = os.path.normpath("%s/etc/crypttab" % instPath) |
2207 |
|
|
crypttab = self.crypttab() |
2208 |
|
|
open(crypttab_path, "w").write(crypttab) |
2209 |
|
|
|
2210 |
|
|
# /etc/mdadm.conf |
2211 |
|
|
mdadm_path = os.path.normpath("%s/etc/mdadm.conf" % instPath) |
2212 |
|
|
mdadm_conf = self.mdadmConf() |
2213 |
|
|
if mdadm_conf: |
2214 |
|
|
open(mdadm_path, "w").write(mdadm_conf) |
2215 |
|
|
|
2216 |
|
|
# /etc/multipath.conf |
2217 |
|
|
multipath_conf = self.multipathConf() |
2218 |
|
|
if multipath_conf: |
2219 |
|
|
multipath_path = os.path.normpath("%s/etc/multipath.conf" % |
2220 |
|
|
instPath) |
2221 |
|
|
conf_contents = multipath_conf.write(self.devicetree.mpathFriendlyNames) |
2222 |
|
|
f = open(multipath_path, "w") |
2223 |
|
|
f.write(conf_contents) |
2224 |
|
|
f.close() |
2225 |
|
|
else: |
2226 |
|
|
log.info("not writing out mpath configuration") |
2227 |
|
|
iutil.copy_to_sysimage("/etc/multipath/wwids", root_path=instPath) |
2228 |
|
|
if self.devicetree.mpathFriendlyNames: |
2229 |
|
|
iutil.copy_to_sysimage("/etc/multipath/bindings", root_path=instPath) |
2230 |
|
|
|
2231 |
|
|
def crypttab(self): |
2232 |
|
|
# if we are upgrading, do we want to update crypttab? |
2233 |
|
|
# gut reaction says no, but plymouth needs the names to be very |
2234 |
|
|
# specific for passphrase prompting |
2235 |
|
|
if not self.cryptTab: |
2236 |
|
|
self.cryptTab = CryptTab(self.devicetree) |
2237 |
|
|
self.cryptTab.populate() |
2238 |
|
|
|
2239 |
|
|
devices = self.mountpoints.values() + self.swapDevices |
2240 |
|
|
|
2241 |
|
|
# prune crypttab -- only mappings required by one or more entries |
2242 |
|
|
for name in self.cryptTab.mappings.keys(): |
2243 |
|
|
keep = False |
2244 |
|
|
mapInfo = self.cryptTab[name] |
2245 |
|
|
cryptoDev = mapInfo['device'] |
2246 |
|
|
for device in devices: |
2247 |
|
|
if device == cryptoDev or device.dependsOn(cryptoDev): |
2248 |
|
|
keep = True |
2249 |
|
|
break |
2250 |
|
|
|
2251 |
|
|
if not keep: |
2252 |
|
|
del self.cryptTab.mappings[name] |
2253 |
|
|
|
2254 |
|
|
return self.cryptTab.crypttab() |
2255 |
|
|
|
2256 |
|
|
def mdadmConf(self): |
2257 |
|
|
""" Return the contents of mdadm.conf. """ |
2258 |
|
|
arrays = self.devicetree.getDevicesByType("mdarray") |
2259 |
|
|
arrays.extend(self.devicetree.getDevicesByType("mdbiosraidarray")) |
2260 |
|
|
arrays.extend(self.devicetree.getDevicesByType("mdcontainer")) |
2261 |
|
|
# Sort it, this not only looks nicer, but this will also put |
2262 |
|
|
# containers (which get md0, md1, etc.) before their members |
2263 |
|
|
# (which get md127, md126, etc.). and lame as it is mdadm will not |
2264 |
|
|
# assemble the whole stack in one go unless listed in the proper order |
2265 |
|
|
# in mdadm.conf |
2266 |
|
|
arrays.sort(key=lambda d: d.path) |
2267 |
|
|
if not arrays: |
2268 |
|
|
return "" |
2269 |
|
|
|
2270 |
|
|
conf = "# mdadm.conf written out by anaconda\n" |
2271 |
|
|
conf += "MAILADDR root\n" |
2272 |
|
|
conf += "AUTO +imsm +1.x -all\n" |
2273 |
|
|
devices = self.mountpoints.values() + self.swapDevices |
2274 |
|
|
for array in arrays: |
2275 |
|
|
for device in devices: |
2276 |
|
|
if device == array or device.dependsOn(array): |
2277 |
|
|
conf += array.mdadmConfEntry |
2278 |
|
|
break |
2279 |
|
|
|
2280 |
|
|
return conf |
2281 |
|
|
|
2282 |
|
|
def multipathConf(self): |
2283 |
|
|
""" Return the contents of multipath.conf. """ |
2284 |
|
|
mpaths = self.devicetree.getDevicesByType("dm-multipath") |
2285 |
|
|
if not mpaths: |
2286 |
|
|
return None |
2287 |
|
|
mpaths.sort(key=lambda d: d.name) |
2288 |
|
|
config = MultipathConfigWriter() |
2289 |
|
|
whitelist = [] |
2290 |
|
|
for mpath in mpaths: |
2291 |
|
|
config.addMultipathDevice(mpath) |
2292 |
|
|
whitelist.append(mpath.name) |
2293 |
|
|
whitelist.extend([d.name for d in mpath.parents]) |
2294 |
|
|
|
2295 |
|
|
# blacklist everything we're not using and let the |
2296 |
|
|
# sysadmin sort it out. |
2297 |
|
|
for d in self.devicetree.devices: |
2298 |
|
|
if not d.name in whitelist: |
2299 |
|
|
config.addBlacklistDevice(d) |
2300 |
|
|
|
2301 |
|
|
return config |
2302 |
|
|
|
2303 |
|
|
def fstab (self): |
2304 |
|
|
format = "%-23s %-23s %-7s %-15s %d %d\n" |
2305 |
|
|
fstab = """ |
2306 |
|
|
# |
2307 |
|
|
# /etc/fstab |
2308 |
|
|
# Created by anaconda on %s |
2309 |
|
|
# |
2310 |
|
|
# Accessible filesystems, by reference, are maintained under '/dev/disk' |
2311 |
|
|
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info |
2312 |
|
|
# |
2313 |
|
|
""" % time.asctime() |
2314 |
|
|
|
2315 |
|
|
devices = sorted(self.mountpoints.values(), |
2316 |
|
|
key=lambda d: d.format.mountpoint) |
2317 |
|
|
devices += self.swapDevices |
2318 |
|
|
devices.extend([self.devshm, self.devpts, self.sysfs, self.proc]) |
2319 |
|
|
netdevs = self.devicetree.getDevicesByInstance(NetworkStorageDevice) |
2320 |
|
|
for device in devices: |
2321 |
|
|
# why the hell do we put swap in the fstab, anyway? |
2322 |
|
|
if not device.format.mountable and device.format.type != "swap": |
2323 |
|
|
continue |
2324 |
|
|
|
2325 |
|
|
# Don't write out lines for optical devices, either. |
2326 |
|
|
if isinstance(device, OpticalDevice): |
2327 |
|
|
continue |
2328 |
|
|
|
2329 |
|
|
fstype = getattr(device.format, "mountType", device.format.type) |
2330 |
|
|
if fstype == "swap": |
2331 |
|
|
mountpoint = "swap" |
2332 |
|
|
options = device.format.options |
2333 |
|
|
else: |
2334 |
|
|
mountpoint = device.format.mountpoint |
2335 |
|
|
options = device.format.options |
2336 |
|
|
if not mountpoint: |
2337 |
|
|
log.warning("%s filesystem on %s has no mountpoint" % \ |
2338 |
|
|
(fstype, |
2339 |
|
|
device.path)) |
2340 |
|
|
continue |
2341 |
|
|
|
2342 |
|
|
options = options or "defaults" |
2343 |
|
|
for netdev in netdevs: |
2344 |
|
|
if device.dependsOn(netdev): |
2345 |
|
|
if mountpoint == "/": |
2346 |
|
|
options = options + ",_rnetdev" |
2347 |
|
|
else: |
2348 |
|
|
options = options + ",_netdev" |
2349 |
|
|
break |
2350 |
|
|
devspec = device.fstabSpec |
2351 |
|
|
dump = device.format.dump |
2352 |
|
|
if device.format.check and mountpoint == "/": |
2353 |
|
|
passno = 1 |
2354 |
|
|
elif device.format.check: |
2355 |
|
|
passno = 2 |
2356 |
|
|
else: |
2357 |
|
|
passno = 0 |
2358 |
|
|
fstab = fstab + device.fstabComment |
2359 |
|
|
fstab = fstab + format % (devspec, mountpoint, fstype, |
2360 |
|
|
options, dump, passno) |
2361 |
|
|
|
2362 |
|
|
# now, write out any lines we were unable to process because of |
2363 |
|
|
# unrecognized filesystems or unresolveable device specifications |
2364 |
|
|
for line in self.preserveLines: |
2365 |
|
|
fstab += line |
2366 |
|
|
|
2367 |
|
|
return fstab |